状态机的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