跳转至

实验练习

说明

  1. 每一次实验我们会为大家准备若干练习题目。不同的题目有着不同的难度,请大家量力而行;
  2. 你可以查阅任何开源资料完成实验练习,但不能直接抄袭。一经发现,我们将取消所有抄袭参与者本次的实验成绩。情节特别严重的,我们将按照学校的有关规定进行处理;
  3. 部分题目我们提供了代码框架,你需要在框架指定的位置完成代码内容。你也可以自由修改框架代码,但需要根据题目要求完成练习;
  4. 如果你使用了我们预料之外的方法完成练习也没有关系,我们将根据思考量与任务量单独确定给分情况;
  5. 本次实验的部分题目需要上板验证,具体的端口变量与对应关系请按照题目要求设定

提醒

为了保证效率,请大家在找助教 Debug 前务必编写对应的仿真文件。我们不接受没有仿真波形的 Debug 请求。


必做内容

每一名同学都需要完成必做部分的内容。

题目 1:乘法器基础版(3 分)

请根据实验文档中给出的移位乘法器设计方法,设计一个基础的参数化移位乘法器。部分信号约定如下:

  • start 为开始运算信号,默认值为 0。当 start 由 0 变为 1 时需要初始化内部的被乘数、乘数和乘积寄存器,并从下下个时钟周期开始进行乘法计算(初始化需要一个周期)。
  • finish 为运算完成信号,默认值为 0。当 finish 变为 1 时,表明此时乘法器的输出端口 res 为有效的乘积结果。为了保证稳定性,finish 信号需要持续至少一个时钟周期的高电平。
  • rst 为复位信号,默认值为 0。当 rst 变为 1 时,乘法器内部的被乘数、乘数和乘积寄存器清零,且状态跳转到 IDLE。
  • ab 为被乘数和乘数,均为 WIDTH 位宽的变量。
  • res 为最终的乘积,为 2*WIDTH 位宽的变量。

请通过仿真验证移位乘法器的正确性。本题的框架代码如下:

MUL.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module MUL #(
    parameter                               WIDTH = 32
) (
    input                   [ 0 : 0]            clk,
    input                   [ 0 : 0]            rst,
    input                   [ 0 : 0]            start,
    input                   [WIDTH-1 : 0]       a,
    input                   [WIDTH-1 : 0]       b,
    output      reg         [2*WIDTH-1:0]       res,
    output      reg         [ 0 : 0]            finish
);
reg [2*WIDTH-1 : 0]     multiplicand;       // 被乘数寄存器
reg [  WIDTH-1 : 0]     multiplier;         // 乘数寄存器
reg [2*WIDTH-1 : 0]     product;            // 乘积寄存器

localparam IDLE = 2'b00;            // 空闲状态。这个周期寄存器保持原值不变。当 start 为 1 时跳转到 INIT。
localparam INIT = 2'b01;            // 初始化。下个周期跳转到 CALC
localparam CALC = 2'b10;            // 计算中。计算完成时跳转到 DONE
localparam DONE = 2'b11;            // 计算完成。下个周期跳转到 IDLE
reg [1:0] current_state, next_state;

// 请完成有限状态机以及乘法器模块的设计

always @(*)
    finish = (current_state == DONE);

endmodule

你可以使用如下的测试文件:

MUL_tb.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
module MUL_tb #(
    parameter WIDTH = 32
) ();
reg  [WIDTH-1:0]    a, b;
reg                 rst, clk, start;
wire [2*WIDTH-1:0]  res;
wire                finish;
integer             seed;

initial begin
    clk = 0;
    seed = 2024; // 种子值
    forever begin
        #5 clk = ~clk;
    end
end

initial begin
    rst = 1;
    start = 0;
    #20;
    rst = 0;
    #20;
    repeat (5) begin
        a = $random(seed);          // $random 返回的是 32 位随机数,如果你需要得到少于 32 位的随机数,可以通过 % 运算得到
        b = $random(seed + 1);      // 你可以通过设置种子值改变随机数的序列
        start = 1;
        #20 start = 0;
        #380;
    end
    $finish;
end

MUL mul(
    .clk        (clk),
    .rst        (rst),
    .start      (start),
    .a          (a),
    .b          (b),
    .res        (res),
    .finish     (finish)
);
endmodule
提示

为了实现乘法器模块,你可以先实现如下的几个子模块,最后在 MUL 模块中例化即可。例化这些模块只是一种选择,考虑到模块化设计可能带来的不便,你完全可以不使用例化的做法,而直接将全部逻辑写在MUL模块中。

  • 参数化的移位寄存器,用于存储被乘数和乘数;
  • 带有写使能信号的寄存器,用于存储乘积;
  • 中央控制器,用于实现有限状态机,接收并给出对应的控制信号。

为此,你需要更改我们给出的框架代码。一个可行的代码框架如下:

