前言
2023年秋数字电子技术课程设计,选择的是相对简单的一个题目。
一个反应计时器,详细资料欢迎前往github查看。
本资料仅用于学习交流,抄袭所产生一切后果请自负。
题目要求
(1)电路有三个输入按键:clear,start 和 stop,使用一个 LED 作为视觉刺激指示灯,
在七段数码管上显示相应的信息。
(2)当按下 clear 键时,电路回到初始状态,七段数码管给出一个初始显示,同时 LED
指示灯熄灭。
(3)当按下 start 键,七段数码管熄灭,产生一段 2s 到 6s(包括 2s 和 6s)之间的随机
时间之后,LED 指示灯点亮,计数器开始加计数。计数器每 1ms 加 1,它的值以 XXX 的
格式显示在数码管上。
(4)被测试者看到 LED 指示灯点亮后,立即按下 stop 键,此时计数器暂停计数,数
码管显示的就是被测试者的反应时间。大多数人的反应时间在 0.1-0.3s 之间。
(5)如果不按下 stop 键,计时器达到 999 之后停止计数。
(6)如果 LED 指示灯点亮前,按下 stop 键,被视为犯规,数码管上应给出犯规指示。
(7)连续进行多次测试后,可查阅所有测试结果中的最短时间、最长时间和平均时间。
(8)两个人比赛,显示两人的反应时间及获胜者。
整体框架
模块划分:
顶层模块
- FinalDesign.v
主控
- MainLogic.v
计数器
- Counter.v
动态扫描数码管
- DynamicScanTubes.v
测试文件
- FinalDesign_tb.v
源码
顶层文件
module FinalDesign (
clk_50M,clear,start,stop,DIG,codeout,LED,LED_InRuning
);
//输入:
//clk_50M:50MHz时钟,clear:清零,start:开始,stop:停止
input clk_50M,clear,start,stop;
//输出:
//DIG:数码管选位
output wire[7:0] DIG;
//codeout:七段数码管输出
output wire[6:0] codeout;
//LED:LED指示灯
output wire LED;
//LED_InRuning:用于标识运行中
output wire LED_InRuning;
//内部信号:
//CounterOut_wire:连接计数器输出,要求计算到999
//CounterFlag_wire:用于控制计数器的开始、停止、清零
//00->清零,01->停止,10->开始
//ErrorFlag_wire:用于控制数码管显示犯规指示
wire [9:0]CounterOut_wire;
wire [1:0]CounterFlag_wire;
wire ErrorFlag_wire;
//主逻辑,生成随机数,在start信号到来时,开始计时,同时点亮LED,
//在stop信号到来时,停止计时,同时熄灭LED
//然后检查是否犯规,如果犯规,显示E,否则显示计时结果
MainLogic ML(
.clk_50M(clk_50M),
.clear(clear),
.start(start),
.stop(stop),
.CounterFlag(CounterFlag_wire),//根据不同输入信号产生不同的CounterFlag
.ErrorFlag(ErrorFlag_wire),//控制数码管显示犯规指示
.LED(LED),//控制LED指示灯
.LED_InRuning(LED_InRuning)
);
//计数器,每1ms加1,计数到999时停止计数
//由MainLogic通过传递CounterFlag控制开始、停止、清零
Counter C(
.clk_50M(clk_50M),
.CounterFlag(CounterFlag_wire),
.CounterOut(CounterOut_wire)
);
//动态扫描数码管,显示计数器的值,以及犯规指示(以E表示)
DynamicScanTubes DST(
.clk_50M(clk_50M),
.DataIn(CounterOut_wire),
.ErrorFlag(ErrorFlag_wire),
.DIG(DIG),
.codeout(codeout)
);
endmodule
主控逻辑
//主逻辑,生成随机数,在start信号到来时,开始计时,同时点亮LED,
//在stop信号到来时,停止计时,同时熄灭LED
//然后检查是否犯规,如果犯规,显示E,否则显示计时结果
module MainLogic(
clk_50M,clear,start,stop,CounterFlag,ErrorFlag,LED,LED_InRuning
);
//输入:
//clk_50M:50MHz时钟,clear:清零,start:开始,stop:停止
input clk_50M,clear,start,stop;
//输出:
//CounterFlag:用于控制计数器的开始、停止、清零
//00->清零,01->停止,10->开始
output reg [1:0]CounterFlag;
//ErrorFlag:用于控制数码管显示犯规指示
output reg ErrorFlag;
//LED:LED指示灯
output reg LED;
//LED_InRuning:用于标识运行中
output reg LED_InRuning;
//内部信号:
//RandomGenerator:随机数生成器,用于产生随机数
//实质是一个32位的计数器,每个时钟周期加1。因为时钟频率非常高
//所以可以认为每个时钟周期加1的时间间隔非常短,可以认为是随机的
reg [31:0]RandomGenerator;
//RandomNum:用于表示随机时间,2s到6s之间
//在50MHz时钟下,2s到6s之间的时间为100000000到300000000
reg [31:0]RandomNum;
//clear_temp:保存clear信号的上一次状态,用于检测clear信号的上升沿
//start_temp:保存start信号的上一次状态,用于检测start信号的上升沿
//stop_temp:保存stop信号的上一次状态,用于检测stop信号的上升沿
reg clear_temp,start_temp,stop_temp;
//counter,内置计数器,用于计算随机时间,在随机时间结束后,开始计时,同时点亮LED
reg [31:0]counter;
initial begin
//初始化
clear_temp <= 1'b0;
start_temp <= 1'b0;
stop_temp <= 1'b0;
CounterFlag <= 2'b00;
ErrorFlag <= 1'b0;
LED <= 1'b0;
counter <= 32'b0;
RandomNum <= 32'b0;
RandomGenerator <= 32'b0;
end
always @(posedge clk_50M) begin
//更新随机数生成器,无所谓溢出问题
RandomGenerator <= RandomGenerator + 1;
//检测clear信号的上升沿
if(clear_temp == 1'b0 && clear == 1'b1) begin
CounterFlag <= 2'b00;
ErrorFlag <= 1'b0;
LED <= 1'b0;
LED_InRuning <= 1'b0;
counter <= 32'b0;
RandomNum <= 32'b0;
end
//检测start信号的上升沿
if(start_temp == 1'b0 && start == 1'b1) begin
LED_InRuning <= 1'b1;
//随机数范围为0到2^32-1,将其限制在100000000到300000000之间
RandomNum <= RandomGenerator % 200000000 + 100000000;
end
//检测stop信号的上升沿
if(stop_temp == 1'b0 && stop == 1'b1) begin
//停止计时,同时熄灭LED
CounterFlag <= 2'b01;
LED_InRuning <= 1'b0;
LED <= 1'b0;
end
//检查是否到达随机时间
if (RandomNum != 0 && counter == RandomNum && CounterFlag == 2'b00) begin
//开始计时,同时点亮LED
CounterFlag <= 2'b10;
LED <= 1'b1;
end
//如果没有到达随机时间,更新内置计数器
else if (RandomNum != 0 && counter != RandomNum && CounterFlag == 2'b00) begin
counter <= counter + 1;//更新内置计数器
end
//如果此时处于暂停状态,检查是否犯规
else if (RandomNum != 0 && CounterFlag == 2'b01) begin
//检查是否犯规,如果犯规,将ErrorFlag置1,否则置0
if (counter < RandomNum) begin
ErrorFlag <= 1'b1;
end
else begin
ErrorFlag <= 1'b0;
end
end
//更新clear_temp,start_temp,stop_temp
clear_temp <= clear;
start_temp <= start;
stop_temp <= stop;
end
endmodule
计数器
//计数器,每1ms加1,计数到999时停止计数
//由MainLogic通过传递CounterFlag控制开始、停止、清零
module Counter(
clk_50M,CounterFlag,CounterOut
);
//输入:
//clk_50M:50MHz时钟
//CounterFlag:用于控制计数器的开始、停止、清零
//00->清零,01->停止,10->开始
input clk_50M;
input [1:0]CounterFlag;
//输出:
//CounterOut:计数器输出,要求计算到999
output reg [9:0]CounterOut;
//内部信号:
//counter:内置计数器,在50MHz时钟下,每1ms计数50000次
reg [31:0]counter;
initial begin
//初始化
counter <= 32'b0;
CounterOut <= 10'b0;
end
always @(posedge clk_50M) begin
//根据CounterFlag控制计数器的开始、停止、清零
case(CounterFlag)
2'b00:begin
//清零
counter <= 32'b0;
CounterOut <= 10'b0;
end
2'b01:begin
//停止
counter <= counter;
CounterOut <= CounterOut;
end
2'b10:begin
//计数
if (counter != 32'd50000) begin
counter <= counter + 1;
end
else begin
counter <= 32'b0;
if (CounterOut != 10'd999) begin
CounterOut <= CounterOut + 1;
end
else begin
CounterOut <= 10'd999;
end
end
end
default:begin
//异常情况,停止计数
counter <= counter;
CounterOut <= CounterOut;
end
endcase
end
endmodule
动态扫描数码管
module DynamicScanTubes(clk_50M, DataIn, ErrorFlag, DIG, codeout);
//输入信号
input clk_50M;//50MHz时钟
input ErrorFlag;//犯规指示
input [9:0] DataIn;//计数器的值
//输出信号
//DIG:数码管选位
output reg[7:0] DIG;
//codeout:七段数码管输出
output reg[6:0] codeout;
//内部信号
//SEL:数位选择信号,控制显示DataIn的某一位
reg [1:0] SEL;
//Y:数码管显示的值,由DataIn和SEL决定
reg [9:0] Y;
//new_clk:时钟分频后的时钟
reg new_clk;
//counter:时钟分频计数器
reg [15:0] counter;
initial begin
counter <= 0;//计数器清零
new_clk <= 0;//时钟初始化
SEL <= 2'b00;
Y <= 10'b0;
DIG <= 8'b0000_0000;
end
//时钟分频
always @(posedge clk_50M) begin
if (counter == 1500) begin
counter <= 0;//计数器清零
new_clk <= ~new_clk;//时钟跳变
end else begin
counter <= counter + 1;//计数器加一
end
end
//模三计数器,用于控制数位选择信号SEL
always @(posedge new_clk)
begin
if (SEL == 2'd2)
SEL <= 2'd0;
else
SEL <= SEL + 1;
end
//数码管显示
always @(SEL)
begin
case (SEL)
2'd0: Y <= DataIn % 10;//输入数据的倒数第一位
2'd1: Y <= (DataIn / 10) % 10;//输入数据的倒数第二位
2'd2: Y <= DataIn / 100;//输入数据的倒数第三位
default: Y <= 4'b0000;
endcase
end
//数码管译码
always @(*)
begin
if (ErrorFlag == 1)
codeout = 7'b1111001;//E
else
case(Y)
//gfedcba
4'd0 : codeout = 7'b0111111;
4'd1 : codeout = 7'b0000110;
4'd2 : codeout = 7'b1011011;
4'd3 : codeout = 7'b1001111;
4'd4 : codeout = 7'b1100110;
4'd5 : codeout = 7'b1101101;
4'd6 : codeout = 7'b1111101;
4'd7 : codeout = 7'b0000111;
4'd8 : codeout = 7'b1111111;
4'd9 : codeout = 7'b1101111;
default: codeout = 7'b0000000;
endcase
end
//数码管显示
always @(SEL)
begin
case (SEL)
2'd0: DIG <= 8'b0000_0001;//第一个数码管亮起
2'd1: DIG <= 8'b0000_0010;//第二个数码管亮起
2'd2: DIG <= 8'b0000_0100;//第三个数码管亮起
default: DIG <= 8'b0000_0000;
endcase
end
endmodule
测试文件
`timescale 1ns/1ns
module FinalDesign_tb;
reg clk_50M;
reg clear;
reg start;
reg stop;
wire [7:0] DIG;
wire [6:0] codeout;
wire LED;
// Instantiate the design under test
FinalDesign DUT (
.clk_50M(clk_50M),
.clear(clear),
.start(start),
.stop(stop),
.DIG(DIG),
.codeout(codeout),
.LED(LED)
);
// Clock generation, 20ns period, 50MHz frequency
always #10 clk_50M = ~clk_50M;
//1 second = 1000000000;
// Testbench stimulus
initial begin
// Initialize inputs
clk_50M = 0;
stop = 0;
start = 0;
clear = 0;
#1000;
clear = 1;
// Wait for a few clock cycles
#10000;
// Release clear signal
clear = 0;
// Wait for a few clock cycles
#1000000000;
//1s
// Set start signal
start = 1;
#10000;
start = 0;
// Wait for 2~6 seconds
#1000000000
#1000000000
#1000000000
//3s
// Wait for 0~1 second(Catch the signal)
#500000000
//0.5s
//Set stop signal
stop = 1;
#10000;
stop = 0;
#100000000;//Waiting for 0 ~ 1 second(0~1000000000ns)
end
endmodule