为什么 If 语句会导致 verilog 中的闩锁?

Why does an If statement cause a latch in verilog?

我正在尝试在 Verilog 中编写一个 controller/data-path 实现,但我对什么会导致不需要的闩锁感到困惑。本质上,我有一个状态机在 negedge 时钟上更新。该状态机根据机器所处的状态向数据路径发送 5 个控制信号(loadSquare、loadDelta、addDelta 等)。数据路径和控制器的代码如下所示。

数据路径

//Control lines
reg addSquare, addDelta, decDelta;
reg loadSquare, loadDelta;

//Input lines
reg [8:0] square, delta;

//Output register
reg [7:0] outReg;

always @(posedge clk) begin
  if (loadSquare)
     square = 9'h1;  //used on initialization

  if (loadDelta)
     delta = 9'h3;   //used on initialization

  if (addSquare)
     square = square + delta; 

  if (addDelta)
     delta = delta + 2'h2;

  if (decDelta)
     outReg = (delta>>1) - 1;  //used for output
  else
     outReg = Input;
end

控制器

//Output of module
assign Output = outReg;

//Finite State Machine
always @(currentState) begin
    case(currentState)
        2'h0:   begin       //initialize values, wait for start
        {loadSquare, loadDelta} = 2'b11;
        {addSquare, addDelta, decDelta} = 3'h0;
     end
        2'h1: begin
        {loadSquare, loadDelta} = 2'b00;
        {addSquare, addDelta, decDelta} = 3'b110;  //add square and delta
     end
        2'h2: begin
        {loadSquare, loadDelta} = 2'b00;
        {addSquare, addDelta, decDelta} = 3'b001;  //decrement delta, wait for reset
     end
        default: ; // unused
    endcase

//Next state logic implemented on negedge clk (not shown)

此代码在 Xilinx 中生成以下警告:

WARNING:Xst:737 - Found 1-bit latch for signal <addDelta>. Latches may be generated from incomplete case or if statements. We do not recommend the use of latches in FPGA/CPLD designs, as they may lead to timing problems.
WARNING:Xst:737 - Found 1-bit latch for signal <decDelta>. Latches may be generated from incomplete case or if statements. We do not recommend the use of latches in FPGA/CPLD designs, as they may lead to timing problems.
WARNING:Xst:737 - Found 1-bit latch for signal <loadDelta>. Latches may be generated from incomplete case or if statements. We do not recommend the use of latches in FPGA/CPLD designs, as they may lead to timing problems.
WARNING:Xst:1294 - Latch <loadDelta> is equivalent to a wire in block <ModuleName>.
WARNING:Xst:1294 - Latch <decDelta> is equivalent to a wire in block <ModuleName>.
WARNING:Xst:1294 - Latch <addDelta> is equivalent to a wire in block <ModuleName>.

我知道不完整的 if 语句会导致闩锁。为了尝试解决这个问题,我尝试了 2 种不同的实现,但它们没有删除警告。我对 "decDelta" 案例特别困惑,因为我不明白我在这个条件语句中没有考虑到什么。

尝试#1

always @(posedge clk) begin
  if (loadSquare)
     square = 9'h1;
  else
     square = square;

  if (loadDelta)
     delta = 9'h3;
  else
     delta = delta;

  //... and so on

试试#2

always @(posedge clk) begin
  square = square;
  delta = delta;

  if (loadSquare)
     square = 9'h1;

  if (loadDelta)
     delta = 9'h3;

  //... and so on

当我 运行 模拟时,代码按预期工作,但我想更多地了解导致这些警告的原因。

当一个变量必须保留其先前的值时,如果它未分配一个始终块中的值,则推断出锁存器.必须创建一个锁存器来存储这个当前值。

闩锁 会导致各种竞争条件。不需要的锁存器会在 组合电路 中产生反馈,即将输出路由回输入 - 这可能是不可预测的,从而导致不稳定的电路行为。

不完整 if-else 语句将生成不需要的闩锁。如果没有为所有可能的输入条件定义条件之一,则 if-else 语句被视为 "incomplete"。同样,不完整的 case 语句,即没有 default 语句的语句也可以推断为 latch.

A complete if-else语句引用了以下Mux:

不完整 if-else指的是从输出到输入的反馈路径,为了保持 以前的值。类似适用于 case 语句。

通常,必须避免组合循环:

A general intention of a combinational circuit is that the output is a function of input only and the circuit should not contain any internal state (i.e., memory).

作为一个矛盾,verilog标准规定一个变量必须retain/hold它的前一个值如果它没有被赋值始终块中的值。这是闩创建的根本原因。

避免闩锁,必须牢记以下几点:

  • 包括所有ifcase语句的分支。
  • 每个分支中的每个输出信号赋值。

在这里,为了避免创建锁存器,您也可以分支并显式 分配所有输出变量,以便其他输入 grounded.