ShiftReg.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
module ShiftReg #(
    parameter                   WIDTH           = 32,
    parameter                   MODE            = 0     // 为 0 代表左移,为 1 代表右移
)(
    input                   [ 0 : 0]            clk,
    input                   [ 0 : 0]            rst,

    input                   [WIDTH-1: 0]        din,
    input                   [ 0 : 0]            set,    // 置位信号
    input                   [ 0 : 0]            en,     // 移位信号
    output      reg         [WIDTH-1: 0]        dout
);
// ......
endmodule
Register.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module Register #(
    parameter                   WIDTH           = 32
) (
    input                   [ 0 : 0]            clk,
    input                   [ 0 : 0]            rst,

    input                   [WIDTH-1: 0]        din,
    input                   [ 0 : 0]            we,     // 写使能信号
    output      reg         [WIDTH-1: 0]        dout
);
always @(posedge clk) begin
    if (rst)
        dout <= 0;
    else if (we)
        dout <= din;
end
endmodule
Control.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
module Control (
    input                   [ 0 : 0]            clk,
    input                   [ 0 : 0]            rst,

    // 这里可能需要一些控制信号的端口,例如最低位为 0、移位寄存器初始化、移位寄存器移位等             
);
localparam IDLE = 2'b00;            // 空闲状态。这个周期寄存器保持原值不变。当 start 为 1 时跳转到 INIT。
localparam INIT = 2'b01;            // 初始化。下个周期跳转到 CALC
localparam CALC = 2'b10;            // 计算中。计算完成时跳转到 DONE
localparam DONE = 2'b11;            // 计算完成。下个周期跳转到 IDLE
reg [1:0] current_state, next_state;

// 实现 FSM
endmodule
提示

以下是助教编写的计算32位有符号/无符号乘法的python代码,可以方便地按照有无符号分别解析输入的数据并输出,你可以使用该程序来方便验证正确性。你也可以在仿真文件中同时例化一个使用乘号的保证正确的乘法器,在波形图中比较你的乘法器和正确乘法器的结果。 点击下载

题目 2:乘法器优化版(2 分)

请根据实验文档中的优化方法改进移位乘法器。要求通过仿真验证其正确性。

本题的模块端口约定与上一题相同。

MUL.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
module MUL #(
    parameter                               WIDTH = 32
) (
    input                   [ 0 : 0]        clk,
    input                   [ 0 : 0]        rst,
    input                   [ 0 : 0]        start,
    input                   [WIDTH-1 : 0]   a,
    input                   [WIDTH-1 : 0]   b,
    output      reg         [2*WIDTH-1:0]   res,
    output      reg         [ 0 : 0]        finish
);
reg [WIDTH-1 : 0]     multiplicand;       // 被乘数寄存器
reg [2*WIDTH : 0]     product;            // 乘积寄存器
// Write your code here

// End of your code
endmodule

本题需要在 FPGA 平台上完成测试(上板测试时请令 WIDTH = 4),要求用 sw[7] 作为 rst 信号,{{1'b0}, {sw[5:3]}} 作为被乘数,sw[2:0] 作为乘数,按钮作为 start 信号。finish 信号无需指定端口(因为时间太短了看不到)。乘积 res 通过数码管低两位显示,如果乘积高四位为 0,则数码管上显示 0 即可。部分约束文件如下:

## SWITCH
set_property -dict { PACKAGE_PIN B17   IOSTANDARD LVCMOS33 } [get_ports { b[0]}]
set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports { b[1]}]
set_property -dict { PACKAGE_PIN D17   IOSTANDARD LVCMOS33 } [get_ports { b[2]}]
set_property -dict { PACKAGE_PIN C17   IOSTANDARD LVCMOS33 } [get_ports { a[0]}]
set_property -dict { PACKAGE_PIN C18   IOSTANDARD LVCMOS33 } [get_ports { a[1]}]
set_property -dict { PACKAGE_PIN C19   IOSTANDARD LVCMOS33 } [get_ports { a[2]}]
# set_property -dict { PACKAGE_PIN E19   IOSTANDARD LVCMOS33 } [get_ports {sw[6]}]
set_property -dict { PACKAGE_PIN D19   IOSTANDARD LVCMOS33 } [get_ports { rst[7]}]

## BUTTON
set_property -dict {PACKAGE_PIN F20 IOSTANDARD LVCMOS33} [get_ports { start}]

提示

上板时 MUL 模块需要额外套一层 Top 模块,同时需要正确连接 Segment 模块。

题目 3:性能比较(1 分)

请阅读实验文档中性能分析部分内容,将你写的乘法器(优先使用优化版,优化版未完成可使用基础版)与 Verilog 中自带的乘法进行性能比较(时间性能测试只需 WNS 小于 1 即可),你需要大致估计两个乘法器的运行时间,对于使用 Verilog 乘法的乘法器,其只需要一个周期,故运行时间为 \(1/f_{max1}\);而你写的乘法器除去初始化和最后赋值的阶段,共需要 WIDTH 个周期,其运行时间为 \(\text{WIDTH}/f_{max2}\)。注意:请将两个乘法器的 WIDTH 改为 4,从而适配约束文件。

查找相关资料简要说明还有哪些方法可以得到效率更高的乘法器(言之有理即可),你或许可以参考这里

以下是使用 Verilog 乘法运算符的乘法器:

mul.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
module MUL #(
    parameter WIDTH = 32
)(
    input                   [ 0 : 0]        clk,
    input                   [ 0 : 0]        rst,
    input                   [ 0 : 0]        start,
    input                   [WIDTH-1:0]     a,
    input                   [WIDTH-1:0]     b,
    output      reg         [2*WIDTH-1:0]   res,
    output      reg         [ 0 : 0]        finish
);
reg [WIDTH-1:0] temp_a, temp_b;
always @(posedge clk) begin
    if (rst) begin
        res <= 0;
        finish <= 1'B0;
    end
    else if (start) begin
        temp_a <= a;
        temp_b <= b;
        res <= temp_a * temp_b;
        finish <= 1'B1;
    end
    else
        finish <= 1'B0;
