关于状态机

205 阅读3分钟

状态机的Verilog写法

1.状态机:简单说是通过不同的状态迁移来完成一些特定的顺序逻辑。
2.状态机的分类:
(1)Moore型:状态机的状态变化仅和当前状态有关。
(2)Mealy型:状态机的状态变化不仅与当前的状态有关,还取决于当前的输入条件。
3.状态机编码:
独热码、格雷码,其中使用独热码永远只有一个位为1,即可保持位变化少,但是用独热码后会有多余的状态,于是在写case语句时,需要要加上default的情况,不然的话,会生成锁存器。其中default分支可以用默认项表示,也可以用确定向来表示,以确保能最初状态。
4.状态机的写法:
状态机一般包括三种写法:一段式、两段式和三段式。
(1)一段式:整个状态机写到一个always模块里面,在该模块中既描述状态转移,又描述状态的输入和输出;

(2)二段式:用两个always模块来描述状态机,一个always模块采用同步时序描述状态转移;另一个模块采用组合逻辑判断状态转移条件,描述状态转移规律以及输出;

(3)三段式:使用三个模块,一个always模块采用同步时序描述状态转移,一个always采用组合逻辑判断状态转移条件,描述状态转移规律,另一个always模块描述状态输出(可以用组合电路输出,也可以时序电路输出)。


小例子:
假如有三个状态,IDLE,S1,S2,在IDLE状态的时候,若en=1,则进入到S1状态,否则保持原状态不变。在S1状态的时候,若en=1,则进入到S2状态,否则保持原状态不变。在S2状态的时候,若en=1,则进入到IDLE状态,同时输出vld=1,否则保持原状态不变。
在这里插入图片描述

  module state(clk,rst_n,en,vld);
   input clk,rst_n;
	input en;
	output reg vld;
	
   reg [2:0]  state;
	
   localparam IDLE= 3'b001;    //parameter声明全局常量,可用在整个工程中,localparam仅在当前module中有效
	localparam S1=   3'b010;
	localparam S2=   3'b100;
   
	reg [2:0]  state_n;
	reg [2:0]  state_c;
	
	 always @ (posedge clk or negedge rst_n)begin
	    if(!rst_n)
		    state_c <= IDLE;
		 else 
		   state_c <= state_n; 
		 
	end
	always @(*) begin
	  case(state_c)
	    IDLE:begin
		    if(en) 
			    state_n = S1;
			 else
			   state_n = IDLE; 
	      end
	   S1:begin
		    if(en) 
			    state_n = S2;
			 else
			   state_n = S1; 
	      end
	    S2:begin
		    if(en) 
			    state_n = IDLE;
			 else
			   state_n = S2; 
	      end
	  endcase
	end
	
 always @(posedge clk or negedge rst_n)begin
       if(!rst_n)
		    vld <= 0;
		 else if (state_c == S2 && en==1)
		       vld<=1;
		 else
		    vld <= 0;	 

 end   
endmodule 

testbench

`timescale 1ns/1ps //时间精度
`define Clock 20 //时间周期
module state_tb;

   reg clk,rst_n;
	reg en;
	wire vld;
state state1(
     .clk(clk),
	  .rst_n(rst_n),
	  .en(en),
	  .vld(vld)   
	 );

//--   状态机名称查看器

localparam S0           = 4'b0001           ;
localparam S1           = 4'b0010           ;
localparam S2           = 4'b0100           ;
//2字符16位  这里是用ASICC表示,其中的S0就是IDLE
reg [15:0]              state_name          ;

always@(*)begin
    case(state1.state_c)
        S0:     state_name = "S0";
        S1:     state_name = "S1";
        S2:     state_name = "S2";
        default:state_name = "S0";
    endcase
end	
	 
initial		
    clk = 0;
always #(`Clock/2) clk= ~clk;

initial begin
   rst_n=0; #(`Clock*3);
   rst_n=1;
end	

initial begin
    en=0;
	 #(`Clock);en=1;#(`Clock);en=0;#(`Clock);en=0;
	 #(`Clock*2); en=1;#(`Clock*2);en=1;#(`Clock*2);en=0;
	 #(`Clock*2);en=1;#(`Clock*2);en=1;#(`Clock*2);en=1;
	 #(`Clock*2);en=0;#(`Clock*2);en=1;#(`Clock*2);en=1;
	 #(`Clock*2);en=0;#(`Clock*2);en=0;#(`Clock*2);en=1;
	 #(`Clock*2); en=0;#(`Clock*2);en=1;#(`Clock*2);en=0;
	 #(`Clock*50);
	 $stop;
end
endmodule

在这里插入图片描述


举例子:设计一个关于售货机的状态机,要求只能投入一元和两元钱,脉动饮料的价格是四元,若超出则找零,否则不找,当达到四元时,给出脉动饮料,否则不给。


用word里面的流程图所绘制的
reg [1:0] in;

状态:四个状态采用独热码进行编码
S0(0001):初始状态,未投币或者已取出饮料。

S1(0010):投币一元的状态。

S2(0100):投币两元的状态。

S3(1000):找零和输出饮料的状态。

在这里插入图片描述

上图为编写代码后生成的状态图,与我们想要设计的状态图相同,符合要求。


三段式的写法进行verilog编写:

module auto_sale(clk,rst_n,in,out,out_vld);
//端口声明

  input clk;
  input rst_n;
  input [1:0] in;
  
  output reg   out;
  output reg out_vld;
  
//信号定义
  reg [3:0]  state;
  reg [3:0]  state_next;
  
//状态机参数
localparam S0   =4'b0001;
localparam S1   =4'b0010;
localparam S2   =4'b0100;
localparam S3   =4'b1000;

//状态机的第一段,这一段:同步时序描述状态转移

always @(posedge clk or negedge rst_n)begin

 if(!rst_n)
    state <= S0;
 else
    state <= state_next;
end

//状态机的第一段,这一段:采用组合逻辑判断状态转移条件,描述状态转移规律

always @(*)begin
  case(state)
    S0:begin
	     if(in==2'b01)
		     state_next = S1;
		  else if(in==2'b10)
		     state_next = S2;
	     else
		     state_next = state;		  	 
	 end
	

    S1:begin
	     if(in==2'b01)
		     state_next = S2;
		  else if(in==2'b10)
		     state_next = S3;
	     else
		     state_next = state;	
	 end
	
    	
    S2:begin
	     if(in==2'b01)
		     state_next = S3;
		  else if(in==2'b10)
		     state_next = S0;
	     else
		     state_next = state;	 
	 end
	

    S3:begin
	     if(in==2'b01 || in==2'b10)
		     state_next = S0;
	     else
		     state_next = state;	
	 end  
    default : state_next = S0;
	 endcase
 end 
 //状态机的第三段:描述状态输出,可以是时序逻辑,也可以是组合逻辑
 
//分成两部分,第一部分是找零部分
always @(posedge clk or negedge rst_n)begin
     if(!rst_n)
         out <= 0;     
     else if(state==S3 && in==2)  //这里相当于输入进来了五元,所以需要找零
         out <= 1;
     else
         out <= 0;
end


//第二部分是输出脉动的部分
always @(posedge clk or negedge rst_n)begin
     if(!rst_n)
         out_vld <= 0;     
     else if((state==S3 && in !=2'b00)||(state==S2 && in==2'b10))  //这里相当于输入进来了五元,所以出脉动
         out_vld <= 1;
     else
         out_vld <= 0;
end

endmodule 

testbench编写:

`timescale 1ns/1ps  //时间精度
`define    Clock 20 //时钟周期module auto_sale_tb;
// 端口 
reg                     clk                 ;
reg                     rst_n               ;
reg  [1:0]              in                  ;

wire                    out                 ;
wire                    out_vld             ;


//  模块例化
 auto_sale auto_sale0(
    .clk                (clk ),
    .rst_n              (rst_n ),
    .in                 (in  ),
    .out                (out ),
    .out_vld            (out_vld)
);


//--   状态机名称查看器

localparam S0           = 4'b0001           ;
localparam S1           = 4'b0010           ;
localparam S2           = 4'b0100           ;
localparam S3           = 4'b1000           ;
//2字符16位

reg [15:0]              state_name          ; //因为S0这种属于两个字符,为16位

always@(*)begin
    case(auto_sale0.state)
        S0:     state_name = "S0";
        S1:     state_name = "S1";
        S2:     state_name = "S2";
        S3:     state_name = "S3";
        default:state_name = "S0";
    endcase
end


//   时钟信号和复位信号

initial begin
    clk = 1;
    forever
    #(`Clock/2) clk = ~clk;
end

initial begin
    rst_n = 0; #(`Clock*20+1);
    rst_n = 1;
end


//--   设计输入信号

initial begin
    #1;
    in = 2'b00;
    #(`Clock*20+1); //初始化完成
//情况1
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*10);
//情况2
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*10);
//情况3
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*10);
//情况4
    in = 2'b01;         //1块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 0;
    #(`Clock*10);
//情况5
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*1);
    in = 2'b10;         //2块钱
    #(`Clock*1);
    in = 2'b00;
    #(`Clock*10);
    $stop;
end
//情况可以根据需求进行编写,来对应看结果是否正确,这里只写出来五种不同的情况来进行分析
endmodule