移位寄存器
基本的移位寄存器由D触发器构成,前级输出接到后级输入。移位寄存器分为左移和右移。在某些情况下,移位寄存器可以实现数据的串并转换。
parameter N = 4;
reg [N-1:0]q;
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
q <= 0;
else begin //右移
q[0] <= a;
q[N-1:1] <= q[N-2:0];
/* //左移
q[N-2:0] <= q[N-1:1];
q[N-1] <= 0;
*/
end
end
边沿检测
边沿检测模块用于检测输入或FPGA内部信号的上升沿和下降沿,可监控信号的状态变化。一般用三个移位寄存器存储数据(q[0]、q[1]、q[2] = data_in),这里延迟了三个时钟周期,可消除信号上升时间导致的亚稳态。
上升沿:posedge = q[1] & ~q[2];
下降沿:negedge = ~q[1] & q[2];
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
q <= 0;
else begin
q[0] <= data_in;
q[1] <= q[0];
q[2] <= q[1];
end
end
assign pos_edge = q[1] & ~q[2];
assign neg_edge = ~q[1] & q[2];
按键消抖
机械按键按下时会经过十几毫秒的震荡才逐渐稳定下来,因此在使用按键控制某信号时一般要添加按键消抖模块用于消除震荡,基本思路是比较前后时钟沿时刻输入的值,当值不变时再延时20ms,将稳定的结果作为输出。
//设时钟频率为50MHz
parameter CNT_MAX = 20'd100_0000; //根据时钟频率确定
reg [19:0]cnt;
reg data_in_reg1;
reg data_in_reg2;
always @(posedge clk or negedge rst_n) begin //震荡检测
if(!rst_n)
data_in_reg1 <= 0;
data_in_reg2 <= 0;
else
data_in_reg1 <= data_in; //延时两个时钟周期
data_in_reg2 <= data_in_reg1;
end
always @(posedge clk or negedge rst_n) begin //20ms延时模块
if(!rst_n)
cnt <= 0;
else if(data_in_reg1 != data_in_reg2 )
cnt <= CNT_MAX;
else if(cnt > 0)
cnt <= cnt - 1;
else
cnt <= 0;
end
always @(posedge clk or negedge rst_n) begin //输出模块
if(!rst_n)
data_out <= 0;
else if(cnt == 1)
data_out <= data_in_reg2;
else
data_out <= data_out;
end
时钟分频
根据分频器的分频比例(分频前的频率和分频后的频率比值)是偶数还是奇数,将分频器分为偶数分频器和奇数分频器。
偶数分频器:计数器计数到N/2-1时信号翻转,如6分频电路,cnt计数到2时让clk_6div翻转,即0、1、2为高,3、4、5为低,实现6分频。
奇数分频器:需要两个中间变量,一个对上升沿计数到N/2-1时赋值高电平,一个对下降沿计数到N/2-1时赋值高电平,再对两个时钟变量进行与操作即可。
parameter N = 5; //分频系数
parameter EDGE = N/2-1; //取沿数
reg [2:0] cnt ; //3位计数器
reg out_clk1;
reg out_clk2;
//计数器模块
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
cnt <= 3'd0;
else if(cnt == N - 1'b1)
cnt <= 3'd0;
else
cnt <= cnt + 'd1;
end
//out_clk1在上升沿进行翻转
always@(posedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
out_clk1 <= 1'b0;
else if(cnt <= EDGE)
out_clk1 <= 1'b0;
else
out_clk1 <= 1'b1;
end
//out_clk1在下降沿进行翻转
always@(negedge sys_clk or negedge sys_rst_n) begin
if(!sys_rst_n)
out_clk2 <= 1'b0;
else if(cnt <= EDGE)
out_clk2 <= 1'b0;
else
out_clk2 <= 1'b1;
end
assign out_clk = out_clk1 & out_clk2;
endmodule
状态机
对于某一任务,如果是按顺序执行,例如其步骤是1-2-3-4-5,这个时候用计数器实现是最方便的。但在一些比较复杂的控制场合,例如步骤是2-5-1-3-4,其跳转顺序是乱序的,就要用状态机来实现。
状态机分为Mealy型和Moore型,其中,Moore型的输出与输入无关,而Mealy型有关。一般来讲,Mealy型会比Moore型少一个状态。
标准的三段式状态机写法步骤为:
0、状态空间定义
1、(时序)状态的转换
2、(组合)下个状态的判断
3、(时序)各状态下的动作
//检测序列“1101”
module FSM_sequence(
input clk,
input rst_n,
input a,
output reg z
);
reg [4:0]current_state;
reg [4:0]next_state;
//状态空间定义
parameter S0 = 5'b00001;
parameter S1 = 5'b00010;
parameter S2 = 5'b00100;
parameter S3 = 5'b01000;
parameter S4 = 5'b10000;
//状态跳转
always @(posedge clk or negedge rst_n) begin
if(!rst_n) begin
current_state <= S0;
end
else
next_state <= current_state;
end
//下个状态的判断
always @(*) begin
case(current_state):
S0:next_state = x ? S1 : S0;//1
S1:next_state = x ? S2 : S0;//1
S2:next_state = x ? S2 : S3;//0
S3:next_state = x ? S4 : S0;//1
S4:next_state = x ? S2 : S0;
default:next_state = S0;
endcase
end
//各状态下动作
always @(posedge clk or negedge rst_n) begin
if(!rst_n)
z <= 0;
else begin
if(next_state == S4)
z <= 1;
else
z <= 0;
end
endmodule