一、功能介绍
该项目由state_dete、seg、seg_j、key_filter、top五个模块组成
复位按键:复位
按键1:模拟序列1输入
按键2:模拟序列0输入
LED:检测到数值101翻转
数码管:显示输入值和101出现次数(十六进制显示)
二、时序图和状态转换图
时序图
状态转换图
三、程序设计
1.顶层模块
// A code block
var foo = 'bar';
// An highlighted block
//模块顶层
module top(
input clk ,
input rst_n ,
input [2:0] key_in ,
output [3:0] led ,
output [5:0] seg_sel ,
output [7:0] segment
);
//状态机消抖模块
wire [2:0] key_down;
key_filter #( .KEY_W (3),.TIME_20MS ( 5)) u_key_filter(
/*input */ .clk (clk),
/*input */ .rst_n (rst_n),
/*input [KEY_W - 1:0]*/ .key_in (key_in),
/*output [KEY_W - 1:0]*/ .key_out(key_down)
);
//状态机序列检测
wire [19:0] value;
state_dete u_state_dete(
/*input */.clk (clk ),
/*input */.rst_n (rst_n ),
/*input [1:0] */.key_down (key_down),
.value (value),
/*output reg [3:0] */.led (led)
);
//数码管数字显示
wire [23:0] din;
seg_j u_seg_j(
/*input */ .clk (clk) ,
/*input */ .rst_n (rst_n) ,
/*input [20:0] */ .value (value) ,
/*input [1:0] */ .key_down (key_down),
/*output [23:0]*/ .din (din)
);
//数码管驱动
seg u_seg(
/*input */ .clk (clk) ,
/*input */ .rst_n (rst_n) ,
/*input [23:0]*/ .din (din) ,
/*input [5:0] */ .point (6'b111110),
/*output reg [5:0] */ .seg_sel (seg_sel) ,
/*output reg [7:0] */ .segment (segment)
);
endmodule
2.按键消抖模块
// A code block
var foo = 'bar';
// An highlighted block
module key_filter # (parameter KEY_W = 3,TIME_20MS = 5)(
input clk ,
input rst_n ,
input [KEY_W - 1:0] key_in ,
output [KEY_W - 1:0] key_out
);
// 参数定义
localparam IDLE = 4'b0001; //初始状态
localparam DOWN = 4'b0010; //按键按下抖动
localparam HOLD = 4'b0100; //按键按下后稳定
localparam UP = 4'b1000; //按键上升抖动
// 信号定义
reg [3:0] state_c ; //现态
reg [3:0] state_n ; //次态
// 状态转移条件定义
wire idle2down ;
wire down2idle ;
wire down2hold ;
wire hold2up ;
wire up2idle ;
reg [KEY_W - 1:0] key_r0 ; //同步
reg [KEY_W - 1:0] key_r1 ; //打拍
wire [KEY_W - 1:0] nedge ; //下降沿
wire [KEY_W - 1:0] pedge ; //上升沿
// 20ms计数器
reg [19:0] cnt_20ms ;
wire add_cnt_20ms ;
wire end_cnt_20ms ;
reg [KEY_W - 1:0] key_out_r; // 输出寄存
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
state_c <= IDLE;
end
else begin
state_c <= state_n;
end
end
assign idle2down = (state_c == IDLE) && nedge; // 检测到下降沿
assign down2idle = (state_c == DOWN) && (pedge && ~end_cnt_20ms); // 计时未到20ms时且出现上升沿表示按键意外抖动,回到初始态
assign down2hold = (state_c == DOWN) && (~pedge && end_cnt_20ms); // 计时到20ms时没有出现上升沿标志按键按下后保持稳定
assign hold2up = (state_c == HOLD) && (pedge); // 检测到上升沿跳转到上升态
assign up2idle = (state_c == UP) && end_cnt_20ms; // 计数器计数到20ms跳转到初始态
always@(*)begin
case(state_c)
IDLE: begin
if(idle2down)begin
state_n = DOWN;
end
else begin
state_n = state_c;
end
end
DOWN: begin
if(down2idle)begin
state_n = IDLE;
end
else if(down2hold)begin
state_n = HOLD;
end
else begin
state_n = state_c;
end
end
HOLD: begin
if(hold2up)begin
state_n = UP;
end
else begin
state_n = state_c;
end
end
UP: begin
if(up2idle)begin
state_n = IDLE;
end
else begin
state_n = state_c;
end
end
default:state_n = state_c;
endcase
end
// 20ms计数器
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt_20ms <= 0;
end
else if(add_cnt_20ms)begin
if(pedge || end_cnt_20ms)begin
cnt_20ms <= 0;
end
else begin
cnt_20ms <= cnt_20ms + 1'b1;
end
end
end
assign add_cnt_20ms = state_c == DOWN || state_c == UP; // 当按键按下或上弹时开始计数
assign end_cnt_20ms = add_cnt_20ms && (cnt_20ms == TIME_20MS - 1); // 当计数到最大值或检测到上升沿计数器清零
// 同步打拍
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_r0 <= {KEY_W{1'b1}};
key_r1 <= {KEY_W{1'b1}};
end
else begin
key_r0 <= key_in;
key_r1 <= key_r0;
end
end
assign nedge = ~key_r0 & key_r1; // 检测下降沿
assign pedge = key_r0 & ~key_r1; // 检测上升沿
// 按键赋值
always@(posedge clk or negedge rst_n)begin
if(!rst_n)begin
key_out_r <= {KEY_W{1'b0}};
end
else if(hold2up) begin
key_out_r <= ~key_r1;
end
else begin
key_out_r <= {KEY_W{1'b0}};
end
end
assign key_out = key_out_r;
endmodule
3.序列状态转换模块
// A code block
var foo = 'bar';
// An highlighted block
/*******************************************
-------------------------------------------
Component name:state_dete
Author :lcy
time :2021/12/4
Description :三段式状态机序列检测将序列出现
101的次数传输出去出现一次led翻转
src :
--------------------------------------------
********************************************/
module state_dete(
input clk ,
input rst_n ,
input [2:0] key_down,
output reg [3:0] led ,
output reg [19:0] value
);
//参数定义
localparam //工程文件doc状态图
A = 4'b0001,
B = 4'b0010,
C = 4'b0100,
D = 4'b1000;
//中间信号定义
reg [3:0] current_state ;
reg [3:0] next_state ;
wire AB ;
wire BC ;
wire CA ;
wire CD ;
wire DB ;
wire DC ;
//状态机三段式 当前状态
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
current_state <= A;
end
else begin
current_state <= next_state;
end
end
//次状态
always @(*)begin
case (current_state)
A : begin if (AB) begin
next_state = B;
end
else begin
next_state = current_state;
end
end
B : begin if (BC) begin
next_state = C;
end
else begin
next_state = current_state;
end
end
C : begin if (CD) begin
next_state = D;
end
else if (CA) begin
next_state = A;
end
else begin
next_state = current_state;
end
end
D : begin if (DB) begin
next_state = B;
end
else if (DC) begin
next_state = C;
end
else begin
next_state = current_state;
end
end
default: begin
next_state = current_state;
end
endcase
end
//状态转换条件
assign AB = current_state == A && key_down[0] == 1;
assign BC = current_state == B && key_down[1] == 1;
assign CA = current_state == C && key_down[1] == 1;
assign CD = current_state == C && key_down[0] == 1;
assign DB = current_state == D && key_down[0] == 1;
assign DC = current_state == D && key_down[1] == 1;
//状态机检测到101 led翻转,并将出现的次数输出给seg_j
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
value <= 0;
led <= 4'b0000;
end
else if(current_state == C && key_down[0] == 1)begin
led <= ~led;
value <= value+1;
end
end
endmodule
4.数码管显示
// A code block
var foo = 'bar';
// An highlighted block
/*******************************************
-------------------------------------------
Component name:seg_j
Author :lcy
time :2021/12/4
Description :将state_dete模块101出现的次数
和按键产生的序列传输给seg驱动模块
src :
--------------------------------------------
********************************************/
module seg_j(
input clk ,
input rst_n ,
input [20:0] value ,
input [1:0] key_down,
output [23:0] din
);
//中间信号定义
reg [3:0] value_0;
//按键按下显示1,0值
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
value_0 <= 0;
end
else if(key_down[0])begin
value_0 <= 1;
end
else if (key_down[1]) begin
value_0 <= 0;
end
end
assign din = {value_0,value[3:0],value[7:4],value[11:8],value[15:12],value[19:16]}; //数码管显示值
endmodule
5.数码管驱动
// A code block
var foo = 'bar';
// An highlighted block
/*******************************************
-------------------------------------------
Component name:seg
Author :
time :2021/12/4
Description :seg数码管驱动
src :
--------------------------------------------
********************************************/
module seg (
input clk ,
input rst_n ,
input [23:0] din ,
input [5:0] point ,
output reg [5:0] seg_sel ,
output reg [7:0] segment
);
parameter DELAY = 1_000_00;
localparam ZER = 7'b100_0000,
ONE = 7'b111_1001,
TWO = 7'b010_0100,
THR = 7'b011_0000,
FOU = 7'b001_1001,
FIV = 7'b001_0010,
SIX = 7'b000_0010,
SEV = 7'b111_1000,
EIG = 7'b000_0000,
NIN = 7'b001_0000,
A = 7'b000_1000,
B = 7'b000_0011,
C = 7'b100_0110,
D = 7'b010_0001,
E = 7'b000_0110,
F = 7'b000_1110;
reg [3:0] num;
reg seg_point;
reg [19:0] cnt;
wire add_cnt;
wire end_cnt;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
cnt <= 0;
end
else if(add_cnt)begin
if(end_cnt)begin
cnt <= 0;
end
else begin
cnt <= cnt + 1;
end
end
else begin
cnt <= cnt;
end
end
assign add_cnt = 1'b1;
assign end_cnt = add_cnt && cnt == DELAY - 1;
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
seg_sel <= 6'b111110;
end
else if(end_cnt)begin
seg_sel <= {seg_sel[4:0],seg_sel[5]};
end
else begin
seg_sel <= seg_sel;
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
num <= 4'b0;
seg_point <= 1'b1;
end
else begin
case (seg_sel)
6'b111110 : begin num <= din[3:0];seg_point <= point [0];end
6'b111101 : begin num <= din[7:4];seg_point <= point [1];end
6'b111011 : begin num <= din[11:8];seg_point <= point [2];end
6'b110111 : begin num <= din[15:12];seg_point <= point [3];end
6'b101111 : begin num <= din[19:16];seg_point <= point [4];end
6'b011111 : begin num <= din[23:20];seg_point <= point [5];end
default: begin num <= 4'b0;seg_point <= 1'b1;end
endcase
end
end
always @(posedge clk or negedge rst_n)begin
if(!rst_n)begin
segment <= 8'b1111_1111;
end
else begin
case (num)
4'd0: segment <= {seg_point,ZER};
4'd1: segment <= {seg_point,ONE};
4'd2: segment <= {seg_point,TWO};
4'd3: segment <= {seg_point,THR};
4'd4: segment <= {seg_point,FOU};
4'd5: segment <= {seg_point,FIV};
4'd6: segment <= {seg_point,SIX};
4'd7: segment <= {seg_point,SEV};
4'd8: segment <= {seg_point,EIG};
4'd9: segment <= {seg_point,NIN};
4'd10: segment <= {seg_point,A};
4'd11: segment <= {seg_point,B};
4'd12: segment <= {seg_point,C};
4'd13: segment <= {seg_point,D};
4'd14: segment <= {seg_point,E};
4'd15: segment <= {seg_point,F};
default: segment <= 8'b11111111;
endcase
end
end
endmodule
四、tb仿真
1.tb程序
// A code block
var foo = 'bar';
// An highlighted block
`timescale 1ns/1ns
module top_tb();
//激励信号定义
reg tb_clk ;
reg tb_rst_n ;
reg [2:0] tb_key_in ;
wire [3:0] tb_led ;
wire [5:0] tb_seg_sel ;
wire [7:0] tb_segment ;
//输出信号定义
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
top u_top(
/*input */ .clk (tb_clk) ,
/*input */ .rst_n (tb_rst_n) ,
/*input [2:0] */ .key_in (tb_key_in) ,
/*output [3:0] */ .led (tb_led) ,
/*output [5:0] */ .seg_sel (tb_seg_sel),
/*output [7:0] */ .segment (tb_segment)
);
//产生时钟
initial tb_clk = 1'b0;
always #(CLOCK_CYCLE/2) tb_clk = ~tb_clk;
integer i,j;
//产生激励
initial begin
tb_rst_n = 1'b1;
#(CLOCK_CYCLE*4);
tb_rst_n = 1'b0;
#(CLOCK_CYCLE*4);
tb_rst_n = 1'b1;
repeat(10)begin //重复执行10次 ,顺序执行
for(i=0;i<10000;i=i+1)begin //按键0检测
#2
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b001;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b010;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b001;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b010;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b001;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b010;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b001;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b010;
#(10*CLOCK_CYCLE);
tb_key_in = 3'b011;
#(10*CLOCK_CYCLE);
end
tb_key_in = 2'b11;
//#(50*CLOCK_CYCLE);
//for(i=0;i<20;i=i+1)begin //按键1检测
//j= {$random}%30;//j的取值范围就是0-30;
//#2
//tb_key_in[1] = {$random};//0 或者 1,模拟按键抖动过程
//#(j*CLOCK_CYCLE);
//end
//
//tb_key_in = 2'b11;
end
#(CLOCK_CYCLE);
end
endmodule
2.Modelsim仿真图