跳转至

实验练习

说明

  1. 每一次实验我们会为大家准备若干练习题目。不同的题目有着不同的难度,请大家量力而行;
  2. 你可以查阅任何开源资料完成实验练习,但不能直接抄袭。一经发现,我们将取消所有抄袭参与者本次的实验成绩。情节特别严重的,我们将按照学校的有关规定进行处理。
  3. 部分题目我们提供了代码框架,你需要在框架指定的位置完成代码内容。你也可以自由修改框架代码,但需要根据题目要求完成练习。

必做内容

每一名同学都需要完成必做部分的内容,其中题目4,5中任选一题完成即可。

题目 1:if 语句与锁存器(1 分)

本题来自于 OJ ID-131。你可以在该平台上验证自己的设计。

有时候,语法正确的代码并不一定能产生功能正常的电路,一般来说都是因为不小心引入了锁存器造成的。例如下面的 Verilog 语句:

1
2
3
4
5
6
7
8
9
always @(*) begin
    if (cpu_overheated)
        shut_off_computer = 1;
end

always @(*) begin
    if (~arrived)
        keep_driving = ~gas_tank_empty;
end

Image title

为消除锁存器,我们应当使组合逻辑过程块中的条件完备,即 if 语句后应加上 else 语句。试修改上述两段代码,以消除锁存器。

输入格式

  • 输入信号 cpu_overheated, 位宽 1bit,控制 shut_off_computer 信号。
  • 输入信号 arrived, 位宽 1bit,控制 keep_driving 信号。
  • 输入信号 gas_tank_empty,位宽 1bit, 作为 keep_driving 的输入信号之一。

输出格式

  • 输出信号 shut_off_computer,位宽 1bit,要求 cpu_overheated 为真时输出 1'b1,反之输出 1'b0。
  • 输出信号 keep_driving,位宽 1bit,要求 arrived 为假时输出 ~gas_tank_empty,反之输出 1'b0。

代码框架如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
module top_module (
    input                       cpu_overheated,
    output  reg                 shut_off_computer,
    input                       arrived,
    input                       gas_tank_empty,
    output  reg                 keep_driving
);
    // Edit the code below
    always @(*) begin
        if (cpu_overheated)
            shut_off_computer = 1'b1;
    end

    always @(*) begin
        if (~arrived)
            keep_driving = ~gas_tank_empty;
    end
endmodule

题目 2:case 语句与锁存器(1 分)

本题来自于 OJ ID-135。你可以在该平台上验证自己的设计。

假设您正在构建一个电路来处理游戏中 PS/2 键盘上的扫描码。现在给定接收到的扫描码的最后两个字节,您需要指示是否按下了键盘上的一个箭头键。这涉及到一个相当简单的映射,包含五种可能的情况:上、下、左、右,以及不属于任何一种。

Image title

您的电路有一个 16bits 输入和四个 1bit 输出,该电路识别这四个扫描码并确认正确的输出。为避免产生锁存,必须在所有情况下为四个输出指定一个值。这可能涉及许多不必要的输入。解决这个问题的一个简单方法是在 case 语句之前为输出分配一个『默认值』:

1
2
3
4
5
6
always @(*) begin
    up = 1'b0; down = 1'b0; left = 1'b0; right = 1'b0;
    case (scancode)
        ... // Set to 1 as necessary.
    endcase
end

这种类型的代码确保在所有可能的情况下为输出赋值(0),除非 case 语句重写赋值。这也意味着 default 项变得不必要。请试着将上述代码补充完整,以实现期望的电路功能。

代码框架如下:

1
2
3
4
5
6
7
8
9
module top_module (
    input       [15:0]                  scancode,
    output reg                          left,
    output reg                          down,
    output reg                          right,
    output reg                          up
); 
// Write your codes here.
endmodule

题目 3:计数器 Pro(3 分)

这是我们在实验教程中展示的计数器代码:

Counter.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
module Counter #(
    parameter   MAX_VALUE = 8'd100
)(
    input                   clk,
    input                   rst,
    output                  out
);

reg [7:0] counter;
always @(posedge clk) begin
    if (rst)
        counter <= 0;
    else begin
        if (counter >= MAX_VALUE)
            counter <= 0;
        else
            counter <= counter + 8'b1;
    end
end

assign out = (counter == MAX_VALUE);
endmodule

