跳转至

实验练习

说明

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

提醒

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


必做内容

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

题目 1:加法器(3 分)

请参照实验文档中对 4bits 超前进位加法器和层次扩展的描述,设计并编写一个 32bits 加法器。要求分别基于 4bits 超前进位加法器与 8bits 超前进位加法器实现。本题的分数组成如下:

  • 4 位超前进位加法器 + 层次扩展成 32 位:1 分
  • 8 位超前进位加法器:1 分
  • 8 位超前进位加法器 + 层次扩展成 32 位:1 分

本题要求自行编写仿真文件进行测试,代码框架与部分仿真信号输入如下:

Adder_LookAhead4.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Adder_LookAhead4 (
    input                   [ 3 : 0]            a, b,
    input                   [ 0 : 0]            ci,         // 来自低位的进位
    output                  [ 3 : 0]            s,          // 和
    output                  [ 0 : 0]            co          // 向高位的进位
);

wire    [3:0] C;
wire    [3:0] G;
wire    [3:0] P;

assign  G = a & b;
assign  P = a ^ b;

assign  C[0] = G[0] | ( P[0] & ci );
assign  C[1] = G[1] | ( P[1] & G[0] ) | ( P[1] & P[0] & ci );
assign  C[2] = G[2] | ( P[2] & G[1] ) | ( P[2] & P[1] & G[0] ) | ( P[2] & P[1] & P[0] & ci );
assign  C[3] = G[3] | ( P[3] & G[2] ) | ( P[3] & P[2] & G[1] ) | ( P[3] & P[2] & P[1] & G[0] ) | ( P[3] & P[2] & P[1] & P[0] & ci );

// TODO:确定 s 和 co 的产生逻辑

endmodule
Adder.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
module Adder (
    input                   [31 : 0]        a, b,
    input                   [ 0 : 0]        ci,
    output                  [31 : 0]        s,
    output                  [ 0 : 0]        co
);
wire    [6:0] cmid;
Adder_LookAhead4 adder0(
    .a(a[3:0]),
    .b(b[3:0]),
    .ci(ci),
    .s(s[3:0]),
    .co(cmid[0])
);

// TODO:继续例化 Adder_LookAhead4 模块并正确连接

endmodule

下面是一些你可能会用到的代码:

1
2
3
4
5
6
7
assign  C[0] = G[0] | ( P[0] & ci );
assign  C[1] = G[1] | ( P[1] & G[0] ) | ( P[1] & P[0] & ci );
assign  C[2] = G[2] | ( P[2] & G[1] ) | ( P[2] & P[1] & G[0] ) | ( P[2] & P[1] & P[0] & ci );
assign  C[3] = G[3] | ( P[3] & G[2] ) | ( P[3] & P[2] & G[1] ) | ( P[3] & P[2] & P[1] & G[0] ) | ( P[3] & P[2] & P[1] & P[0] & ci );
assign  C[4] = G[4] | ( P[4] & G[3] ) | ( P[4] & P[3] & G[2] ) | ( P[4] & P[3] & P[2] & G[1] ) | ( P[4] & P[3] & P[2] & P[1] & G[0] ) | ( P[4] & P[3] & P[2] & P[1] & P[0] & ci );
assign  C[5] = G[5] | ( P[5] & G[4] ) | ( P[5] & P[4] & G[3] ) | ( P[5] & P[4] & P[3] & G[2] ) | ( P[5] & P[4] & P[3] & P[2] & G[1] ) | ( P[5] & P[4] & P[3] & P[2] & P[1] & G[0] ) | ( P[5] & P[4] & P[3] & P[2] & P[1] & P[0] & ci );
assign  C[6] = G[6] | ( P[6] & G[5] ) | ( P[6] & P[5] & G[4] ) | ( P[6] & P[5] & P[4] & G[3] ) | ( P[6] & P[5] & P[4] & P[3] & G[2] ) | ( P[6] & P[5] & P[4] & P[3] & P[2] & G[1] ) | ( P[6] & P[5] & P[4] & P[3] & P[2] & P[1] & G[0] ) | ( P[6] & P[5] & P[4] & P[3] & P[2] & P[1] & P[0] & ci );
Adder_tb.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
module Adder_tb();
    // ...
    initial begin
        a=32'hffff; b=32'hffff; ci=1'b1;
        #10;
        a=32'h0f0f; b=32'hf0f0; ci=1'b1;
        #10;
        a=32'h1234; b=32'h4321; ci=1'b0;
    end
    // ...