if (loadSquare)
     square <= 9'h1;  //used on initialization
else
     square <= 9'h0;  // similar for all the variables

另一个备选方案是在每个时钟滴答时分配一个默认值。

always @ (posedge clk)
begin
square <= 9'h0;    // similar for all the variables
if (loadSquare)
     square <= 9'h1;  //used on initialization
end

旁注:我在这里使用了非阻塞赋值语句,用于正确的触发器合成。

有关详细的综合信息,请参阅 FPGA prototyping by Verilog examples by Pong P. Chu pdf. Also, this and this 有关闩锁创建的链接可能会有用。

图片礼貌 doulous.com.

锁存器是由状态机逻辑引起的。下面的 always 块对 currentState 而不是时钟敏感。这不是 坏事 ,但需要一些额外的预防措施来阻止 latch 创建:

  1. 使用 default 案例或
  2. 使用默认分配或
  3. 使用不同的 FSM 模式

这是你的代码,我使用默认赋值进行了添加:

//Finite State Machine
always @(currentState) begin
    // default assignments
    {loadSquare, loadDelta} = 2'b0;
    {addSquare, addDelta, decDelta} = 3'h0;        

    case(currentState)
      2'h0:   begin       //initialize values, wait for start
        {loadSquare, loadDelta} = 2'b11;
        {addSquare, addDelta, decDelta} = 3'h0;
      end
      2'h1: begin
        {loadSquare, loadDelta} = 2'b00;
        {addSquare, addDelta, decDelta} = 3'b110;  //add square and delta
      end
      2'h2: begin
        {loadSquare, loadDelta} = 2'b00;
        {addSquare, addDelta, decDelta} = 3'b001;  //decrement delta, wait for reset
      end
      default: ; // unused
    endcase

//Next state logic implemented on negedge clk (not shown)

有关闩锁创建的更多信息,请查看@sharvil111 的回答。他以更笼统的方式解决了这个话题。

锁存器是一个基本的存储元件,它是打开或关闭的,即它是电平敏感的。触发器基本上是两个锁存器,其中一个在使能信号的反相上运行,这使得它对边沿敏感。

当使用 always @(posedge clk) 时,您暗示了一个在 clk 的上升沿加载数据值的触发器。闩锁不会隐含在此过程中 (always @(posedge clk))。

正如 Sharvil111 所描述的那样,当您在组合部分(即 always @* 进程)中留下未定义的状态时,闩锁是隐含的。如果某些东西在条件的一部分中未定义,那么它会保留其值。值保留是状态,并且由于组合部分对边缘不敏感,您已强制工具插入闩锁。

为避免这种情况,请完全定义条件输出:

always @(currentState) begin
case(currentState)
    2'h0:   begin       //initialize values, wait for start
    {loadSquare, loadDelta} = 2'b11;
    {addSquare, addDelta, decDelta} = 3'h0;
 end
    2'h1: begin
    {loadSquare, loadDelta} = 2'b00;
    {addSquare, addDelta, decDelta} = 3'b110;  //add square and delta
 end
    2'h2: begin
    {loadSquare, loadDelta} = 2'b00;
    {addSquare, addDelta, decDelta} = 3'b001;  //decrement delta, wait for reset
 end
    default: begin
    {loadSquare, loadDelta} = 2'b00;
    {addSquare, addDelta, decDelta} = 3'b000;
    end
endcase

当组合块中的变量未在块功能的所有可能排列中分配值时,推断为锁存器。

case(currentState)
2'h0:   begin       //initialize values, wait for start
  {loadSquare, loadDelta} = 2'b11;
  {addSquare, addDelta, decDelta} = 3'h0;
end
2'h1: begin
  {loadSquare, loadDelta} = 2'b00;
  {addSquare, addDelta, decDelta} = 3'b110;  //add square and delta
end
2'h2: begin
  {loadSquare, loadDelta} = 2'b00;
  {addSquare, addDelta, decDelta} = 3'b001;  //decrement delta, wait for reset
end
default:  ; // unused // <-- not assigned so assumed keep; inferred latches
endcase
...

做类似addSquare = addSquare;的事情仍然是一个推断闩锁。 addSquare(以及所有其他变量)需要分配给常数、触发器(边沿敏感触发器)或常数和触发器值的组合函数项。

如果您确实不需要 addSquare(以及所有其他变量),那么只需将它们分配给 default 条件中的常量即可。

如果确实需要保留该值,则需要添加一个同步分配给变量的触发器。在default条件下,变量需要赋值给翻牌。示例:

always @(posedge clk) begin
  ...
  addSquare_keep <= addSquare;
  ...
end
always @* begin
  ...
  case(currentState)
  ...
  default : begin
    ...
    addSquare = addSquare_keep;
    ...
  end
  endcase
  ...
end