6.2.3.2 两段式状态机描述方法(推荐写法)
为了使FSM 描述清晰简介,易于维护,易于附加时序约束,使综合器和布局布线器更好的优化设计,推荐使用两段式FSM 描述方法。
本例的两段式描述代码如下:
//2-paragraph method to describe FSM
//Describe sequential state transition in 1 sequential always block
//State transition conditions in the other combinational always block
//Package state output by task. Then register the output
module state2 ( nrst,clk,
i1,i2,
o1,o2,
err
);
input nrst,clk;
input i1,i2;
output o1,o2,err;
reg o1,o2,err;
reg [2:0] NS,CS;
parameter [2:0] //one hot with zero idle
IDLE = 3'b000,
S1 = 3’b001,
S2 = 3’b010,
ERROR = 3’b100;
//sequential state transition
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;
//combinational condition judgment
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
//output task
task IDLE_out;
{o1,o2,err} = 3'b000;
endtask
task S1_out;
{o1,o2,err} = 3'b100;
endtask
task S2_out;
{o1,o2,err} = 3'b010;
endtask
task ERROR_out;
{o1,o2,err} = 3'b111;
endtask
endmodule
两段式写法是推荐的FSM 描述方法之一,在此我们仔细讨论一下代码结构。两段式FSM 的核心就是:一个always 模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律。两段式写法可以概括为图6-5 描述的结构。
图6-5 两段式FSM 描述结构图
本例中,同步时序描述状态转移的always 模块代码如下:
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;
其实这是一种程式化的描述结构,无论具体到何种FSM 设计,都可以定义两个状态寄存器“CS”和“NS”,分别代表当前状态和下一状态,然后根据所需的复位方式(同步复位或异步复位),在时钟沿到达时将NS 赋给CS。需要注意的是这个同步时序模块的赋值要采用非阻塞赋值“<=”。
本例中,另一个采用组合逻辑判断状态转移条件的always 模块代码如下:
//combinational condition judgment
always @ (nrst or 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
这个使用组合逻辑判断状态转移条件的always 模块也可以看成格式化的书写结构。其中always 的敏感列表为当前状态“CS”,复位信号和输入条件(如果是米勒状态机,则必须有输入条件;如果是摩尔状态机,一般敏感表和后续逻辑判定没有输入),请大家注意电平敏感表必须列完整。本例中这段电平敏感列表为:
always @ (nrst or CS or i1 or i2)
一般来说,在这个组合always 敏感表下先写一个默认的下一状态“NS”的描述,然后根据实际的状态转移条件由内部的case 或者if...else 条件判断确定正确的转移。如本例中下面这段代码,
……
begin
NS = ERROR;
ERROR_out;
case (CS)
……
推荐在敏感表下的默认状态为不定状态X,这样描述的好处有两个:第一在仿真时可以很好的考察所设计的FSM 的完备性,如果所设计的FSM 不完备,则会进入任意状态,仿真很容易发现;第二个好处是综合器对不定态X 的处理是“Don’t Care”,即任何没有定义的状态寄存器向量都会被忽略。这里赋值不定态的效果和使用casez 或casex 替代case 的效果非常相似。在每个case 模块的内部的结构也非常相似,都是先描述当前状态的组合逻辑输出,然后根据输入条件(米勒FSM)判定下一个状态。该组合逻辑模块中所有的赋值推荐采用阻塞赋值“=”。
请大家注意,虽然下一状态寄存器NS 为寄存器类型,但是在两段式FSM 的判断状态转移
条件的always 模块中,实际上对应的真实硬件电路是纯组合逻辑电路。
对于每个输出,一般用组合逻辑描述,比较简便的方法是用task/endtask 将输出封装起来,这样做的好处不仅仅是写法简单,而且利于复用共同的输出。例如本例中S1 状态的输出被封装为S1_out,在组合逻辑always 模块中直接调用即可。
task S1_out;
{o1,o2,err} = 3'b100;
endtask
组合逻辑容易产生毛刺,因此如果时序允许,请尽量对组合逻辑的输出插入一个寄存器节拍,这样可以很好的保证输出信号的稳定性。
6.2.3.3 三段式状态机描述方法(推荐写法)
两段式FSM 描述方法虽然有很多好处,但是它有一个明显的弱点就是其输出一般使用组合逻辑描述,而组合逻辑易产生毛刺等不稳定因素,并且在FPGA/CPLD 等逻辑器件中过多的组合逻辑会影响实现的速率(这点与ASIC 设计不同)。所以在上节我们特别提到了在两段式FSM 描述方法中,如果时序允许插入一个额外的时钟节拍,则尽量在在后级电路对FSM 的组合逻辑输出用寄存器寄存一个节拍,则可以有效地消除毛刺。但是很多情况下,设计并不允许额外的节拍插入(Latency),此时,解决之道就是采用3 段式FSM 描述方法。三段式描述方法与两段式描述方法相比,关键在于使用同步时序逻辑寄存FSM 的输出。
本例的三段式描述代码如下:
//3-paragraph method to describe FSM
//Describe sequential state transition in the 1st sequential always block
//State transition conditions in the 2nd combinational always block
//Describe the FSM out in the 3rd sequential always block
module state2 ( nrst,clk,
i1,i2,
o1,o2,
err
);
input nrst,clk;
input i1,i2;
output o1,o2,err;
reg o1,o2,err;
reg [2:0] NS,CS;
parameter [2:0] //one hot with zero idle
IDLE = 3'b000,
S1 = 3'b001,
S2 = 3'b010,
ERROR = 3'b100;
//1st always block, sequential state transition
always @ (posedge clk or negedge nrst)
if (!nrst)
CS <= IDLE;
else
CS <=NS;
//2nd always block, combinational condition judgment
always @ (nrst or CS or i1 or i2)
begin
NS = 3'bx;
case (CS)
IDLE: begin
if (~i1) NS = IDLE;
if (i1 && i2) NS = S1;
if (i1 && ~i2) NS = ERROR;
end
S1: begin
if (~i2) NS = S1;
if (i2 && i1) NS = S2;
if (i2 && (~i1)) NS = ERROR;
end
S2: begin
if (i2) NS = S2;
if (~i2 && i1) NS = IDLE;
if (~i2 && (~i1)) NS = ERROR;
end
ERROR: begin
if (i1) NS = ERROR;
if (~i1) NS = IDLE;
end
endcase
end
//3rd always block, the sequential FSM output
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
三段式写法可以概括为图6-6 描述的结构。
图6-6 三段式FSM 描述结构图
对比一下上节两段式FSM 的描述,读者可以清晰发现三段式与两段式FSM 描述的最大区别在于两段式采用了组合逻辑输出,而三段式巧妙地根据下一状态的判断,用同步时序逻辑寄存FSM 的输出。本例中就是下面一段代码,
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
有的读者可能会问,一段式写法也是用寄存器同步了FSM 的输出,为什么前面介绍一段式的输出代码容易混淆,不利于维护呢?请大家对比一下6.2.3.1 小节的这段一段式输出的代码,
……
case (NS)
IDLE: begin
if (~i1) begin{o1,o2,err}<=3'b000;NS <= IDLE; end
if (i1 && i2) begin{o1,o2,err}<=3'b100;NS <= S1; end
if (i1 && ~i2) begin{o1,o2,err}<=3'b111;NS <= ERROR;end
end
……
通过对比,可以清晰地看到:使用一段式建模FSM 的寄存器输出的时候,必须要综合考虑现态在何种状态转移条件下会进入哪些次态,然后在每个现态的case 分支下分别描述每个次态的输出,这显然不符合思维习惯;而三段式建模描述FSM 的状态机输出时,只需指定case 敏感表为次态寄存器,然后直接在每个次态的case 分支中描述该状态的输出即可,根本不用考虑状态转移条件。本例的FSM 很简单,如果设计的FSM 相对复杂,三段式的描述优势就会凸显出来。
另一方面,三段式描述方法与两段式描述相比,虽然代码结构复杂了一些,但是换来的优势是使FSM 做到了同步寄存器输出,消除了组合逻辑输出的不稳定与毛刺的隐患,而且更利于时序路径分组,一般来说在FPGA/CPLD 等可编程逻辑器件上的综合与布局布线效果更佳。