endmodule

提示

tb 文件仅作参考,我们鼓励大家自行设计合适的信号输入,这对你的能力提升也会有很大的帮助。本题需要完成的内容有:

  • 补全 Adder_LookAhead4 模块的相关代码,基于此在 Adder 模块中使用层次扩展的方法得到 32 位加法器;
  • 自己编写 Adder_LookAhead8 模块;
  • 基于 Adder_LookAhead8 模块在 Adder 模块中使用层次扩展的方法得到 32 位加法器。

题目 2:ALU(4 分)

请参照实验文档中对 ALU 及其部件的描述,设计并编写一个完整的 32 位 ALU,支持表格中列出的全部十二种运算。其中加法、减法、有符号比较、无符号比较四种运算需要基于超前进位加法器或自己设计的模块实现,而不能直接使用相应的运算符;其他的运算可以使用 Verilog 自带的运算符实现。

本题的分数组成如下:

  • 加法运算:上一题已经实现,故不额外算分
  • 减法运算:0.5 分
  • 有符号比较:1 分
  • 无符号比较:0.5 分
  • 其他运算:1 分
  • 信号选择:1 分

非 32 位的 ALU 会被酌情扣分。

本题要求自行编写仿真文件进行测试,代码框架与部分仿真信号输入如下:

ALU.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
module ALU(
    input                   [31 : 0]        src0, src1,
    input                   [11 : 0]        sel,
    output                  [31 : 0]        res
);
// Write your code here
wire [31:0] adder_out;
wire [31:0] sub_out;
wire [0 :0] slt_out;
wire [0 :0] sltu_out;

Adder adder(
    .a(src0),
    .b(src1),
    .ci(1'B0),
    .s(adder_out),
    .co()
);

AddSub sub(
    .a(src0),
    .b(src1),
    .out(sub_out),
    .co()
);

Comp comp(
    .a(src0),
    .b(src1),
    .ul(sltu_out),
    .sl(slt_out)
);

// TODO:完成 res 信号的选择

// End of your code
endmodule
ALU_tb.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module ALU_tb();
    //...
    initial begin
        src0=32'hffff; src1=32'hffff; sel=12'h001;
        repeat(11) begin
            #30 sel = sel << 1;
        end
    end
    //...
endmodule

你需要参考教程,编写实现 Adder、AddSub、Comp 模块。

提示

针对无符号比较,你可以多尝试一些样例以测试实现是否正确。如:32'hffff与32'h1, 32'h1与32'h0。

选择性必做内容

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

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

题目 1:可视化 ALU(2 分)

本小题中的 ALU 限制到 3 位,与 Lab3 中的七段数码管显示模块结合,实现一个可视化的 ALU。

其中,用 sw[5:3]sw[2:0] 分别表示输入 src0、src1,使用 sw[7:6] 代表 sel 选择 ALU 运算,输出结果显示在七段数码管上。其中, sw[7:6]=2'b00 时表示加法;sw[7:6]=2'b01 时表示减法;sw[7:6]=2'b10 时表示有符号比较;sw[7:6]=2'b11 时表示无符号比较。

此外,考虑到 ALU 是一个组合逻辑单元,因此其运算结果需可以根据输入的变化实时更新。比如:sw[7:0]=8'b00_100_011 时,七段数码管显示 7;拨动开关使得 sw[7:0]=8'b11_100_011 时,七段数码管显示 0。

本题要求上板测试,代码框架与对应部分的限制文件如下:

Top.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
module Top(
    input                   [ 0 : 0]        clk,
    input                   [ 0 : 0]        rst,

    input                   [ 2 : 0]        src0, src1,
    input                   [ 1 : 0]        sel,

    output      reg         [ 3 : 0]        seg_data,
    output      reg         [ 2 : 0]        seg_an
);
// Write your code here

// End of your code
endmodule
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { clk }];

