实验练习
说明
每一次实验我们会为大家准备若干练习题目。不同的题目有着不同的难度,请大家量力而行;
你可以查阅任何开源资料完成实验练习,但不能直接抄袭 。一经发现,我们将取消所有抄袭参与者本次的实验成绩。情节特别严重的,我们将按照学校的有关规定进行处理。
部分题目我们提供了代码框架,你需要在框架指定的位置完成代码内容。你也可以自由修改框架代码,但需要根据题目要求完成练习。
必做内容
每一名同学都需要完成必做部分的内容,其中题目4,5中任选一题完成即可。
题目 1:if 语句与锁存器(1 分)
本题来自于 OJ ID-131 。你可以在该平台上验证自己的设计。
有时候,语法正确的代码并不一定能产生功能正常的电路,一般来说都是因为不小心引入了锁存器造成的。例如下面的 Verilog 语句:
always @( * ) begin
if ( cpu_overheated )
shut_off_computer = 1 ;
end
always @( * ) begin
if ( ~ arrived )
keep_driving = ~ gas_tank_empty ;
end
为消除锁存器,我们应当使组合逻辑过程块中的条件完备,即 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 键盘上的扫描码。现在给定接收到的扫描码的最后两个字节,您需要指示是否按下了键盘上的一个箭头键。这涉及到一个相当简单的映射,包含五种可能的情况:上、下、左、右,以及不属于任何一种。
您的电路有一个 16bits 输入和四个 1bit 输出,该电路识别这四个扫描码并确认正确的输出。为避免产生锁存,必须在所有情况下为四个输出指定一个值。这可能涉及许多不必要的输入。解决这个问题的一个简单方法是在 case 语句之前为输出分配一个『默认值』:
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 项变得不必要。请试着将上述代码补充完整,以实现期望的电路功能。
代码框架如下:
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
现在,我们需要对其进行一点改进。
现在的计数器复位值是 0,最小值是 0,最大值是 MAX_VALUE。请修改代码,使得计数器的最小值也可以由模块参数 MIN_VALUE 指定。
在 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
题目 4:生成波形(2 分)
请编写仿真文件,生成下图所示的波形。时钟周期设定为 10ns,变量 bus 的位宽自行指定(图中存在偏差,实际上变量 bus 应在时钟上升沿变化)。图中的阴影部分代表电路状态不确定,你可以自行指定其状态。
本题的分数组成如下:
成功绘制出波形图:1分
不使用对时间打表的方式(zero除外)完成:1分
需要提示?
zero是一个会归零a的信号。
所有跳变都发生在clk的上升沿。
回忆一下计数器的思想。
或许应该尝试一下for和repeat循环?
题目 5:1的个数pro(2 分)
设计一个Verilog模块,统计32位输入数据 a 中从第 b 位到第 c 位(包含两端)这个区间内1的个数。其中 b 和 c 都是5位无符号数,且保证 b < c。
注意:位索引从0开始,即第0位是最低位(LSB),第31位是最高位(MSB)。
模块接口
CountOnes.v 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 1 ns / 1 ps
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 module GrayCounter (
input clk ,
input rst ,
output out
);
// your code here
endmodule
需要提示?
可以对4个比特一个一个分析,不一定要找出通用的规律。
题目 3:『众数』统计(3 分)
请设计一个模块,实时输出对于任意给定数目 8bits 无符号整数中出现次数超过一半的数(如果不存在则可以输出序列中任意一个数)。请结合 Vivado 仿真验证自己设计的正确性。方便起见,我们假定输入的数个数不超过 100。
模块的部分信息如下:
FindMode 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
对应的仿真波形如下: