流水线乘加树

需求

  • 计算两个长度为2的幂次方的向量的对应位置相乘相加结果
  • 输入为补码,输出为补码(支持负数)
  • 输入位宽可配置,输入向量的宽度可配置,输出位宽由以上两项决定

设计规划

参数表

参数名称 说明 默认值
DIN_WIDTH 输入位宽 8
DIN_NUM_LOG 输入向量的宽度的log2值(宽度$$2^{DIN_NUM_LOG}$$) 2

注:输出位宽由以上决定,为$$DOUT_WIDTH = DIN_WIDTH \times 2 + DIN_NUM_LOG - 1$$

端口列表

端口名 类型 位宽 说明
clk input 1 系统时钟
rst_n input 1 系统复位
din_valid input 1 输入数据有效,高有效
mla_din1 input (2 ** DIN_NUM_LOG) * DIN_WIDTH 输入向量1
mla_din2 input (2 ** DIN_NUM_LOG) * DIN_WIDTH 输入向量2
dout_valid output 1 输出信号有效,高有效
mla_dout output DIN_WIDTH * 2 + DIN_NUM_LOG - 1 输出结果

功能描述

功能

$mla_dout = \sum_{i = 0}{2{DIN_NUM_LOG}} mla_din1[i] \times mla_din2[i] $

其中,mla_din1[i]和mla_din2[i]按位宽存储在输入mla_din1和mla_din2,每个均为补码中,如图:

输入数据形式

时序

  • 当输入有效din_valid有效时,开始计算;当dout_valid有效时,结果有效。
  • 设计为流水线式,输入可以连续送入,输出可以连续输出,输出具有保序性。
  • 输入有效到输出有效的间隔由DIN_NUM_LOG决定
IO时序

结构

以DIN_NUM_LOG=2为例:

结构设计
  • 输入的mla_din在内部被分解并对应相乘
  • 使用每层带寄存器的加法树实现累加
  • 有效信号随对应数据流动

代码实现

RTL设计

module声明

module mla_tree #(
    parameter DIN_WIDTH = 8,
    parameter DIN_NUM_LOG = 2
)(
    input clk,
    input rst_n,

    input din_valid,
    input [(2 ** DIN_NUM_LOG) * DIN_WIDTH - 1:0]mla_din1,
    input [(2 ** DIN_NUM_LOG) * DIN_WIDTH - 1:0]mla_din2,

    output dout_valid,
    output [DIN_WIDTH * 2 + DIN_NUM_LOG - 2:0]mla_dout
);

解码输入

  • 将输入的向量解码进入数组,方便代码编写
  • 处理输入数据,将补码形式转为“符号位-原码”表示,便于使用无符号乘法器
  • din_valid随数据流动,产生unpack_valid