set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports { rst }];

set_property -dict { PACKAGE_PIN D14   IOSTANDARD LVCMOS33 } [get_ports { src1[0] }];
set_property -dict { PACKAGE_PIN F16   IOSTANDARD LVCMOS33 } [get_ports { src1[1] }];
set_property -dict { PACKAGE_PIN G16   IOSTANDARD LVCMOS33 } [get_ports { src1[2] }];
set_property -dict { PACKAGE_PIN H14   IOSTANDARD LVCMOS33 } [get_ports { src0[0] }];
set_property -dict { PACKAGE_PIN E16   IOSTANDARD LVCMOS33 } [get_ports { src0[1] }];
set_property -dict { PACKAGE_PIN F13   IOSTANDARD LVCMOS33 } [get_ports { src0[2] }];
set_property -dict { PACKAGE_PIN G13   IOSTANDARD LVCMOS33 } [get_ports { sel[0] }];
set_property -dict { PACKAGE_PIN H16   IOSTANDARD LVCMOS33 } [get_ports { sel[1] }];

set_property -dict { PACKAGE_PIN A14   IOSTANDARD LVCMOS33 } [get_ports { seg_data[0] }];
set_property -dict { PACKAGE_PIN A13   IOSTANDARD LVCMOS33 } [get_ports { seg_data[1] }];
set_property -dict { PACKAGE_PIN A16   IOSTANDARD LVCMOS33 } [get_ports { seg_data[2] }];
set_property -dict { PACKAGE_PIN A15   IOSTANDARD LVCMOS33 } [get_ports { seg_data[3] }];
set_property -dict { PACKAGE_PIN B17   IOSTANDARD LVCMOS33 } [get_ports { seg_an[0] }];
set_property -dict { PACKAGE_PIN B16   IOSTANDARD LVCMOS33 } [get_ports { seg_an[1] }];
set_property -dict { PACKAGE_PIN A18   IOSTANDARD LVCMOS33 } [get_ports { seg_an[2] }];

提示

Verilog 编程很多时候就是一个「搭积木」的过程,本题中所需要的「积木」已在之前的实验中搭好,即本次实验完成的 ALU 模块与 Lab3 完成的 Segment 模块,大家需要做的仅仅是考虑如何将已有的「积木」连接起来。

题目 2:可视化 ALU-plus(3 分)

本题是上一题的升级版,上一题中,限于开关的数目,我们的 ALU 只能可视化三位运算;本题通过用两位开关选择不同的输入模式,实现分时复用,从而可视化了五位运算。此外,前一题是组合逻辑电路,所有开关的输入都会即时地影响运算的结果;而这里则是一个时序逻辑,我们会在不同次输入中分别改变并存储 src0、src1 以及 sel,通过多个周期完成输入与运算结果的输出。

具体的说,我们有以下的功能要求:

enable 信号与按钮相连,rst 信号与 sw[7] 相连,ctrl 信号与 sw[6:5] 相连,in 信号与 sw[4:0] 相连。在 enable 为高电平时才进行信号输入,ctrl[1:0] 用于选择本次信号输入的模式:

  • ctrl=2'b00 时,按下 enable,表示选择 ID = in[4:0] 的 ALU 运算,in[4:0] 不在 1~12 范围内的视为零运算,即无论操作数 src0 和 src1 的值,输出结果都为 0。

  • ctrl=2'b01 时,按下 enable,表示将 src0 的值改为 in[4:0]

  • ctrl=2'b10 时,按下 enable,表示将 src1 的值改为 in[4:0]

  • ctrl=2'b11 时,按下 enable,表示输出结果,通过 seg_anseg_data 以七段数码管的形式显示出来。(注意:数码管的输出数据 output_data 只在此时更新)

比如:ctrl 设为 2'b00in[4:0] 设为 5'b01001,按下 enable,选择了逻辑左移运算;ctrl 设为 2'b01in[4:0] 设为 5'b01011,按下 enable,此时 src0 的值变为 5'b01011ctrl 设为 2'b10in[4:0] 设为 5'b00011,按下 enable,此时 src1 的值变为 5'b00011ctrl 设为 2'b11,按下 enable,此时输出结果 5'b11000,并通过 seg_anseg_data 在七段数码管上以十六进制的形式显示出来。

