状态机的Verilog写法
“硬件设计很讲究并行设计思想,虽然用Verilog描述的电路大都是并行实现的,但是对于实际的工程应用,往往需要让硬件来实现一些具有一定顺序的工作,这就要用到状态机思想。什么是状态机呢?简单的说,就是通过不同的状态迁移来完成一些特定的顺序逻辑。硬件的并行性决定了用Verilog描述的硬件实现(臂如不同的always语句)都是并行执行的,那么如果希望分多个时间完成一个任务,怎么办?也许可以用多个使能信号来衔接多个不同的模块,但是这样做多少显得繁琐。状态机的提出会大大简化这一工作。”
——特权同学《深入浅出玩转FPGA》
一、状态机分类
1.Moore型:状态机的状态变化仅和当前状态有关(特权同学《深入浅出玩转FPGA》);时序逻辑电路的输出只取决于当前状态(夏宇闻《Verilog数字系统设计》)。设计高速电路时常用此类状态机,把状态变化直接用作输出。
2.Mealy型:状态机的状态变化不仅与当前的状态有关,还取决于当前的输入条件(特权同学《深入浅出玩转FPGA》);时序逻辑的输出不但取决于状态还取决于输入(夏宇闻《Verilog数字系统设计》)。平常使用较多的是此类状态机。
“其实这几种状态机之间,只要做一些改变,便可以从一种形式转变为另一种形式。把状态机精确的分为这类或那类,其实并不重要,重要的是设计者如何把握输出的结构能满足设计的整体目标,包括定时的准确性和灵活性。”
——夏宇闻《Verilog数字系统设计》
二、状态机编码
状态机的参数定义采用的都是独热码,和格雷码相比,虽然独热码多用了触发器,但所用组合电路可以省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。采用独热编码后有了多余的状态,就有一些不可达到的状态。为此在case语句的最后需要增加default分支向。这可以用默认项表示该项,也可以用确定项表示,以确保回到初始状态。一般综合器都可以通过综合指令的控制来合理地处理默认项。
三、实例分析
状态机一般有三种不同的写法,即一段式、两段式和三段式的状态机写法,他们在速度、面积、代码可维护性等各个方面互有优劣,不要对任何一种写法给出“一棍子打死”的定论。手头上刚好有一个状态机的例子,借此记录一下三种状态机的Verilog写法。
3.1 要求
售货机里有价值4元的脉动饮料,支持1元和2元硬币。请设计一个状态机,检测投入的硬币,当累计投入币值大于等于脉动价格时,售货机自动找零并弹出1瓶脉动饮料。硬币和商品都是一个一个的进出,不会出现一次性投很多个硬币弹出很多瓶脉动的情况。
信号 | 含义 |
clk | 时钟信号 |
rst_n | 复位信号 |
in | 输入信号,币值,有1和2两种,投钱 |
out | 输出信号,币值,有1和2两种,找零 |
out_vld | 输出信号,脉动,为1则输出1瓶脉动 |
3.2 状态转移图
根据要求,我们先把状态转移图画出来,绘画软件:Visio,如果没有安装也可以用wps自带应用的“流程图”功能:
3.3 testbench
1 `timescale 1ns/1ps //时间精度 2 `define Clock 20 //时钟周期 3 4 module FSM_3_tb; 5 //--------------------< 端口 >------------------------------------------ 6 reg clk ; 7 reg rst_n ; 8 reg [1:0] in ; 9 wire [1:0] out ; 10 wire out_vld ; 11 12 //---------------------------------------------------------------------- 13 //-- 模块例化 14 //---------------------------------------------------------------------- 15 FSM_3 u_FSM_3 16 ( 17 .clk (clk ), 18 .rst_n (rst_n ), 19 .in (in ), 20 .out (out ), 21 .out_vld (out_vld ) 22 ); 23 24 //---------------------------------------------------------------------- 25 //-- 状态机名称查看器 26 //---------------------------------------------------------------------- 27 localparam S0 = 4'b0001 ; 28 localparam S1 = 4'b0010 ; 29 localparam S2 = 4'b0100 ; 30 localparam S3 = 4'b1000 ; 31 //2字符16位 32 reg [15:0] state_name ; 33 34 always@(*)begin 35 case(u_FSM_3.state_c) 36 S0: state_name = "S0"; 37 S1: state_name = "S1"; 38 S2: state_name = "S2"; 39 S3: state_name = "S3"; 40 default:state_name = "S0"; 41 endcase 42 end 43 44 //---------------------------------------------------------------------- 45 //-- 时钟信号和复位信号 46 //---------------------------------------------------------------------- 47 initial begin 48 clk = 1; 49 forever 50 #(`Clock/2) clk = ~clk; 51 end 52 53 initial begin 54 rst_n = 0; #(`Clock*20+1); 55 rst_n = 1; 56 end 57 58 //---------------------------------------------------------------------- 59 //-- 设计输入信号 60 //---------------------------------------------------------------------- 61 initial begin 62 #1; 63 in = 0; 64 #(`Clock*20+1); //初始化完成 65 //情况1-------------------------- 66 in = 1; //1块钱 67 #(`Clock*1); 68 in = 0; 69 #(`Clock*1); 70 in = 1; //1块钱 71 #(`Clock*1); 72 in = 0; 73 #(`Clock*1); 74 in = 1; //1块钱 75 #(`Clock*1); 76 in = 0; 77 #(`Clock*1); 78 in = 1; //1块钱 79 #(`Clock*1); 80 in = 0; 81 #(`Clock*10); 82 //情况2-------------------------- 83 in = 1; //1块钱 84 #(`Clock*1); 85 in = 0; 86 #(`Clock*1); 87 in = 1; //1块钱 88 #(`Clock*1); 89 in = 0; 90 #(`Clock*1); 91 in = 1; //1块钱 92 #(`Clock*1); 93 in = 0; 94 #(`Clock*1); 95 in = 2; //2块钱 96 #(`Clock*1); 97 in = 0; 98 #(`Clock*10); 99 //情况3-------------------------- 100 in = 1; //1块钱 101 #(`Clock*1); 102 in = 0; 103 #(`Clock*1); 104 in = 1; //1块钱 105 #(`Clock*1); 106 in = 0; 107 #(`Clock*1); 108 in = 2; //2块钱 109 #(`Clock*1); 110 in = 0; 111 #(`Clock*10); 112 //情况4-------------------------- 113 in = 1; //1块钱 114 #(`Clock*1); 115 in = 0; 116 #(`Clock*1); 117 in = 2; //2块钱 118 #(`Clock*1); 119 in = 0; 120 #(`Clock*1); 121 in = 2; //2块钱 122 #(`Clock*1); 123 in = 0; 124 #(`Clock*10); 125 //情况5-------------------------- 126 in = 2; //2块钱 127 #(`Clock*1); 128 in = 0; 129 #(`Clock*1); 130 in = 2; //2块钱 131 #(`Clock*1); 132 in = 0; 133 #(`Clock*10); 134 135 136 $stop; 137 end 138 139 140 endmodule
3.4 代码设计
(1)一段式状态机
只定义一个转移状态:state,总体结构是一段always时序逻辑,用于描述状态转移和输出。由于是时序逻辑能够自动保持,所以可以省略else。但建议在初始状态时(例如下文的S0),else处赋一下初始值。
1 //====================================================================== 2 // --- 名称 : FSM_1 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售货机练习,采用一段式状态机 6 //====================================================================== 7 8 module FSM_1 9 //---------------------<端口声明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信号定义>--------------------------------------- 18 reg [3:0] state ; 19 //---------------------<状态机参数>------------------------------------- 20 localparam S0 = 4'b0001 ; 21 localparam S1 = 4'b0010 ; 22 localparam S2 = 4'b0100 ; 23 localparam S3 = 4'b1000 ; 24 25 //---------------------------------------------------------------------- 26 //-- 状态机第1段 27 //---------------------------------------------------------------------- 28 always@(posedge clk or negedge rst_n)begin 29 if(!rst_n)begin 30 state <= S0; 31 out <= 0 ; 32 out_vld <= 0 ; 33 end 34 else begin 35 case(state) 36 S0: begin 37 if(in==1)begin 38 state <= S1; 39 end 40 else if(in==2)begin 41 state <= S2; 42 end 43 else begin 44 out <= 0 ; 45 out_vld <= 0 ; 46 end 47 end 48 S1: begin 49 if(in==1)begin 50 state <= S2; 51 end 52 else if(in==2)begin 53 state <= S3; 54 end 55 end 56 S2: begin 57 if(in==1)begin 58 state <= S3; 59 end 60 else if(in==2)begin 61 state <= S0; 62 out_vld <= 1 ; 63 end 64 end 65 S3: begin 66 if(in==1)begin 67 state <= S0; 68 out_vld <= 1 ; 69 end 70 else if(in==2)begin 71 state <= S0; 72 out <= 1 ; 73 out_vld <= 1 ; 74 end 75 end 76 default:state <= S0; 77 endcase 78 end 79 end 80 81 82 83 endmodule
仿真波形如下:
结论:波形和预想一致!
(2)二段式状态机
二段式状态机,第一段用时序逻辑描述state_c(现态)和state_n(次态),第二段用组合逻辑描述状态转移和输出。由于是组合逻辑,为避免产生锁存器,else处一定要写上 if 中说使用了的信号。
1 //====================================================================== 2 // --- 名称 : FSM_2 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售货机练习,采用二段式状态机 6 //====================================================================== 7 8 module FSM_2 9 //---------------------<端口声明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信号定义>--------------------------------------- 18 reg [3:0] state_c ; 19 reg [3:0] state_n ; 20 //---------------------<状态机参数>------------------------------------- 21 localparam S0 = 4'b0001 ; 22 localparam S1 = 4'b0010 ; 23 localparam S2 = 4'b0100 ; 24 localparam S3 = 4'b1000 ; 25 26 //---------------------------------------------------------------------- 27 //-- 状态机第1段 28 //---------------------------------------------------------------------- 29 always@(posedge clk or negedge rst_n)begin 30 if(!rst_n) 31 state_c <= S0; 32 else 33 state_c <= state_n; 34 end 35 36 //---------------------------------------------------------------------- 37 //-- 状态机第2段 38 //---------------------------------------------------------------------- 39 always@(*)begin 40 case(state_c) 41 S0: begin 42 if(in==1)begin 43 state_n = S1; 44 end 45 else if(in==2)begin 46 state_n = S2; 47 end 48 else begin 49 state_n = state_c; 50 out = 0 ; 51 out_vld = 0 ; 52 end 53 end 54 S1: begin 55 if(in==1)begin 56 state_n = S2; 57 end 58 else if(in==2)begin 59 state_n = S3; 60 end 61 else begin 62 state_n = state_c; 63 end 64 end 65 S2: begin 66 if(in==1)begin 67 state_n = S3; 68 end 69 else if(in==2)begin 70 state_n = S0; 71 out_vld = 1 ; 72 end 73 else begin 74 state_n = state_c; 75 out_vld = 0; 76 end 77 end 78 S3: begin 79 if(in==1)begin 80 state_n = S0; 81 out_vld = 1 ; 82 end 83 else if(in==2)begin 84 state_n = S0; 85 out = 1 ; 86 out_vld = 1 ; 87 end 88 else begin 89 state_n = state_c; 90 out = 0; 91 out_vld = 0; 92 end 93 end 94 default:state_n = S0; 95 endcase 96 end 97 98 99 endmodule
仿真波形如下所示:
结论:波形和预想一致!但是产生了毛刺,这也是二段式状态机的缺点。
毛刺产生原因:状态机通常包含主控时序进程、主控组合进程和辅助进程三个部分。其中,主控组合进程的任务是根据外部输入的控制信号和当前状态的状态值确定下一 状态的取向,并确定对外输出内容和对内部其他组合或时序进程输出控制信号的内容。一方面,由于有组合逻辑进程的存在,状态机输出信号会出现毛刺——竞争冒险现象;另一方面,如果状态信号是多位值的,则在电路中对应了多条信号线。由于存在传输延迟,各信号线上的值发生改变的时间则存在先后,从而使得状态迁移时在初始状态和目的状态之间出现临时状态——毛刺。
简单理解为:state_n 会因为组合逻辑原因不断出现临时状态,这些状态是无效的,而输出也因为组合逻辑原因产生这些临时状态,即毛刺。
(3)三段式状态机
三段式状态机,第一段用时序逻辑描述state_c(现态)和state_n(次态),第二段用组合逻辑描述状态转移,第三段用时序逻辑描述输出,第三段可以是多个always块。
1 //====================================================================== 2 // --- 名称 : FSM_3 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2018-12-15 5 // --- 描述 : 售货机练习,采用三段式状态机 6 //====================================================================== 7 8 module FSM_3 9 //---------------------<端口声明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信号定义>--------------------------------------- 18 reg [3:0] state_c ; 19 reg [3:0] state_n ; 20 //---------------------<状态机参数>------------------------------------- 21 localparam S0 = 4'b0001 ; 22 localparam S1 = 4'b0010 ; 23 localparam S2 = 4'b0100 ; 24 localparam S3 = 4'b1000 ; 25 26 //---------------------------------------------------------------------- 27 //-- 状态机第1段 28 //---------------------------------------------------------------------- 29 always @(posedge clk or negedge rst_n)begin 30 if(!rst_n) 31 state_c <= S0; 32 else 33 state_c <= state_n; 34 end 35 36 //---------------------------------------------------------------------- 37 //-- 状态机第2段 38 //---------------------------------------------------------------------- 39 always @(*)begin 40 case(state_c) 41 S0: begin 42 if(in==1) 43 state_n = S1; 44 else if(in==2) 45 state_n = S2; 46 else 47 state_n = state_c; 48 end 49 S1: begin 50 if(in==1) 51 state_n = S2; 52 else if(in==2) 53 state_n = S3; 54 else 55 state_n = state_c; 56 end 57 S2: begin 58 if(in==1) 59 state_n = S3; 60 else if(in==2) 61 state_n = S0; 62 else 63 state_n = state_c; 64 end 65 S3: begin 66 if(in==1 || in==2) // in != 0也行 67 state_n = S0; 68 else 69 state_n = state_c; 70 end 71 default:state_n = S0; 72 endcase 73 end 74 75 //---------------------------------------------------------------------- 76 //-- 状态机第3段 77 //---------------------------------------------------------------------- 78 //找零钱 79 always @(posedge clk or negedge rst_n)begin 80 if(!rst_n) 81 out <= 0; 82 else if(state_c==S3 && in==2) 83 out <= 1; 84 else 85 out <= 0; 86 end 87 88 //输出脉动 89 always @(posedge clk or negedge rst_n)begin 90 if(rst_n==1'b0) 91 out_vld <= 0; 92 else if((state_c==S2 && in==2) || (state_c==S3 && in!=0)) 93 out_vld <= 1; 94 else 95 out_vld <= 0; 96 end 97 98 99 endmodule
仿真波形如下所示:
结论:波形和预想一致!这也是较多书籍推荐的写法。
(4)一段式和三段式结合的状态机(by 威三学院FPGA教程)
V3学院状态机,只定义一个转移状态:state。第一段用时序逻辑描述state状态转移,第二段用时序逻辑描述输出,第二段可以是多个always块。由于是时序逻辑能够自动保持,所以可以省略else。这种状态机的优点是既消除了组合逻辑可能产生的毛刺,又减少了代码量。
1 //====================================================================== 2 // --- 名称 : FSM_V3 3 // --- 作者 : xianyu_FPGA 4 // --- 日期 : 2019-06-12 5 // --- 描述 : 售货机练习,采用V3学院的状态机 6 //====================================================================== 7 8 module FSM_V3 9 //---------------------<端口声明>--------------------------------------- 10 ( 11 input clk , 12 input rst_n , 13 input [1:0] in , 14 output reg [1:0] out , 15 output reg out_vld 16 ); 17 //---------------------<信号定义>--------------------------------------- 18 reg [3:0] state ; 19 //---------------------<状态机参数>------------------------------------- 20 localparam S0 = 4'b0001 ; 21 localparam S1 = 4'b0010 ; 22 localparam S2 = 4'b0100 ; 23 localparam S3 = 4'b1000 ; 24 25 //---------------------------------------------------------------------- 26 //-- 状态机 27 //---------------------------------------------------------------------- 28 always @(posedge clk or negedge rst_n)begin 29 if(!rst_n) 30 state <= S0; 31 else begin 32 case(state) 33 S0: begin 34 if(in==1) 35 state <= S1; 36 else if(in==2) 37 state <= S2; 38 end 39 S1: begin 40 if(in==1) 41 state <= S2; 42 else if(in==2) 43 state <= S3; 44 end 45 S2: begin 46 if(in==1) 47 state <= S3; 48 else if(in==2) 49 state <= S0; 50 end 51 S3: begin 52 if(in==1 || in==2) // in != 0也行 53 state <= S0; 54 end 55 default:state <= S0; 56 endcase 57 end 58 end 59 60 //---------------------------------------------------------------------- 61 //-- 输出 62 //---------------------------------------------------------------------- 63 //找零钱 64 always @(posedge clk or negedge rst_n)begin 65 if(!rst_n) 66 out <= 0; 67 else if(state==S3 && in==2) 68 out <= 1; 69 else 70 out <= 0; 71 end 72 73 //输出脉动 74 always @(posedge clk or negedge rst_n)begin 75 if(rst_n==1'b0) 76 out_vld <= 0; 77 else if((state==S2 && in==2) || (state==S3 && in!=0)) 78 out_vld <= 1; 79 else 80 out_vld <= 0; 81 end 82 83 84 endmodule
仿真波形如下所示:
结论:波形和预想一致!
四、状态机名称查看器
可以看到,我的Modelsim波形中出现了一个信号state_name,里面显示了状态机的名称,这是怎么做到的呢?方法有很多种,这里介绍两种。
4.1 testbench法
testbench里增加一段参数转ASCII码的代码,如下所示:
1 //---------------------------------------------------------------------- 2 //-- 状态机名称查看器 3 //---------------------------------------------------------------------- 4 localparam S0 = 4'b0001 ; 5 localparam S1 = 4'b0010 ; 6 localparam S2 = 4'b0100 ; 7 localparam S3 = 4'b1000 ; 8 //2字符16位 9 reg [15:0] state_name ; 10 11 always@(*)begin 12 case(u_FSM_3.state_c) 13 S0: state_name = "S0"; 14 S1: state_name = "S1"; 15 S2: state_name = "S2"; 16 S3: state_name = "S3"; 17 default:state_name = "S0"; 18 endcase 19 end
在Modelsim中点击信号state_name,右键选择用ASSIC码查看就可以看到状态机的名称,而不再是头疼的的0001、0010等字符。编写时注意一下位宽,一个ASSIC码字符宽度是8位,例如“S0”有2个字符则需要16位宽。
4.2 do/tcl文件法
首先你得学会怎么使用Modelsim的自动化脚本仿真,那么我们只要再do文件中加入这段代码即可:
1 # ====================================================================== 2 # == 状态机名称查看器 3 # ====================================================================== 4 5 # 结构体设置 6 virtual type { 7 {4'b0001 S0} 8 {4'b0010 S1} 9 {4'b0100 S2} 10 {4'b1000 S3} 11 } fsm_type; 12 13 # 结构体和信号名关联,命名为state_name 14 virtual function {(fsm_type)/fsm_tb/u_fsm/state} state_name
参考资料:
[1]小梅哥FPGA教程
[2]威三学院FPGA教程
[3]吴厚航. 深入浅出玩转FPGA[M]. 北京航空航天大学出版社, 2013.
[4]夏宇闻. Verilog数字系统设计教程.第3版[M]. 北京航空航天大学出版社, 2013.
[5]韩彬, 于潇宇, 张雷鸣. FPGA设计技巧与案例开发详解[M]. 电子工业出版社, 2014.