reg [DIN_WIDTH - 1:0]din1_unpack[2 ** DIN_NUM_LOG - 1:0];
reg [DIN_WIDTH - 1:0]din2_unpack[2 ** DIN_NUM_LOG - 1:0];
integer j;
always @(posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        for (j = 0; j < 2 ** DIN_NUM_LOG ; j = j + 1) begin
            din1_unpack[j] <= 'b0; 
            din2_unpack[j] <= 'b0;
        end
    end else if (din_valid)begin
        for (j = 0; j < 2 ** DIN_NUM_LOG ; j = j + 1) begin
            din1_unpack[j][DIN_WIDTH - 1] <= mla_din1[DIN_WIDTH * (j + 1) - 1];
            din1_unpack[j][DIN_WIDTH - 2:0] <= (mla_din1[DIN_WIDTH * (j + 1) - 1])?(~mla_din1[DIN_WIDTH * j +:DIN_WIDTH - 1]+1'b1):mla_din1[DIN_WIDTH * j +:DIN_WIDTH - 1];
            din2_unpack[j][DIN_WIDTH - 1] <= mla_din2[DIN_WIDTH * (j + 1) - 1];
            din2_unpack[j][DIN_WIDTH - 2:0] <= (mla_din2[DIN_WIDTH * (j + 1) - 1])?(~mla_din2[DIN_WIDTH * j +:DIN_WIDTH - 1]+1'b1):mla_din2[DIN_WIDTH * j +:DIN_WIDTH - 1];
        end
    end
end

reg unpack_valid;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        unpack_valid <= 'b0;        
    end else begin
        unpack_valid <= din_valid;
    end
end

乘法器

  • 将输入数值部分相乘,同时手动控制结果的符号位
  • unpack_valid随数据流动,产生mul_valid
integer y;
reg [2 * DIN_WIDTH - 2:0]mul_result[2 ** DIN_NUM_LOG - 1:0];
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        for (y = 0; y < 2 ** DIN_NUM_LOG ; y = y + 1) begin
            mul_result[y] <= 'b0;
        end
    end else if(unpack_valid)begin
        for (y = 0; y < 2 ** DIN_NUM_LOG ; y = y + 1) begin
            if (din1_unpack[y] != 'b0 && din2_unpack[y] != 'b0) begin
                mul_result[y][2 * DIN_WIDTH - 2] <= din1_unpack[y][DIN_WIDTH - 1] ^ din2_unpack[y][DIN_WIDTH - 1];
            end else begin
                mul_result[y][2 * DIN_WIDTH - 2] <= 'b0; 
            end
            mul_result[y][2 * DIN_WIDTH - 3:0] <= din1_unpack[y][DIN_WIDTH - 2:0] * din2_unpack[y][DIN_WIDTH - 2:0];
        end
    end
end

reg mul_valid;
always @ (posedge clk or negedge rst_n) begin
    if (~rst_n) begin
        mul_valid <= 'b0;
    end else begin
        mul_valid <= unpack_valid;
    end
end

加法树

  • 将输入转换为补码,之后逐级相加
  • mul_valid逐级流动,产生每层的layer_dout_valid
genvar i,k;
generate
    for (k = DIN_NUM_LOG; k > 0; k = k - 1) begin:mla_layer

        wire [2 * DIN_WIDTH - 2 + (DIN_NUM_LOG - k):0]layer_din[2 ** k - 1:0];
        wire layer_din_valid;
        reg [2 * DIN_WIDTH - 1 + (DIN_NUM_LOG - k):0]layer_dout[2 ** (k - 1) - 1:0];
        reg layer_dout_valid;
        integer x;

        if (k == DIN_NUM_LOG) begin
            for (i = 0; i < 2 ** k ; i = i + 1) begin
                assign layer_din[i][2 * DIN_WIDTH - 3:0] = (mul_result[i][2 * DIN_WIDTH - 2])?(~mul_result[i][2 * DIN_WIDTH - 3:0] + 1'b1):mul_result[i][2 * DIN_WIDTH - 3:0]; 
                assign layer_din[i][2 * DIN_WIDTH - 2] = mul_result[i][2 * DIN_WIDTH - 2];
            end
            assign layer_din_valid = mul_valid;
        end else begin
            for (i = 0; i < 2 ** k; i = i + 1) begin
                assign layer_din[i] = mla_layer[k + 1].layer_dout[i];
            end
            assign layer_din_valid = mla_layer[k + 1].layer_dout_valid;
        end

        always @ (posedge clk or negedge rst_n) begin
            if (~rst_n) begin
                for (x = 0; x < 2 ** (k - 1) ; x = x + 1) begin
                    layer_dout[x] <= 'b0;
                end
            end else if (layer_din_valid) begin
                for (x = 0; x < 2 ** (k - 1) ; x = x + 1) begin
                    layer_dout[x] <= {layer_din[2 * x][2 * DIN_WIDTH - 2 + (DIN_NUM_LOG - k)],layer_din[2 * x]} + {layer_din[2 * x + 1][2 * DIN_WIDTH - 2 + (DIN_NUM_LOG - k)],layer_din[2 * x + 1]};
                    // layer_dout[x] <= layer_din[2 * x] + layer_din[2 * x + 1];
                end
            end
        end

        always @ (posedge clk or negedge rst_n) begin
            if (~rst_n) begin
                layer_dout_valid <= 'b0;
            end else begin
                layer_dout_valid <= layer_din_valid;
            end
        end
    end
endgenerate

取出结果

从循环生成语句的最后一级取出输出

assign dout_valid = mla_layer[1].layer_dout_valid;
assign mla_dout = mla_layer[1].layer_dout[0];

endmodule // mla_tree

Testbench

dut声明与连接

module tb_mla_tree();

parameter DIN_WIDTH = 8;
parameter DIN_NUM_LOG = 4;

logic clk;
logic rst_n;
logic din_valid;
logic [(2 ** DIN_NUM_LOG) * DIN_WIDTH - 1:0]mla_din1;
logic [(2 ** DIN_NUM_LOG) * DIN_WIDTH - 1:0]mla_din2;
logic dout_valid;
logic [DIN_WIDTH * 2 + DIN_NUM_LOG - 2:0]mla_dout;

mla_tree #(
    .DIN_WIDTH  (DIN_WIDTH),
    .DIN_NUM_LOG(DIN_NUM_LOG)
) dut (
    .clk       (clk),
    .rst_n     (rst_n),
    .din_valid (din_valid),
    .mla_din1  (mla_din1),
    .mla_din2  (mla_din2),
    .dout_valid(dout_valid),
    .mla_dout  (mla_dout)
);

时钟与复位信号

initial begin
    clk = 0;
    forever begin
        #50 clk = ~clk;
    end
end

initial begin
    rst_n = 1'b1;
    #5 rst_n = 1'b0;
    #10 rst_n = 1'b1;
end

激励产生

激励产生函数

function logic[DIN_WIDTH - 1:0] data_random();
    integer x;
    x = (DIN_WIDTH)'($urandom_range(0,2 ** DIN_WIDTH));
    if (x[DIN_WIDTH - 1] == 1'b1 && x[DIN_WIDTH - 2:0] == 'b0) begin
        return 0;//不产生 -2 ** (DIN_WIDTH - 1)
    end else begin
        return x;
    end
endfunction

激励产生

initial begin
    din_valid = 'b0;
    repeat(100) begin
        for (int i = 0; i < 2 ** DIN_NUM_LOG ; i++) begin
            mla_din1[i * DIN_WIDTH +:DIN_WIDTH] = data_random();
            mla_din2[i * DIN_WIDTH +:DIN_WIDTH] = data_random();
        end
        @(negedge clk);
        din_valid = 1;  
    end
    $stop;
end

参考模型

输入解码函数

将补码形式的logic数据转为带符号的integer类型数据

function integer decode(logic[DIN_WIDTH - 1:0] din);
    if(din[DIN_WIDTH - 1] == 1'b1) begin
        return integer'(din) - 2 ** DIN_WIDTH;
    end else begin
        return integer'(din);
    end
endfunction

参考模型

integer tb_din1[2 ** DIN_NUM_LOG - 1:0];
integer tb_din2[2 ** DIN_NUM_LOG - 1:0];
integer tb_result;
integer scoreboard[$]; //计分板
initial begin
    forever begin
        @(posedge clk);
        if(din_valid == 1'b1) begin
            // 获取输入数据
            for (int i = 0; i < 2 ** DIN_NUM_LOG ; i++) begin
                tb_din1[i] = decode(mla_din1[i * DIN_WIDTH +:DIN_WIDTH]);
                tb_din2[i] = decode(mla_din2[i * DIN_WIDTH +:DIN_WIDTH]);
            end
            // 计算参考结果
            tb_result = 0;
            for (int i = 0; i < 2 ** DIN_NUM_LOG ; i++) begin
                tb_result = tb_result + tb_din1[i] * tb_din2[i];
            end 
            // 将参考结果送入计分板
            scoreboard.push_back(tb_result);
        end
    end
end

计分板

integer tb_compare;
initial begin
    forever begin
        @(negedge clk);
        if (dout_valid) begin
            tb_compare = scoreboard.pop_front();
            if ((DIN_WIDTH * 2 + DIN_NUM_LOG - 1)'(tb_compare) == mla_dout) begin
                $display("%d == %d",tb_compare,integer'(mla_dout));
            end else begin
                $display("%h != %h",(DIN_WIDTH * 2 + DIN_NUM_LOG - 1)'(tb_compare),mla_dout);
                $stop;
            end
        end
    end
end
?著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,128评论 6 493
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,316评论 3 388
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事?!?“怎么了?”我有些...
    开封第一讲书人阅读 159,737评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,283评论 1 287
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,384评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,458评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,467评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,251评论 0 269
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,688评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,980评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,155评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,818评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,492评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,142评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,382评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,020评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,044评论 2 352

推荐阅读更多精彩内容

  • 第2章 基本语法 2.1 概述 基本句法和变量 语句 JavaScript程序的执行单位为行(line),也就是一...
    悟名先生阅读 4,145评论 0 13
  • 背景 一年多以前我在知乎上答了有关LeetCode的问题, 分享了一些自己做题目的经验。 张土汪:刷leetcod...
    土汪阅读 12,743评论 0 33
  • 覆盆子的反抗心?? 注意遮挡关系还有明暗,真有趣吧,是我的女儿讲给我的,她已经学了三年,虽然是儿童创意画,但是也很...
    清凉世界雨阅读 519评论 2 16
  • 大家都说女生的心思难猜,其实男生才是天生的演员(no offence)。他们与生俱来的蜜语甜言和诚实可靠的外表,很...
    若思古阅读 740评论 1 3
  • 上一篇文章中,有一位读者问我湿气重该怎么办?这一篇应邀,便讲一讲湿气重该怎么办? 首先,我们来了解一下,湿气是怎么...
    然谷中医阅读 1,765评论 0 7