疑问

七段数码管的输出 output_data 不是 32 位吗?是的!你只需要在运算结果前面的位补 0 即可使用 Segment 完成输出。

该设计的参考电路图如下:

Image title
可视化 ALU

本题按照 ctrl[1:0] 的不同情况的功能完整性给分:2'b00 的功能 1 分,2'b012'b10的功能 1 分,2'b11 的功能 1 分。

本题要求仿真和上板测试,代码框架与部分仿真信号输入及对应部分的限制文件如下:

Top.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
module Top(
    input                   [ 0 : 0]        clk,
    input                   [ 0 : 0]        rst,
    input                   [ 0 : 0]        enable,

    input                   [ 4 : 0]        in,
    input                   [ 1 : 0]        ctrl,

    output                  [ 3 : 0]        seg_data,
    output                  [ 2 : 0]        seg_an
);
// Write your code here

// End of your code
endmodule
Top_tb.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Top_tb();
    //...
    initial begin
        in=5'b10110; ctrl=2'b01; enable=1'b1;
        #10;
        enable=1'b0;
        #30;
        in=5'b01101; ctrl=2'b10; enable=1'b1;
        #10;
        enable=1'b0;
        #30;
        in=5'b00010; ctrl=2'b00; enable=1'b1;
        #10;
        enable=1'b0;
        #30;
        ctrl=2'b11; enable=1'b1;
        #10;
        in=5'b01101; ctrl=2'b01; enable=1'b0;
        #30 ctrl=2'b11; enable=1'b1;
    end
    //...
endmodule
set_property -dict { PACKAGE_PIN E3    IOSTANDARD LVCMOS33 } [get_ports { clk }];

set_property -dict { PACKAGE_PIN B18   IOSTANDARD LVCMOS33 } [get_ports { enable }];

set_property -dict { PACKAGE_PIN D14   IOSTANDARD LVCMOS33 } [get_ports { in[0] }];
set_property -dict { PACKAGE_PIN F16   IOSTANDARD LVCMOS33 } [get_ports { in[1] }];
set_property -dict { PACKAGE_PIN G16   IOSTANDARD LVCMOS33 } [get_ports { in[2] }];
set_property -dict { PACKAGE_PIN H14   IOSTANDARD LVCMOS33 } [get_ports { in[3] }];
set_property -dict { PACKAGE_PIN E16   IOSTANDARD LVCMOS33 } [get_ports { in[4] }];
set_property -dict { PACKAGE_PIN F13   IOSTANDARD LVCMOS33 } [get_ports { ctrl[0] }];
set_property -dict { PACKAGE_PIN G13   IOSTANDARD LVCMOS33 } [get_ports { ctrl[1] }];
set_property -dict { PACKAGE_PIN H16   IOSTANDARD LVCMOS33 } [get_ports { rst }];

set_property -dict { PACKAGE_PIN A14   IOSTANDARD LVCMOS33 } [get_ports { seg_data[0] }];
set_property -dict { PACKAGE_PIN A13   IOSTANDARD LVCMOS33 } [get_ports { seg_data[1] }];
set_property -dict { PACKAGE_PIN A16   IOSTANDARD LVCMOS33 } [get_ports { seg_data[2] }];
set_property -dict { PACKAGE_PIN A15   IOSTANDARD LVCMOS33 } [get_ports { seg_data[3] }];
set_property -dict { PACKAGE_PIN B17   IOSTANDARD LVCMOS33 } [get_ports { seg_an[0] }];
set_property -dict { PACKAGE_PIN B16   IOSTANDARD LVCMOS33 } [get_ports { seg_an[1] }];
set_property -dict { PACKAGE_PIN A18   IOSTANDARD LVCMOS33 } [get_ports { seg_an[2] }];

提示

本题由于需要支持 enable,而 FPGAOL 上只有一个按钮,故将 rst 连接到最左侧的开关上。

题目 3:移位器(3 分)

