FPGA 状态机
随着对FPGA的不断学习,发现状态机在FPGA的逻辑设计中,是个及其重要的概念和能力,是个必须掌握的知识点,本文是结合网上资料及野火的《FPGA Verilog开发实战指南——基于Altera EP4CE10》一书的理解的个人总结。
参考一:https://bbs.elecfans.com/jishu_909019_1_1.html
参考二:https://www.cnblogs.com/wanghuaijun/p/8284697.html
参考三:《FPGA Verilog开发实战指南——基于Altera EP4CE10》
一、状态机概念
将每个情况抽象成各个状态,各状态根据当前状态和输入条件,决定下一状态进入到什么状态,以此达成相应的目的。软件编程中也有状态机的概念,目前本人还没有具体了解。
二、状态机类型
状态机共有Moore、Mealy两种类型,
1. Moore的输出只跟当前状态有关
2. Mealy的输出与当前状态及输入情况有关
三、状态机编码
状态机的每种状态需要编码,以便使用case语句。而编码方式又分为二进制码、格雷码、独热码等多种方式,不同的编码方式根据器件的组合逻辑和寄存器资源情况,比较好的适用情况如下。
截取自《FPGA Verilog开发实战指南——基于Altera EP4CE10》
四、状态机的编写方式
状态机有三种编写方式,分别为一段式、二段式、三段式。
1. 一段式:将所有的状态转移、输入、输出都写在一个always语句块中。
2. 二段式:第一段采用同步时序逻辑描述状态转移,第二段采用组合逻辑描述状态转移条件及输出。
3. 三段式:第一段采用同步时序逻辑描述状态转移,第二段采用组合逻辑描述状态转移条件,第三段采用同步时序逻辑,或者组合逻辑描述输出。
4. 新二段式(野火提到的方式):第一段采用同步时序逻辑描述状态转移条件,第二段采用同步时序逻辑,或者组合逻辑描述输出。相当于三段式第一段与第二段合并为第一段,原第三段变为第二段。
注意:编写方式的区分,不以使用多少个always语句块判断,而是以上面的定义的方式来判定。因为组合逻辑容易产生毛刺,所以输出在有选择的情况下,选择同步时序逻辑输出。
五、编写格式
5.1 一段式:
1 //采用Mealy型状态机,一段式写法 2 //1. 空闲状态----所有灯熄灭 3 //2. 状态S1----------点亮LED[0] 4 //3. 状态S2----------点亮LED[1] 5 //4. 状态S3----------点亮LED[2] 6 module st( 7 input clk, 8 input rst_n, 9 10 input [1:0]key, 11 12 output reg [2:0]led 13 ); 14 15 parameter IDLE = 4'b0001; 16 parameter S1 = 4'b0010; 17 parameter S2 = 4'b0100; 18 parameter S3 = 4'b1000; 19 20 reg [3:0] state; 21 22 //状态转移、状态转移条件、输出都写在一个always语句块中 23 always @(posedge clk or negedge rst_n) 24 if(!rst_n) 25 begin 26 state <= IDLE; 27 led <= 3'b111; 28 end 29 else 30 begin 31 case(state) 32 IDLE: if(key == 2'b01) 33 begin 34 state <= S1; 35 led <= 3'b110; 36 end 37 else if(key == 2'b10) 38 begin 39 state <= S2; 40 led <= 3'b101; 41 end 42 else if(key == 2'b11) 43 begin 44 state <= S3; 45 led <= 3'b011; 46 end 47 else 48 begin 49 state <= IDLE; 50 led <= 3'b111; 51 end 52 53 S1: if(key == 2'b01) 54 begin 55 state <= S2; 56 led <= 3'b101; 57 end 58 else if(key == 2'b10) 59 begin 60 state <= S3; 61 led <= 3'b011; 62 end 63 else if(key == 2'b11) 64 begin 65 state <= IDLE; 66 led <= 3'b111; 67 end 68 else 69 begin 70 state <= S1; 71 led <= 3'b110; 72 end 73 74 S2: if(key == 2'b01) 75 begin 76 state <= S3; 77 led <= 3'b011; 78 end 79 else if(key == 2'b10 || key == 2'b11) 80 begin 81 state <= IDLE; 82 led <= 3'b111; 83 end 84 else 85 begin 86 state <= S2; 87 led <= 3'b101; 88 end 89 90 91 S3: if(key != 2'b00) 92 begin 93 state <= IDLE; 94 led <= 3'b111; 95 end 96 else 97 begin 98 state <= S3; 99 led <= 3'b011; 100 end 101 102 default: begin 103 state <= IDLE; 104 led <= 3'b111; 105 end 106 107 endcase 108 end 109 110 111 endmodule
5.2 二段式:
1 //采用Mealy型状态机,二段式写法 2 //1. 空闲状态----所有灯熄灭 3 //2. 状态S1----------点亮LED[0] 4 //3. 状态S2----------点亮LED[1] 5 //4. 状态S3----------点亮LED[2] 6 module st( 7 input clk, 8 input rst_n, 9 10 input [1:0]key, 11 12 output reg [2:0]led 13 ); 14 15 parameter IDLE = 4'b0001; 16 parameter S1 = 4'b0010; 17 parameter S2 = 4'b0100; 18 parameter S3 = 4'b1000; 19 20 reg [3:0] currnt_state; 21 reg [3:0] next_state; 22 23 //二段式,第一段,将状态转移写入一个always语句块中 24 always @(posedge clk or negedge rst_n) 25 if(!rst_n) 26 currnt_state <= IDLE; 27 else 28 currnt_state <= next_state; 29 30 //二段式,第二段,将状态转移条件、输出写入一个always语句块中,采用组合逻辑,即电平触发,组合逻辑方式输出不可避免产生毛刺 31 always @(currnt_state or key) 32 case(currnt_state) 33 IDLE: if(key == 2'b01) 34 begin 35 next_state <= S1; 36 led <= 3'b110; 37 end 38 else if(key == 2'b10) 39 begin 40 next_state <= S2; 41 led <= 3'b101; 42 end 43 else if(key == 2'b11) 44 begin 45 next_state <= S3; 46 led <= 3'b011; 47 end 48 else 49 begin 50 next_state <= IDLE; 51 led <= 3'b111; 52 end 53 54 S1: if(key == 2'b01) 55 begin 56 next_state <= S2; 57 led <= 3'b101; 58 end 59 else if(key == 2'b10) 60 begin 61 next_state <= S3; 62 led <= 3'b011; 63 end 64 else if(key == 2'b11) 65 begin 66 next_state <= IDLE; 67 led <= 3'b111; 68 end 69 else 70 begin 71 next_state <= S1; 72 led <= 3'b110; 73 end 74 75 S2: if(key == 2'b01) 76 begin 77 next_state <= S3; 78 led <= 3'b011; 79 end 80 else if(key == 2'b10 || key == 2'b11) 81 begin 82 next_state <= IDLE; 83 led <= 3'b111; 84 end 85 else 86 begin 87 next_state <= S2; 88 led <= 3'b101; 89 end 90 91 92 S3: if(key != 2'b00) 93 begin 94 next_state <= IDLE; 95 led <= 3'b111; 96 end 97 else 98 begin 99 next_state <= S3; 100 led <= 3'b011; 101 end 102 103 default: begin 104 next_state <= IDLE; 105 led <= 3'b111; 106 end 107 108 endcase 109 110 endmodule
5.3 三段式:
1 //采用Mealy型状态机,三段式写法 2 //1. 空闲状态----所有灯熄灭 3 //2. 状态S1----------点亮LED[0] 4 //3. 状态S2----------点亮LED[1] 5 //4. 状态S3----------点亮LED[2] 6 module st( 7 input clk, 8 input rst_n, 9 10 input [1:0]key, 11 12 output reg [2:0]led 13 ); 14 15 parameter IDLE = 4'b0001; 16 parameter S1 = 4'b0010; 17 parameter S2 = 4'b0100; 18 parameter S3 = 4'b1000; 19 20 reg [3:0] currnt_state; 21 reg [3:0] next_state; 22 23 //三段式,第一段,将状态转移写入一个always语句块中 24 always @(posedge clk or negedge rst_n) 25 if(!rst_n) 26 currnt_state <= IDLE; 27 else 28 currnt_state <= next_state; 29 30 //三段式,第二段,将状态转移条件写入一个always语句块中,采用组合逻辑,即电平触发 31 always @(currnt_state or key) 32 case(currnt_state) 33 IDLE: if(key == 2'b01) 34 begin 35 next_state <= S1; 36 end 37 else if(key == 2'b10) 38 begin 39 next_state <= S2; 40 end 41 else if(key == 2'b11) 42 begin 43 next_state <= S3; 44 end 45 else 46 begin 47 next_state <= IDLE; 48 end 49 50 S1: if(key == 2'b01) 51 begin 52 next_state <= S2; 53 end 54 else if(key == 2'b10) 55 begin 56 next_state <= S3; 57 end 58 else if(key == 2'b11) 59 begin 60 next_state <= IDLE; 61 end 62 else 63 begin 64 next_state <= S1; 65 end 66 67 S2: if(key == 2'b01) 68 begin 69 next_state <= S3; 70 end 71 else if(key == 2'b10 || key == 2'b11) 72 begin 73 next_state <= IDLE; 74 end 75 else 76 begin 77 next_state <= S2; 78 end 79 80 81 S3: if(key != 2'b00) 82 begin 83 next_state <= IDLE; 84 end 85 else 86 begin 87 next_state <= S3; 88 end 89 90 default: begin 91 next_state <= IDLE; 92 end 93 94 endcase 95 96 //三段式,第三段,采用同步时序逻辑,或者组合逻辑描述输出,因为组合逻辑容易产生毛刺,所以这里使用同步时序输出 97 always @(posedge clk or negedge rst_n) 98 case(currnt_state) 99 IDLE: led <= 3'b111; 100 S1: led <= 3'b110; 101 S2: led <= 3'b101; 102 S3: led <= 3'b011; 103 default: led <= 3'b111; 104 endcase 105 106 endmodule
5.4 野火提到的新式二段式:
1 //采用Mealy型状态机,野火提到的二段式新写法 2 //1. 空闲状态----所有灯熄灭 3 //2. 状态S1----------点亮LED[0] 4 //3. 状态S2----------点亮LED[1] 5 //4. 状态S3----------点亮LED[2] 6 module st( 7 input clk, 8 input rst_n, 9 10 input [1:0]key, 11 12 output reg [2:0]led 13 ); 14 15 parameter IDLE = 4'b0001; 16 parameter S1 = 4'b0010; 17 parameter S2 = 4'b0100; 18 parameter S3 = 4'b1000; 19 20 reg [3:0] state; 21 22 23 //二段式,第一段,将状态转移、状态转移条件写入一个always语句块中,采用时序逻辑 24 always @(posedge clk or negedge rst_n) 25 if(!rst_n) 26 state <= IDLE; 27 else case(state) 28 IDLE: if(key == 2'b01) 29 begin 30 state <= S1; 31 end 32 else if(key == 2'b10) 33 begin 34 state <= S2; 35 end 36 else if(key == 2'b11) 37 begin 38 state <= S3; 39 end 40 else 41 begin 42 state <= IDLE; 43 end 44 45 S1: if(key == 2'b01) 46 begin 47 state <= S2; 48 end 49 else if(key == 2'b10) 50 begin 51 state <= S3; 52 end 53 else if(key == 2'b11) 54 begin 55 state <= IDLE; 56 end 57 else 58 begin 59 state <= S1; 60 end 61 62 S2: if(key == 2'b01) 63 begin 64 state <= S3; 65 end 66 else if(key == 2'b10 || key == 2'b11) 67 begin 68 state <= IDLE; 69 end 70 else 71 begin 72 state <= S2; 73 end 74 75 76 S3: if(key != 2'b00) 77 begin 78 state <= IDLE; 79 end 80 else 81 begin 82 state <= S3; 83 end 84 85 default: begin 86 state <= IDLE; 87 end 88 89 endcase 90 91 //二段式,第二段,采用同步时序逻辑,或者组合逻辑描述输出,因为组合逻辑容易产生毛刺,所以这里使用同步时序输出 92 always @(posedge clk or negedge rst_n) 93 case(state) 94 IDLE: led <= 3'b111; 95 S1: led <= 3'b110; 96 S2: led <= 3'b101; 97 S3: led <= 3'b011; 98 default: led <= 3'b111; 99 endcase 100 101 endmodule
六、实验
以《FPGA Verilog开发实战指南——基于Altera EP4CE10》状态机一章后面的拓展训练为例,可知共有六个状态,如下
代码(未实现可乐的输出及找零,若要实现,再加两个always语句块输出即可):
1 //采用Mealy型状态机,三段式写法 2 module state( 3 input clk, //系统输入时钟 4 input rst_n, //系统复位信号,低电平有效 5 6 input m_half, //0.5元 7 input m_one, //1元 8 9 output reg [3:0]led //4个LED灯控制 10 11 ); 12 13 //状态编码使用独热码方式 14 parameter IDLE = 7'b000_0001; //空闲状态 15 parameter STATE1 = 7'b000_0010; //0.5元 16 parameter STATE2 = 7'b000_0100; //1元 17 parameter STATE3 = 7'b000_1000; //1.5元 18 parameter STATE4 = 7'b001_0000; //2元 19 parameter STATE5 = 7'b010_0000; //2.5元 20 parameter STATE6 = 7'b100_0000; //3元 21 22 parameter SYS_FRQ = 50; //时钟输入频率,50MHz 23 parameter STATE_TIME = 24'd10_000_000; //状态停留时间,单位为us,这里是10秒 24 parameter LED_FLOW_TIME = 18'd200_000; //LED流水灯亮间隔时间,单位us,这里是0.2s 25 26 parameter STATE_CNT_TIME = STATE_TIME * SYS_FRQ; //状态停留,所需要得时钟周期数 27 parameter LED_FLOW_CNT_TIME = LED_FLOW_TIME * SYS_FRQ; //LED流水灯,每个灯亮的时钟周期数 28 29 reg [28:0] state4_cnt; //STATE4 10s计数器 30 reg [28:0] state5_cnt; //STATE5 10s计数器 31 reg [28:0] state6_cnt; //STATE6 10s计数器 32 33 reg [23:0] flow_cnt; //0.2s计数器 34 reg led_flow_flag; //流水灯流向标志,0--从左往右,1--从右往左 35 reg [1:0]led_flow_cnt; //流水灯计数器 36 37 reg [6:0] current_state; //当前状态 38 reg [6:0] next_state; //下一个状态 39 40 41 42 wire key_m_one; 43 wire key_m_half; 44 45 wire [1:0] coin; 46 47 48 //将两个按键拼接在一起,方便后面判断 49 assign coin = {key_m_one, key_m_half}; 50 51 //一元投币按键滤波,key_flag为高则表示按键按下 52 key_filter key1( 53 .clk(clk), 54 .rst_n(rst_n), 55 .key_in(m_one), 56 .key_flag(key_m_one) 57 58 ); 59 60 //0.5元投币按键滤波,key_flag为高则表示按键按下 61 key_filter key2( 62 .clk(clk), 63 .rst_n(rst_n), 64 .key_in(m_half), 65 .key_flag(key_m_half) 66 67 ); 68 69 70 71 //三段式,第一段,采用同步时序逻辑描述状态转移 72 always @(posedge clk or negedge rst_n) 73 if(!rst_n) 74 current_state <= IDLE; 75 else 76 current_state <= next_state; 77 78 79 //三段式,第二段,采用组合逻辑描述状态转移条件,电平触发 80 always @(*) 81 begin 82 //state1_6_flag <= 1'b1; 83 case(current_state) 84 IDLE : if(coin == 2'b01) 85 next_state <= STATE1; 86 else if(coin == 2'b10) 87 next_state <= STATE2; 88 else 89 next_state <= IDLE; 90 91 STATE1 : if(coin == 2'b01) 92 next_state <= STATE2; 93 else if(coin == 2'b10) 94 next_state <= STATE3; 95 else 96 next_state <= STATE1; 97 98 STATE2 : if(coin == 2'b01) 99 next_state <= STATE3; 100 else if(coin == 2'b10) 101 next_state <= STATE4; 102 else 103 next_state <= STATE2; 104 105 STATE3 : if(coin == 2'b01) 106 next_state <= STATE4; 107 else if(coin == 2'b10) 108 next_state <= STATE5; 109 else 110 next_state <= STATE3; 111 112 STATE4 : if(coin == 2'b01) 113 next_state <= STATE5; 114 else if(coin == 2'b10) 115 next_state <= STATE6; 116 else if(state4_cnt == STATE_CNT_TIME - 1'b1) //状态4,若一直持续10s,则状态跳转到空闲状态 117 next_state <= IDLE; 118 else 119 next_state <= STATE4; 120 121 STATE5 : if(coin == 2'b01 || coin == 2'b10) 122 next_state <= STATE6; 123 else if(state5_cnt == STATE_CNT_TIME - 1'b1) //状态5,若一直持续10s,则状态跳转到空闲状态 124 next_state <= IDLE; 125 else 126 next_state <= STATE5; 127 128 STATE6 : if(state6_cnt == STATE_CNT_TIME - 1'b1) //状态5,若一直持续10s,则状态跳转到空闲状态 129 next_state <= IDLE; 130 else 131 next_state <= STATE6; 132 133 default : next_state <= IDLE; 134 135 endcase 136 end 137 138 //三段式,第三段,采用同步时序逻辑,或者组合逻辑描述输出,因为组合逻辑容易产生毛刺,所以这里使用同步时序输出 139 always @(posedge clk or negedge rst_n) 140 if(!rst_n) 141 led <= 4'b1111; //野火EP4CE10 Pro开发板,LED为高时熄灭 142 else 143 case(current_state) 144 IDLE : led <= 4'b1111; 145 STATE1 : led <= 4'b1110; 146 STATE2 : led <= 4'b1100; 147 STATE3 : led <= 4'b1000; 148 STATE4 : led <= 4'b0000; 149 STATE5 : if(state5_cnt == 29'd0) //刚进入状态5时,给LED赋初值,以便实现流水效果 150 led <= 4'b1110; 151 else if(flow_cnt == LED_FLOW_CNT_TIME - 1'b1) 152 led <= {led[2:0],led[3]}; 153 else 154 led <= led; 155 156 STATE6 : if(state6_cnt == 29'd0) 157 led <= 4'b1110; //刚进入状态5时,给LED赋初值,以便实现流水效果 158 else if(led_flow_flag == 1'b0) //从右往左 159 begin 160 if(flow_cnt == LED_FLOW_CNT_TIME - 1'b1) 161 led <= {led[2:0], led[3]}; 162 else 163 led <= led; 164 end 165 else if(led_flow_flag == 1'b1) //从左往右 166 begin 167 if(flow_cnt == LED_FLOW_CNT_TIME - 1'b1) 168 led <= {led[0], led[3:1]}; 169 else 170 led <= led; 171 end 172 else 173 led <= led; 174 175 endcase 176 177 178 179 //流水灯每个灯亮计数器,只有状态5~6用到 180 always @(posedge clk or negedge rst_n) 181 if(!rst_n) 182 flow_cnt <= 24'b0; 183 else if(flow_cnt == LED_FLOW_CNT_TIME - 1'b1) 184 flow_cnt <= 24'b0; 185 else 186 flow_cnt <= flow_cnt + 1'b1; 187 188 189 //流水灯单方向流水灯计数器,只有状态5~6用到 190 always @(posedge clk or negedge rst_n) 191 if(!rst_n) 192 led_flow_cnt <= 2'b0; 193 else if(flow_cnt == LED_FLOW_CNT_TIME - 1'b1) 194 begin 195 if(led_flow_cnt == 2'b11) 196 led_flow_cnt <= 2'b0; 197 else 198 led_flow_cnt <= led_flow_cnt + 1'b1; 199 end 200 201 else 202 led_flow_cnt <= led_flow_cnt; 203 204 205 //状态4计数器 206 always @(posedge clk or negedge rst_n) 207 if(!rst_n) 208 state4_cnt <= 29'b0; 209 else if(current_state == STATE4) 210 begin 211 if(state4_cnt == STATE_CNT_TIME - 1'b1) 212 state4_cnt <= 29'b0; 213 else 214 state4_cnt <= state4_cnt + 1'b1; 215 end 216 else 217 state4_cnt <= 29'b0; 218 219 220 //状态5计数器 221 always @(posedge clk or negedge rst_n) 222 if(!rst_n) 223 state5_cnt <= 29'b0; 224 else if(current_state == STATE5) 225 begin 226 if(state5_cnt == STATE_CNT_TIME - 1'b1) 227 state5_cnt <= 29'b0; 228 else 229 state5_cnt <= state5_cnt + 1'b1; 230 end 231 else 232 state5_cnt <= 29'b0; 233 234 235 //状态6计数器 236 always @(posedge clk or negedge rst_n) 237 if(!rst_n) 238 state6_cnt <= 29'b0; 239 else if(current_state == STATE6) 240 begin 241 if(state6_cnt == STATE_CNT_TIME - 1'b1) 242 state6_cnt <= 29'b0; 243 else 244 state6_cnt <= state6_cnt + 1'b1; 245 end 246 247 else 248 state6_cnt <= 29'b0; 249 250 251 //状态6流向标志,流水方向边界,分别为四个LED灯的两边,即LED[0]、LED[3],在这两个地方需要转换标志,以改变其流向 252 always @(posedge clk or negedge rst_n) 253 if(!rst_n) 254 led_flow_flag <= 1'b0; 255 else if(led[0] == 1'b0) 256 led_flow_flag <= 1'b0; 257 else if(led[3] == 1'b0) 258 led_flow_flag <= 1'b1; 259 else 260 led_flow_flag <= led_flow_flag; 261 262 263 endmodule
按键滤波代码:
1 //按键滤波,低为按键按下,输出高表示按键按下 2 module key_filter #( 3 parameter CNT_MAX = 20'd999_999 //20ms延时时间 4 5 )( 6 input clk, 7 input rst_n, 8 input key_in, 9 10 11 output reg key_flag 12 13 ); 14 15 reg [19:0] cnt_20ms; //20ms计数器 16 17 //检测按键为低时,开始重零计时 18 always @(posedge clk or negedge rst_n) 19 if(!rst_n) 20 cnt_20ms <= 20'b0; 21 else if(key_in == 1'b1) 22 cnt_20ms <= 20'b0; 23 else if(cnt_20ms == CNT_MAX && key_in == 1'b0) 24 cnt_20ms <= cnt_20ms; 25 else 26 cnt_20ms <= cnt_20ms + 1'b1; 27 28 //一旦延时20ms还是检测为低时,则表示一次有效按键按下 29 always @(posedge clk or negedge rst_n) 30 if(!rst_n) 31 key_flag <= 1'b0; 32 else if(cnt_20ms == CNT_MAX - 1'b1) 33 key_flag <= 1'b1; 34 else 35 key_flag <= 1'b0; 36 37 38 endmodule
总结:
1. 在状态机比较复杂时,最好使用三段式,或者新式二段式,这样比较清晰明了,从以上代码就可以看出
2. 输出采用时序逻辑,这样可以消除组合逻辑不可避免的毛刺
3. 带有按键输入的,一定要进行消抖,不然其状态及其不稳定,写这篇文章已体验多次
4. 另外,代码中的0.2s计数器,其实跟状态不是同步的,所以在流水灯亮第一颗灯时,其时间大概率不会是0.2s,对于要求高的,需要将0.2s与状态同步
5. 状态4~6都各自使用了一个10s的计数器,这样代码会比较容易实现,如果都用同一个,本人是没有写出来,越写越乱,与其这么乱,不如就加两个计数器,简单明了
6. 以上代码,包括编写格式都实际上机验证,可以直接验证。编写格式代码,需要加上按键消抖模块