第8章 Verilog有限状态机设计
8.1 有限状态机 有限状态机(Finite State Machine,FSM)是时序电路设计中经常采用的一种方式,尤其适用于设计数字系统的控制模块,在一些需要控制高速器件的场合,用状态机进行设计是解决问题的一种很好的方案,具有速度快,结构简单,可靠性高等优点。如交通灯控制电路、自动售货机、AD/DA转换等。 有限状态机非常适于用FPGA器件实现,用VerilogHDL语言中的case语句就能很好的描述基于状态机的设计,再通过EDA工具软件的综合,一般可以生成性能极优的状态机电路。 状态机可以认为是组合逻辑和寄存器逻辑的特殊组合,它一般包括两部分:组合逻辑部分和寄存器逻辑部分。寄存器用于存储状态,组合电路用于状态译码和产生输出信号。
8.1 有限状态机 摩尔型(Moore)状态机 米里型(Mealy)状态机 根据输入、输出及状态之间的关系,状态机可分为两类: 8.1 有限状态机 根据输入、输出及状态之间的关系,状态机可分为两类: Moore状态机:其输出值只取决于当前状态,与输入值无关; Mealy状态机:其输出值不但和当前状态有关,还和输入值相关。 摩尔型(Moore)状态机 米里型(Mealy)状态机
实用的状态机,一般都设计为同步时序方式,它在时钟的触发下,完成各个状态之间的转换,并产出相应的输出。 状态机有三种表示方法:状态图、状态表和流程图,这三种表示方法是等价的,相互之间可以转换。其中状态图是最常用的表示方法。 例:假设简易邮票自动售货机,只允许投入硬币(0.5元或1元),投入硬币达到2元后输出邮票,大于两元时,输出邮票并找零。 分析:系统需记忆的累积币值是四种状态:0元、0.5元、1元、1.5元 投入1元 状态间的跳转 投入0.5元 投入0.5元 投入0.5元 0元 0.5元 1元 1.5元 状态 引起转换的输入条件,或输出信号 投入1元 投入1元,输出邮票 投入0.5元,输出邮票 投入1元,输出邮票并找零
0元 0.5元 1元 1.5元 投入0.5元 投入1元 投入1元,输出邮票 投入0.5元,输出邮票 投入1元,输出邮票并找零 S0 S1 S2 S3 module MooreFSM (A, B,ClkM, Z,Y); …… parameter S0=2’b00, S1=2’b01, S2=2’b10, S3=2‘b11; //定义四个状态(状态编码) reg [0:1] MooreState; //存储状态的寄存器 always @ (posedge ClkM) //状态转换描述 case (MooreState) S0: ….. S1: …… S2:…… S3:……. ..... endcase endmodule
例:用状态机设计模5计数器 /输出 状态 module fsm(clk,z,qout); input clk; /0 output reg z; output reg[2:0] qout; always @(posedge clk) //此过程定义状态转换 case(qout) 3'b000: qout<=3'b001; 3'b001: qout<=3'b010; 3'b010: qout<=3'b011; 3'b011: qout<=3'b100; 3'b100: qout<=3'b000; default: qout<=3'b000; endcase end always @(qout) /*此过程产生输出逻辑*/ 3'b100: z=1'b1; default:z=1'b0; endmodule 000 001 010 011 100 /1 /0 module count5(clk, z,qout); input clk; output reg z; output reg[2:0] qout; always @(posedge clk) if(qout<4) //5-1 qout<=qout+1; else qout<=0; assign z=(qout==3’b100)?1:0; endmodule
8.2 有限状态机的几种描述方式 在状态机设计中主要包含三个对象: 当前状态,或称为现态(Current State,CS) 输入/输出 次态 8.2 有限状态机的几种描述方式 在状态机设计中主要包含三个对象: 当前状态,或称为现态(Current State,CS) 下一个状态,或称为次态(Next State,NS) 输出逻辑(OutLogic,OL) Verilog 描述有限状态机时,有以下几种描述方式: (1)三过程描述:即现态(CS)、次态(NS)、输出逻辑(OL)各用一个always过程描述。 (2)双过程描述(CS+NS、OL双过程描述):使用两个always过程来描述有限状态机,一个过程描述现态和次态时序逻辑(CS+NS);另一个过程描述输出逻辑(OL)。 (3)双过程描述(CS、NS+OL双过程描述):一个过程用来描述现态(CS);另一个过程描述次态和输出逻辑(NS+OL)。 (4)单过程描述:在单过程描述方式中,将状态机的现态、次态和输出逻辑(CS+NS+OL)放在一个always过程中进行描述。
例:“101”序列检测器的Verilog描述 方法一:用三个过程描述 module fsm1_seq101(clk,clr,x,z); input clk,clr,x; output reg z; reg[1:0] state,next_state; parameter S0=2'b00,S1=2'b01,S2=2'b11,S3=2'b10; /*状态编码.采用格雷编码方式*/ always @(posedge clk or posedge clr) /*该过程定义当前状态*/ if(clr) state<=S0; //异步复位,s0为起始状态 else state<=next_state; always @(state or x) /*该过程定义次态*/ case (state) S0: begin if(x) next_state<=S1; else next_state<=S0; end S1: begin if(x) next_state<=S1; else next_state<=S2; end S2: begin if(x) next_state<=S3; else next_state<=S0; end S3:begin if(x) next_state<=S1; else next_state<=S2; end default: next_state<=S0; endcase always @(state) /*该过程产生输出逻辑*/ case(state) S3: z=1'b1; default:z=1'b0; endcase endmodule S0 : next_state <= (x) ? S1 : S0; S1 : next_state <= (x) ? S1 : S2; S2 : next_state <= (x) ? S3 : S0; S3 : next_state <= (x) ? S1 : S2; default: next_state<=s0;
例:“101”序列检测器的Verilog描述 方法二:用两个过程描述(CS+NS、OL双过程描述) module fsm2_seq101(clk,clr,x,z); input clk,clr,x; output reg z; reg[1:0] state; parameter S0=2'b00,S1=2'b01,S2=2'b11,S3=2'b10; /*状态编码.采用格雷编码方式*/ always @(posedge clk or posedge clr) /*该过程定义状态变化*/ if(clr) state<=S0; //异步复位,s0为起始状态 else case (state) S0: begin if(x) state <=S1; else state <=S0; end S1: begin if(x) state <=S1; else state <=S2; end S2: begin if(x) state <=S3; else state <=S0; end S3:begin if(x) state <=S1; else state <=S2; end default: state <=S0; endcase always @(state) /*该过程产生输出逻辑*/ case(state) S3: z=1'b1; default:z=1'b0; endcase endmodule
例:“101”序列检测器的Verilog描述 方法三:用两个过程描述( CS、NS+OL双过程描述) module fsm3_seq101(clk,clr,x,z); input clk,clr,x; output reg z; reg[1:0] state,next_state; parameter S0=2'b00,S1=2'b01,S2=2'b11,S3=2'b10; /*状态编码.采用格雷编码方式*/ always @(posedge clk or posedge clr) /*该过程定义当前状态*/ if(clr) state<=S0; //异步复位,s0为起始状态 else state<=next_state; always @(state or x) /*该过程定义次态和产生输出逻辑*/ case (state) S0: begin if(x) begin next_state<=S1; z=1’b0;end else begin next_state<=S0; z=1’b0;end end S1: begin if(x) begin next_state<=S1; z=1’b0;end else begin next_state<=S2; z=1’b0;end end S2: begin if(x) begin next_state<=S3; z=1’b0;end else begin next_state<=S0; z=1’b0;end end S3:begin if(x) begin next_state<=S1; z=1’b1;end else begin next_state<=S2; z=1’b1;end end default: begin next_state<=S0; z=1’b0;end endcase endmodule
例:“101”序列检测器的Verilog描述 方法四:单过程描述 module fsm4_seq101(clk,clr,x,z); input clk,clr,x; output reg z; reg[1:0] state; parameter S0=2'b00,S1=2'b01,S2=2'b11,S3=2'b10; /*状态编码*/ always @(posedge clk or posedge clr) if(clr) state<=S0; //异步复位,s0为起始状态 else case(state) S0: if(x) begin state<=S1; z <=1'b0; end else begin state<=S0; z <=1'b0; end S1: if(x) begin state<=S1; z <=1'b0; end else begin state<=S2; z <=1'b0; end S2: if(x) begin state<=S3; z <=1'b0; end else begin state<=S0; z <=1'b0; end S3: if(x) begin state<=S1; z <=1'b1; end else begin state<=S2; z <=1'b1; end default: begin state<=S0; z <=1'b0; end endcase endmodule
方法一:用三个过程描述 综合结果 方法二和方法三:用两个过程描述 状态机视图 RTL视图 门级结构视图 三过程或双过程描述结构清晰,并且时序逻辑与组合逻辑分开,便于修改 RTL视图 门级结构视图
方法四:单过程描述 综合结果 双过程 单过程 状态机视图 RTL视图 门级结构视图 单过程描述是将现态、次态、输出全放在一个过程中描述,可有效避免输出逻辑出现毛刺的问题,在将输出信号作为控制逻辑的场合用的较多,但要注意:单过程描述输出逻辑会比双过程或三过程的输出逻辑延迟一个时钟周期 方法四:单过程描述 综合结果 双过程 单过程 RTL视图 状态机视图 门级结构视图
8.3 状 态 编 码 8.3.1 常用的编码方式 状态机设计中,为了表示状态,对状态要进行编码,即称为状态编码,常用的编码方式有以下几种: 8.3 状 态 编 码 例:parameter S0=2'b00, S1=2'b01, S2=2'b11,S3=2'b10; 8.3.1 常用的编码方式 状态机设计中,为了表示状态,对状态要进行编码,即称为状态编码,常用的编码方式有以下几种: 1、顺序编码:采用顺序的二进制数表示每个状态; 如可用2位二进制数00、01、10、11表示4个状态。顺序编码的缺点是从一个状态变换到相邻的状态时,有可能有多个比特位同时发生变化,造成电路容易产生毛刺,引发逻辑错误。 2、格雷编码:以格雷码表示各个状态,即相邻状态之间只有一个比特位发生变化,如00,01,11,10。格雷码可有效减少瞬变的次数,以及产生毛刺和一次暂态的可能。 3、约翰逊编码:以约翰逊计数器的输出作为状态的编码,如000,100,110,111,011,001,000。(移位寄存器,高位取反后送入输入端,相邻两个码之间也只有一个比特位不同。 4、一位独热码:采用n位来编写n个状态的状态机,如四个状态:0001,0010,0100,1000。独热码使用的触发器较多,但可以有效的节省和简化组合电路,对于FPGA来说,使用一位独热码可以有效提高电路的速度和可靠性,也有利于提高器件资源的利用率,因此对于FPGA,建议采用该方式编码。
8.3 状 态 编 码 状态 顺序编码 格雷编码 约翰逊编码 独热码 state0 000 0000 00000001 state1 001 8.3 状 态 编 码 状态 顺序编码 格雷编码 约翰逊编码 独热码 state0 000 0000 00000001 state1 001 1000 00000010 state2 010 011 1100 00000100 state3 1110 00001000 state4 100 1111 00010000 state5 101 0111 00100000 state6 110 111 0011 01000000 state7 0001 10000000
一位热码编码选择对话框(Quartus Ⅱ) 8.3 状 态 编 码 一位热码编码选择对话框(Quartus Ⅱ)
8.3.2 状态编码的定义 在VeilogHDL语言中,有两种方式可以用于定义状态编码,分别用parameter和define语句实现。 parameter state1=2’b00,state2=2‘b01,.....; case(state) state1:.... state2:.... 方式二:用`define语句定义 `define state1 2’b00 //不要加分号 `define state2 2’b01 case(state) `state1:.... `state2:.... 一般情况下,更倾向于采用方式一来定义状态编码。 调用时注意不要溜掉反引号“‘”
8.4 有限状态机设计要点 8.4.1 复位和起始状态的选择 1.起始状态的选择 : 起始状态是指电路复位后所处的状态,选择一个合理的起始状态将使整个系统简洁、高效。 多数EDA软件会自动为基于状态机的设计选择一个最佳的起始状态。 实用的状态机一般都设计为由唯一时钟边沿触发的同步运行方式,时钟信号和复位信号对每一个有限状态机来说都很重要。
8.4 有限状态机设计要点 实用的状态机都应有复位信号,有限状态机的复位信号有同步复位和异步复位两种。 2.有限状态机的同步复位 同步复位是复位信号有效后还要等到时钟跳变沿到来时,才可进行复位。 always @(posedge clk) if(clr) state<=S0; //同步复位,s0为起始状态 else state<=next_state; 3.有限状态机的异步复位 always @(posedge clk or posedge clr) if(clr) state<=S0; //异步复位,s0为起始状态 else state<=next_state;
8.4.2 多余状态的处理 处理多余状态一般有如下两种方法: 在状态机设计中,通常会出现大量的多余状态,比如:采用n位编码,总的状态数为2n个,若需要的状态< 2n,则会出现多余的状态。 假设目前有6个状态,若采用顺序编码:需要3位二进制数3’b000~3’b101六个状态,则会多余两个状态3’b110,3’b111; 若采用独热码的方式编码,需要六位二进制数,6’b000001, 6’b000010, 6’b000100,6’b001000,6’b010000,6’b100000六个状态,会多余26-6=58个状态。 处理多余状态一般有如下两种方法: (1)在case语句中用default分支决定如果进入无效状态所采取的措施; (2)编写必要的Verilog源代码明确定义进入无效状态所采取的行为。 注意:并非所有的综合软件都能按照default语句综合出有效避免死循环的电路,所有这种方法的有效性应视所用综合软件的性能而定。
例题1:用状态机设计流水灯 采用有限状态机设计一个彩灯控制器,要求控制12个LED灯实现如下的演示花型: 全灭->从两边往中间逐个亮->全亮->从中间往两边逐个灭->全灭; 循环执行上述过程。 每个状态的变化时间为0.5S(2Hz) 要求:(1)用双过程描述(一个描述状态跳转,一个描述输出) (2)单过程描述(状态跳转和输出在同一个过程中描述)
module light1(clk,reset,Z,com); input clk,reset; output reg[11:0] Z; output com; reg[23:0] count; wire clkout; reg[3:0] state; always @(posedge clk) //分频电路,产生2Hz信号 if(count<5000000) count<=count+1; else count<=0; assign clkout=(count==5000000)?1:0; parameter s0=4‘b0000,s1=4’b0001,s2=4‘b0010,s3=4’b0011, //状态编码 s4=4'b0100,s5=4'b0101,s6=4'b0110,s7=4'b0111, s8=4'b1000,s9=4'b1001,s10=4'b1010,s11=4'b1011; always @(posedge clkout) //状态跳转 if(reset) state<= s0; //同步复位 else case(state) s0:state<=s1;s1:state<=s2;s2:state<=s3;s3:state<=s4; s4:state<=s5;s5:state<=s6;s6:state<=s7;s7:state<=s8; s8:state<=s9;s9:state<=s10;s10:state<=s11;s11:state<=s0; default:state<=s0; endcase always @(state) //输出控制 case(state) s0: Z=12'b000000000000; s1: Z=12'b100000000001; s2: Z=12'b110000000011; s3: Z=12'b111000000111; s4: Z=12'b111100001111; s5: Z=12'b111110011111; s6: Z=12'b111111111111; s7: Z=12'b111110011111; s8: Z=12'b111100001111; s9: Z=12'b111000000111; s10:Z=12'b110000000011; s11:Z=12'b100000000001; default:Z=12'b000000000000; assign com=1‘b1; //LED共阴极端 endmodule
module light2(clk,reset,Z,com); input clk,reset; output reg[11:0] Z; output com; reg[23:0] count; wire clkout; reg[3:0] state; always @(posedge clk) //分频电路,产生2Hz信号 if(count<5000000) count<=count+1; else count<=0; assign clkout=(count==5000000)?1:0; parameter s0=4'b0000,s1=4'b0001,s2=4'b0010,s3=4'b0011,s4=4'b0100,s5=4'b0101, s6=4'b0110,s7=4'b0111,s8=4'b1000,s9=4'b1001,s10=4'b1010,s11=4'b1011; always @(posedge clkout) //状态跳转、输出逻辑 if(reset) state<= s0; //同步复位 else case(state) s0:begin state<=s1;Z<=12'b000000000000;end s1:begin state<=s2;Z<=12'b100000000001;end s2:begin state<=s3;Z<=12'b110000000011;end s3:begin state<=s4;Z<=12'b111000000111;end s4:begin state<=s5;Z<=12'b111100001111;end s5:begin state<=s6;Z<=12'b111110011111;end s6:begin state<=s7;Z<=12'b111111111111;end s7:begin state<=s8;Z<=12'b111110011111;end s8:begin state<=s9;Z<=12'b111100001111;end s9:begin state<=s10;Z<=12'b111000000111;end s10:begin state<=s11;Z<=12'b110000000011;end s11:begin state<=s0;Z<=12'b100000000001;end default:begin state<=s0;Z<=12'b000000000000;end endcase assign com=1'b1; endmodule