现在,我们需要对其进行一点改进。

  1. 现在的计数器复位值是 0,最小值是 0,最大值是 MAX_VALUE。请修改代码,使得计数器的最小值也可以由模块参数 MIN_VALUE 指定。

  2. 在 1 的基础上,增加一个 1bit 输入信号 enable 用于控制计数器的工作状态。要求:enable 为高电平时,计数器在 MIN_VALUE 至 MAX_VALUE 之间正常工作;enable 为低电平时,counter 变量复位并保持在 0。从 enable 变为低电平开始到 counter 复位并保持在 0 的间隔不超过三个时钟周期。此外,rst 信号的优先级应当高于 enable 信号。

    你需要结合仿真波形证明自己设计的正确性。例如:下面是助教的仿真文件与结果:

    q3_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
    module q3_tb();
    reg clk, rst, en;
    wire out_TA;
    initial begin
        clk = 0; rst = 1; en = 0;
        #10;
        rst = 0;
        #10;
        en = 1;
        #20;
        en = 0;
        #20;
        en = 1;
        #20;
        rst = 1;
        #20;
        rst = 0;
        #200;
        en = 0;
    end
    always #5 clk = ~clk;
    Counter #(
        .MIN_VALUE(8'd10), 
        .MAX_VALUE(8'd13)
    ) counter (
        .clk(clk),
        .rst(rst),
        .enable(en),
        .out(out_TA)
    );
    endmodule
    

    Image title

题目 4:生成波形(2 分)

请编写仿真文件,生成下图所示的波形。时钟周期设定为 10ns,变量 bus 的位宽自行指定(图中存在偏差,实际上变量 bus 应在时钟上升沿变化)。图中的阴影部分代表电路状态不确定,你可以自行指定其状态。

本题的分数组成如下:

  • 成功绘制出波形图:1分
  • 不使用对时间打表的方式(zero除外)完成:1分

Image title

需要提示?
  • zero是一个会归零a的信号。
  • 所有跳变都发生在clk的上升沿。
  • 回忆一下计数器的思想。
  • 或许应该尝试一下forrepeat循环?

题目 5:1的个数pro(2 分)

设计一个Verilog模块,统计32位输入数据 a 中从第 b 位到第 c 位(包含两端)这个区间内1的个数。其中 b 和 c 都是5位无符号数,且保证 b < c。

注意:位索引从0开始,即第0位是最低位(LSB),第31位是最高位(MSB)。

模块接口

CountOnes.v
1
2
3
4
5
6
module CountOnes(
    input       [31:0]         a,
    input       [4:0]          b,
    input       [4:0]          c,
    output reg  [5:0]         out
);

输入输出样例: 例:a = 32'b1010_1100_0011_1100_1010_1100_0011_1100 b = 5'd2, c = 5'd7 a[2:7] = 6'b001111 out = 5'd4 下面是供大家检查使用的testbench

CountOnes_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
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
   `timescale 1ns / 1ps

module tb_CountOnes();
    reg [31:0] a;
    reg [4:0] b, c;
    wire [5:0] out;

    integer passed = 0;
    integer failed = 0;
    integer test_num = 0;

    CountOnes uut (.a(a), .b(b), .c(c), .out(out));

    function automatic [5:0] manual_count;
        input [31:0] data;
        input [5:0] start, end_bit;
        integer i;
        reg [5:0] count;
        begin
            count = 0;
            for (i = start; i <= end_bit; i = i + 1) begin
                if (data[i]) count = count + 1;
            end
            manual_count = count;
        end
    endfunction

    task run_test;
        input [31:0] test_a;
        input [5:0] test_b, test_c;
        reg [5:0] manual_result;
        reg [5:0] expected;
        begin
            test_num = test_num + 1;
            a = test_a;
            b = test_b;
            c = test_c;
            #20;

            manual_result = manual_count(test_a, test_b, test_c);
            expected = manual_result; // 预期结果就是手动计算的结果

            $display("Test %0d: a=32'h%h, b=%0d, c=%0d", 
                     test_num, test_a, test_b, test_c);
            $display("  Expected: %0d, Actual: %0d", 
                     expected, out);

            if (out === expected) begin
                $display("  PASS");
                passed = passed + 1;
            end else begin
                $display("  FAILED");
                failed = failed + 1;
            end
            $display("");
        end
    endtask

    // 生成随机但保证b<=c的测试
    task random_test;
        integer i;
        reg [31:0] rand_a;
        reg [4:0] rand_b, rand_c;
        begin
            for (i = 0; i < 30; i = i + 1) begin
                rand_a = $random;
                rand_b = $random & 5'b11111; // 确保在0-31范围内
                rand_c = $random & 5'b11111;

                // 保证b<=c
                if (rand_b > rand_c) begin
                    rand_b = rand_b ^ rand_c;
                    rand_c = rand_b ^ rand_c;
                    rand_b = rand_b ^ rand_c;
                end

                run_test(rand_a, rand_b, rand_c);
            end
        end
    endtask

    initial begin
        $display("CountOnes Test Suite (Randomized with b<=c guarantee)");
        $display("====================================================");

        // 特定边界测试用例
        $display("--- Specific Boundary Tests ---");
        // 1. 全0测试
        run_test(32'h00000000, 0, 31);

        // 2. 全1测试
        run_test(32'hFFFFFFFF, 0, 31);

        // 3. 单个位测试 - 最低位
        run_test(32'h00000001, 0, 0);

        // 4. 单个位测试 - 最高位
        run_test(32'h80000000, 31, 31);

        // 5. 交替模式测试
        run_test(32'hAAAAAAAA, 0, 31);

        // 6. 小范围测试
        run_test(32'h0000000F, 0, 3);

        // 7. 中间范围测试
        run_test(32'h0000FF00, 8, 15);

        // 8. 相同起始结束位置
        run_test(32'h00000008, 3, 3);

        // 9. 部分范围测试 - 低16位
        run_test(32'h0000FFFF, 0, 15);

        // 10. 部分范围测试 - 高16位
        run_test(32'hFFFF0000, 16, 31);

        $display("--- Randomized Tests ---");
        // 随机测试用例
        random_test();

        $display("TEST SUMMARY");
        $display("=========================================");
        $display("Total tests:  %0d", test_num);
        $display("Passed:       %0d", passed);
        $display("Failed:       %0d", failed);

        if (failed == 0) begin
            $display("SUCCESS: All tests passed!");
        end else begin
            $display("ERROR: %0d tests failed!", failed);
        end

        $finish;
    end
endmodule

`

选择性必做内容

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

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

题目 1:奇怪的代码(2 分)

下面是一段被加密的代码。请自行编写仿真文件,设计特定的输入,测试该模块的功能。

Foo.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
module Foo (
    input                       clk,
    input                       EP6A,
    input [7:0]                 PALCA_AC,
    input [7:0]                 __ACACQ,
    input                       PLAOQC_A,
    output [7:0]                EA__ACAC_ANV
);
reg [7:0] PAKC_ACTRW, OJANCAXCE, _QW_AC__QC, PAL__AWDK;
localparam RTADX=-1;
always@(posedge clk)begin
if(EP6A)begin PAKC_ACTRW<=PALCA_AC;OJANCAXCE<=__ACACQ;_QW_AC__QC<=-RTADX<<3&PAL__AWDK;end
else if(PLAOQC_A)begin PAKC_ACTRW<=OJANCAXCE;OJANCAXCE<=PAKC_ACTRW-(~OJANCAXCE-RTADX);end
else _QW_AC__QC<=PAL__AWDK;end
always@(posedge clk)begin
if (EP6A)PAL__AWDK<=PAL__AWDK+_QW_AC__QC;
else PAL__AWDK<=(RTADX>>31)-1;end
assign EA__ACAC_ANV=OJANCAXCE|{8{PAL__AWDK}};
endmodule

请在实验报告中指出该模块每个输入、输出端口的功能,并附上一些你的推理过程。

提示:位宽为 8bits 的信号为数据信号,其余为控制信号。

题目 2:计数器 天上天下天地无双版(2分)

格雷码是一种编码系统,相邻两位之间只有1个比特的区别,相较于普通的二进制编码,格雷码能避免出现竞争-冒险现象,更加稳定。格雷码甚至能在解决汉诺塔问题、遗传算法理论等地方得到应用。

请设计一个计数器模块,以 4 位格雷码计数,每个时钟周期增加 1 ,到达最大值后归零,并在这个周期内给out信号置1,长度为1个周期,不断循环。

模块的部分信息如下:

GrayCounter.v
1
2
3
4
5
6
7
8
module GrayCounter(
input                               clk,
input                               rst,
output                              out        
); 
    // your code here

endmodule
需要提示?

可以对4个比特一个一个分析,不一定要找出通用的规律。

题目 3:『众数』统计(3 分)

请设计一个模块,实时输出对于任意给定数目 8bits 无符号整数中出现次数超过一半的数(如果不存在则可以输出序列中任意一个数)。请结合 Vivado 仿真验证自己设计的正确性。方便起见,我们假定输入的数个数不超过 100。

模块的部分信息如下:

FindMode
1
2
3
4
5
6
7
8
9
module FindMode (
    input                               clk,
    input                               rst,
    input                               next,
    input       [7:0]                   number,
    output reg  [7:0]                   out
); 
// Your codes here.
endmodule

其中:

  • rst 有效时会清除先前的记录,视作从下一个数开始为第一个数;
  • next 有效时代表此时通过 number 输入了一个数。输入过程是时钟同步的;
  • out 输出截至目前输入的结果。

例如:对于输入序列,

10, 20, 30, 10, 10, 20, 30, 30, 30, 10, 10, 10

一个可接受的输出序列如下:

10, 20, 30, 10, 10, 10, 30, 30, 30, 30, 30, 10

对应的仿真波形如下:

Image title

评论