顺序控制逻辑的Verilog HDL描述

绝大多数控制逻辑都能用有限状态机设计,换句话说,有限状态机是设计控制逻辑的有效手段。本节主要介绍用Verilog描述状态机的方法。

状态机的结构和描述方法

状态机模型有摩尔型(Moore)和米利型(Mealy)两种,如图 1图 2所示,摩尔型状态机的输出仅在状态变化时改变;在整个状态周期内保持不变,即使输入信号有变化。米利型状态机的输出不仅和当前状态有关,还直接受输入影响,变化可能出现在任何时刻。

sv-4 摩尔型状态机结构
图 1. 摩尔(Moore)型状态机结构
sv-5 米利型状态机结构
图 2. 米利(Mealy)型状态机结构
sv-6 小车控制状态图
图 3. 车速控制状态图

下面通过一个车速控制的例子说明有限状态机的Verilog描述。假设汽车有四种行驶状态:停止、低速、中速、高速,一个表示速度值的输出信号,两个输入信号“加速”和“减速”控制速度的转换。图 3示出了状态图,代码见例 1

例 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块,如例 2。整个状态机分为时序逻辑和组合逻辑两个部分,对应结构见图 4

例 2. 二段式状态机描述
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
sv-7 状态机的两段式结构
图 4. 状态机的两段式结构

输出逻辑和次态逻辑合并之后,从结构上不能直接分辨出状态机的类型是摩尔型还是米利型,只能从代码上分析。例 2中,输出speed和输入acc、brake无关,只和当前状态有关,仍然是摩尔型状态机。实际上,用Verilog描述状态机,并不一定需要区分摩尔型和米利型。过去之所以要区分米利型和摩尔型,是因为它们的设计方法不同。

有的时候也能见到另外一种两段式结构,就是将状态描述和次态计算逻辑合并为一段,见下面例 3。这种描述虽然也能运行,但是不利于综合器优化代码,不利于时序约束,不便于阅读、维护,而且不能很好的表示米利型状态机,所以不推荐采用这种结构。

例 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的代码构成一个完整的状态机描述。

例 4. One-hot状态编码和状态寄存器定义
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

例 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

几个原则

(1) 复位后状态机应该有一个确定的状态;

(2) 如果有未知的或不关心的次态,用default 给次态赋值,否则会产生锁存器;

(3) 状态机描述只包含控制信号,其他功能(如计数器等)尽量放在状态机之外。