该章节对状态机的初步理解有所帮助,但更多干货和实际编码还需要再多学习和参考一下
第12章 同步状态机的原理、结构和设计
概述
可综合的Verilog HDL和VHDL的语法只是它们各自语言的一个子集。由于寄存器传输级(RTL)描述的是以时序逻辑抽象所得到的有限状态机为依据,所以,把一个时序逻辑抽象成一个同步有限状态机是设计可综合风格的Verilog VHDL模块的关键。
12.1 状态机的结构
下图是数字电路设计中常用的时钟同步状态机的结构(Mealy状态机)。其中,
状态寄存器是由一组触发器组成,用来记忆状态机当前所处的状态。如果状态寄存器由n个触发器组成,这个状态机最多可以记忆 2 n 2^n 2n个状态。
所有的触发器的时钟端都连接在一个共同的时钟信号上,所以状态的改变只可能发生在时钟的跳变沿上。
下一个状态 = F(当前状态,输入信号);
输出信号 = G(当前状态,输入信号)。
12.2 Mealy状态机和Moore状态机的不同点
Mealy状态机:时序逻辑的输出不但取决于状态还取决于输入;
Moore状态机:时序逻辑电路的输出只取决于当前状态,即 输出信号 = G(当前状态),如下图所示
参考其他博客Verilog状态机,补充Mealy状态机和Moore状态机区别:
Moore 型状态机的输出只与当前状态有关,与当前输入无关。输出会在一个完整的时钟周期内保持稳定,即使此时输入信号有变化,输出也不会变化。输入对输出的影响要到下一个时钟周期才能反映出来。这也是 Moore 型状态机的一个重要特点:输入与输出是隔离开来的。
Mealy 型状态机的输出,不仅与当前状态有关,还取决于当前的输入信号。Mealy 型状态机的输出是在输入信号变化以后立刻发生变化,且输入变化可能出现在任何状态的时钟周期内。因此,同种逻辑下,Mealy 型状态机输出对输入的响应会比 Moore 型状态机早一个时钟周期。
12.3 如何用Verilog来描述可综合的状态机
在Verilog HDL中可以用多种方法来描述有限状态机,最常用的方法是用always语句和case语句
下图所示的状态转移图表示了一个简单的4状态的有限状态机,同步时钟是clk,输入信号是A和Reset,输出信号是K2和K1
整个章节使用该案例贯穿讲解不同的编码方法,重点是状态机的编码等,但参考代码中关于不同状态时K2和K1的输出完全没看明白,不知道是不是写错了,还是我理解问题。
12.3.1 用可综合Verilog模块设计状态机的典型办法
module fsm(Clock, Reset, A, K2, K1, state);
input Clock, Reset, A;
output K2, K1;
output [1:0] sate;
reg K2, K1;
reg [1:0] state;
parameter Idle = 2'b00;
parameter Start = 2'b01;
parameter Stop = 2'b10;
parameter Clear = 2'b11;
always @(posedge Clock)
if(!Reset)
begin
state <= Idle;
K2 <= 0;
K1 <= 0;
end
else
case(state)
Idle:
if(A) begin
state <= Start;
K1 <= 0;
end
else begin
state <= Idle;
K2 <= 0;
K1 <= 0;
end
Start:
if(!A) begin
state <= Stop;
end
else begin
state <= Start;
end
Stop:
if(A) begin
state <= Clear;
K2 <= 1;
end
else begin
state <= Stop;
// 问题:在Stop和Clear状态时的保持,为啥K2和K1是这个输出,看图看不出来
// K2 <= 0;
// K1 <= 0;
end
Clear:
if(!A) begin
state <= Idle;
K2 <= 0;
K1 <= 0;
end
else begin
state <= Clear;
// K2 <= 0;
// K1 <= 1;
end
default: state <= 2'bxx;
endcase
endmodule
12.3.2 用可综合Verilog模块设计、用独热码表示状态的状态机
与上述例子不同的是状态的编码模式,采用独热编码,其他一致部分代码一致
parameter Idle = 4'b1000,
Start = 4'b0100,
Stop = 4'b0010,
Clear = 4'b0001;
对于FPGA实现的有限状态机建议采用独热码,因为虽然独热编码多用了两个触发器,但所用组合电路可省一些,因而使电路的速度和可靠性有显著提高,而总的单元数并无显著增加。
采用独热编码后有了多余的状态,就有一些不可到达的状态。为此,在case语句的最后需要增加default分支项。这可以用默认项表示该项,也可以用确定项表示,以确定回到Idle状态。一般综合器都可以通过综合指令的控制来合理地处理默认项。
12.3.3 用可综合Verilog模块设计、由输出指定的码表示状态的状态机
把输出直接指定为状态码:把状态码的指定与状态机控制的输出联系起来,把状态的变化直接用作输出,这样做可以提高输出信号的开关速度并节省电路器件。但这种方法的缺点是,开关的维持时间必须与状态的维持时间一致(如果要完全实现上面两例子的开关输出波形,需要增加状态才能实现)这种设计方法常用在高速状态机中。
module fsm(Clock, Reset, A, K2, K1, state);
input Clock, Reset, A;
output K2, K1;
output [4:0] state;
reg [4:0] state;
assign K2 = state[4]; // 把状态变量的最高位用作输出K2
assign K1 = state[0];
parameter
// -------- K2_i_j_K1 -------- output coded state assignment
Idle = 5'b0_0_0_0_0,
Start = 5'b0_0_0_1_0,
Stop = 5'b0_0_1_0_0,
StopToClear = 5'b1_1_0_0_0,
Clear = 5'b0_1_0_1_0,
ClearToIdle = 5'b0_0_1_1_1;
always @(posedge Clock)
if(! Reset)
begin
// state <= Zero; ??? Zero啥,哪来的Zero
state <= Idle;
end
else
case(state)
Idle:
if(A)
state <= Start;
else
state <= Idle;
Start:
if(A)
state <= Stop;
else
state <= Start;
Stop:
if(A)
state <= StopToClear;
else
state <= Stop;
StopToClear: state <= Clear; // 保持输出
Clear:
if(!A)
state <= ClearToIdle;
else
state <= Clear;
ClearToIdle: state <= Idle; // 保持输出
default: state <= Idle;
endcase
endmodule
12.3.4 用可综合Verilog模块设计复杂的多输出状态机时常用的方法
在比较复杂的状态机设计过程中,往往把状态的变化与输出开关的控制分成两部分来考虑。为了调试方便,还常常把每一个输出开关写成一个个独立的always组合块。
module fsm(Clock, Reset, A, K2, K1);
input Clock, Reset, A;
output K2, K1;
reg K2, K1;
reg [1:0] state, nextstate;
parameter
Idle = 2'b00;
Start = 2'b01;
Stop = 2'b10;
Clear = 2'b11;
// -------- 每一个时钟沿产生一次可能的状态变化 --------
always @(posedge Clock)
if(!Reset)
state <= Idle;
else
state <= nextstate;
// -----------------------------------------------
// -------- 产生下一状态的组合逻辑 --------
always @(state or A)
case(state)
Idle:
if(A)
nextstate = Start;
else
nextstate = Idle;
Start:
if(!A)
nextstate = Stop;
else
nextstate = Start;
Stop:
if(A)
nextstate = Clear;
else
nextstate = Stop;
Clear:
if(!A)
nextstate = Idle;
else
nextstate = Clear;
default:
nextstate = 2'bxx;
encase
// -----------------------------------------------
// -------- 产生输出K1的组合逻辑 --------
always @(state or Reset or A)
if(!Reset) K1 = 0;
else
if(state == Clear && !A) // 从Clear转向Idle
K1 = 1;
else K1 = 0;
// -------- 产生输出K2的组合逻辑 --------
always @(state or Reset or A)
if(!Reset) K2 = 0;
else
if(state == Stop && A) // 从Stop转向Clear
K2 = 1;
else K2 = 0;
// -----------------------------------------------
endmodule
上述代码案例,遵循的是三段式编写规范,这种风格描述比较适合大型的状态机,查错和修改比较容易,Synopsys公司的综合器建议使用这种风格来描述状态机。
上述4个例子是同一状态机的4种不同的Verilog HDL模型,它们都是可综合的,在设计复杂程度不同的状态机时有它们各自的优势。如用不同的综合器对这4个例子进行综合,综合出的逻辑电路可能会有些不同,但逻辑功能是相同的
如下为测试不同风格状态机测试模块以供参考
`timescale 1ns/1ns
modult t;
reg a;
reg clock, rst;
wire k2, k1;
initial begin // initial 常用于仿真时信号的给出
a = 0;
rst = 1; // 给复位信号变量赋初始值
clock = 0; // 给时钟变量赋初始值
#22 rst = 0; // 使复位信号有效
#133 rst = 1; // 经过多个周期后使复位信号无效
end
always #50 clock = ~clock; // 产生周期性的时钟
always @(posedge clock) begin // 在每次时钟正跳变沿时刻产生不同的a
#30 a = {$ random} % 2; // 每次a是0还是1是随机的
#(3 * 50 + 12); // a的值维持一段时间
end
initial begin
#100000 $stop; // 系统任务,暂停仿真以便观察仿真波形
end
// -------------- 调用被测试模块 t. m -----------------
fsm m(.Clock(clock), .Reset(rst), .A(a), .K2(k2), .K1(k1));
endmodule
小结
有限状态机设计的一般步骤:
(1)逻辑抽象,得出状态转换图:就是把给出的一个实际逻辑关系表示为时序逻辑函数,可以用状态转换表来描述,也可以用状态转换图来描述。这就需要:
① 分析给定的逻辑问題,确定输入变量、输出变量以及电路的状态数。通常是取原因(或条件)作为输入变量,取结果作为输出变量。
② 定义输入、输出逻辑状态的含意,并将电路状态顺序编号。
③ 按照要求列出电路的状态转换表或画出状态转换图。
这样,就把给定的逻辑问题抽象到一个时序逻辑函数了。
(2)状态化简:如果在状态较换图中出现这样两个状态,它们在相同的输入下转换到同一状态去,并得到一样的输出,则称为等价状态。显然等价状态是重复的,可以合井为一个电路的状态数越少,存储电路也就越简单。状态化简的目的就在于将等价状态尽可能地合并,以得到最简的状态转换图。
(3)状态分配:状态分配又称状态编码。通常有很多编码方法,编码方案选择得 当,设计的电路可以简单,反之,选得不好,则设计的电路就会复杂许多。在实际设计时,须综合考虑电路复杂度与电路性能之间的折衷。在触发器资源丰富的 FPGA 或 ASIC 设计中,采用独热编码 (one-hot-coding)既可以使电路性能得到保证又可充分利用其触发器数量多的优势,也可以采取输出编码的状态指定来简化电路结构,并提高状态机的运行速度。
(4)选定触发器的类型并求出状态方程、驱动方程和输出方程。
(5)按照方程得出逻辑图:用 Verilog HDL 来描述有限状态机,可以充分发挥硬件描述语言的抽象建模能力,使用always块语句和case(if)等条件语句及赋值语句即可方便实现。具体的逻辑化简、逻辑电路到触发器映射均可由计算机自动完成。上述设计步骤中的第(2)步及(4)、(5)步不再需要很多的人为干预,使电路设计工作得到简化,效率也有很大的提高。