跳转至

实验练习

说明

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

提醒

为了减少检查的时间,请大家提前烧写好题目所需的 Bit 流文件。我们不接受因为烧写时间过长而无法完成检查的理由。

必做内容

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

题目 1:开关与 LED(2 分)

请编写 Verilog 代码,要求当拨动开关时,对应的 LED 灯便会亮起/关闭。开关与 LED 灯的对应关系如下表所示:

sw[0] - led[7]
sw[1] - led[6]
sw[2] - led[5]
sw[3] - led[4]
sw[4] - led[3]
sw[5] - led[2]
sw[6] - led[1]
sw[7] - led[0]

你的代码应当能在 FPGAOL 平台上运行。下面是我们提供的代码框架:

Top.v
1
2
3
4
5
6
module Top (
    input   [7:0]                       sw,
    output  [7:0]                       led
);
// Write your codes here.
endmodule

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

在 Lab2 中,我们已经有了一个简易计数器的代码:

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

请基于该模块,在 FPGAOL 上实现一个简易的闪光器,让 LED[7:0] 以 1Hz 的频率闪烁(亮 0.5s,灭 0.5s,以此循环)。提示:平台上的时钟频率为 100MHz,你可能需要修改部分变量的位宽。

题目 3:七段数码管(3 分)

本小题中我们将带大家逐步学习如何点亮七段数码管。

正如我们在教程中介绍的那样,在有多个数码管的情况下,我们通常采用分时复用的方式轮流点亮每个数码管,并保证在同一时间只会有一个数码管被点亮。为了实现这一功能,你需要完成下面的内容:

  1. 计时切换当前被点亮的数码管编号。我们使用一个 3bits 位宽变量 seg_id 标记当前点亮的是哪一个数码管。请借助计数器以 400Hz 的频率让 seg_id 增加 1。这样,每 \(\frac{1}{400}=0.0025\)s seg_id 就会变化一次,且由于位宽只有 3bits,因此变化的范围只有 0 ~ 7,恰好对应 8 个数码管。你可以参考下面的代码框架实现这项功能:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    reg [31:0] counter;
    always @(posedge clk) begin
        // Update counter
    end
    
    reg [2:0] seg_id;
    always @(posedge clk) begin
        // Update seg_id
    end
    
  2. 根据编号确定数码管显示的内容。我们希望在 8 个数码管上显示一个 32bits 的数据 output_data,而每个数码管上只能显示 4bits 的数值 seg_data。因此,我们需要确定每个数码管被点亮时需要显示的数据。请根据下面的代码框架确定 seg_data 的产生逻辑。提示:应当让编号小的数码管显示低位数据。

    1
    2
    3
    4
    5
    6
    7
    wire [31:0] output_data;
    always @(*) begin
        seg_data = 0;
        seg_an = seg_id;    // <- Same for all cases
    
        // Update seg_data according to seg_id. Hint: Use "case".
    end
    

请根据以上内容将下面的数码管显示模块 Segment.v 补充完整。模块的输入输出端口规定如下:

Segment.v
1
2
3
4
5
6
7
8
module Segment(
    input                       clk,
    input                       rst,
    input       [31:0]          output_data,

    output reg  [ 3:0]          seg_data,
    output reg  [ 2:0]          seg_an
);

其中 rst 信号用于对模块内的信号进行复位操作。你可以将 Segment 模块看作一个硬件 API,外部模块只需要将数据交付到 output_dataSegment 模块就会自动将其显示在七段数码管上。

为了检验该模块的正确性,请在项目中添加下面的 Top 模块,并在 FPGAOL 上运行。

Top.v
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
module Top(
    input                   clk,
    input                   btn,
    output [2:0]            seg_an,
    output [3:0]            seg_data
);
Segment segment(
    .clk(clk),
    .rst(btn),
    .output_data(32'h22111234),     // <- 改为你学号中的 8 位数字
    .seg_data(seg_data),
    .seg_an(seg_an)
);
endmodule

对应的部分约束文件如下:

## FPGAOL HEXPLAY

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] }];

选择性必做内容

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

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

题目 1:三级寄存器边沿检测(1 分)

在教程中我们使用两级寄存器进行边沿检测。为了提高系统的稳定性,请在此基础上完成使用三级寄存器边沿检测的 Verilog 代码。代码框架如下:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module edge_capture(
    input             clk,
    input             rst,

    input             sig_in,  // Signal input
    output            pos_edge,
    output            neg_edge
);
// Your codes should start from here

// End of your codes
endmodule

你需要结合仿真波形证明自己设计的正确性。

题目 2:Counter 青春版(2 分)

Logisim 中提供了一些时序组件,我们可以借助这些组件实现 Counter 模块的功能。

  1. Logisim 项目窗格中 Memory 目录下提供了一个名为 Register 的组件,它可以实现基本的存储功能。

    Image title

    与之对应的 Verilog 代码如下:

    Register.v
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    14
    15
    16
    module Register #(
        parameter WIDTH = 8
    ) (
        input                                       clk,
        input                                       rst,
        input                                       en,
        input       [WIDTH-1: 0]                    D,
        output reg  [WIDTH-1: 0]                    Q
    );
    always @(posedge clk or posedge rst) begin
        if (rst)        // 异步复位
            Q <= 0;
        else if (en)    // 时序电路里可以不补全 if 逻辑
            Q <= D;
    end
    endmodule
    

    请在 Logisim 中按下图的方式摆放电路,并正确连接信号,实现一个基本的 8bits 位宽寄存器功能。注:Register 组件不需要修改任何参数。InputOutput 对应位宽为 8bits 的输入输出端口。

    Image title

  2. 接下来,我们将添加计数功能。你需要在电路里添加一个加法器(Arithmetic 目录下的 Adder,注意位宽),并让输出端 Q 经过加法器后重新连接到输入端 D。加法器的另一个输入可以连接到输入 Input,也可以直接连接常量 1(Wiring 目录下的 Constant,注意位宽)。现在,你的电路应当能够在 clk 的上升沿让 Output 自增 1

题目 3:带有掩码的数码管(2 分)

FPGAOL 上的数码管共有 8 个。在必做部分的题目 3 中,我们的数码管会让这 8 个数码管全部亮起。现在,请修改 Segment 模块,为前 7 个数码管添加掩码控制功能。

补充介绍:掩码

掩码是一串二进制代码,用于对目标字段进行按位与运算,屏蔽当前的输入位。什么意思呢?假定当前的输入为 In = 8'h4f,如果我们的掩码 Mask = 8'b1111_0000,那么电路实际得到的输入是 In & Mask = 8'h40,低 4 位就被『掩盖』了。

在这里,掩码并不是一个用于『掩盖』的 Mask,而是特指按位使能控制的输入信号。

具体而言,Segment 模块接收一个 8bits 位宽的输入信号 output_valid。当 output_valid[i] 为 1 时,代表编号为 i 的数码管应当亮起(其中 \(i\ne0\),数码管全部亮起对应 output_valid = 8'hffoutput_valid = 8'hfe)。

修改后的模块输入如下:

1
2
3
4
5
6
7
8
9
module Segment(
    input                       clk,
    input                       rst,
    input       [31:0]          output_data,
    input       [ 7:0]          output_valid,

    output reg  [ 3:0]          seg_data,
    output reg  [ 2:0]          seg_an
);
Tips

由于平台的设置,任何时刻一定至少有一个数码管会亮起,因为 seg_an 信号只有 3bits,无论如何设置都会对应一个数码管的编号。简单起见,我们让 0 号数码管保持常亮。这也就是为什么只有前七个数码管具有掩码控制功能。

请将 output_valid 信号与开关相连,对应关系为 output_valid[i] = sw[i]\(i\ne0\))。这样,我们就可以通过拨动开关控制对应的数码管亮/灭情况。数码管显示的数据依然为自己的学号。

例如:下面是助教实现的效果:

Image title

Image title

提示:理论上你只需要修改 seg_an 信号和 seg_data 信号的逻辑就可以实现掩码功能。此外,特定时刻『某编号数码管熄灭』可以通过『让 0 号数码管亮起』实现。

选做内容

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

题目 1:Counter 青春无极限版

在选择性必做的题目 2 中,你已经在 Logisim 中实现了一个简易的计数器,但该计数器需要累加到 8'hff 后才能复位。现在,请借助比较器(Arithmetic 目录下的 Comparator)实现 Counter.v 中达到上限后归 0 的功能。其中上限值由一个常量指定。

Image title

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

题目 2:流水灯青春无极限版

  1. 目前的流水灯功能还比较单一。请修改代码,为其添加变速功能。要求由开关 sw[1:0] 控制 LED 移动的速度,对应关系如下:

    • sw[1:0] = 2'b00:0.5Hz
    • sw[1:0] = 2'b01:1Hz
    • sw[1:0] = 2'b10:2Hz
    • sw[1:0] = 2'b11:4Hz
  2. 在 1 的基础上,请修改代码,为流水灯增加变向功能。要求由开关 sw[7] 控制 LED 移动的方向,对应关系如下:

    • sw[7] = 1'b1:向右移动
    • sw[7] = 1'b0:向左移动

最后更新: October 18, 2023

评论

Authors: wintermelon008