归约运算符无法正常工作

Reduction operator does not work properly

我有一个 FSM 设计,它使用计数器在特定状态内计数并保持在那里直到表达式 &counter 产生 TRUE,但是当它完成时(得到 1111...111 - 通过模拟器检查)&counter 永远不会 HIGH 并像无限循环一样停留在该状态。如何克服这个问题?我将提供代码的错误部分。我是 verilog 的新手,所以希望这是一个容易发现的问题。

reg [2:0] State, NextState;
reg [20:0] counter;
reg EN;

//State register 
always @(posedge Clk) begin 
    if(Rst) begin
        counter <= 0;
        State <= S0;
    end
    else begin
        State <= NextState;
        counter <= EN ? counter + 1 : 0;
    end
end

//Next State Logic
always @(State, COL)
    case(State)
        S0: begin 
            NextState <= S1;
            EN <= 0;
        end
        S1: begin 
            if(ROW) begin
                if(COL) begin 
                    if(&counter[18:0]) begin // <--- THIS EXP. NEVER GETS 1
                        PrevCOL <= COL;
                        NextState <= S3;
                        EN <= 0;
                    end
                    else begin
                        EN <= 1;
                        NextState <= S1;
                    end
                end
                else
                    NextState <= S2;
            end
            else
                NextState <= S0;
        end
        S2:  NextState <= S1;
        S3: begin
            if(PrevCOL == COL) begin
                if(&counter) begin // <-------- THIS EXPRESSION NEVER GETS 1
                    NextState <= S4;
                    EN <= 0;
                    case(ROW)
                        8: 
                            case(COL)
                                8: ID <= I0;
                                4: ID <= I1;
                                2: ID <= I2;
                                1: ID <= I3;
                            endcase
                        4:
                            case(COL)
                                8: ID <= I4;
                                4: ID <= I5;
                                2: ID <= I6;
                                1: ID <= I7;
                            endcase
                        2:
                            case(COL)
                                8: ID <= I8;
                                4: ID <= I9;
                                2: ID <= I10;
                                1: ID <= I11;
                            endcase
                        1:
                            case(COL)
                                8: ID <= I12;
                                4: ID <= I13;
                                2: ID <= I14;
                                1: ID <= I15;
                            endcase
                    endcase
                end
                else begin
                    NextState <= S3;
                    EN <= 1;
                end
            end
            else
                NextState <= S0;
        end
        S4: NextState <= S0;
    endcase

有问题的区域在评论中指出。为什么它不起作用?

谢谢!

对于组合逻辑,避免自己编写敏感列表。您应该替换为:

always @(State, COL)

always @(*)

您也可以使用 always_comb。基本上,当计数器递增时,您的第二个 always 块不会执行。它仅在 State 或 COL 信号发生变化时执行。

其次,您不应在组合逻辑中使用非阻塞赋值 (<=)。用阻塞赋值 (=).

替换第二个 "always" 块中的所有内容

正如 Convergent 指出的那样。 always @(State, COL)应该是always @*(与always @(*)同义)或者SystemVerilog的always_comb,组合块中应该使用阻塞赋值(=);非非阻塞 (<=).

另一个问题是 ENPrevCOLID 中推断出的复杂锁存器。当我说锁存器时,我指的是电平敏感(活动 high/low)锁存器。当我说触发器时,我指的是边沿触发触发器。

推断锁存器是当 reg 没有为 always 块中的所有可能路径分配值时。就其本身而言,锁存器本身并不是一件坏事,只要它是有意的,数据尊重建立和保持时间,并且使能引脚没有毛刺。通常,这些都不是必需的,并且被许多人认为是设计错误。 FPGA 通常比锁存器有更多的触发器和逻辑门(锁存器使形式意图逻辑门的时序比专用锁存器更差)。如果使用锁存器,最好尽可能简单,将其放在与解码逻辑分开的always块中,并使用非阻塞赋值。 Verilog 只有隐含锁存器; lint 和 LEC(逻辑等效检查)将针对可能的设计错误发出警告。 SystemVerilog 允许显式锁存; link 并且 LEC 不会发出警告。下面的例子。总之,只在需要时使用锁存器并保持简单。

// Verilog clean implicit latch
always @* begin
  if (lat_en)    // requirement: 'lat_en' must be glitch free
    lat <= data; // requirement: 'data' must respects setup/hold time
end
// SystemVerilog explicit latch
always_latch begin
  if (lat_en)    // requirement: 'lat_en' must be glitch free
    lat <= data; // requirement: 'data' must respects setup/hold time
end

推断的 复杂 闩锁比简单的闩锁更危险。复杂锁存器的启用引脚或数据基于其他组合逻辑或锁存器。很难满足时序要求并且经常出现故障。这意味着您的代码可以 运行 正常一段时间,然后意外地得到错误的数据。它们很难调试,因为时序 window 当合成和应用 SDF 注释时,故障可能会在更改时发生,并且可以根据其他变量再次更改 FPGA 或硅片。根据事件调度,RLT 模拟甚至可能在 0 时间发生故障。再次总结,仅在需要时使用锁存器并保持简单。

当谈到 RTL 编码风格时,我通常遵循 Cliff Cummings 的 2-always 块解决方案的建议(一个用于下一状态组合逻辑,另一个用于同步分配):

我的个人资料中还有 link 有用的 Verilog/SystemVerilog 参考资料。

我建议将 ENPrevCOLID 变成适当的触发器,并在组合块中计算分配的 next_*

//Synchronous Logic Assignments
always @(posedge Clk) begin // SV: use 'always_ff' instead of 'always'
  if(Rst) begin
    counter <= 0;
    State <= S0;
    EN <= 0;
    PrevCOL <= 0;
    ID <= 0;
  end
  else begin
    State <= NextState;
    counter <= next_EN ? counter + 1 : 0;
    EN <= next_EN;
    PrevCOL <= next_PrefCOL;
    ID <= next_ID;
  end
end

//Next State Logic Calculations
always @* begin // SV: use 'always_comb' instead of 'always @*'
  // Default value assignments
  next_EN = EN;
  next_PrevCOL = PrevCOL;
  next_ID = ID;

  // Update value logic
  case(State)
    // Update NextState and next_* values in here
  endcase
end

仅供参考 always @*,其中 RHS 只是常量,可能永远不会在模拟中执行。 LRM 未定义此条件,模拟器的某些创建者决定实施时间 0 执行,其他人则没有。它将正确合成。 SystemVerilog 的 always_comb 更好,因为它将在时间 0 执行。参见 IEEE Std 1800-2012 § 9.2.2.2.2 always_combalways @* 相比.使用 Verilog,您可以使用 assign 语句。