一、FPGA开发流程
1、基本内容
verilog语法+设计思想(状态机、线性序列机)+学习仿真
2、做事逻辑
写一套硬件描述语言,能够在指定的硬件平台上实现功能。
设计定义(如,LED一秒闪烁一次)
设计输入(编写逻辑)
综合工具(专业的EDA工具对逻辑分析并得到门级电路,如vivado)
功能仿真(数字电路仿真接近真实情况)
布局布线(硬件电路实现)
总结:
(1)设计定义
(2)设计输入
(3)分析综合(EDA、Vivado)
(4)功能仿真
(5)布局布线
(6)分析性能
(7)板级调试
二、二选一多路选择器
1、模块逻辑设计:
module mux2(
a,
b,
sel,
out
);
input a;
input b;
input sel;
output out;
assign out = (sel == 0)?a:b;
endmodule
2、功能仿真验证:
`timescale 1ns / 1ns
module mux2_tb();
reg S0;
reg S1;
reg S2;
wire mux2_out;
mux2 mux2_inst0(
.a(S0),
.b(S1),
.sel(S2),
.out(mux2_out)
);
initial begin
S0 = 0; S1 = 0; S2 = 0;
#20;
S0 = 0; S1 = 0; S2 = 1;
#20;
S0 = 0; S1 = 1; S2 = 0;
#20;
S0 = 0; S1 = 1; S2 = 1;
#20;
S0 = 1; S1 = 0; S2 = 0;
#20;
S0 = 1; S1 = 0; S2 = 1;
#20;
S0 = 1; S1 = 1; S2 = 0;
#20;
S0 = 1; S1 = 1; S2 = 1;
#20;
end
endmodule
三、38译码器
1、基本原理
2、模块逻辑设计:
module decoder_3_8(
A0,
A1,
A2,
Y0,
Y1,
Y2,
Y3,
Y4,
Y5,
Y6,
Y7
);
input A0;
input A1;
input A2;
output reg Y0;
output reg Y1;
output reg Y2;
output reg Y3;
output reg Y4;
output reg Y5;
output reg Y6;
output reg Y7;
always@(*)
case({A2,A1,A0})
3'b000:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0001;
3'b001:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0010;
3'b010:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0100;
3'b011:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_1000;
3'd4:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0001_0000;
3'd5:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0010_0000;
3'd6:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0100_0000;
3'd7:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b1000_0000;
default:{Y7,Y6,Y5,Y4,Y3,Y2,Y1,Y0} = 8'b0000_0000;
endcase
endmodule
这里的case语句中用了位拼接语句,用{ }表示。case语句一定要加default。
常用数值的表示:3'b001(3代表位宽,表示3位宽的二进制数1),3'd5(3位宽的十进制数5),8'h5e(8位宽的十六进制数5e)。
时序逻辑过程中要赋值的output需要定义为reg类型,直接用于assign输出的可以用wire类型。
3、功能仿真验证:
`timescale 1ns/1ns
module decoder_3_8_tb();
reg A0;
reg A1;
reg A2;
wire Y0;
wire Y1;
wire Y2;
wire Y3;
wire Y4;
wire Y5;
wire Y6;
wire Y7;
decoder_3_8 decoder_3_8_inst0(
.A0(A0),
.A1(A1),
.A2(A2),
.Y0(Y0),
.Y1(Y1),
.Y2(Y2),
.Y3(Y3),
.Y4(Y4),
.Y5(Y5),
.Y6(Y6),
.Y7(Y7)
);
initial begin
A2 = 0; A1 = 0; A0 = 0;
#20;
A2 = 0; A1 = 0; A0 = 1;
#20;
A2 = 0; A1 = 1; A0 = 0;
#20;
A2 = 0; A1 = 1; A0 = 1;
#20;
A2 = 1; A1 = 0; A0 = 0;
#20;
A2 = 1; A1 = 0; A0 = 1;
#20;
A2 = 1; A1 = 1; A0 = 0;
#20;
A2 = 1; A1 = 1; A0 = 1;
#20;
$stop;
end
endmodule
`timescale 1ns / 1ns定义了最小的时间单位。如果是1ns/1ps,那说明最小的时间整数单位是1ns(也就是#1表示的值),但是小数可以最小表示到1ps。
四、时序逻辑:D触发器和计数器
1、基本原理
D触发器:
计数器:
2、模块逻辑设计
//做一个T=0.5s翻转闪烁一次的led灯
//芯片晶振一个周期Tclk是20ns,那么计数器cnt_max=T/Tclk=500*1000*1000/20=25_000_000
module led_twinkle(
Clk,
Reset_n,
Led
);
input Clk;
input Reset_n;
output reg Led;
reg [24:0] counter;
//计数器
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == 25_000_000 - 1)
counter <= 0;
else
counter <= counter + 1'd1;
//led翻转
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 1'b0;
else if(counter == 25_000_000 - 1)
Led <= ~Led;
endmodule
注意,counter == 25_000_000 - 1,这里需要减1,是因为counter计满归0(counter <= 0)也需算一次计数时间周期。
计数器的逻辑和LED的逻辑分开写。由于是并行处理,所以每个逻辑都可单独写,比较清晰。
3、功能仿真验证
`timescale 1ns/1ns
module led_twinkle_tb();
reg Clk;
reg Reset_n;
wire Led;
led_twinkle led_twinkle(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
#201;
Reset_n = 1;
# 2000_000_000; //延时2s
$stop;
end
endmodule
仿真中规中矩,没啥特别的。先initial时钟,再进行10ns的翻转;然后initial复位端并进行延时观察。
五、使用移位和38译码器实现LED流水灯
1、移位实现流水灯
module led_run(
Clk,
Reset_n,
Led
);
input Clk;
input Reset_n;
output reg [7:0] Led;
reg [24:0] counter;
//计数器逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == 25_000_000 - 1) //0.5s为单位,仿真时候调成25000
counter <= 0;
else
counter <= counter + 1'd1;
//led逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 8'b0000_0001;
else if(counter == 25_000_000 - 1)begin
Led <= {Led[6:0], Led[7]};
end
endmodule
这里的Led <= {Led[6:0], Led[7]}又用到了位拼接的方法,也可以用Led <= Led<<1 来表示左移1位,但是这样的话还需要添加左移溢出后清零的操作。
2、38译码器实现流水灯
模块逻辑设计
module led_run3(
Clk,
Reset_n,
Led
);
input Clk;
input Reset_n;
output wire [7:0] Led; //这里的led因为是38译码器的输出,是下层连线到上层,所以要用wire
reg [24:0] counter; //总计数器
parameter MCNT = 25_000_000 - 1; //用参数表示值了
//计数器1逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter <= 0;
else if(counter == MCNT) //0.5s为单位,计数25_000_000
counter <= 0;
else
counter <= counter + 1'd1;
reg [2:0] counter2; //计数器2,作为38译码器的输入,8位
//38译码器计数器逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter2 <= 0;
else if(counter == MCNT)
counter2 <= counter2 + 1'b1;
//38译码器实现流水灯
decoder_3_8 decoder_3_8_inst0(
.A0(counter2[0]),
.A1(counter2[1]),
.A2(counter2[2]),
.Y0(Led[0]),
.Y1(Led[1]),
.Y2(Led[2]),
.Y3(Led[3]),
.Y4(Led[4]),
.Y5(Led[5]),
.Y6(Led[6]),
.Y7(Led[7])
);
endmodule
这里需要用到2个计数器了,counter1负责0.5s计数,counter2负责移位计数。
调用了之前的38译码器模块进行例化。
功能仿真验证:
`timescale 1ns/1ns
module led_run3_tb();
reg Clk;
reg Reset_n;
wire [7:0] Led;
// 也可以这样例化
// led_run3
// #(
// .MCNT(25_000 - 1)
// )
// led_run3_inst0(
// .Clk(Clk),
// .Reset_n(Reset_n),
// .Led(Led)
// );
led_run3 led_run3_inst0(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led)
);
defparam led_run3_inst0.MCNT = 25_000 - 1; //重新定义
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
#201;
Reset_n = 1;
//#2000_000_000; //run 4s
#40_000_000;
$stop;
end
endmodule
语句:defparam led_run3_inst0.MCNT = 25_000 - 1 对例化模块中参数的重定义,和逻辑设计代码的语句:parameter MCNT = 25_000_000 - 1 相对应。这里用于节约仿真时间。
六、参数化设计实现模块的重用
1、模块逻辑设计
首先要在source里添加led_twinkle的模块。记得实现手动copy或者添加的时候点copy选项(二者选其一即可)。
module led_twinkle_4(
Clk,
Reset_n,
Led
);
input Clk;
input Reset_n;
output [3:0] Led;
parameter MCNT_0 = 50_000_000 - 1;
parameter MCNT_1 = 25_000_000 - 1;
parameter MCNT_2 = 12_500_000 - 1;
parameter MCNT_3 = 6_250_000 - 1;
led_twinkle led_twinkle_4_inst0(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led[0])
);
defparam led_twinkle_4_inst0.MCNT = MCNT_0;
led_twinkle led_twinkle_4_inst1(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led[1])
);
defparam led_twinkle_4_inst1.MCNT = MCNT_1;
led_twinkle led_twinkle_4_inst2(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led[2])
);
defparam led_twinkle_4_inst2.MCNT = MCNT_2;
led_twinkle led_twinkle_4_inst3(
.Clk(Clk),
.Reset_n(Reset_n),
.Led(Led[3])
);
defparam led_twinkle_4_inst3.MCNT = MCNT_3;
endmodule
2、阻塞赋值和非阻塞赋值
七、线性序列机的原理和应用
1、实现目标
任务3:
这里直接实现任务4的代码了。
要点分析:
普通情况下,间隔时间不一定是1s,因此需要考虑间隔要求不是1s的情况。
如果空闲状态和动态变化状态作为一个整体处理,则计数值不太好确定。
解决方案:
将两个状态分开处理,各自使用一个计数器实现。
空闲态间隔时间用计数器counter2实现。
变化态的最小时间段是0.25s时间,用一个计数器counter0实现
8个状态,再用一个3位计数器counter1实现
counter0和counter2由于存在计数的停止和发生,因此需要两个使能信号对其控制。
2、模块逻辑设计
module led_ctrl_4(
Clk,
Reset_n,
SW,
Led
);
input Clk;
input Reset_n;
input [7:0] SW;
output reg Led;
reg [26:0] counter0; //0.25s进位
reg [2:0] counter1; //8个counter2进1位
reg [26:0] counter2; //1s进位
reg en_counter0; // 为1时,counter0才能计数
reg en_counter2; // 为1时,counter2才能计数
parameter TIME_UNIT_MS = 1000;
parameter MCNT0 = 125_00_000 - 1; // 0.25s
parameter MCNT1 = 8-1; // 8个状态
parameter MCNT2 = 50_000_000 - 1; // 1s
// counter0逻辑 计数控制LED的切换时间
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter0 <= 0;
else if(en_counter0)
begin
if(counter0 == MCNT0)
counter0 <= 0;
else
counter0 <= counter0 + 1'd1;
end
// counter1逻辑 计数当前LED状态是第几个状态
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter1 <= 0;
else if(counter0 == MCNT0)
counter1 <= counter1 + 1'd1;
// counter2逻辑 计数空闲状态时间
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
counter2 <= 0;
else if(en_counter2)
begin
if(counter2 == MCNT2)
counter2 <= 0;
else
counter2 <= counter2 + 1'd1;
end
// en_counter0逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
en_counter0 <= 1'd0;
else if((counter1 == 7)&&(counter0 == MCNT0))
en_counter0 <= 1'd0;
else if(counter2 == MCNT2)
en_counter0 <= 1'd1;
// en_counter2逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
en_counter2 <= 1'd1;
else if((counter1 == 7)&&(counter0 == MCNT0))
en_counter2 <= 1'd1;
else if(counter2 == MCNT2)
en_counter2 <= 1'd0;
// Led逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
Led <= 1'd0;
else if(en_counter2)
Led <= 1'd0;
else
begin
case(counter1)
0:Led <= SW[0];
1:Led <= SW[1];
2:Led <= SW[2];
3:Led <= SW[3];
4:Led <= SW[4];
5:Led <= SW[5];
6:Led <= SW[6];
7:Led <= SW[7];
default: Led <= Led;
endcase
end
endmodule
3、功能仿真验证
`timescale 1ns/1ns
module led_ctrl_4_tb();
reg Clk;
reg Reset_n;
reg [7:0] SW;
wire Led;
led_ctrl_4 led_ctrl_4_inst0(
.Clk(Clk),
.Reset_n(Reset_n),
.SW(SW),
.Led(Led)
);
defparam led_ctrl_4_inst0.TIME_UNIT_MS = 1;
defparam led_ctrl_4_inst0.MCNT0 = 125_00 - 1;
defparam led_ctrl_4_inst0.MCNT2 = 50_000 - 1;
initial Clk = 1;
always #10 Clk = ~Clk;
initial begin
Reset_n = 0;
SW = 8'b1010_1001;
#201;
Reset_n = 1;
#40_000_000;
SW = 8'b1000_0001;
#40_000_000;
$stop;
end
endmodule
仿真结果图:
八、串口通信发送
1、基本原理
UART:通用异步收发器。是一种串行异步通信协议,规定了传输数据时数据的传输方式和所使用的信号。
不同接口:
波特率:
9600:每秒钟传输9600个码元,每个码元占用时间为1/9600秒
115200:同理
2、模块逻辑设计
设计要求:
模块:
要求分析:
程序设计:
由于考虑到模块的复用性,该模块代码不加入LED翻转,仅考虑UART串口数据发送。
module uart_byte_tx(
Clk,
Data,
Reset_n,
Send_Go,
uart_tx,
Tx_Done
);
input Clk;
input [7:0] Data;
input Reset_n;
input Send_Go; //评判外界是否需要发送数据,高电平则启动发送
output reg uart_tx;
output reg Tx_Done;
parameter BAUD = 9600;
parameter CLOCK_FREQ = 50_000_000;
parameter MCNT_BAUD = CLOCK_FREQ / BAUD - 1; //通过波特率算出 波特率计数器 最大计数值
parameter MCNT_BIT = 10 - 1;
reg [29:0] baud_div_cnt; //波特率计数器,计数到1/9600 * 1_000_000_000 / 20 - 1时清零
reg [3:0] bit_count; //位计数器
reg en_baud_cnt; //波特率使能
reg [7:0] r_Data; //寄存器存储数据
wire w_Tx_Done; //让Tx_Done可以时序输出
//波特率计数器逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
baud_div_cnt <= 0;
else if(en_baud_cnt)
begin
if(baud_div_cnt == MCNT_BAUD)
baud_div_cnt <= 0;
else
baud_div_cnt <= baud_div_cnt + 1'd1;
end
else
baud_div_cnt <= 0;
//波特率计数器使能逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
en_baud_cnt <= 1'd0;
else if(w_Tx_Done)
en_baud_cnt <= 1'd0;
else if(Send_Go)
en_baud_cnt <= 1'd1;
//位计数器逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
bit_count <= 0;
else if(baud_div_cnt == MCNT_BAUD)
begin
if(bit_count == MCNT_BIT)
bit_count <= 0;
else
bit_count <= bit_count + 1'd1;
end
//寄存器逻辑(用于位发送)
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
r_Data <= 0;
else if(Send_Go) //延时结束那一刻,对寄存器写入Data
r_Data <= Data;
else
r_Data <= r_Data;
//位发送逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
uart_tx <= 1'd1;
else if(en_baud_cnt == 0)
uart_tx <= 1'd1;
else
begin
case(bit_count)
0: uart_tx <= 1'd0;
1: uart_tx <= r_Data[0];
2: uart_tx <= r_Data[1];
3: uart_tx <= r_Data[2];
4: uart_tx <= r_Data[3];
5: uart_tx <= r_Data[4];
6: uart_tx <= r_Data[5];
7: uart_tx <= r_Data[6];
8: uart_tx <= r_Data[7];
9: uart_tx <= 1'd1;
default: uart_tx <= uart_tx;
endcase
end
// Tx_Done逻辑,判断数据是否发送完毕,时序逻辑
assign w_Tx_Done = ((bit_count == MCNT_BIT)&&(baud_div_cnt == MCNT_BAUD)); //数据发送完后,Tx_Done置1
always @(posedge Clk)
Tx_Done <= w_Tx_Done;
endmodule
九、串口通信接收
1、模块逻辑设计
设计要求:
模块:
要点:
(1)对于串口接收,如何从串行数据中准确获取每一位数据?
在计数中点采样。
(2)如何检测串口接收数据的起始位的下降沿?
通过D触发器存储上一个时钟输入的数据,与现在时钟输入的数据比较,判断是上升还是下降。
(3)如何设计波特率分频计数器使能逻辑?
当检测到下降沿时,使能端关闭打开,开始计数;计数完结束位时,使能端关闭,停止计数。
特殊情况,当检测到下降沿后,立刻又检测到上升沿时,则视为毛刺信号干扰,仍需关闭使能。
(4)如何知道当前读取的信号位于UART信号的哪一位?
定义一个位计数器。
(5)如何实现位接收逻辑?
用一个8位寄存器的接收
(6)产生一个标志信号,在每次接收完毕后通知外界接收完成
(7)uart_rx作为输入是异步信号,如果恰好在Clk上升沿附近发生变化的时候,就会无法满足D触发器存储数据的必要条件,导致D触发器无法准确判断该时刻uart_rx信号状态,导致D触发器输出振荡,不稳定,(亚稳态)如何解决?
在100MHz的时序逻辑电路中,使用两级D触发器进行同步(打拍),将纯外部输入信号同步为时序信号,可以有效避免亚稳态。
(8)一个优化:不同系统在UART通信时,时钟频率不一样,有误差,存在发送所需要的位时间小于标准时间的情况,那么对于接收器来说,接收器接收一个数据的时间就会比发送方发送数据要耗时长一点。如果发送方连续发数据时,可能会导致某次数据接收还在上一个数据,但发送数据起始位已经到达了,接收端错过该数据的起始位,从而导致数据传输错误。
解决方案:接收方在接收停止位的时候,只使用0.5位的时间。为发送器提供了0.5位的容错时间。
程序设计:
module uart_byte_rx(
Clk,
Reset_n,
uart_rx,
Rx_Done,
Rx_Data
);
input Clk;
input Reset_n;
input uart_rx;
output reg Rx_Done;
output reg [7:0] Rx_Data;
parameter CLOCK_FREQ = 50_000_000; //时钟频率
parameter BAUD = 9600; //波特率
parameter MCNT_BAUD = CLOCK_FREQ / BAUD - 1; //波特计数最大值
reg [29:0] baud_div_cnt; //波特率计数器
reg en_baud_cnt; //波特率使能标志
reg [3:0] bit_cnt; //位计数器
reg r_uart_rx; //边沿检测逻辑,D触发器输出信号
reg [7:0] r_Rx_Data; //存储uart_rx信号,齐了后一起输出给Rx_Data
wire w_Rx_Done; //信号接收完成标志
wire nege_uart_rx; //下降沿判断标志
reg dff0_uart_rx; //D触发器
reg dff1_uart_rx;
//波特率计数逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
baud_div_cnt <= 0;
else if(en_baud_cnt)
begin
if(baud_div_cnt == MCNT_BAUD)
baud_div_cnt <= 0;
else
baud_div_cnt <= baud_div_cnt + 1'd1;
end
else
baud_div_cnt <= 0;
//UART信号边沿检测逻辑
always @(posedge Clk)
dff0_uart_rx <= uart_rx;
always @(posedge Clk)
dff1_uart_rx <= dff0_uart_rx;
always @(posedge Clk)
r_uart_rx <= dff1_uart_rx;
assign nege_uart_rx = ((dff1_uart_rx == 0) && (r_uart_rx == 1));
//波特率计数器使能逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
en_baud_cnt <= 0;
else if(nege_uart_rx) //下降沿到来
en_baud_cnt <= 1;
else if((baud_div_cnt == MCNT_BAUD/2) && (bit_cnt == 0) && (dff1_uart_rx == 1)) //排除毛刺导致的下降沿
en_baud_cnt <= 0;
else if((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2)) //位计数器计满,uart_rx接收完毕
en_baud_cnt <= 0;
//位计数器逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
bit_cnt <= 0;
else if((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2))
bit_cnt <= 0;
else if(baud_div_cnt == MCNT_BAUD)
bit_cnt <= bit_cnt + 1'd1;
//位接收逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
r_Rx_Data <= 0;
else if(baud_div_cnt == MCNT_BAUD/2)
begin
case(bit_cnt)
1: r_Rx_Data[0] <= dff1_uart_rx;
2: r_Rx_Data[1] <= dff1_uart_rx;
3: r_Rx_Data[2] <= dff1_uart_rx;
4: r_Rx_Data[3] <= dff1_uart_rx;
5: r_Rx_Data[4] <= dff1_uart_rx;
6: r_Rx_Data[5] <= dff1_uart_rx;
7: r_Rx_Data[6] <= dff1_uart_rx;
8: r_Rx_Data[7] <= dff1_uart_rx;
default: r_Rx_Data <= r_Rx_Data;
endcase
end
//接收完成标志信号逻辑
assign w_Rx_Done = ((bit_cnt == 9) && (baud_div_cnt == MCNT_BAUD/2));
always @(posedge Clk)
Rx_Done <= w_Rx_Done;
//r_Rx_Data逻辑,寄存器存储后再输出给Rx_Data
always @(posedge Clk)
if(Rx_Done)
Rx_Data <= r_Rx_Data;
endmodule
2、功能仿真验证
module uart_byte_rx_tb();
reg Clk;
reg Reset_n;
reg uart_rx;
wire Rx_Done;
wire [7:0] Rx_Data;
uart_byte_rx uart_byte_rx_inst0(
.Clk(Clk),
.Reset_n(Reset_n),
.uart_rx(uart_rx),
.Rx_Done(Rx_Done),
.Rx_Data(Rx_Data)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial
begin
Reset_n = 0;
uart_rx = 1;
#201;
Reset_n = 1;
#200;
//开始传输
//8'b0101_0101
uart_rx = 0; #(5208*20); //起始位
uart_rx = 1; #(5208*20); //bit0
uart_rx = 0; #(5208*20); //bit1
uart_rx = 1; #(5208*20); //bit2
uart_rx = 0; #(5208*20); //bit3
uart_rx = 1; #(5208*20); //bit4
uart_rx = 0; #(5208*20); //bit5
uart_rx = 1; #(5208*20); //bit6
uart_rx = 0; #(5208*20); //bit7
uart_rx = 1; #(5208*20); //停止位
#(5208*20*10);
//8'b1010_1010
uart_rx = 0; #(5208*20); //起始位
uart_rx = 0; #(5208*20); //bit0
uart_rx = 1; #(5208*20); //bit1
uart_rx = 0; #(5208*20); //bit2
uart_rx = 1; #(5208*20); //bit3
uart_rx = 0; #(5208*20); //bit4
uart_rx = 1; #(5208*20); //bit5
uart_rx = 0; #(5208*20); //bit6
uart_rx = 1; #(5208*20); //bit7
uart_rx = 1; #(5208*20); //停止位
#(5208*20*10);
//8'b1111_0000
uart_rx = 0; #(5208*20); //起始位
uart_rx = 0; #(5208*20); //bit0
uart_rx = 0; #(5208*20); //bit1
uart_rx = 0; #(5208*20); //bit2
uart_rx = 0; #(5208*20); //bit3
uart_rx = 1; #(5208*20); //bit4
uart_rx = 1; #(5208*20); //bit5
uart_rx = 1; #(5208*20); //bit6
uart_rx = 1; #(5208*20); //bit7
uart_rx = 1; #(5208*20); //停止位
#(5208*20*10);
//8'b0000_1111
uart_rx = 0; #(5208*20); //起始位
uart_rx = 1; #(5208*20); //bit0
uart_rx = 1; #(5208*20); //bit1
uart_rx = 1; #(5208*20); //bit2
uart_rx = 1; #(5208*20); //bit3
uart_rx = 0; #(5208*20); //bit4
uart_rx = 0; #(5208*20); //bit5
uart_rx = 0; #(5208*20); //bit6
uart_rx = 0; #(5208*20); //bit7
uart_rx = 1; #(5208*20); //停止位
#(5208*20*10);
$stop;
end
endmodule
3、亚稳态
十、基于状态机的按键消抖
1、基本原理
按键工作状态:
(1)按键未按下,空闲态,电路输出高电平
(2)按键按下抖动,电路输出在高低电平之间跳转
(3)按下抖动停止,静止态,电路输出低电平
(4)按键松开抖动,电路输出在高低电平之间跳转
(5)松开抖动停止,电路输出高电平
2、模块逻辑设计
要点:
(1)如何判断抖动已经停止了?
思路:按键按下时,在检测到下降沿后,判断低电平的持续时间,如果低于20ms,则认为是抖动;如果超过20ms,则认为按键按下。
按键释放时,在检测到上升沿后,判断高电平持续时间,如果低于20ms,则认为是抖动;如果超过20ms,则认为按键释放。
(2)状态转移图分析
程序设计:
module key_filter(
Clk,
Reset_n,
Key,
Key_P_Flag,
Key_R_Flag,
Key_State
);
input Clk;
input Reset_n;
input Key;
output reg Key_P_Flag;
output reg Key_R_Flag;
output reg Key_State;
reg [1:0] state;
localparam IDLE = 0; //状态机定义
localparam P_FILTER = 1;
localparam WAIT_R = 2;
localparam R_FILTER = 3;
parameter MCNT = 1_000_000;
reg [29:0] cnt;
reg sync_d0_Key;
reg sync_d1_Key;
reg r_Key;
wire pedge_key;
wire nedge_key;
wire time_20ms_reached;
assign time_20ms_reached = (cnt >= MCNT);
//同步D触发器逻辑
always @(posedge Clk)
sync_d0_Key <= Key;
always @(posedge Clk)
sync_d1_Key <= sync_d0_Key;
always @(posedge Clk)
r_Key <= sync_d1_Key;
//上升沿下降沿逻辑
assign pedge_key = ((r_Key == 0) && (sync_d1_Key == 1));
assign nedge_key = ((r_Key == 1) && (sync_d1_Key == 0));
//状态机逻辑
always @(posedge Clk or negedge Reset_n)
if(!Reset_n)
begin
state <= IDLE;
Key_P_Flag <= 1'd0;
Key_R_Flag <= 1'd0;
cnt <= 0;
Key_State <= 1'd1;
end
else
begin
case(state)
IDLE:
begin
Key_R_Flag <= 1'd0;
if(nedge_key)
state <= P_FILTER;
end
P_FILTER:
begin
if(time_20ms_reached)
begin
state <= WAIT_R;
Key_P_Flag <= 1'd1;
Key_State <= 1'd0;
cnt <= 0;
end
else if(pedge_key)
begin
state <= IDLE;
cnt <= 0;
end
else
begin
state <= state;
cnt <= cnt + 1'd1;
end
end
WAIT_R:
begin
Key_P_Flag <= 1'd0;
if(pedge_key)
state <= R_FILTER;
end
R_FILTER:
begin
if(time_20ms_reached)
begin
state <= IDLE;
Key_R_Flag <= 1'd1;
Key_State <= 1'd1;
cnt <= 0;
end
else if(nedge_key)
begin
state <= WAIT_R;
cnt <= 0;
end
else
begin
state <= state;
cnt <= cnt + 1'd1;
end
end
endcase
end
//20ms计数器逻辑 (不用了,有小错误,由于D触发器会有延迟一个时钟)
// always @(posedge Clk or negedge Reset_n)
// if(!Reset_n)
// cnt <= 0;
// else if((state == P_FILTER) || (state == R_FILTER))
// cnt <= cnt + 1'd1;
// else
// cnt <= 0;
endmodule
3、功能仿真验证
module key_filter_tb();
reg Clk;
reg Reset_n;
reg Key;
wire Key_P_Flag;
wire Key_R_Flag;
wire Key_State;
key_filter key_filter_inst(
.Clk(Clk),
.Reset_n(Reset_n),
.Key(Key),
.Key_P_Flag(Key_P_Flag),
.Key_R_Flag(Key_R_Flag),
.Key_State(Key_State)
);
initial Clk = 1;
always #10 Clk = ~Clk;
initial
begin
Reset_n = 0;
Key = 1;
#201;
Reset_n = 1;
//第一次按下测试
//空闲稳定
Key = 1; #100_000_000; //空闲 稳定 100ms
//按下抖动
Key = 0; #18_000_000; //按下 抖动 低18ms
Key = 1; #2_000_000; //按下 抖动 高2ms
Key = 0; #1_000_000; //按下 抖动 低1ms
Key = 1; #200_000; //按下 抖动 高0.2ms
Key = 0; #20_000_000; //按下 稳定 低20ms
//按下稳定
Key = 0; #50_000_000; //按下 稳定 50ms
//释放抖动
Key = 1; #2_000_000; //释放 抖动 高2ms
Key = 0; #1_000_000; //释放 抖动 低1ms
Key = 1; #20_000_000; //释放 稳定 高20ms
//释放稳定
Key = 1; #50_000_000; //释放 稳定 50ms
//第二次按下尝试
//空闲稳定
Key = 1; #100_000_000; //空闲 稳定 100ms
//按下抖动
Key = 0; #18_000_000; //按下 抖动 低18ms
Key = 1; #2_000_000; //按下 抖动 高2ms
Key = 0; #1_000_000; //按下 抖动 低1ms
Key = 1; #200_000; //按下 抖动 高0.2ms
Key = 0; #20_000_000; //按下 稳定 低20ms
//按下稳定
Key = 0; #50_000_000; //按下 稳定 50ms
//释放抖动
Key = 1; #2_000_000; //释放 抖动 高2ms
Key = 0; #1_000_000; //释放 抖动 低1ms
Key = 1; #20_000_000; //释放 稳定 高20ms
//释放稳定
Key = 1; #50_000_000; //释放 稳定 50ms
$stop;
end
endmodule
这里要注意,如果单独写计数器逻辑的话,用到的state的值是上一个时钟周期存储的state,会有一个时钟的延时性,造成误差。因此精准的计数器逻辑需要写在状态机的case中,跟随状态变换而变化。
仿真结果: