Zedboard—实验六秒计数
本节实验将继续使用PmodSSD来实现秒计数,并且详细演示了了仿真调试代码的过程。
实现过程:
1. 需要知道什么时候开始计数;
2. 让计数器可以保持计数值;
3. 将计数值送入数码管显示;
产生秒脉冲
使用上节实验中产生的毫秒脉冲,在此基础上计数100次实现秒脉冲。
integer ms_count = 0;
reg sec_pulse;
always @(posedge clk)
begin
sec_pulse <= 0;
if (ms_pulse)
if (ms_count == 999)
begin
ms_count <= 0;
sec_pulse <= 1;
end
else
ms_count <= ms_count+1;
end
同时产生秒脉冲信号sec_pulse使得每隔一秒,计数器加一。代码如下:
reg [7:0] sec_count = 0;
always @ (posedge clk)
if (sec_pulse)
sec_count <= sec_count +1;
这个仿真带来一个问题,我们能够看到毫秒脉冲ms_pulse信号。但是秒脉冲信号sec_pulse呢?这需要仿真一秒时间来观察脉冲,需要花一定的时间,同时存储所有的数据也会占用大量的硬盘空间。一秒就是100000000个时钟周期 。无论如何在仿真的时候都应该减少计数。
仿真类似上述的长计数情况,是一个通用的需求。接下来介绍top文件中的一个参数。
加速仿真
大家想象一个常数使用在设计中。这个参数值在仿真模块中永远不会变。不同的模块中的实例可以有不同的参数值。
所要介绍的参数能够保持计数器的计数值。该参数默认的值为100000。在该默认值作用下,每个1ms能够产生一个ms_pulse信号。参数声明在模块端口声明之前。
module top
#(
parameter ms_limit = 100000
) (
input clk,
input [7:0] switch,
output reg [7:0] led,
output reg [6:0] ssd,
output reg ssdcat
);
接下来改变计数器声明count赋值ms_limit-1。这个改变其实没有影响原本设计的功能。
always @(posedge clk)
begin
ms_pulse <= 0;
if (count == ms_limit-1)
begin
count <= 0;
ms_pulse <= 1;
end
else
count <= count+1;
end
修改测试文件,将ms_limit的默认值改为100。这将使仿真时,每经过100时钟周期产生一个ms_limit脉冲,而不是之前的100000。
top #(.ms_limit(100)) top
(
.clk(clk),
.switch(switch),
.led(led)
);
再次运行仿真,观察秒计数器,是不是每隔1个毫秒计数一次。
使用之前驱动ssd输出的代码:
wire [3:0] digit;
always @(posedge clk)
case (digit)
0: ssd <= 7'b1111110;
1: ssd <= 7'b0110000;
2: ssd <= 7'b1101101;
3: ssd <= 7'b1111001;
4: ssd <= 7'b0110011;
5: ssd <= 7'b1011011;
6: ssd <= 7'b1011111;
7: ssd <= 7'b1110000;
8: ssd <= 7'b1111111;
9: ssd <= 7'b1110011;
10: ssd <= 7'b1110111;
11: ssd <= 7'b0011111;
12: ssd <= 7'b1001110;
13: ssd <= 7'b0111101;
14: ssd <= 7'b1001111;
15: ssd <= 7'b1000111;
endcase
assign digit = ssdcat ? sec_count[7:4] : sec_count[3:0];
当然,要确保这些信号在使用前有过声明。 例化到FPGA中,观察每秒钟数码管显示是否正确。
输出正确吗?
虽然已经仿真了设计,但是输出正确与否很难通过观察数码管的二进制值来判断。至少很难直观的检查出结果。
7段数码管模块
这就需要一个七段数码管显示的模块。将其应用于test bench,可以使输出的二进制值代替为十进制数字显示值。模块输出输入代码如下:
module ssd_digit
input enable,
input [6:0] ssd,
output reg [3:0] value
);
将数码管七段编码值对应为十进制显示值:
always @(*)
if (enable)
case (ssd)
7'b1111110: value = 0;
7'b0110000: value = 1;
7'b1101101: value = 2;
7'b1111001: value = 3;
7'b0110011: value = 4;
7'b1011011: value = 5;
7'b1011111: value = 6;
7'b1110000: value = 7;
7'b1111111: value = 8;
7'b1110011: value = 9;
7'b1110111: value = 10;
7'b0011111: value = 11;
7'b1001110: value = 12;
7'b0111101: value = 13;
7'b1001111: value = 14;
7'b1000111: value = 15;
endcase
在Test bench中例化模块
这里需要两个数码显示模块,并且需要8根线连接输出数码模块。同时,还需要enable使能信号,实际上就是ssdcat信号。
wire [7:0] digits;
ssd_digit PmodSSD0
(
.enable(~ssdcat),
.ssd(ssd),
.value(digits[3:0])
);
ssd_digit PmodSSD1
(
.enable(ssdcat),
.ssd(ssd),
.value(digits[7:4])
);
自我检测
运行仿真并且观察输出值,检查输出是否正确。在实际工程中,为了方便每次修改代码并验证代码正确性。需要编写一个能够自我检测的仿真测试程序。这样每次代码修改完后,都不必例化到FPGA中验证。如果它通过了测试文件,就说明代码是正确的。
秒计数模块
这个模块需要输入时钟信号,同时输出八位计数值。应用上节中缩短仿真时间的方法,在仿真文件中修改参数值:
`timescale 1ns/1ns
module model
#(
parameter ms_limit = 100000
)
(
input clk,
output [7:0] seconds
);
integer counter = 0;
always @(posedge clk)
counter <= counter+1;
assign seconds = counter / (ms_limit * 1000);
endmodule
仿真文件中例化代码:
wire [7:0] model_seconds;
model
#(.ms_limit(100))
model
(
.clk(clk),
.seconds(model_seconds)
);
比较结果
接下来就是比较仿真结果的正确性。
always @(posedge clk)
begin
num_checks = num_checks+1;
if (digits != model_seconds)
begin
$display("ERROR: digits value %0x does not match expected value %0x at time %0fns",
digits,model_seconds,$realtime);
num_errors = num_errors+1;
end
end
测试代码与RTL的编写是由区别的。注意上面代码中的num_check和num_errors的赋值用的是阻塞式赋值。这是为了在每个时钟沿都检测LED输出以及计数值。如果使用非阻塞式赋值,只能在每个时钟周期计数一个检查结果,由于不知道那个模块是先运行的,因此掩盖了运行错误。
仿真20秒(由于缩短了仿真时间,其实是毫秒)后停止。将重复语句替换为延时语句:
initial
begin
wait (model_seconds == 20);
$display("Simulation complete at time %0fns.",$realtime);
if (num_errors > 0)
$display("*** Simulation FAILED %0d/%0d",num_errors,num_checks);
else
$display("*** Simulation PASSED %0d/%0d",num_errors,num_checks);
$finish;
end
运行仿真,显示错误如下:
run all
ERROR: digits value 0 does not match expected value 1 at time 1000000.000000ns
ERROR: digits value 0 does not match expected value 1 at time 1000010.000000ns
ERROR: digits value 0 does not match expected value 1 at time 1000020.000000ns
ERROR: digits value 11 does not match expected value 1 at time 1001010.000000ns
ERROR: digits value 0 does not match expected value 1 at time 1002010.000000ns
ERROR: digits value 11 does not match expected value 1 at time 1003010.000000ns
ERROR: digits value 0 does not match expected value 1 at time 1004010.000000ns
ERROR: digits value 11 does not match expected value 1 at time 1005010.000000ns
...
ERROR: digits value 33 does not match expected value 13 at time 19995010.000000ns
ERROR: digits value 11 does not match expected value 13 at time 19996010.000000ns
ERROR: digits value 33 does not match expected value 13 at time 19997010.000000ns
ERROR: digits value 11 does not match expected value 13 at time 19998010.000000ns
ERROR: digits value 33 does not match expected value 13 at time 19999010.000000ns
Simulation complete at time 19999990.000000ns.
*** Simulation FAILED 18136/4000000
$finish called at time : 19999990 ns : File "/home/pete/tutorial6/tutorial6.srcs/sim_1/new/bench.v" Line 85
4000000比较中有18136个错误。在波形图具体分析错误,观察1000000ns仿真时刻的时序:
sec_count和model_seconds计数值并不一致,并且时间延迟了几个时钟周期。分析之前产生ms_pulse的代码:
integer count = 0;
reg ms_pulse = 0;
always @(posedge clk)
begin
ms_pulse <= 0;
if (count == ms_limit-1)
begin
count <= 0;
ms_pulse <= 1;
end
else
count <= count+1;
end
注意ms_pulse是如何由0变为1的。对其赋值做以下修改:
integer count = 0;
wire ms_pulse = count == ms_limit-1;
always @(posedge clk)
if (ms_pulse)
count <= 0;
else
count <= count+1;
将count和ms_limit-1的比较结果赋值给ms_pulse。这样不仅使得代码更简洁,并且减少了sec_count的延迟。
但是计数值仍然延迟于sec_pulse。修改代码,消除计数延迟:
integer ms_count = 0;
wire sec_pulse = ms_count == 999;
always @(posedge clk)
if (ms_pulse)
if (sec_pulse)
ms_count <= 0;
else
ms_count <= ms_count+1;
仿真波形图如下:
可以看到计数值的更新对齐了sec_pluse,但是sec_pulse不再是一个脉冲,而引起sec_count计数值的频繁计数。修改代码限制sec_pulse的产生:将逻辑运算(ms_count==999 && ms_pulse)的值赋给sec_pulse。
wire sec_pulse = ms_count == 999 && ms_pulse;
仿真波形图:
脉冲信号都对齐了,七段数码管解码输出还有一个时钟的延迟。
计数延迟一个周期,改变秒输出为寄存器类型,并且在每个上升沿对其赋值:
always @(posedge clk)
seconds <= counter / (ms_limit * 1000);
还要确保修改了输出声明seconds为reg。
运行仿真:
继续查找错误
提示错误:
ERROR: digits value 11 does not match expected value 1 at time 1001000.000000ns
锁定到仿真图中的时间点:
注意到digit数码管信号在ssdcat上升沿产生了一个错误值`h11。这是由于ssdcat片选信号与数码管显示信号的改变是同时发生的。对ssdcat信号进行延时错开数码管显示刷新时刻。代码修改如下:
reg ms_pulse_delay = 0;
always @(posedge clk)
ms_pulse_delay <= ms_pulse;
initial ssdcat = 0;
always @(posedge clk)
if (ms_pulse_delay) ssdcat <= ~ssdcat;
仿真波形:
附件
top.v
`timescale 1ns / 1ns
module top
#(
parameter ms_limit = 100000
) (
input clk,
input [7:0] switch,
output reg [7:0] led,
output reg [6:0] ssd,
output reg ssdcat
);
always @(posedge clk) led <= switch;
wire [3:0] digit;
always @(posedge clk)
case (digit)
0: ssd <= 7'b1111110;
1: ssd <= 7'b0110000;
2: ssd <= 7'b1101101;
3: ssd <= 7'b1111001;
4: ssd <= 7'b0110011;
5: ssd <= 7'b1011011;
6: ssd <= 7'b1011111;
7: ssd <= 7'b1110000;
8: ssd <= 7'b1111111;
9: ssd <= 7'b1110011;
10: ssd <= 7'b1110111;
11: ssd <= 7'b0011111;
12: ssd <= 7'b1001110;
13: ssd <= 7'b0111101;
14: ssd <= 7'b1001111;
15: ssd <= 7'b1000111;
endcase
integer count = 0;
wire ms_pulse = count == ms_limit-1;
always @(posedge clk)
if (ms_pulse)
count <= 0;
else
count <= count+1;
reg ssdcat_pre = 0;
always @(posedge clk)
if (ms_pulse)
ssdcat_pre <= ~ssdcat_pre;
initial ssdcat = 0;
always @(posedge clk)
ssdcat <= ssdcat_pre;
integer ms_count = 0;
wire sec_pulse = ms_count == 999 && ms_pulse;
always @(posedge clk)
if (ms_pulse)
if (sec_pulse)
ms_count <= 0;
else
ms_count <= ms_count+1;
reg [7:0] sec_count = 0;
always @(posedge clk)
if (sec_pulse)
sec_count <= sec_count+1;
assign digit = ssdcat_pre ? sec_count[7:4] : sec_count[3:0];
endmodule
bench.v
`timescale 1ns / 1ns
module bench;
reg clk = 1;
always #5 clk = ~clk;
reg [7:0] switch;
wire [7:0] led;
wire ssdcat;
wire [6:0] ssd;
top #(.ms_limit(100)) top
(
.clk(clk),
.switch(switch),
.led(led),
.ssd(ssd),
.ssdcat(ssdcat)
);
wire [7:0] digits;
ssd_digit PmodSSD0
(
.enable(~ssdcat),
.ssd(ssd),
.value(digits[3:0])
);
ssd_digit PmodSSD1
(
.enable(ssdcat),
.ssd(ssd),
.value(digits[7:4])
);
wire [7:0] model_seconds;
model
#(.ms_limit(100))
model
(
.clk(clk),
.seconds(model_seconds)
);
always @(posedge clk)
switch <= $random;
reg [7:0] expected_led;
always @(posedge clk)
expected_led <= switch;
integer num_checks = 0;
integer num_errors = 0;
always @(posedge clk)
begin
num_checks = num_checks+1;
if (expected_led != led)
begin
if (num_errors < 100)
$display("ERROR: led value %0x does not match expected value %0x at time %0.0fns",
led,expected_led,$realtime);
num_errors = num_errors+1;
end
end
reg [3:0] check_digits;
reg [3:0] check_model_digits;
always @(posedge clk)
begin
check_digits = ssdcat ? digits[7:4] : digits[3:0];
check_model_digits = ssdcat ? model_seconds[7:4] : model_seconds[3:0];
num_checks = num_checks+1;
if (check_digits != check_model_digits)
begin
if (num_errors < 100)
$display("ERROR: check_digits value %0x does not match expected check_model_digits value %0x at time %0.0fns",
check_digits,check_model_digits,$realtime);
num_errors = num_errors+1;
end
end
initial
begin
wait (model_seconds == 20);
$display("Simulation complete at time %0fns.",$realtime);
if (num_errors > 0)
$display("*** Simulation FAILED %0d/%0d",num_errors,num_checks);
else
$display("*** Simulation PASSED %0d/%0d",num_errors,num_checks);
$finish;
end
endmodule
ssd_digit.v
`timescale 1ns / 1ns
module ssd_digit
(
input enable,
input [6:0] ssd,
output reg [3:0] value
);
always @(*)
if (enable)
case (ssd)
7'b1111110: value = 0;
7'b0110000: value = 1;
7'b1101101: value = 2;
7'b1111001: value = 3;
7'b0110011: value = 4;
7'b1011011: value = 5;
7'b1011111: value = 6;
7'b1110000: value = 7;
7'b1111111: value = 8;
7'b1110011: value = 9;
7'b1110111: value = 10;
7'b0011111: value = 11;
7'b1001110: value = 12;
7'b0011001: value = 12;
7'b0111101: value = 13;
7'b1001111: value = 14;
7'b1000111: value = 15;
default: value = 'bx;
endcase
endmodule
model.v
`timescale 1ns/1ns
module model
#(
parameter ms_limit = 100000
)
(
input clk,
output reg [7:0] seconds
);
integer counter = 0;
always @(posedge clk)
counter <= counter+1;
always @(posedge clk)
seconds <= counter / (ms_limit * 1000);
endmodule
原文链接