实验练习
说明
- 每一次实验我们会为大家准备若干练习题目。不同的题目有着不同的难度,请大家量力而行;
- 你可以查阅任何开源资料完成实验练习,但不能直接抄袭。一经发现,我们将取消所有抄袭参与者本次的实验成绩。情节特别严重的,我们将按照学校的有关规定进行处理;
- 部分题目我们提供了代码框架,你需要在框架指定的位置完成代码内容。你也可以自由修改框架代码,但需要根据题目要求完成练习;
- 本次实验大部分题目都需要上板验证,具体的端口变量与对应关系可以自行选择。
提醒
为了减少检查的时间,请大家提前烧写好题目所需的 Bit 流文件。我们不接受因为烧写时间过长而无法完成检查的理由。
注意
由于实验平台更新,vivado中创建项目时,需要选择的芯片型号变更为 xc7a35tfgg484-1,相应的,fpga平台应选择ZYNQ,约束文件应使用fpgaol_zynq.xdc。请大家注意!
必做内容
每一名同学都需要完成必做部分的内容。
题目 1:开关与 LED(2 分)
请编写 Verilog 代码,要求当拨动开关时,对应的 LED 灯便会亮起/关闭。开关与 LED 灯的对应关系如下表所示:
sw[0] - led[3]
sw[1] - led[2]
sw[2] - led[1]
sw[3] - led[0]
sw[4] - led[7]
sw[5] - led[6]
sw[6] - led[5]
sw[7] - led[4]
你的代码应当能在 FPGAOL 平台上运行。下面是我们提供的代码框架:
Top.v | |
---|---|
1 2 3 4 5 6 |
|
题目 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 |
|
请基于该模块,在 FPGAOL 上实现一个简易的闪光器,让 LED[7:0] 以 1Hz 的频率闪烁(亮 0.5s,灭 0.5s,以此循环)。提示:平台上的时钟频率为 100MHz,你可能需要修改部分变量的位宽。
题目 3:七段数码管(3 分)
本小题中我们将带大家逐步学习如何点亮七段数码管。
正如我们在教程中介绍的那样,在有多个数码管的情况下,我们通常采用分时复用的方式轮流点亮每个数码管,并保证在同一时间只会有一个数码管被点亮。为了实现这一功能,你需要完成下面的内容:
-
计时切换当前被点亮的数码管编号。我们使用一个 3bits 位宽变量
seg_id
标记当前点亮的是哪一个数码管。请借助计数器以 400Hz 的频率让seg_id
增加 1。这样,每 \(\frac{1}{400}=0.0025\)sseg_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
-
根据编号确定数码管显示的内容。我们希望在 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 |
|
其中 rst
信号用于对模块内的信号进行复位操作。你可以将 Segment 模块看作一个硬件 API,外部模块只需要将数据交付到 output_data
,Segment
模块就会自动将其显示在七段数码管上。
为了检验该模块的正确性,请在项目中添加下面的 Top 模块,并在 FPGAOL 上运行。
Top.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
|
对应的部分约束文件如下:
## FPGAOL HEXPLAY
set_property -dict { PACKAGE_PIN F18 IOSTANDARD LVCMOS33 } [get_ports { seg_data[0] }];
set_property -dict { PACKAGE_PIN E18 IOSTANDARD LVCMOS33 } [get_ports { seg_data[1] }];
set_property -dict { PACKAGE_PIN B20 IOSTANDARD LVCMOS33 } [get_ports { seg_data[2] }];
set_property -dict { PACKAGE_PIN A20 IOSTANDARD LVCMOS33 } [get_ports { seg_data[3] }];
set_property -dict { PACKAGE_PIN A18 IOSTANDARD LVCMOS33 } [get_ports { seg_an[0] }];
set_property -dict { PACKAGE_PIN A19 IOSTANDARD LVCMOS33 } [get_ports { seg_an[1] }];
set_property -dict { PACKAGE_PIN F19 IOSTANDARD LVCMOS33 } [get_ports { seg_an[2] }];
选择性必做内容
选择性必做内容是针对不同层次学生设计的分层内容。不同难度的题目对应不同的分值,请大家根据自身实际情况进行选择。
请在下面的题目中任选一题完成。多选按选择的第一题计分。
题目 1:三级寄存器边沿检测(1 分)
在教程中我们使用两级寄存器进行边沿检测。为了提高系统的稳定性,请在此基础上完成使用三级寄存器边沿检测的 Verilog 代码。代码框架如下:
1 2 3 4 5 6 7 8 9 10 11 12 |
|
你需要结合仿真波形证明自己设计的正确性。
题目 2:Counter 青春版(2 分)
Logisim 中提供了一些时序组件,我们可以借助这些组件实现 Counter 模块的功能。
-
Logisim 项目窗格中 Memory 目录下提供了一个名为 Register 的组件,它可以实现基本的存储功能。
与之对应的 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 组件不需要修改任何参数。
Input
和Output
对应位宽为 8bits 的输入输出端口。 -
接下来,我们将添加计数功能。你需要在电路里添加一个加法器(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'hff
或 output_valid = 8'hfe
)。
修改后的模块输入如下:
1 2 3 4 5 6 7 8 9 |
|
Tips
由于平台的设置,任何时刻一定至少有一个数码管会亮起,因为 seg_an
信号只有 3bits,无论如何设置都会对应一个数码管的编号。简单起见,我们让 0 号数码管保持常亮。这也就是为什么只有前七个数码管具有掩码控制功能。
请将 output_valid
信号与开关相连,对应关系为 output_valid[i] = sw[i]
(\(i\ne0\))。这样,我们就可以通过拨动开关控制对应的数码管亮/灭情况。数码管显示的数据依然为自己的学号。
例如:下面是助教实现的效果:
提示:理论上你只需要修改 seg_an
信号和 seg_data
信号的逻辑就可以实现掩码功能。此外,特定时刻『某编号数码管熄灭』可以通过『让 0 号数码管亮起』实现。
选做内容
选做内容为扩展内容,不计入实验成绩。大家可以根据兴趣自行完成。
题目 1:Counter 青春无极限版
在选择性必做的题目 2 中,你已经在 Logisim 中实现了一个简易的计数器,但该计数器需要累加到 8'hff 后才能复位。现在,请借助比较器(Arithmetic 目录下的 Comparator)实现 Counter.v 中达到上限后归 0 的功能。其中上限值由一个常量指定。
Counter.v | |
---|---|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
|
题目 2:流水灯青春无极限版
-
目前的流水灯功能还比较单一。请修改代码,为其添加变速功能。要求由开关
sw[1:0]
控制 LED 移动的速度,对应关系如下:sw[1:0] = 2'b00
:0.5Hzsw[1:0] = 2'b01
:1Hzsw[1:0] = 2'b10
:2Hzsw[1:0] = 2'b11
:4Hz
-
在 1 的基础上,请修改代码,为流水灯增加变向功能。要求由开关
sw[7]
控制 LED 移动的方向,对应关系如下:sw[7] = 1'b1
:向右移动sw[7] = 1'b0
:向左移动