状态机的基本概念
硬件设计很讲究并行设计思想,虽然用verilog描述的电路实现大都是并行的,但是对于实际的工程应用中,我们往往需要让硬件来实现一些具有一定顺序的工作,这就要用到状态机的思想。什么是状态机呢?简单的说,就是通过不同的状态迁移来完成一些特定的顺序逻辑。硬件的并行性决定了用verilog描述的硬件实现(譬如不同的always语句)都是并行执行的,那么如果我们希望分多个时间完成一个任务怎么办?也许可以用多个使能信号来衔接多个不同的模块,但是这样做多少显得有些繁琐。状态机的提出就会大大简化这一工作。
下面举一个SRAM控制的例子来说明状态机。如图1所示,它表示了一个SRAM控制的状态变化。
首先,是在系统复位信号rst_n=0(复位有效)后,进入IDLE状态。每当rst_n=0(复位有效)时,都会保持在IDLE状态;当rst_n=1(复位完成),如果rd_req=1就进入WR_S1状态,如果wr_req=1就会进入RD_S1的状态,否则保持IDLE状态不变。相应的,只要满足一定条件或者有时不需要任何条件,系统会在这些固定的状态间进行切换。这样做的好处就在于每当需要操作SRAM时,其它模块只要发出一个wr_req或者rd_req信号(置高),系统就会进入相应状态并根据不同状态对SRAM的控制总线、地址总线和数据总线进行赋值。
构成状态机的基本要素是状态机的输入、输出和状态。输入就是一些引发状态变化的条件,比如上例中的wr_req和rd_req的变化就会引发状态的迁移,那么它们就是输入;输出就是状态变化后引起的变化,如上例中的控制总线、地址总线和数据总线的输出值就是由状态变化决定的;状态就是IDLE、WR_S1、WR_S2等,它们一般是由一些逻辑值来表示。
状态机写法
状态机一般有2种不同的写法,即两段式和三段式的状态机写法。
下面就前面提出的SRAM控制状态机给出2种不同的写法以及它们综合出的效果,供大家学习参考。wr_req和rd_req作为输入,cmd为输出,cstate、nstate为状态寄存器。
两段式状态机
//两段式状态机
reg[3:0] cstate;
reg[3:0] nstate;
always @(posedge clk or negedge rst_n)
if(!rst_n) cstate <= IDLE;
else cstate <= nstate;
always @(cstate or wr_req or rd_req) begin
case(cstate)
IDLE: if(wr_req) begin
nstate = WR_S1;
cmd = 3'b011;
end
else if(rd_req) begin
nstate = RD_S1;
cmd = 3'b011;
end
else begin
nstate = IDLE;
cmd = 3'b111;
end
WR_S1: begin
nstate = WR_S2;
cmd = 3'b101;
end
WR_S2: begin
nstate = IDLE;
cmd = 3'b111;
end
RD_S1: if(wr_req) begin
nstate = WR_S2;
cmd = 3'b101;
end
else begin
nstate = RD_S2;
cmd = 3'b110;
end
RD_S2: if(wr_req) begin
nstate = WR_S1;
cmd = 3'b011;
end
else begin
nstate = IDLE;
cmd = 3'b111;
end
default: nstate = IDLE;
endcase
end
三段式状态机
//两段式状态机
reg[3:0] cstate;
reg[3:0] nstate;
always @(posedge clk or negedge rst_n)
if(!rst_n) cstate <= IDLE;
else cstate <= nstate;
always @(cstate or wr_req or rd_req) begin
case(cstate)
IDLE: if(wr_req) nstate = WR_S1;
else if(rd_req) nstate = RD_S1;
else nstate = IDLE;
WR_S1: nstate = WR_S2;
WR_S2: nstate = IDLE;
RD_S1: if(wr_req) nstate = WR_S2;
else nstate = RD_S2;
RD_S2: if(wr_req) nstate = WR_S1;
else nstate = IDLE;
default: nstate = IDLE;
endcase
end
always @(posedge clk or negedge rst_n) begin
if(!rst_n) cmd <= 3'b111;
else
case(nstate)
IDLE: if(wr_req) cmd <= 3'b011;
else if(rd_req) cmd <= 3'b011;
else cmd <= 3'b111;
WR_S1: cmd <= 3'b101;
WR_S2: cmd <= 3'b111;
RD_S1: if(wr_req) cmd <= 3'b101; else cmd <= 3'b110;
RD_S2: if(wr_req) cmd <= 3'b011; else cmd <= 3'b111;
default: ;
endcase
end
两段式状态机是一种常用的写法,它把时序逻辑和组合逻辑划分开来,时序逻辑里进行当前状态和下一状态的切换,组合逻辑里实现各个输入输出以及状态判断。这种写法相对容易维护,不过组合逻辑输出较易出现毛刺等常见问题。三段式状态机写法也是一种比较推荐的写法,代码容易维护,时序逻辑的输出解决了两段式写法中组合逻辑的毛刺问题。但是从资源消耗上比较,三段式的资源消耗多一些。
注:以上文章来源于《深入浅出玩转FPGA》特权同学。谢谢原作者的付出。