end
endmodule

题目 4:可选有无符号乘法(2 分)

基于你写的乘法器(优化版优先),增加一位指示有无符号的输入mul_signed,低位进行无符号乘法运算,高位进行有符号乘法运算(即输入输出都视为有符号数补码)。通过仿真测试正确性。

测试文件可以借用题目 1 中给出的测试文件,并增加有无符号使能的接口,你可能需要增加 repeat 中的循环次数从而得到负数的随机数,或者直接设置乘数和被乘数的值。 你可以借助题目 1 中给出的python计算程序来计算正确结果。 你可以在仿真文件中使用'wire correct = ($signed(res) == $signed(a) * $signed(b));'来快捷验证有符号乘法的正确性。

选择性必做内容

选择性必做内容是针对不同层次学生设计的分层内容。不同难度的题目对应不同的分值,请大家根据自身实际情况进行选择。

请在下面的题目中任选一题完成。多选按选择的第一题计分。

题目 1:Logisim 搭建乘法器(2 分)

请参考文档中四位乘法器的实现,在 Logisim 中搭建一个四位乘法器。

Image title

该乘法器输入两个四位的数,输出一个八位的乘积,你需要使用 Splitter 将输入分为四个一位的信号以及将八个一位的信号结合成一个八位的信号。以下是Splitter中的一些设置项:

Image title

同时,你可以使用 Logisim 中的全加器(位于下图位置,你可以将光标置于端口查看该端口信号信息);或者使用你自定义的全加器模块。

Image title

题目 2:单周期树形乘法器(2 分)

上一题中的乘法器结构较为复杂,且延迟较高。我们也可以通过加法器树来构造一个更高效的组合逻辑乘法器。以下是该加法树的电路结构示意(以八位乘法为例):

Image title

具体思路是:先得到乘积中每一位对应的结果(乘数该位为 0 则该位对应的结果为 0,否则该位对应的结果为被乘数左移该位所在的位数),由此得到乘积的 8 位分别对应的结果,再将八个结果按上图的结构累加计算得到最终乘积。请编写 Verilog 代码实现上述的乘法器结构,并分析模块的最大时钟频率。

本题的框架代码如下:

mul.v
1
2
3
4
5
6
7
module mul_comb (
    input                   [ 7 : 0]        a,
    input                   [ 7 : 0]        b,
    output                  [15 : 0]        res
);
// Write your codes here.
endmodule

选做内容

选做内容为扩展内容,不计入实验成绩。大家可以根据兴趣自行完成。

题目 1:时序树形乘法器

选择性必做题目 2 中的树形乘法器很容易改写为多周期乘法器(甚至是流水化乘法器),你可以考虑在加法树的每一层各切一刀,即每个时钟周期各计算一层,经过多个周期完成运算。例如上图中的 8*8 乘法器,从输入操作数到输出结果共需要 3 个周期。请尝试根据以上思路,设计一个树形的多周期 8*8 位乘法器。

题目 2:除法器

必做部分的乘法器的实现逻辑基于竖式计算,使用类似的逻辑也可以实现除法器。每次的计算不是加法,而是剩余被除数和除数的比较,剩余被除数较大则减去除数,商对应位为1。除法器应同时计算得到商和余数。

类似乘法器优化中将乘积和乘数使用同一个寄存器不断右移,除法器也可以将被除数和余数使用同一个寄存器(下称dvd_rmd),高位为剩余的被除数dividend,低位为余数remainder,初始被除数在低位,逐步左移填入余数。 每轮计算dvd_rmd[62:31]与dvs[31:0](divisor 除数)做比较,除数较大则左移时最低位填入0,剩余被除数较大则左移时最低位填入1,同时剩余被除数减去除数。初始dvd_rmd[31]与dvs[0]对齐,最后一轮计算前dvd_rmd[62:31]与dvs[31:0]对齐,最后一轮计算后,dvd_rmd[63:32]为余数,dvd_rmd[31:0]为商。

类似乘法器,你可以使你的除法器支持有无符号选择。有符号计算中,商的符号是被除数和除数符号的异或,余数的符号是被除数的符号。

评论

Authors: Yang Yibo