输入、状态、状态转移条件、输出。
Mealy状态机:时序逻辑的输出不仅取决于当前状态,还与输入有关;
Moore状态机:时序逻辑的输出只与当前状态有关。
① 状态转移图:设计分析时使用,工具自动翻译的代码效率不高,适合规模小的设计;对于大规模设计,HDL更好;
② 状态转移表;
③ HDL描述。
① 逻辑抽象,得到状态转移图:确定输入、输出、状态变量、画状态转移图;
② 状态简化,得到最简的状态转移图:合并等价状态;
③ 状态编码:binary、gray、one-hot编码方式;
④ 用HDL描述。
只有一个always block,把所有的逻辑(输入、输出、状态)都在一个always block的时序逻辑中实现。这种写法看起来很简洁,但是不利于维护,如果状态复杂一些就很容易出错,不推荐这种方法。
在简单的状态机可以使用。
有两个always block,把时序逻辑和组合逻辑分隔开来。时序逻辑里进行当前状态和下一状态的切换,组合逻辑实现各个输入、输出以及状态判断。这种写法不仅便于阅读、理解、维护,而且利于综合器优化代码,利于用户添加合适的时序约束条件,利于布局布线器实现设计。在两段式描述中,当前状态的输出用组合逻辑实现,可能存在竞争和冒险,产生毛刺。
要求对状态机的输出用寄存器打一拍,但很多情况不允许插入寄存器节拍,此时使用三段式描述。其优势在于能够根据状态转移规律,在上一状态根据输入条件判断出当前状态的输出,从而不需要额外插入时钟节拍。
有三个always block,一个时序逻辑采用同步时序的方式描述状态转移,一个采用组合逻辑的方式判断状态转移条件、描述状态转移规律,第三个模块使用同步时序的方式描述每个状态的输出。代码容易维护,时序逻辑的输出解决了两段式组合逻辑的毛刺问题,但是从资源消耗的角度上看,三段式的资源消耗多一些。
模板如下:
always @ ( posedge clk or negedge rst_n )
begin
if ( !rst_n )
CS <= IDLE;
else
CS <= NS;
end
always @*
begin
NS = 'bx; //初始化寄存器,避免生成latch
case (CS) //注意为CS
IDLE: begin
end;
S1: begin
end;
default:
NS = 'bx; //与硬件电路一致
endcase
end
always @ (posedge clk or negedge rst_n)
begin
if ( !rst_n )
begin
end
else
begin
... //初始化一组值,避免latch
case (CS/NS) //这里有2种写法,推荐NS写法(moore型写法)
...
default: ;
endcase
end
end
下面,举例说明:
状态转换图如下所示:
//时序逻辑电路
always @(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
cstate <= IDLE;
cmd <= 3'b000;
end
else
case(cstate)
IDLE:
if(wr_req)
begin
cstate <= WR_S1;
cmd <= 3'b001;
end
else if(rd_req)
begin
cstate <= RD_S1;
cmd <= 3'b011;
end
else
begin
cstate <= IDLE;
cmd <= 3'b000;
end
WR_S1: begin
cstate <= WR_S2;
cmd <= 3'b010;
end
WR_S2: begin
cstate <= IDLE;
cmd <= 3'b000;
end
RD_S1:
if(wr_req)
begin
cstate <= WR_S2;
cmd <= 3'b010;
end
else
begin
cstate <= RD_S2;
cmd <= 3'b100;
end
RD_S2:
if(wr_req)
begin
cstate <= WR_S1;
cmd <= 3'b001;
end
else
begin
cstate <= IDLE;
cmd <= 3'b000;
end
default: cstate <= IDLE;
endcase
end
testbench如下:
`timescale 1 ns/ 100 ps
module fsm1_vlg_tst();
reg clk;
reg rd_req;
reg rst_n;
reg wr_req;
wire [2:0] cmd;
wire [2:0] cstate;
fsm1 i1 (
.clk(clk),
.cmd(cmd),
.cstate(cstate),
.rd_req(rd_req),
.rst_n(rst_n),
.wr_req(wr_req)
);
always #10 clk = ~clk;
initial
begin
clk = 0;rst_n = 1;
wr_req = 0;rd_req = 0;
#2 rst_n = 0;
#10 rst_n = 1;
repeat(100)
begin
#20 wr_req = {$random}%2;
rd_req = {$random}%2;
end
#100 $stop;
end
endmodule
功能仿真波形图:
三段式:
always block①:时序逻辑
//1st always block, sequential logic, store current state
always @(posedge clk or negedge rst_n)
if(!rst_n)
cstate <= IDLE;
else
cstate <= nstate;
always block②:组合逻辑
//2nd always block, combinational logic, decide next state
always @(cstate or wr_req or rd_req)
begin
case(cstate)
IDLE: if(wr_req)
nstate = WR_S1;
else if(rd_req)
nstate = RD_S1;
else
nstate = IDLE;
WR_S1: nstate = WR_S2;
WR_S2: nstate = IDLE;
RD_S1: if(wr_req)
nstate = WR_S2;
else
nstate = RD_S2;
RD_S2: if(wr_req)
nstate = WR_S1;
else
nstate = IDLE;
default: nstate = 3'bx;
endcase
end
always @(敏感电平信号)需要列举完全,可以用“@*”或者“@(*)”代替;
case(表达式)中的表达式为“cstate”,即现态;
阻塞赋值“=”;
default项中“'bx”必须设置,与实际电路一致。
always block③:时序逻辑
//3rd always block, FSM sequential output
always @(posedge clk or negedge rst_n)
if(!rst_n)
cmd <= 3'b000;
else
case(cstate)
IDLE: if(wr_req)
cmd <= 3'b001;
else if(rd_req)
cmd <= 3'b011;
else
cmd <= 3'b000;
WR_S1: cmd <= 3'b010;
WR_S2: cmd <= 3'b000;
RD_S1: if(wr_req)
cmd <= 3'b010;
else
cmd <= 3'b100;
RD_S2: if(wr_req)
cmd <= 3'b001;
else
cmd <= 3'b000;
default:;
endcase
case(表达式)中的表达式为“cstate”,即现态;
非阻塞赋值“<=”;
default项必须设置。
always @(posedge clk or negedge rst_n)
if(!rst_n)
cmd <= 3'b000;
else
case(nstate)
IDLE: cmd <= 3'b000;
WR_S1: cmd <= 3'b001;
WR_S2: cmd <= 3'b010;
RD_S1: cmd <= 3'b011;
RD_S2: cmd <= 3'b100;
default:;
endcase
endmodule
case(表达式)中的表达式为“nstate”,即次态,这里使用nextstate和state的区别在于,当状态跳转时,基于nextstate的输 出是立刻变化的,而基于state输出会延迟一个周期,其他情况都一样,应该根据自己的时序要求,选择用nextstate还是state。
非阻塞赋值“<=”;
default项必须设置。
参考状态机详解,以及三段式状态机的思维陷阱。