顺序控制逻辑的Verilog HDL描述
绝大多数控制逻辑都能用有限状态机设计,换句话说,有限状态机是设计控制逻辑的有效手段。本节主要介绍用Verilog描述状态机的方法。
状态机的结构和描述方法
状态机模型有摩尔型(Moore)和米利型(Mealy)两种,如图 1和图 2所示,摩尔型状态机的输出仅在状态变化时改变;在整个状态周期内保持不变,即使输入信号有变化。米利型状态机的输出不仅和当前状态有关,还直接受输入影响,变化可能出现在任何时刻。
下面通过一个车速控制的例子说明有限状态机的Verilog描述。假设汽车有四种行驶状态:停止、低速、中速、高速,一个表示速度值的输出信号,两个输入信号“加速”和“减速”控制速度的转换。图 3示出了状态图,代码见例 1。
module car_ctrl ( input clk, reset, acc, brake, output reg [1:0] speed ); localparam [1:0] STOPED = 2'b00; localparam [1:0] LOW = 2'b01; localparam [1:0] MODERATE = 2'b10; localparam [1:0] HIGH = 2'b11; reg [1:0] state, next_state; always @(posedge clk) if (reset) state <= STOPED; else state <= next_state; always @(state, acc, brake) case (state) STOPED: if (acc && !brake) next_state = LOW; else next_state = STOPED; LOW: if (brake) next_state = STOPED; else if (acc) next_state = MODERATE; else next_state = LOW; MODERATE: if (brake) next_state = LOW; else if (acc) next_state = HIGH; else next_state = MODERATE; HIGH: if (brake) next_state = MODERATE; else next_state = HIGH; default: next_state = STOPED; endcase always @(state) speed = state; //本例的状态编码恰巧与输出(速度)相同 endmodule
这是一个典型的摩尔型状态机。三个always块对应了图 1的三个部分,第一个always块是时序逻辑,描述状态寄存器state;第二个always块是组合逻辑,描述次态逻辑next_state;第三个always块也是组合逻辑,描述输出逻辑speed。
always @(posedge clk) if (reset) state <= STOPED; else state <= next_state; always @(state, acc, brake) case (state) STOPED: speed=2'b00; if (acc && !brake) next_state = LOW; else next_state = STOPED; LOW: speed=2'b01; if (brake) next_state = STOPED; else if (acc) next_state = MODERATE; else next_state = LOW; MODERATE: speed=2'b10; if (brake) next_state = LOW; else if (acc) next_state = HIGH; else next_state = MODERATE; HIGH: speed=2'b11; if (brake) next_state = MODERATE; else next_state = HIGH; default: speed=2'b00; next_state = STOPED; endcase
输出逻辑和次态逻辑合并之后,从结构上不能直接分辨出状态机的类型是摩尔型还是米利型,只能从代码上分析。例 2中,输出speed和输入acc、brake无关,只和当前状态有关,仍然是摩尔型状态机。实际上,用Verilog描述状态机,并不一定需要区分摩尔型和米利型。过去之所以要区分米利型和摩尔型,是因为它们的设计方法不同。
有的时候也能见到另外一种两段式结构,就是将状态描述和次态计算逻辑合并为一段,见下面例 3。这种描述虽然也能运行,但是不利于综合器优化代码,不利于时序约束,不便于阅读、维护,而且不能很好的表示米利型状态机,所以不推荐采用这种结构。
module car_ctrl_bad ( input clk, acc, brake, output reg [1:0] speed ); localparam [1:0] STOPED = 2'b00; localparam [1:0] LOW = 2'b01; localparam [1:0] MODERATE = 2'b10; localparam [1:0] HIGH = 2'b11; reg [1:0] state; always @(posedge clk) case (state) STOPED: if (acc && !brake) state = LOW; else state = STOPED; LOW: if (brake) state = STOPED; else if (acc) state = MODERATE; else state = LOW; MODERATE: if (brake) state = LOW; else if (acc) state = HIGH; else state = MODERATE; HIGH: if (brake) state = MODERATE; else state = HIGH; default: next_state = STOPED; endcase always @(state) speed = state; //本例的状态编码恰巧与输出(速度)相同 endmodule
状态机描述的几个问题
状态编码
除了二进制编码,还有一种常见的状态编码叫做One-hot编码,它的每个编码中只有一位为1。以车速控制状态机为例,采用One-hot编码的状态定义如例 4,它可以和例 2的代码构成一个完整的状态机描述。
localparam [3:0] STOPED = 4'b0001; localparam [3:0] LOW = 4'b0010; localparam [3:0] MODERATE = 4'b0100; localparam [3:0] HIGH = 4'b1000; reg [3:0] state, next_state;
可以看出,One-hot编码使用的触发器较多,但计算次态的组合电路较小,适合于FPGA采用。
消除输出信号的毛刺
由状态机产生的输出信号,可能会产生毛刺。毛刺不一定会对系统产生危害,所以通常会忽视它的存在。但是在高速系统中,这些毛刺可能产生危害,尤其是当它用作边沿触发信号时。那么如何消除输出的毛刺呢?产生毛刺的原因是输出信号由组合逻辑产生,由于不同路径的传输延迟不同,从而使组合逻辑的输出产生瞬间的变化。解决的办法就是采用寄存器输出,仅仅在时钟触发沿到来时输出更新,其他时间产生的毛刺不会传递到输出。代码见例 5。
module car_ctrl ( input clk, reset, acc, brake, output reg [1:0] speed ); localparam [3:0] STOPED = 4'b0001; localparam [3:0] LOW = 4'b0010; localparam [3:0] MODERATE = 4'b0100; localparam [3:0] HIGH = 4'b1000; reg [3:0] state, next_state; reg [1:0] out; always @(posedge clk) if (reset) state <= STOPED; else state <= next_state; always @(state, acc, brake) case (state) STOPED: out=2'b00; if (acc && !brake) next_state = LOW; else next_state = STOPED; LOW: out=2'b01; if (brake) next_state = STOPED; else if (acc) next_state = MODERATE; else next_state = LOW; MODERATE: out=2'b10; if (brake) next_state = LOW; else if (acc) next_state = HIGH; else next_state = MODERATE; HIGH: out=2'b11; if (brake) next_state = MODERATE; else next_state = HIGH; default: out=2'b00; next_state = STOPED; endcase always @(posedge clk) speed = out; endmodule