一、 两段式:
采用同步时序描述状态转移:其实这是一种程式化的描述结构,无论具体到何种 FSM 设计,都可以定义两个状态寄存器“ CS”和“ NS”,分别代表当前状态和下一状态,然后根据所需的复位方式(同步复位或异步复位),在时钟沿到达时将 NS 赋给 CS。
1、第一个always:
采用同步时序描述状态转移:其实这是一种程式化的描述结构,无论具体到何种 FSM 设计,都可以定义两个状态寄存器“ CS”和“ NS”,分别代表当前状态和下一状态,然后根据所需的复位方式(同步复位或异步复位),在时钟沿到达时将 NS 赋给 CS。
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;//下一状态
2.第二个always:
采用组合逻辑判断状态转移条件: 这个使用组合逻辑判断状态转移条件的 always 模块也可以看成格式化的书写结构。其中 always 的敏感列表为当前状态“ CS”,复位信号和输入条件(如果是米勒状态机,则必须有输入条件;如果是摩尔状态机,一般敏感表和后续逻辑判定没有输入)
一般来说,在这个组合 always 敏感表下先写一个默认的下一状态“ NS”的描述,然后根据实际的状态转移条件由内部的 case 或者 if…else 条件判断确定正确的转移: 推荐在敏感表下的默认状态为不定状态 X,这样描述的好处有两个:第一在仿真时可以很好的考察所设计的 FSM 的完备性,如果所设计的 FSM 不完备,则会进入任意状态,仿真很容易发现;第二个好处是综合器对不定态 X 的处理是“ Don’t Care”,即任何没有定义的状态寄存器向量都会被忽略。
always @ (nrst or CS or i1 or i2)//CS:当前状态 i1/i2:输入信号
……
begin
NS = ERROR;
ERROR_out;
case (CS)
……
always @ (CS or i1 or i2)
begin
NS = 3'bx;//初始化为不定态
ERROR_out;//调任务
case (CS)
IDLE: begin
IDLE_out;//调任务
if (~i1) NS = IDLE;
if (i1 && i2) NS = S1;
if (i1 && ~i2) NS = ERROR;
end
S1: begin
S1_out;
if (~i2) NS = S1;
if (i2 && i1) NS = S2;
if (i2 && (~i1)) NS = ERROR;
end
S2: begin
S2_out;
if (i2) NS = S2;
if (~i2 && i1) NS = IDLE;
if (~i2 && (~i1)) NS = ERROR;
end
ERROR: begin
ERROR_out;
if (i1) NS = ERROR;
if (~i1) NS = IDLE;
end
endcase
end
两段式 FSM 描述方法虽然有很多好处,但是它有一个明显的弱点就是其输出一般使用组合逻辑容易产生毛刺,因此如果时序允许,请尽量对组合逻辑的输出插入一个寄存器节拍,这样可以很好的保证输出信号的稳定性
二、三段式:
1.第三个always:
一个 always模块采用同步时序描述状态转移;第二个采用组合逻辑判断状态转移条件,描述状态转移规律;第三个 always 模块使用同步时序电路描述每个状态的输出,这种写法本书称为三段式写法
其写法可以概括为以下结构:
三段式与两段式 FSM 描述的最大区别在于两段式采用了组合逻辑输出,而三段式巧妙地根据下一状态的判断,用同步时序逻辑寄存 FSM 的输出。
三段式第一个always和第二个always 与两段式相同。
always @ (posedge clk or negedge nrst)
if (!nrst)
{o1,o2,err} <= 3'b000;
else
begin
{o1,o2,err} <= 3'b000;
case (NS)
IDLE: {o1,o2,err}<=3'b000;
S1: {o1,o2,err}<=3'b100;
S2: {o1,o2,err}<=3'b010;
ERROR: {o1,o2,err}<=3'b111;
endcase
end
endmodule
一段式建模 FSM 的寄存器输出的时候,必须要综合考虑现态在何种状态转移条件下会进入哪些次态,然后在每个现态的 case 分支下分别描述每个次态的输出,这显然不符合思维习惯;而三段式建模描述 FSM 的状态机输出时,只需指定 case 敏感表为次态寄存器,然后直接在每个次态的 case 分支中描述该状态的输出即可,根本不用考虑状态转移条件。本例的 FSM 很简单,如果设计的 FSM 相对复杂,三段式的描述优势就会凸显出来。
值得注意:在三段式 FSM 描述方法中,判断状态转移的 always 模块的 case 语句判断的条件是当前状态“ CS”而在同步时序 FSM 输出的 always 模块的 case 语句判断的条件是下一状态“ NS”。
三、两段式建模与三段式建模的关系:
TASKOUT:
task IDLE_out;
begin
{w_o1,w_o2,w_err} = 3'b000;
end
四、 状态机设计的其他技巧:
- 1)由于 CPLD 更多地提供组合逻辑资源,而 FPGA 更多地提供触发器资源,所以 CPLD 多使用 gray-code,而 FPGA 多使用 one-hot 编码。
- 2)状态机的定义可以用 parameter 定义,但是不推荐使用`define 宏定义的方式
- 3)完整的状态机应该包含一个默认( default)状态,当转移条件不满足,或者状态发生了突变时要能保证逻辑不会陷入“死循环”。这是对状态机健壮性的一个重要要求,也就是常说的要具备“自恢复”功能。
避免Latches:
- 1)if-else 或 case 语句,结构一定要完整
- 2)不要将赋值信号放在赋值源头,或条件判断中
- 3)敏感信号列表建议多用 always@(*)