大作业实录:基于 verilog 的简易抢答器设计和 quartusII 仿真

  • Post author:
  • Post category:其他




一、设计需求

	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. 及时保存

要及时保存,

修改文档时及时备份

,以免心态崩溃:)



附录


工程文档(quartus13.1)



版权声明:本文为gerdios原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。