四路抢答器 responder 设计
一、设计需求
1. 主持人复位抢答器,按动 Start 开关开始抢答
2. 四位抢答选手,编号01~04,控制抢答开关
3. 抢答倒计时60s
4. 若记时期间有人抢答,将抢答结果锁存,计时复位
5. 若计时期间无人抢答,计时结束后计时复位
6. 显示模块显示倒计时和抢答结果,复位为60和00
二、模块划分和参考代码
1. 系统框图
2. 按键输入模块
设计这个模块是为了进行按键消抖,按键消抖其实就是将输入信号延后几个时钟,待信号稳定时再进行操作,避免机械按键抖动带来误触。参考代码如下:
//按键消抖模块
module key_debounce(
input clk,
input [4:1] key,
output [4:1] key_debounce
);
reg [4:1] key_r,key_rr,key_rrr;
always @(posedge clk) begin
key_rrr = key_rr;
key_rr = key_r;
key_r = key;
end
assign key_debounce = key_rrr & key_rr & key_r;
endmodule
3. 控制模块
设计了三个状态用于控制计时器和编码器,输出控制信号 en_count 控制计时器,lock 用于控制编码器。
参考代码:
// 抢答器的控制模块
module responder_control(
input start,
input touch,
input [3:0] zero_flag,
input clk, rst_n,
output reg en_count, lock_flag
);
reg [1:0] NS, CS;
parameter [1:0]
WAIT = 2'b00,
COUNT = 2'b01,
LOCK = 2'b10;
// state transition
always @ ( posedge clk or negedge rst_n )
if ( !rst_n )
CS <= WAIT;
else
CS <= NS;
// trans condition judgment
always @ ( CS or start or touch or zero_flag )
begin
NS = 2'bx;
case( CS )
WAIT: begin
if ( start )
NS = COUNT;
else
NS = WAIT;
end
COUNT: begin
if ( !touch )
if ( zero_flag == 0 )
NS = WAIT;
else
NS = COUNT;
else
NS = LOCK;
end
LOCK: NS = LOCK;
default: NS = WAIT;
endcase
end
// output of each state
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n ) begin
en_count <= 0;
lock_flag <= 0;
end
else
begin
case( NS )
WAIT: begin en_count <= 0; end
COUNT: begin en_count <= 1; end
LOCK: begin en_count <= 0; lock_flag <= 1; end
default: en_count <= 0;
endcase
end
end
endmodule
4. 倒计时模块
要实现60s倒计时,问题是怎么将时间给显示器,单个数码管是十进制,因此设置两个计时变量 ten,one,分别表示十位和个位。计时器受控制器的 en_count 使能信号控制,同时在计时为零时反馈给控制器 zero_flag 信号,参考代码如下:
// 抢答器的倒计时计数器模块 v2.0
`define TEN 4'b0110
`define ONE 4'b1001
module responder_count(
input clk, rst_n,
input en_count,
output reg [3:0] ten, one,
output [3:0] zero_flag
);
assign zero_flag = ten || one;
// count one
always @ ( posedge clk or negedge rst_n or posedge en_count )
begin
if ( !rst_n ) begin
one <= 0;
end
else begin
if ( en_count == 1 )
if ( one > 0 )
one <= one - 1;
else
one <= `ONE;
else begin
one <= 0;
end
end
end
// count ten
always @ ( posedge clk or negedge rst_n or posedge en_count )
begin
if ( !rst_n ) begin
ten <= `TEN;
end
else begin
if ( en_count == 1 )
if ( one == 0 && ten > 0 )
ten <= ten - 1;
else
ten <= ten;
else begin
ten <= `TEN;
end
end
end
endmodule
5. 抢答信号编码模块(优先编码器)
编码器应该实现将选手的号码编码给显示器输出,同时要有标志位,使得获得抢答结果后立即锁存。使用优先编码器是为了在两人同时抢答时作出选择(选手号码的安排应当随机)。因此设置 touch 变量用于在一次编码成功后向控制器反馈信号,并设置 lock 变量用于接受控制器的锁存命令。参考代码:
// 抢答器的抢答(2-4优先编码器)模块
module responder_encode(
input a, b, c, d,
input rst_n, lock_flag,
output reg [3:0] code,
output reg touch
);
always@( rst_n or lock_flag or a or b or c or d )
begin
if( !rst_n )
{touch, code}=5'b0_0000;
else begin
if ( lock_flag == 0 )
if( a==1 )
{touch, code}=5'b1_0001;
else if( b==1 )
{touch, code}=5'b1_0010;
else if( c==1 )
{touch, code}=5'b1_0011;
else if( d==1 )
{touch, code}=5'b1_0100;
else
{touch, code}=5'b0_0000;
else
{touch, code}={touch, code};
end
end
endmodule
6. 七段数码管译码模块
七段共阳极数码管的译码表,分别显示计时和抢答结果。
参考代码:
// 七段数码管的译码模块
module responder_decode(
input [3:0] ten, one,
input [3:0] code,
output reg [6:0] seg_ten, seg_one, seg_code
);
always @ ( * ) begin
case( ten )
0: begin seg_ten = 7'b100_0000; end // g -> a
1: begin seg_ten = 7'b111_1001; end
2: begin seg_ten = 7'b010_0100; end
3: begin seg_ten = 7'b011_0000; end
4: begin seg_ten = 7'b001_1001; end
5: begin seg_ten = 7'b001_0010; end
6: begin seg_ten = 7'b000_0010; end
7: begin seg_ten = 7'b111_1000; end
8: begin seg_ten = 7'b000_0000; end
9: begin seg_ten = 7'b001_0000; end
default: begin seg_ten = 7'b000_0001; end
endcase
end
always @ ( * ) begin
case( one )
0: begin seg_one = 7'b100_0000; end // g -> a
1: begin seg_one = 7'b111_1001; end
2: begin seg_one = 7'b010_0100; end
3: begin seg_one = 7'b011_0000; end
4: begin seg_one = 7'b001_1001; end
5: begin seg_one = 7'b001_0010; end
6: begin seg_one = 7'b000_0010; end
7: begin seg_one = 7'b111_1000; end
8: begin seg_one = 7'b000_0000; end
9: begin seg_one = 7'b001_0000; end
default: begin seg_one = 7'b000_0001; end
endcase
end
always @ ( * ) begin
case( code )
0: begin seg_code = 7'b100_0000; end // g -> a
1: begin seg_code = 7'b111_1001; end
2: begin seg_code = 7'b010_0100; end
3: begin seg_code = 7'b011_0000; end
4: begin seg_code = 7'b001_1001; end
5: begin seg_code = 7'b001_0010; end
6: begin seg_code = 7'b000_0010; end
7: begin seg_code = 7'b111_1000; end
8: begin seg_code = 7'b000_0000; end
9: begin seg_code = 7'b001_0000; end
default: begin seg_code = 7'b000_0001; end
endcase
end
endmodule
7. 数码管动态扫描模块
动态扫描的目的是减小数码管输出电流,降低功耗,它利用人眼视觉暂留,同时只有一套管子点亮。这里使用了一个 FSM 来实现。
// 抢答器7段数码管(8组)动态扫描模块
module responder_scan(
input clk,
input rst_n,
input [6:0] seg_ten, seg_one, seg_code,
output reg [7:0] an,
output reg [6:0] data
);
reg [7:0] CS, NS;
parameter [7:0]
IDLE = 8'b0000_0001,
s2 = 8'b0000_0010,
s3 = 8'b0000_0100,
s4 = 8'b0000_1000,
s5 = 8'b0001_0000,
s6 = 8'b0010_0000,
s7 = 8'b0100_0000,
s8 = 8'b1000_0000;
always @ ( negedge rst_n or posedge clk ) begin
if ( !rst_n )
CS <= IDLE;
else
CS <= NS;
end
always @ ( CS ) begin
case ( CS )
IDLE: NS <= s2;
s2: NS <= s3;
s3: NS <= s4;
s4: NS <= s5;
s5: NS <= s6;
s6: NS <= s7;
s7: NS <= s8;
s8: NS <= IDLE;
default: NS <= IDLE;
endcase
end
always @ ( negedge rst_n or posedge clk )begin
if ( !rst_n ) {an, data} <= 'b1111_1110_1100_0000;
else case ( NS )
IDLE: begin an <= 'b1111_1110; data <= seg_one; end // an0 data one
s2: begin an <= 'b1111_1101; data <= seg_ten; end // an1 data ten
s3: begin an <= 'b1111_1011; data <= 'b100_0000; end // an2 data 0
s4: begin an <= 'b1111_1011; data <= 'b100_0000; end // an3 data 0
s5: begin an <= 'b1111_1011; data <= 'b100_0000; end // an4 data 0
s6: begin an <= 'b1111_1011; data <= 'b100_0000; end // an5 data 0
s7: begin an <= 'b1111_1011; data <= 'b100_0000; end // an6 data 0
s8: begin an <= 'b0111_1111; data <= seg_code; end // an7 data code
default: begin an <= 'b1111_1110; data <= 'b100_0000; end // an0 data 0
endcase
end
endmodule
8. 分频模块
实际应当产生四个时钟,分别用于秒计时,数码管动态扫描,设备操作和按键消抖,参考中未给出按键消抖频率。为了方便检验功能正确,暂时使用了二分频等简易分频。
// 抢答器秒计时的时钟模块, 偶数分频器
`define C 1 // 偶数 n 分频, C = n/2 - 1;
`define S 2
`define O 4
module responder_clk(
input clk, rst_n,
output reg clk_count, clk_scan, clk_opt
);
reg [3:0] cnt1;
reg [3:0] cnt2;
reg [3:0] cnt3;
// clk for counter
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n ) begin
cnt1 <= 0;
clk_count <= 0;
end
else begin
if ( cnt1 < `C )
cnt1 <= cnt1 + 1;
else begin
cnt1 <= 0;
clk_count <= ~clk_count;
end
end
end
// clk for device
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n ) begin
cnt2 <= 0;
clk_opt <= 0;
end
else begin
if ( cnt2 < `O )
cnt2 <= cnt2 + 1;
else begin
cnt2 <= 0;
clk_opt <= ~clk_opt;
end
end
end
// clk for scan
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n ) begin
cnt3 <= 0;
clk_scan <= 0;
end
else begin
if ( cnt3 < `S )
cnt3 <= cnt3 + 1;
else begin
cnt3 <= 0;
clk_scan <= ~clk_scan;
end
end
end
endmodule
9. 顶层
未加入按键消抖。
顶层连线:
// Copyright (C) 1991-2015 Altera Corporation. All rights reserved.
// Your use of Altera Corporation's design tools, logic functions
// and other software and tools, and its AMPP partner logic
// functions, and any output files from any of the foregoing
// (including device programming or simulation files), and any
// associated documentation or information are expressly subject
// to the terms and conditions of the Altera Program License
// Subscription Agreement, the Altera Quartus II License Agreement,
// the Altera MegaCore Function License Agreement, or other
// applicable license agreement, including, without limitation,
// that your use is for the sole purpose of programming logic
// devices manufactured by Altera and sold by Altera or its
// authorized distributors. Please refer to the applicable
// agreement for further details.
// PROGRAM "Quartus II 64-Bit"
// VERSION "Version 15.0.0 Build 145 04/22/2015 SJ Full Version"
// CREATED "Tue Dec 01 12:01:21 2020"
module responder(
start,
clk,
rst_n,
a,
b,
c,
d,
an,
data
);
input wire start;
input wire clk;
input wire rst_n;
input wire a;
input wire b;
input wire c;
input wire d;
output wire [7:0] an;
output wire [6:0] data;
wire SYNTHESIZED_WIRE_0;
wire [3:0] SYNTHESIZED_WIRE_1;
wire SYNTHESIZED_WIRE_2;
wire [3:0] SYNTHESIZED_WIRE_3;
wire [3:0] SYNTHESIZED_WIRE_4;
wire [3:0] SYNTHESIZED_WIRE_5;
wire SYNTHESIZED_WIRE_6;
wire clk_count;
wire clk_opt;
wire clk_scan;
wire [6:0]seg_code;
wire [6:0]seg_one;
wire [6:0]seg_ten;
responder_clk clk_inst(
.clk(clk),
.rst_n(rst_n),
.clk_count(clk_count),
.clk_opt(clk_opt),
.clk_scan(clk_scan)
);
responder_control b2v_inst(
.start(start),
.touch(SYNTHESIZED_WIRE_0),
.clk(clk_opt),
.rst_n(rst_n),
.zero_flag(SYNTHESIZED_WIRE_1),
.en_count(SYNTHESIZED_WIRE_2),
.lock_flag(SYNTHESIZED_WIRE_6));
defparam b2v_inst.COUNT = 2'b01;
defparam b2v_inst.LOCK = 2'b10;
defparam b2v_inst.WAIT = 2'b00;
responder_count b2v_inst1(
.clk(clk_count),
.rst_n(rst_n),
.en_count(SYNTHESIZED_WIRE_2),
.one(SYNTHESIZED_WIRE_4),
.ten(SYNTHESIZED_WIRE_5),
.zero_flag(SYNTHESIZED_WIRE_1));
responder_decode b2v_inst2(
.code(SYNTHESIZED_WIRE_3),
.one(SYNTHESIZED_WIRE_4),
.ten(SYNTHESIZED_WIRE_5),
.seg_code(seg_code),
.seg_one(seg_one),
.seg_ten(seg_ten));
responder_encode b2v_inst3(
.a(a),
.b(b),
.c(c),
.d(d),
.rst_n(rst_n),
.lock_flag(SYNTHESIZED_WIRE_6),
.touch(SYNTHESIZED_WIRE_0),
.code(SYNTHESIZED_WIRE_3));
responder_scan scan_inst(
.clk(clk_scan),
.rst_n(rst_n),
.seg_ten(seg_ten),
.seg_one(seg_one),
.seg_code(seg_code),
.an(an),
.data(data)
);
endmodule
三、quartusII 功能仿真
1. testbench
// 抢答器的测试
module responder_tb;
reg start_sig;
reg clk_sig;
reg rst_n_sig;
reg a_sig;
reg b_sig;
reg c_sig;
reg d_sig;
wire [7:0] an;
wire [6:0] data;
responder responder_inst
(
.start(start_sig) , // input start_sig
.clk(clk_sig) , // input clk_sig
.rst_n(rst_n_sig) , // input rst_n_sig
.a(a_sig) , // input a_sig
.b(b_sig) , // input b_sig
.c(c_sig) , // input c_sig
.d(d_sig) , // input d_sig
.an(an) , // output [7:0] an
.data(data) // output [6:0] data
);
initial fork
clk_sig = 0;
{start_sig, rst_n_sig, a_sig, b_sig, c_sig, d_sig} = 0;
forever #10 clk_sig = ~clk_sig;
#50 rst_n_sig = 1;
#100 start_sig = 1; #200 start_sig = 0;
// 检验是否能完成抢答功能
#300 b_sig = 1; #350 b_sig = 0;
#330 a_sig = 1; #380 a_sig = 0;
join
endmodule
2. modelsim 仿真
四、一些问题的解决和反思
1. modelsim 仿真出现 error load design
(转)Modelsim:error loading design解决方案
要注意:综合是否有错误,顶层例化是否有误,甚至 testbench 文件是否有误
2. 顶层设计
1)顶层设计使用IP核绘图较为方便,避免接线失误。
2)之后可以转换为 hdl 文件(FILE -> CREAT/UPDATE -> creat hdl …),但是要注意转换后的文件不会默认添加进工程中,要手动添加
3. 分层设计思想
对于每个模块应当在设计之后
马上验证
,不能等最后综合顶层时才验证
4. 及时保存
要及时保存,
修改文档时及时备份
,以免心态崩溃:)
附录