学习verilog
状态机的的几种常见写法。
有限状态机是一种用来进行对象行为建模的工具,其作用主要是描述对象在它的生命周期内所经历的状态序列,以及如何响应来自外界的各种事件。有限状态机被用于描述电路的状态变化。
状态机概览
状态机可分为两类:
Mealy 状态机:
状态机的输出和当前状态和输入相关
Moore状态机:
状态机的输出只和当前状态相关。
状态机的4个要素现态、条件、动作、次态。其中“现态”和“条件”作为产生”次态“和”动作“的原因。
- 现态:是指当前所处的状态
- 条件:又称为“事件”。当一个条件被满足,将会触发一个动作,或者执行一次状态的迁移
- 动作:条件满足后执行的动作。动作执行完毕后,可以迁移到新的状态,也可以仍旧保持原状态。动作不是必需的,当条件满足后,也可以不执行任何动作,直接迁移到新状态。
- 次态:条件满足后要迁往的新状态。次态一旦被激活,就会转变成现态。
在实现过程中需要注意的问题:
- 区分”状态“与”动作“:动作时改变后所做出的动作,一般动作执行结束后不会一致保持,但是状态会一直保持,直到下一个状态的改变。
状态机的描述方法分为3种:
一段式状态机
二段式状态机
三段式状态机
四段式状态机
一段式状态机
在描述比较简单的逻辑转换关系时可以直接使用一段式状态机,一段式状态机的特征是只有一个always
的时序逻辑块,状态转移,以及输出的变化都在该always
块中进行更改。一段式状态机在软件开发中使用的比较多的,但是在硬件描述中则不是非常推荐。
注意:
在声明状态常量时使用localparam
关键字进行声明而不是 parameter
,这样可以避免Vivado
的综合工具报警告
module m1(
input clk_i,
input rst_n_i;
output out_o
);
reg[1:0] state;
// 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
localparam [1:0] S0 = 2'b00;
localparam [1:0] S1 = 2'b01;
localparam [1:0] S2 = 2'b10;
localparam [1:0] S3 = 2'b11;
always @(posedge clk_i) begin
if (!rst_i) begin
state <= 2'd0;
out_o <= 1'b0;
end
else begin
// 使用 case 语句描述状态的变化以及输出的变换
case (state)
s0: begin
out_r <=1'b0;
state <= S1;
end
S2: begin
out_r <=1'b1;
state <= S3;
end
S3: begin
out_r <=1'b0;
state <= S4;
end
S4: begin
out_r <=1'b1;
state <= S3;
end
endcase
end
end
assign out_o = out_r;
endmodule
二段式状态机
两段式状态机的特征是采用两个 always 模块实现状态机的功能,其中一个 always
采用同步时序逻辑描述状态转移,另一个 always
采用组合逻辑来判断状态条件转移。
module m1(
input clk_i,
input rst_n_i;
output out_o
);
reg[1:0] current_state;
reg[1:0] next_state;
// 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
localparam [1:0] S0 = 2'b00;
localparam [1:0] S1 = 2'b01;
localparam [1:0] S2 = 2'b10;
localparam [1:0] S3 = 2'b11;
// 描述状态的转换
always @(posedge clk_i) begin
if(!rst_n_i)
Current_state<=0;
else
Current_state<=next_state;
end
// 描述下一个状态的转换以及输出的变换
always @(*) begin
case (state)
s0: begin
out_r =1'b0;
next_state = S1;
end
S2: begin
out_r =1'b1;
next_state = S3;
end
S3: begin
out_r =1'b0;
next_state = S4;
end
S4: begin
out_r =1'b1;
next_state = S3;
end
endcase
end
assign out_o = out_r;
endmodule
三段式状态机
三段式状态机的特征是在第一个 always
模块采用同步时序逻辑方式描述状态转移,第二个 always
采用组合逻辑方式描述状态转移规律,第三个 always
采用组合逻辑方式描述电路的输出。这种状态机的组合逻辑与时序逻辑完全独立。
module m1(
input clk_i,
input rst_n_i;
output out_o
);
reg[1:0] current_state;
reg[1:0] next_state;
// 表示状态的常量声明,在实际工程中需要对状态名称进行更准确的描述。
localparam [1:0] S0 = 2'b00;
localparam [1:0] S1 = 2'b01;
localparam [1:0] S2 = 2'b10;
localparam [1:0] S3 = 2'b11;
// 改变当前电路的状态, 这一段一般不用变化
always @(posedge clk_i) begin
if(!rst_n_i)
Current_state<=0;
else
Current_state<=next_state;
end
// 描述状态的转换
always @(*) begin
case (Current_state)
s0: begin
next_state = S1;
end
S2: begin
next_state = S3;
end
S3: begin
next_state = S4;
end
S4: begin
next_state = S3;
end
default : next_state = S0;
endcase
end
// 描述电路输出
always @(*) begin
case (Current_state)
S0, S1, S2: out_r = 1'b1;
S3, S4: out_r = 1'b0;
default: out_r=out_r;
endcase
end
assign out_o = out_r;
endmodule
四段式状态机
四段式状态机是在三段式状态机的基础上进行的修改,可以更加清晰地分离状态变化以及输出,并且可以避免写出 state_c <= state_c
之类的代码,避免生成锁存器,它包含四个部分:
状态信息的声明:
localparam IDLE = 4'd0;
localparam S1 = 4'd1;
localparam S2 = 4'd2;
localparam S3 = 4'd3;
时序逻辑部分,该部分的内容在所有代码中都不需要改变,格式化地描述现态和次态的转换。
reg[3:0] state_c; // 当前的状态寄存器
reg[3:0] state_n; // 下一个状态
always @(posedge clk_sys or negedge rst_n) begin
if (!rst_n)
state_c <= IDLE;
else
state_c <= state_n;
end
组合逻辑部分,该部分代码描述状态转移的条件:
always @(*) begin
case (state_c)
IDLE: begin
if (c_idle_s1)
state_n = S1;
else
state_n = state_c; // 避免生成锁存器
end
S1: begin
if (c_s1_s2)
state_n = S2;
else
state_n = state_c;
end
S2: begin
if (c_s2_s3)
state_n = S3;
else
state_n = state_c;
end
IDLE: begin
if (c_s3_idle)
state_n = IDLE;
else
state_n = state_c;
end
endcase
end
组合逻辑部分,定义状态转移的信号。注意条件命名规范: c_现态_次态
。
assign c_idle_d1 = state_c == IDLE && 转移条件;
assign c_s1_s2 = state_c == S1 && 转移条件;
assign c_s2_s3 = state_c == S2 && 转移条件;
assign c_s3_idle = state_c == S3 && 转移条件;
设计输出信号,此部分不限于使用时序逻辑还是组合逻辑进行描述,只需要清晰就可以了。
一般一个 always 语句只控制一个信号,有多少个输出信号就有多少个always 块
always @(posedge clk_sys or negedge rst_n) begin
if (!rst_n)
out1 <= 1'b0;
else begin
case (state_c)
IDLE, S1, S2: out1 <= 1'b0;
S3 : out1 <= 1'b1;
default : out1 <= 1'b0;
endcase
end
end
使用组合逻辑的分支判断的时候,每个分支只能造成一个变量发生改变,且保证每个分支中都在改变这个变量,否则,就会综合出锁存器电路。
其他
除了可以使用 localparam
定义参数外还可以使用 typedef
定义状态的名称
// 状态定义
typedef enum {
STATE_IDLE,
STATE_A,
STATE_B
} state_t;