赞
踩
本节介绍有限状态机部分。
有限状态机(Finite-State Machine,FSM),简称状态机,是时序电路设计中经常采用的方式,尤其适用于设计数字系统的控制模块,在一些需要控制高速器件的场合,用状态机进行设计是解决问题的一种很好的实现方案,具有速度快,结构简单,可靠性高等优点。用Verilog HDL的case,if-else等语句能很好的描述基于状态机的设计。
状态机可以认为是组合逻辑和寄存器逻辑的特殊组合,它一般包括两个部分:组合逻辑部分和寄存器逻辑部分。寄存器用于存储状态,组合电路用于状态译码和产生输出信号。状态机的下一个状态及输出,不仅与输入信号有关,而且还与寄存器当前所处的状态有关。
根据输出信号产生方法的不同,状态机可以分为两类:Moore 状态机和 Mealy 状态机。
Moore 型状态机的输出只与当前状态有关,与当前输入无关。
输出会在一个完整的时钟周期内保持稳定,即使此时输入信号有变化,输出也不会变化。输入对输出的影响要到下一个时钟周期才能反映出来。这也是 Moore 型状态机的一个重要特点:输入与输出是隔离开来的。
Mealy 型状态机的输出,不仅与当前状态有关,还取决于当前的输入信号。
Mealy 型状态机的输出是在输入信号变化以后立刻发生变化,且输入变化可能出现在任何状态的时钟周期内。因此,同种逻辑下,Mealy 型状态机输出对输入的响应会比 Moore 型状态机早一个时钟周期。
Binary(二进制编码)、gray-code(格雷码)编码使用最少的触发器,较多的组合逻辑,而 one-hot(独热码)编码反之。one-hot 编码的最大优势在于状态比较时仅仅需要比较一个位,一定程度上简化了比较逻辑,减少了毛刺产生的概率。由于 CPLD 更多地提供组合逻辑资源,而 FPGA 更多地提供触发器资源,所以 CPLD 多使用 gray-code,而 FPGA 多使用 one-hot 编码。另一方面,对于小型设计使用 gray-code 和 binary 编码更有效,而大型状态机使用 one-hot 更高效。
在代码中添加综合器的综合约束属性或者在图形界面下设置综合约束属性可以比较方便地改变状态的编码。需要注意的是: Synplicity、Synopsys、Exemplar 等综合工具关于 FSM 的综合约束属性的语法格式各不相同。
一个完备的状态机(健壮性强)应该具备初始化状态和默认状态。当芯片加电或者复位后,状态机应该能够自动将所有判断条件复位,并进入初始化状态。需要注明的一点是,大多数 FPGA 有 GSR(Global Set/Reset)信号,当 FPGA 加电后, GSR 信号拉高,对所有的寄存器、RAM 等单元复位/置位,这时配置于 FPGA 的逻辑并未生效,所以不能保证正确地进入初始化状态。所以使用 GSR 企图进入 FPGA 的初始化状态,常常会产生种种不必要的麻烦。一般的方法是采用异步复位信号,当然也可以使用同步复位,但是要注意同步复位逻辑的设计。解决这个问题的另一种方法是将默认的初始状态的编码设为全零,这样 GSR 复位后,状态机自动进入初始状态。
状态机的定义可以用 parameter
定义,但是不推荐使用 `define
宏定义的方式,因为 `define
宏定义在编译时自动替换整个设计中所定义的宏,而parameter
仅定义模 块内部的参数,定义的参数不会与模块外的其他状态机混淆。例如,一个工程里面有两个 module,各包含一个 FSM,如果设计时都有IDLE
这一名称的状态,使用 `define
宏定义就会混淆起来,而使用 parameter
则不会造成任何不良影响。
如果使用二段式 FSM 描述 Mealy 状态机,输出逻辑可以用 ?
语句描述,或者使用 case
语句判断转移条件与输入信号即可。如果输出条件比较复杂,而且多个状态共用某些输出,则建议使用task/endtask
将输出封装起来,达到模块复用的目的。
完整的状态机应该包含一个默认(default)状态,当转移条件不满足,或者状态发生了突变时,要能保证逻辑不会陷入“死循环”。这是对状态机健壮性的一个重要要求,也就是常说的要具备“自恢复”功能。对编码case
和 if ... else
语句要特别注意, 尽量使用完备的条件判断语句。 Verilog 中,使用 case
语句的时候要用 default
建立默认状态。读者可能注意到,在上节举例的 case
语句中,没有写default
默认状态, 其实可以将其中一个状态不编码,指定其为 default
默认状态,则任何与所列状态机不匹配的状态都会转到 default
状态,从而增强了 FSM 的健壮性,另外也可以添加一个额外的 default
状态,一旦进入这个状态就会自动转到IDLE
状态,从新启动状态机,这样做也增强了状态机的健壮性。
所谓 Full Case 是指:FSM 的所有编码向量都可以与 case 结构的某个分支或 default 默认情况匹配起来。如果一个 FSM 的状态编码是 6 bit,则对应的 64个状态编码都可以与 case 的某个分支或者 default 映射起来。
所谓 Parallel Case 是指:在 case 结构中,每个 case 的判断条件表达式,有且仅有唯一的 case 语句的分支与之对应,即两者关系是一一对应关系。
目前知名综合器如 Synplify Pro、 Precision RTL 和 Synopys 综合工具等都支持 “synthesis full_case”和“synthesis parallel_case”这些综合约束属性,合理使用 Full Case 约束属性,可以增强设计的安全性;合理使用 Parallel Case 约束属性,可以改善状态机译码逻辑。但是设计者必须具体情况具体分析,对于有的设计,这两条语句使用不当,会占用大量逻辑资源,并恶化 FSM 的时序表现。
实用的状态机一般都设计为同步时序方式,它在时钟信号的触发下,完成各个状态之间的转换,并产生相应的输出。状态机有三种表示方法:状态图、状态表和流程图,这三种方法是等价的,相互之间可以相互转换。其中状态图是最常用的表达方式。
对于基于状态机的设计,一般根据设计需求画出状态转移图,确定使用状态机类型,并标注出各种输入输出信号,更有助于编程。
一般使用最多的是 Mealy 型 3 段式状态机,下面用通过设计一个自动售卖机的具体实例来说明状态机的设计过程。
自动售卖机的功能描述如下:
饮料单价 2 元,该售卖机只能接受 0.5 元、1 元的硬币。考虑找零和出货。投币和出货过程都是一次一次的进行,不会出现一次性投入多币或一次性出货多瓶饮料的现象。每一轮售卖机接受投币、出货、找零完成后,才能进入到新的自动售卖状态。
该售卖机的工作状态转移图如下所示,包含了输入、输出信号状态。
其中,coin = 1 代表投入了 0.5 元硬币,coin = 2 代表投入了 1 元硬币。
状态机设计如下:
三段式写法可以概括为下图所示的结构。
// vending-machine // 2 yuan for a bottle of drink // only 2 coins supported: 5 jiao and 1 yuan // finish the function of selling and changing module vending_machine_p3 ( input clk , input rstn , input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan output [1:0] change , output sell //output the drink ); //machine state decode parameter IDLE = 3'd0 ; parameter GET05 = 3'd1 ; parameter GET10 = 3'd2 ; parameter GET15 = 3'd3 ; //machine variable reg [2:0] st_next ; reg [2:0] st_cur ; //(1) state transfer always @(posedge clk or negedge rstn) begin if (!rstn) begin st_cur <= 'b0 ; end else begin st_cur <= st_next ; end end //(2) state switch, using block assignment for combination-logic //all case items need to be displayed completely always @(*) begin //st_next = st_cur ;//如果条件选项考虑不全,可以赋初值消除latch case(st_cur) IDLE: case (coin) 2'b01: st_next = GET05 ; 2'b10: st_next = GET10 ; default: st_next = IDLE ; endcase GET05: case (coin) 2'b01: st_next = GET10 ; 2'b10: st_next = GET15 ; default: st_next = GET05 ; endcase GET10: case (coin) 2'b01: st_next = GET15 ; 2'b10: st_next = IDLE ; default: st_next = GET10 ; endcase GET15: case (coin) 2'b01,2'b10: st_next = IDLE ; default: st_next = GET15 ; endcase default: st_next = IDLE ; endcase end //(3) output logic, using non-block assignment reg [1:0] change_r ; reg sell_r ; always @(posedge clk or negedge rstn) begin if (!rstn) begin change_r <= 2'b0 ; sell_r <= 1'b0 ; end else if ((st_cur == GET15 && coin ==2'h1) || (st_cur == GET10 && coin ==2'd2)) begin change_r <= 2'b0 ; sell_r <= 1'b1 ; end else if (st_cur == GET15 && coin == 2'h2) begin change_r <= 2'b1 ; sell_r <= 1'b1 ; end else begin change_r <= 2'b0 ; sell_r <= 1'b0 ; end end assign sell = sell_r ; assign change = change_r ; endmodule
testbench 设计如下。仿真中模拟了 4 种情景,分别是:
case1 对应连续输入 4 个 5 角硬币;case2 对应 1 元 - 5 角 - 1 元的投币顺序;case3 对应 5 角 - 1 元 - 5 角的投币顺序;case4 对应连续 3 个 5 角然后一个 1 元的投币顺序。
`timescale 1ns/1ps module test ; reg clk; reg rstn ; reg [1:0] coin ; wire [1:0] change ; wire sell ; //clock generating parameter CYCLE_200MHz = 10 ; // always begin clk = 0 ; #(CYCLE_200MHz/2) ; clk = 1 ; #(CYCLE_200MHz/2) ; end //motivation generating reg [9:0] buy_oper ; //store state of the buy operation initial begin buy_oper = 'h0 ; coin = 2'h0 ; rstn = 1'b0 ; #8 rstn = 1'b1 ; @(negedge clk) ; //case(1) 0.5 -> 0.5 -> 0.5 -> 0.5 #16 ; buy_oper = 10'b00_0101_0101 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end //case(2) 1 -> 0.5 -> 1, taking change #16 ; buy_oper = 10'b00_0010_0110 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end //case(3) 0.5 -> 1 -> 0.5 #16 ; buy_oper = 10'b00_0001_1001 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end //case(4) 0.5 -> 0.5 -> 0.5 -> 1, taking change #16 ; buy_oper = 10'b00_1001_0101 ; repeat(5) begin @(negedge clk) ; coin = buy_oper[1:0] ; buy_oper = buy_oper >> 2 ; end end //(1) mealy state with 3-stage vending_machine_p3 u_mealy_p3 ( .clk (clk), .rstn (rstn), .coin (coin), .change (change), .sell (sell) ); //simulation finish always begin #100; if ($time >= 10000) $finish ; end endmodule // test
仿真结果如下:
由图可知,代表出货动作的信号 sell 都能在投币完毕后正常的拉高,而代表找零动作的信号 change 也都能根据输入的硬币场景输出正确的是否找零信号。
两段式写法可以概括为下图所示的结构。
将 3 段式状态机 2、3 段描述合并,其他部分保持不变,状态机就变成了 2 段式描述。
修改部分如下:
//(2) state switch, and output logic //all using block assignment for combination-logic reg [1:0] change_r ; reg sell_r ; always @(*) begin //all case items need to be displayed completely case(st_cur) IDLE: begin change_r = 2'b0 ; sell_r = 1'b0 ; case (coin) 2'b01: st_next = GET05 ; 2'b10: st_next = GET10 ; default: st_next = IDLE ; endcase // case (coin) end GET05: begin change_r = 2'b0 ; sell_r = 1'b0 ; case (coin) 2'b01: st_next = GET10 ; 2'b10: st_next = GET15 ; default: st_next = GET05 ; endcase // case (coin) end GET10: case (coin) 2'b01: begin st_next = GET15 ; change_r = 2'b0 ; sell_r = 1'b0 ; end 2'b10: begin st_next = IDLE ; change_r = 2'b0 ; sell_r = 1'b1 ; end default: begin st_next = GET10 ; change_r = 2'b0 ; sell_r = 1'b0 ; end endcase // case (coin) GET15: case (coin) 2'b01: begin st_next = IDLE ; change_r = 2'b0 ; sell_r = 1'b1 ; end 2'b10: begin st_next = IDLE ; change_r = 2'b1 ; sell_r = 1'b1 ; end default: begin st_next = GET15 ; change_r = 2'b0 ; sell_r = 1'b0 ; end endcase default: begin st_next = IDLE ; change_r = 2'b0 ; sell_r = 1'b0 ; end endcase end
将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真,结果如下:
由图可知,出货信号 sell 和 找零信号 change 相对于 3 段式状态机输出提前了一个时钟周期,这是因为输出信号都是阻塞赋值导致的。
如图中红色圆圈部分,输出信号都出现了干扰脉冲,这是因为输入信号都是异步的,而且输出信号是组合逻辑输出,没有时钟驱动。
实际中,如果输入信号都是与时钟同步的,这种干扰脉冲是不会出现的。如果是异步输入信号,首先应当对信号进行同步。
将 3 段式状态机 1、 2、3 段描述合并,状态机就变成了 1 段式描述。
修改部分如下:
//(1) using one state-variable do describe reg [1:0] change_r ; reg sell_r ; always @(posedge clk or negedge rstn) begin if (!rstn) begin st_cur <= 'b0 ; change_r <= 2'b0 ; sell_r <= 1'b0 ; end else begin case(st_cur) IDLE: begin change_r <= 2'b0 ; sell_r <= 1'b0 ; case (coin) 2'b01: st_cur <= GET05 ; 2'b10: st_cur <= GET10 ; endcase end GET05: begin case (coin) 2'b01: st_cur <= GET10 ; 2'b10: st_cur <= GET15 ; endcase end GET10: case (coin) 2'b01: st_cur <= GET15 ; 2'b10: begin st_cur <= IDLE ; sell_r <= 1'b1 ; end endcase GET15: case (coin) 2'b01: begin st_cur <= IDLE ; sell_r <= 1'b1 ; end 2'b10: begin st_cur <= IDLE ; change_r <= 2'b1 ; sell_r <= 1'b1 ; end endcase default: begin st_cur <= IDLE ; end endcase // case (st_cur) end // else: !if(!rstn) end
将上述修改的新模块例化到 3 段式的 testbench 中即可进行仿真,结果如下:
由图可知,输出信号与 3 段式状态机完全一致。
1 段式状态机的缺点就是许多种逻辑糅合在一起,不易后期的维护。当状态机和输出信号较少时,可以尝试此种描述方式。
3 种描述 FSM 方法的比较
一般来说,3 种 FSM 描述方法可以用下表进行比较。
比较项目 | 一段式描述方法 | 两段式描述方法 | 三段式描述方法 |
---|---|---|---|
推荐等级 | 不推荐 | 推荐 | 推荐 |
代码简洁程度(对于相对复杂的FSM而言) | 冗长 | 最简洁 | 简洁 |
always 模块个数 | 1 | 2 | 3 |
是否利于时序约束 | 不利于 | 利于 | 利于 |
是否有组合逻辑输出 | 可以无组合逻辑输出 | 多数情况有组合逻辑输出 | 无组合逻辑输出 |
是否利于综合与布局布线 | 不利于 | 利于 | 利于 |
代码的可靠性与可维护度 | 低 | 高 | 最好 |
代码风格的规范性 | 低,任意度大 | 格式化,规范 | 格式化,规范 |
如果使用 Moore 型状态机描述售卖机的工作流程,那么还需要再增加 2 个状态编码,用以描述 Mealy 状态机输出时的输入信号和状态机状态。
3 段式 Moore 型状态机描述的自动售卖机 Verilog 代码如下:
module vending_machine_moore ( input clk , input rstn , input [1:0] coin , //01 for 0.5 jiao, 10 for 1 yuan output [1:0] change , output sell //output the drink ); //machine state decode parameter IDLE = 3'd0 ; parameter GET05 = 3'd1 ; parameter GET10 = 3'd2 ; parameter GET15 = 3'd3 ; // new state for moore state-machine parameter GET20 = 3'd4 ; parameter GET25 = 3'd5 ; //machine variable reg [2:0] st_next ; reg [2:0] st_cur ; //(1) state transfer always @(posedge clk or negedge rstn) begin if (!rstn) begin st_cur <= 'b0 ; end else begin st_cur <= st_next ; end end //(2) state switch, using block assignment for combination-logic always @(*) begin //all case items need to be displayed completely case(st_cur) IDLE: case (coin) 2'b01: st_next = GET05 ; 2'b10: st_next = GET10 ; default: st_next = IDLE ; endcase GET05: case (coin) 2'b01: st_next = GET10 ; 2'b10: st_next = GET15 ; default: st_next = GET05 ; endcase GET10: case (coin) 2'b01: st_next = GET15 ; 2'b10: st_next = GET20 ; default: st_next = GET10 ; endcase GET15: case (coin) 2'b01: st_next = GET20 ; 2'b10: st_next = GET25 ; default: st_next = GET15 ; endcase GET20: st_next = IDLE ; GET25: st_next = IDLE ; default: st_next = IDLE ; endcase // case (st_cur) end // always @ (*) // (3) output logic, // one cycle delayed when using non-block assignment reg [1:0] change_r ; reg sell_r ; always @(posedge clk or negedge rstn) begin if (!rstn) begin change_r <= 2'b0 ; sell_r <= 1'b0 ; end else if (st_cur == GET20 ) begin sell_r <= 1'b1 ; end else if (st_cur == GET25) begin change_r <= 2'b1 ; sell_r <= 1'b1 ; end else begin change_r <= 2'b0 ; sell_r <= 1'b0 ; end end assign sell = sell_r ; assign change = change_r ; endmodule
将上述修改的 Moore 状态机例化到 3 段式的 testbench 中即可进行仿真,结果如下:
由图可知,输出信号与 Mealy 型 3 段式状态机相比延迟了一个时钟周期,这是因为进入到新增加的编码状态机时需要一个时钟周期的时延。此时,输出再用非阻塞赋值就会导致最终的输出信号延迟一个时钟周期。这也属于 Moore 型状态机的特点。
输出信号赋值时,用阻塞赋值,则可以提前一个时钟周期。
// (3.2) output logic, using block assignment
reg [1:0] change_r ;
reg sell_r ;
always @(*) begin
change_r = 'b0 ;
sell_r = 'b0 ; //not list all condition, initializing them
if (st_cur == GET20 ) begin
sell_r = 1'b1 ;
end
else if (st_cur == GET25) begin
change_r = 2'b1 ;
sell_r = 1'b1 ;
end
end
输出信号阻塞赋值的仿真结果如下:
由图可知,输出信号已经和 3 段式 Mealy 型状态机一致。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。