软件:PC、Quartus Prime 18.1、Modelsim 10.5b
硬件:Altera FPGA开发板(EP4CE6E22F17C8)
基于开发板上的8位8段数码管和4个机械按键,制作一个抢答器,相关要求如下:
1、 设置四个按键,其中三位选手A、B、C,主持人0;
2、 主持人具有清除所有状态权限
3、 每次抢答开始后,选手需要在10S做出选择,否测视为放弃,一旦某位选手按下按键后,另外两名选手按键将失效
4、 如果没有选手抢答,数码管显示10s倒计时,计时结束时,LED不停闪烁;同时,所有选手按键失效,直到主持人重新开始
5、 最左边显示显示倒计时,低三位从左至右分别表示选手ABC,当某位选手抢答后,将显示对应字符(A or B or C),同时倒计时暂停,直至主持人按下后,所有状态恢复,开启新一轮抢答。
根据开发板的原理图,可得到以下资料
数码管:本质上为一组发光二极管按照一定顺序排列而成,其显示原理与LED无异。
根据硬件原理图所示,发光二极管,所有的阳极都接通3.3V的正电压,也即—高电平,所以如果我们想要
发光二极管导通的话,需要在阴极接通低电平,就可以让LED亮起来。
根据系统要求,可以得到以下框架分布
根据系统构建,可得到以下模块
信号名 | 端口类型 | 数据位宽 | 信号说明 |
---|---|---|---|
Clk | i | 1 | 输入时钟信号,50MHz |
Rst_n | i | 1 | 输入复位信号,低电平有效 |
key_A | i | 1 | 选手A按键 |
key_B | i | 1 | 选手B按键 |
key_C | i | 1 | 选手C按键 |
key_0 | i | 1 | 主持人按键 |
Data_time | O | 16 | 10s倒计时数据 |
Led_en | O | 1 | 倒计时结束,使能LED闪烁 |
/*================================================*\
Filename ﹕ctrl_mode.v
Author ﹕Adolph
Description ﹕中控模块,指令解析,数据显示控制
Called by ﹕responder.v
Revision History ﹕ 2022-6-20 15:20:43
Revision 1.0
Email﹕[email protected]
Company﹕
\*================================================*/
module ctrl_mode(
input clk ,
input rst_n ,
input [3:0] key_ctrl, //按键消抖信号
output [3:0] data ,
output reg [3:0] key_sta , //按键状态信号
output reg led_en
);
//parameter declarations
parameter TIME_S = 26'd50_000_000;
//internal reg / wire signals
reg [25:0] cnt_1s ;
wire add_1s ;
wire end_1s ;
reg latch_log;
reg [3:0] cnt_delay;//显示9-0
wire add_delay;
wire end_delay;
assign data = cnt_delay;
// assign led_en = end_delay;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
led_en <= 1'b0;
end
else if(key_ctrl[0])begin
led_en <= 1'b0;
end
else if(end_delay)begin
led_en <= 1'b1;
end
else begin
led_en <= led_en;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_sta <= 4'b0000;
end
else if(key_ctrl[0])begin //主持人按键按下之后
key_sta <= 4'b0001;
end
else if(latch_log)begin
key_sta <= key_sta;
end
else begin
case(key_ctrl)
4'b0010:key_sta[1] <= 1'b1;
4'b0100:key_sta[2] <= 1'b1;
4'b1000:key_sta[3] <= 1'b1;
default: ;
endcase
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
latch_log <= 1'b1; //初始上电后,锁定生效
end
else if(key_ctrl[0])begin //主持人按下,锁定失效,选手按下有效
latch_log <= 1'b0;
end
else if(key_ctrl[1] || key_ctrl[2] || key_ctrl[3] || end_delay)begin //任一选手按下后,锁定生效;倒计时结束,同样不允许选手按键
latch_log <= 1'b1;
end
else begin
latch_log <= latch_log;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_1s <= 'd0;
end
else if(add_1s)begin
if(end_1s)begin
cnt_1s <= 'd0;
end
else begin
cnt_1s <= cnt_1s + 26'd1;
end
end
else begin
cnt_1s <= cnt_1s;
end
end
assign add_1s = key_sta == 4'b0001;
assign end_1s = add_1s && cnt_1s >= TIME_S - 'd1;
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_delay <= 'd10;
end
else if(key_ctrl[0])begin
cnt_delay <= 'd10;
end
else if(add_delay)begin
if(end_delay)begin
cnt_delay <= cnt_delay;
end
else begin
cnt_delay <= cnt_delay - 4'd1;
end
end
else begin
cnt_delay <= cnt_delay;
end
end
assign add_delay = end_1s;
assign end_delay = add_delay && cnt_delay == 'd1 - 'd1;
endmodule
本模块较为简单,此处不做仿真验证,如有兴趣可自行验证
数码管区驱动在之前的基础上有所改动,大家自行理解
/*================================================*\
Filename ﹕seg_driver.v
Author ﹕Adolph
Description ﹕对输入的数据译码,并驱动数码管显示对应数据
Called by ﹕responder.v
Revision History ﹕ 2022-5-30 14:27:22
Revision 1.0
Email﹕[email protected]
Company﹕
\*================================================*/
module seg_driver(
input clk ,
input rst_n ,
input [03:0] key_ctrl,
input [31:0] dis_data,//倒计时数据
output reg [07:0] dig_sel ,
output reg [07:0] dig_seg
);
//wire [31:0]dis_data;
// assign dig_seg = 8'd0;
// assign dig_sel = 1'b0;
localparam
NUM_0 = 8'hC0,
NUM_1 = 8'hF9,
NUM_2 = 8'hA4,
NUM_3 = 8'hB0,
NUM_4 = 8'h99,
NUM_5 = 8'h92,
NUM_6 = 8'h82,
NUM_7 = 8'hF8,
NUM_8 = 8'h80,
NUM_9 = 8'h90,
NUM_A = 8'h88,
NUM_B = 8'h83,
NUM_C = 8'hC6,
NUM_D = 8'hA1,
NUM_E = 8'h86,
NUM_F = 8'h8E,
LIT_ALL = 8'h00,
BLC_ALL = 8'hFF;
parameter CNT_REF = 25'd1000;
reg [9:0] cnt_20us; //20us计数器
reg [4:0] data_tmp; //用于取出不同位选的显示数据
// assign dis_data = 32'hABCD_4413;
//描述位选信号切换
//描述刷新计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20us <= 10'd0;
end
else if(cnt_20us >= CNT_REF - 10'd1)begin
cnt_20us <= 10'd0;
end
else begin
cnt_20us <= cnt_20us + 10'd1;
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dig_sel <= 8'hfe;//8'b1111_1110
end
else if(cnt_20us >= CNT_REF - 10'd1)begin
dig_sel <= {dig_sel[6:0],dig_sel[7]};
end
else begin
dig_sel <= dig_sel;
end
end
//段选信号描述
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
data_tmp <= 5'd0;
end
else if(key_ctrl[0])begin
data_tmp <= 5'h10;
end
else begin
case(dig_sel)
8'b1111_1110: begin
if(key_ctrl[3])
data_tmp <= 5'hc;
else
data_tmp <= 5'h10;
end
8'b1111_1101: begin
if(key_ctrl[2])
data_tmp <= 5'hb;
else
data_tmp <= 5'h10;
end
8'b1111_1011: begin
if(key_ctrl[1])
data_tmp <= 5'ha;
else
data_tmp <= 5'h10;
end
8'b1111_0111:data_tmp <= 5'h10;
8'b1110_1111:data_tmp <= 5'h10;
8'b1101_1111:data_tmp <= 5'h10;
8'b1011_1111:data_tmp <= 5'h10;
8'b0111_1111:data_tmp <= dis_data[3-:4];
default: data_tmp <= 5'hF;
endcase
end
end
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
dig_seg <= LIT_ALL;
end
else begin
case(data_tmp)
5'h0 : dig_seg <= NUM_0;
5'h1 : dig_seg <= NUM_1;
5'h2 : dig_seg <= NUM_2;
5'h3 : dig_seg <= NUM_3;
5'h4 : dig_seg <= NUM_4;
5'h5 : dig_seg <= NUM_5;
5'h6 : dig_seg <= NUM_6;
5'h7 : dig_seg <= NUM_7;
5'h8 : dig_seg <= NUM_8;
5'h9 : dig_seg <= NUM_9;
5'hA : dig_seg <= NUM_A;
5'hB : dig_seg <= NUM_B;
5'hC : dig_seg <= NUM_C;
5'hD : dig_seg <= NUM_D;
5'hE : dig_seg <= NUM_E;
5'hF : dig_seg <= NUM_F;
5'h10: dig_seg <= BLC_ALL;
default:dig_seg <= LIT_ALL;
endcase
end
end
endmodule
设计文件
module led_water(
input clk ,//50MHz
input rst_n ,//low valid
input blink_en,//闪烁使能信号
output reg [7:0] led_o
);
//参数定义
parameter CNT_MAX = 25'd500_0000;
//信号定义
reg [24:0] cnt;//500ms计数器,计数最大值 2500_0000,
//计时 0-500ms
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
cnt <= 25'd0;
else if(blink_en)begin
if(cnt >= CNT_MAX - 25'd1)
cnt <= 25'd0;
else
cnt <= cnt + 1'b1;
end
else
cnt <= 25'd0;
end
//led 输出
always@(posedge clk or negedge rst_n)begin
if(!rst_n)
led_o <= 8'b0000_0000; //all light
else if(blink_en)begin
if(cnt >= CNT_MAX - 25'd1)
led_o <= ~led_o;
else
led_o <= led_o; //s
end
else begin
led_o <= 8'b0000_0000;
end
end
endmodule
该模块在之前有设计过,这里不做过赘述,可参考抖 verilog实现按键消
`timescale 1ns/1ns //仿真系统时间尺度定义
`define clk_period 20 //时钟周期参数定义
module tb_responder();
//激励信号定义
reg Clk ;
reg Rst_n ;
reg [3:00] key_in ; //
//响应信号定义
wire [7:0] dig_sel ;
wire [7:0] dig_seg ;
wire [7:0] led_o ;
defparam responder.DELAY_TIME = 200;
defparam responder.ctrl_mode.TIME_S = 200;
defparam responder.seg_driver.CNT_REF = 100;
reg [55:00] ASCILL; //
localparam
NUM_0 = 8'hC0,
NUM_1 = 8'hF9,
NUM_2 = 8'hA4,
NUM_3 = 8'hB0,
NUM_4 = 8'h99,
NUM_5 = 8'h92,
NUM_6 = 8'h82,
NUM_7 = 8'hF8,
NUM_8 = 8'h80,
NUM_9 = 8'h90,
NUM_A = 8'h88,
NUM_B = 8'h83,
NUM_C = 8'hC6,
NUM_D = 8'hA1,
NUM_E = 8'h86,
NUM_F = 8'h8E,
LIT_ALL = 8'h00,
BLC_ALL = 8'hFF;
always@(*)begin
case(responder.seg_driver.dig_seg)
NUM_0 : ASCILL = "NUM_0 ";
NUM_1 : ASCILL = "NUM_1 ";
NUM_2 : ASCILL = "NUM_2 ";
NUM_3 : ASCILL = "NUM_3 ";
NUM_4 : ASCILL = "NUM_4 ";
NUM_5 : ASCILL = "NUM_5 ";
NUM_6 : ASCILL = "NUM_6 ";
NUM_7 : ASCILL = "NUM_7 ";
NUM_8 : ASCILL = "NUM_8 ";
NUM_9 : ASCILL = "NUM_9 ";
NUM_A : ASCILL = "NUM_A ";
NUM_B : ASCILL = "NUM_B ";
NUM_C : ASCILL = "NUM_C ";
NUM_D : ASCILL = "NUM_D ";
NUM_E : ASCILL = "NUM_E ";
NUM_F : ASCILL = "NUM_F ";
LIT_ALL : ASCILL = "LIT_ALL";
BLC_ALL : ASCILL = "BLC_ALL";
default : ASCILL = "LIT_ALL";
endcase
end
//实例化
responder responder(
/*input */.clk (Clk ),
/*input */.rst_n (Rst_n ),
/*input [3:0] */.key_in (key_in ), //按键消抖信号
/*output [7:0] */.dig_sel (dig_sel),
/*output [7:0] */.dig_seg (dig_seg),
/*output [7:0] */.led_o (led_o )
);
//产生时钟
initial Clk = 1'b0;
always #(`clk_period / 2) Clk = ~Clk;
//产生激励
initial begin
Rst_n = 1'b0;
key_in = 4'b1111;
#(`clk_period * 20 + 3);
Rst_n = 1'b1;
#(`clk_period * 20);
press_key0;
#(`clk_period * 2000);
press_key3;
$stop(2);
end
reg [15:0] my_rand;
task press_key0 ;
begin
//前抖动
repeat(10)begin
my_rand = {$random} % 50 ;
#my_rand key_in[0] = ~key_in[0];
end
key_in[0] = 1'b0;
#(400 * `clk_period); //21ms > 20ms
//后抖动
repeat(11)begin
my_rand = {$random} % 50 ;
#my_rand key_in[0] = ~key_in[0];
end
key_in[0] = 1'b1;
#(1000 * `clk_period); //21ms > 20ms
end
endtask
task press_key1 ;
begin
//前抖动
repeat(19)begin
my_rand = {$random} % 50 ;
#my_rand key_in[1] = ~key_in[1];
end
key_in[1] = 1'b0;
#(400 * `clk_period); //21ms > 20ms
//后抖动
repeat(10)begin
my_rand = {$random} % 50 ;
#my_rand key_in[1] = ~key_in[1];
end
key_in[1] = 1'b1;
#(1000 * `clk_period); //21ms > 20ms
end
endtask
task press_key2 ;
begin
//前抖动
repeat(13)begin
my_rand = {$random} % 50 ;
#my_rand key_in[2] = ~key_in[2];
end
key_in[2] = 1'b0;
#(400 * `clk_period); //21ms > 20ms
//后抖动
repeat(10)begin
my_rand = {$random} % 50 ;
#my_rand key_in[2] = ~key_in[2];
end
key_in[2] = 1'b1;
#(1000 * `clk_period); //21ms > 20ms
end
endtask
task press_key3 ;
begin
//前抖动
repeat(15)begin
my_rand = {$random} % 50 ;
#my_rand key_in[3] = ~key_in[3];
end
key_in[3] = 1'b0;
#(400 * `clk_period); //21ms > 20ms
//后抖动
repeat(11)begin
my_rand = {$random} % 50 ;
#my_rand key_in[3] = ~key_in[3];
end
key_in[3] = 1'b1;
#(1000 * `clk_period); //21ms > 20ms
end
endtask
endmodule
顶层文件在此不作讲解,根据下列RTL视图,相信读者可以很轻易的完成相应代码设计
具体实现效果如预期所述,请大家自行探索
本设计实现了基本的功能
在设计过程中,由于位宽不匹配(quartus不会报错,只会报警告),导致程序运行效果不正确,多次检错才确定具体原因
故,告诫大家,在进行信号描述的时候,一定要将位宽进行匹配