一、状态机
FPGA不同于CPU的一点特点就是CPU是顺序执行的,而FPGA是同步执行(并行)的。那么FPGA如何处理明显具有时间上先后顺序的事件呢?这个时候我们就需要使用到有限状态机了。
状态机简写为 FSM(Finite State Machine),也称为同步有限状态机,我们一般简称为状态机,之所以说“同步”是因为状态机中所有的状态跳转都是在时钟的作用下进行的,而“有限”则是说状态的个数是有限的。
状态机的每一个状态代表一个事件,从执行当前事件到执行另一事件我们称之为状态的跳转或状态的转移,我们需要做的就是执行该事件然后跳转到一下时间,这样我们的系统就“活”了,可以正常的运转起来了。状态机通过控制各个状态的跳转来控制流程,使得整个代码看上去更加清晰易懂,在控制复杂流程的时候,状态机优势明显。
1.1 分类
根据状态机的输出是否与输入条件相关,可将状态机分为两大类,即摩尔(Moore)型状态机和米勒(Mealy) 型状态机。
- 摩尔(Moore)状态机
输出只取决于当前状态,而与输入状态无关。 - 米勒(Mealy)状态机
输出不仅取决于当前状态,还取决于输入状态。
1.2 写法
根据状态机的实际写法,状态机可以分为一段式、二段式和三段式状态机。
- 一段式
整个状态机写到一个 always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出。 - 二段式
用两个 always 模块来描述状态机,其中一个 always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出。不同于一段式状态机的是,它需要定义两个状态,现态和次态,然后通过现态和次态的转换来实现时序逻辑。 - 三段式
在两个 always 模块描述方法的基础上,使用三个always 模块,一个always 模块采用同步时序描述状态转移,一个 always 采用组合逻辑判断状态转移条件,描述状态转移规律,另一个 always 模块描述状态输出。
二、状态机思想编写LED流水灯
这里采用二段式来编写LED流水灯。
- 顶层模块
module top (
input clk,
input rst_n,
input KEY0,
input KEY1,
output [5:0] led
);
wire en_1Hz;
wire key0_debounced, key1_debounced;
reg pause;
wire fsm_rst_n;
reg key1_debounced_prev;
key_debounce u_key0_deb (
.clk(clk),
.rst_n(rst_n),
.key_in(~KEY0), // KEY0低电平有效
.key_out(key0_debounced)
);
key_debounce u_key1_deb (
.clk(clk),
.rst_n(rst_n),
.key_in(~KEY1), // KEY1低电平有效
.key_out(key1_debounced)
);
assign fsm_rst_n = ~key0_debounced;
// 添加初始状态强制赋值
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
pause <= 1'b0; // 明确初始化暂停状态
key1_debounced_prev <= 1'b0;
end else begin
key1_debounced_prev <= key1_debounced;
// 仅当KEY1从释放到按下时触发
if (key1_debounced_prev && !key1_debounced) begin
pause <= ~pause;
end
end
end
clk_divider #(
.CLK_FREQ(50_000_000),
.OUT_FREQ(1)
) u_clk_div (
.clk(clk),
.rst_n(rst_n),
.en(en_1Hz)
);
led_fsm u_led_fsm (
.clk(clk),
.rst_n(fsm_rst_n),
.en(en_1Hz),
.pause(pause),
.led(led)
);
endmodule
- 分频模块
module clk_divider #(
parameter CLK_FREQ = 50_000_000,
parameter OUT_FREQ = 1
) (
input clk,
input rst_n,
output reg en
);
localparam CNT_MAX = CLK_FREQ / OUT_FREQ - 1;
reg [25:0] cnt;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
cnt <= 0;
en <= 0;
end else begin
if (cnt == CNT_MAX) begin
cnt <= 0;
en <= 1;
end else begin
cnt <= cnt + 1;
en <= 0;
end
end
end
endmodule
- 按键消抖模块
module key_debounce(
input clk,
input rst_n,
input key_in,
output reg key_out
);
parameter DEBOUNCE_TIME = 1_000_000; // 20ms@50MHz
reg [19:0] counter;
reg key_reg;
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
counter <= 0;
key_reg <= 1;
key_out <= 1'b0; // 修正复位初始值为0(匹配按键未按下状态)
end else begin
key_reg <= key_in;
if (key_reg != key_in) begin
counter <= 0;
end else if (counter < DEBOUNCE_TIME) begin
counter <= counter + 1;
end else begin
key_out <= key_in;
end
end
end
endmodule
- 流水灯状态机模块
module led_fsm6 (
input clk,
input rst_n,
input en,
input pause,
output [5:0] led
);
parameter S_LED0 = 6'b000001;
parameter S_LED1 = 6'b000010;
parameter S_LED2 = 6'b000100;
parameter S_LED3 = 6'b001000;
parameter S_LED4 = 6'b010000;
parameter S_LED5 = 6'b100000;
reg [5:0] current_state, next_state;
// 状态寄存器(增加pause控制)
always @(posedge clk or negedge rst_n) begin
if (!rst_n) begin
current_state <= S_LED0;
end else if (en && !pause) begin // 只有使能且未暂停时更新
current_state <= next_state;
end
end
// 下一状态逻辑保持不变
always @(*) begin
case (current_state)
S_LED0: next_state = S_LED1;
S_LED1: next_state = S_LED2;
S_LED2: next_state = S_LED3;
S_LED3: next_state = S_LED4;
S_LED4: next_state = S_LED5;
S_LED5: next_state = S_LED0;
default: next_state = S_LED0;
endcase
end
assign led = current_state;
endmodule
三、运行结果
总结
整个代码逻辑并不难,主要用于熟悉层次化设计,顶层模块与子模块级联。
参考资料
https://blog.csdn.net/wuzhikaidetb/article/details/119421783
https://blog.csdn.net/qq_45659777/article/details/124511139
https://blog.csdn.net/weixin_46188211/article/details/122792460
另外加以AI工具DeepSeek的辅助。