ALU 中包含三种移位运算,分别是逻辑左移、逻辑右移和算术右移。此前,我们是使用 Verilog 中自带的符号实现的,而在本小题中,我们需要自己去实现移位操作。在这里,我们只实现逻辑右移 SRL 和算术右移 SRA。

可以注意到,常数位移事实上可以通过位拼接来实现,举例来说,对于 src0[31:0],逻辑左移 3 位,那么 src0<<3 = {src0[28:0],3'b0};逻辑右移 2 位,那么 src0>>2 = {2'b0,src0[31:2]};算术右移 2 位,那么 src0>>>2 = {{2{src0[31]}},src0[31:2]}

对应于前面的超前进位加法器和层次扩展的设计,移位器也有高效但消耗资源、耗时但节省资源的两种实现方式:

  1. 我们可以枚举所有的右操作数,并通过对应的位拼接来实现;注意到右操作数的范围是 0~31,所以我们需要枚举 32 种情况。这种实现方式只需要经过一次查找表和一个位拼接的延迟,就可以查出对应的结果,但是需要消耗大量的资源,共需要 32 个位拼接单元。

  2. 我们也可以按照右操作数 src1[4:0] 二进制的各位数字,对 src0 连续地进行 16、8、4、2、1 移位。举例来说,src1[4:0]=5'b10110 时,可以用位拼接对 src0 移 16 位,再移 4 位,最后移 2 位。这种实现方式最多需要经过五次位拼接操作和多选器的延迟,耗时相对较长,但只需要 5 个位拼接单元,资源压力较小。(类似快速幂的思路)

请你尝试实现这两种移位器,并尝试类似加法器的设计过程,对两种方法做一结合,得到在时间和空间上都占优的方法。

本题以上两种实现方式各占 1 分,结合两种实现方式占 1 分。结合两种方式的实现要求最多经过 3 次位拼接和多选器的延迟,使用不多于 12 个位拼接单元。逻辑右移与算术右移的分数各占一半。

本题要求自行编写仿真文件进行测试,代码框架与部分仿真信号输入如下:

Shifter.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module Shifter(
    input                   [31 : 0]        src0,
    input                   [ 4 : 0]        src1,
    output                  [31 : 0]        res1,       //逻辑右移
    output                  [31 : 0]        res2        //算术右移
);
// Write your code here

// End of your code
endmodule
Shifter_tb.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
module Shifter_tb();
    //...
    initial begin
        src0=32'h1234; src1=5'h00000;
        repeat(32) begin
            #50 src1 = src1 + 1;
        end
    end
    //...
endmodule

选做内容

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

题目 1:性能比较

本次实验中,我们从理论上分析了多种加法器、移位器的实现方式的性能比较,那么,在实际中他们的差距有多大呢?可以参考 lab7 的电路性能分析小节(点击电路性能分析跳转),利用vivado测试比较各种电路的最大工作频率与资源使用情况。

题目 2:倍数检测器——再再临?

提醒:本题有一定难度,想要挑战自己的同学,可以以此稍作消遣。

在前面的两次实验练习中,我们分别尝试了使用数论和状态机的方法,用 Verilog 实现了倍数检测器。这两种方法各有劣势:数论方法的电路复杂度和延迟相对较高,而状态方法使用的周期数较多。结合我们本次实验中的权衡时间与空间的设计思想,你能不能将此二者做一结合呢?

本题要求能够在 16 周期内处理 128 位二进制数的 5 的倍数检测,或者甚至计算出余数。

代码框架如下:

MOD5.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
module MOD5(
    input                               clk,            // 时钟信号
    input                               rst,            // 复位信号,使状态机回到初始态
    input               [128:0]         src,            // 输入数据
    input                               src_valid,      // 表明输入结果是否有效
    output      reg                     res,            // 输出结果
    output      reg     [2:0]           mod,            // 输出余数(可选)
    output      reg                     res_valid       // 表明输出结果是否有效
);
// Write your code here

// End of your code
endmodule

题王归来,众题跪拜,「三周之期已到,恭迎题王归位」「我不会再临了,这就是我最后的波纹」

真的不会再临了吗?


最后更新: December 24, 2023

评论