【问题标题】:verilog always, begin and end evaluationverilog 总是,开始和结束评估
【发布时间】:2012-01-14 22:29:25
【问题描述】:

我正在尝试使用 Pong P. Chu 的书来学习 Verilog。我有一个关于如何评估和实施 always 块的问题。作者代码中的一种风格让我感到困惑。

在这个例子中,他用两个输出寄存器“y1”和“y2”编写了一个 FSM。我感到困惑的部分是 NEXT STATE LOGIC AND OUTPUT LOGIC 始终阻塞,在 begin 语句之后,always@* y1 和 y0 设置为 0。我似乎无论状态如何, y1 和 y0 都会切换到 0在每个时钟周期和信号变化。根据书中的状态图 reg y1 在状态 0 或 1 时应该等于 1。

那么 y1 每个时钟周期都会切换到 0,然后返回到当前状态下的值吗?我认为情况并非如此,我只是对如何评估块感到困惑。有人可以解释这部分代码在做什么。我迷路了。谢谢

module fsm_eg_2_seg
    (
     input wire clk, reset, a, b,
     output reg y0, y1
    );

    //STATE DECLARATION
    localparam [1:0]    s0 =2'b00, 
                    s1=2'b01, 
                    s2=2'b10;

    // SIGNAL DECLARATION
    reg [1:0] state_reg, state_next ;

    //STATE REGISTER
    always @(posedge clk, posedge reset)
        if (reset)
            state_reg <= s0;
        else
            state_reg <= state_next;

    //NEXT STATE LOGIC AND OUTPUT LOGIC
    always @*
    begin
        state_next = state_reg; // default next state: the same
        y1 = 1'b0;              // default output:  0
        y0 = 1'b0;              // default output:  0
        case (state_reg)
            s0:  begin
                y1 = 1'b1;
                if (a)
                    if(b)
                        begin
                            state_next = s2;
                            y0 = 1'b1;
                        end
                    else
                        state_next = s1;
                end
            s1:  begin
                    y1 = 1'b1;
                    if (a) 
                        state_next = s0;
                    end
            s2: state_next = s0;
            default: state_next = s0;
        endcase
    end
endmodule

【问题讨论】:

    标签: verilog


    【解决方案1】:

    表达式

    always @* begin : name_of_my_combinational_logic_block
        // code
    end
    

    描述组合逻辑。通常 clk 和 rst 信号不会从这种类型的 always 块内部读取,因此它们不会像 wisemonkey 所说的那样出现在敏感度列表中。最佳实践是使用 @* 作为组合逻辑的敏感度列表,这样您就不会忘记包含一个信号,这会推断出一些记忆并且不再是组合逻辑。

    在组合逻辑块中,您应该使用所谓的阻塞分配。这些看起来像大多数编程语言中的常规变量赋值,并使用单个等号。您分配给组合逻辑块内的变量(reg)的值立即相对于同一组合逻辑块中的其他语句和表达式发生,但不会传播到此组合逻辑之外阻止,直到你到达终点。 always 块必须在块外看到任何更改之前到达末尾。 Paul S 是对的,您希望在执行 always 块时始终将 something 分配给您的变量,否则您将推断出内存。

    【讨论】:

    • 我原本是想回复你这个评论。但我想我明白了。不是要打死马,而是要确保我理解这种始终阻塞:它推断出组合逻辑?那么always中的case语句会推断出一个连续“执行”的电路吗?还是只有在 always 块中的输入发生变化时才“执行”case 语句?
    • 推断的硬件是组合逻辑,连续执行。然而,组合逻辑的输出只有在输入发生变化时才会发生变化,因此在实践中,始终只在输入发生变化时才执行是完全可以的。不幸的是,Verilog 能够描述非硬件的东西,所以像我们这样的人需要格外小心。在我看来,Pong P. Chu 做得很好,没有展示 Verilog 的所有无用功能,而是专注于实际制作硬件所需的内容:组合逻辑块和时序逻辑块。
    • 是的。楚的书还不错。我喜欢这种编码风格,它很好地解释了电路,但它不是一本关于 Verilog 语言的书。他在书中的示例代码运行良好。现在我试图理解它们为什么起作用!非常感谢。
    • @FrankDejay:这在很多层面上都是错误的和/或误导性的——你不应该接受它作为答案,因为它只会让其他查找这个问题的人感到困惑。搜索“设置组合逻辑的默认值”以了解此处发生的情况。
    【解决方案2】:

    不得不说我不同意 aqua。他(和 wisemonkey)关于@* 的说法是正确的,但其余的都是错误的。

    这两行与空闲状态无关。这些陈述作为良好的编码实践存在。它们确保在评估 always 块时始终将这两个输出分配给。让我们看看为什么这很重要:

    • 想象一下,这两个语句不存在。
    • 接下来假设state_reg = S0a = b = 0
    • 在评估 always 块时,我们输入 case 语句 s0 half,并将 1 分配给 y1
    • a 为零,所以我们不输入 if 语句,我们退出 case,结束块

    y1 == 1y0 == ... 块的末尾,等等,y0 得到了什么?我想它必须保持它的旧值。它没有得到一个新的。

    这意味着y0 可能必须从一个周期到下一个周期记住它的值。这意味着它需要涉及某种内存,例如寄存器或锁存器。在这种情况下,它将是一个锁存器,因为它的编写方式有时会驱动输出,有时会保持输出。

    ...但我们不希望这样。 y1y0 本来是简单的电线。因此,无论状态或输入是什么,我们必须确保它们中的每一个总是被分配到。我们可以通过在逻辑的所有分支中进行分配来做到这一点,但这会变成很多工作。或者,我们可以有一个默认分配,如果需要,我们稍后会覆盖它。

    这些语句没有在s0s1 中引入y10 的原因是因为在always 块中发生的所有事情都没有时间流逝。在顶部分配的0s0s1 中的1 之间没有时间流逝。可见的只是最终状态。

    您会注意到代码对状态变量的作用完全相同。它有一个默认分配下一个状态是当前状态,然后覆盖它满足正确的条件。

    漂亮干净的状态机。没有错。

    【讨论】:

    • 不是为了打死马,而是为了确保我理解这种总是阻塞:它推断组合逻辑?那么 always 中的 case 语句会推断出一个连续“执行”的电路吗?还是只有在 always 块中的输入发生变化时才“执行”case 语句?
    • 同时声明了 y0 和 y1
    • 顺便说一句,y1 和 y0 的声明是 output reg y0, y1。这会改变你的解释吗?哦。 ** always @* ** 是否仅表示在 always 块中声明的输入,或者它是否还指在 always 块中的任何信号,无论它是否被声明为输入、输出或 reg?谢谢。
    • @FrankDejay:它推断组合逻辑,因为在所有情况下都分配了一个变量(所以没有锁存器),我们没有使用像时钟边沿这样的事件来说明代码应该何时运行(所以没有寄存器)。整个 always 块(不仅仅是 case 语句)只要输入的任何更改(@*)都会执行,这相当于它连续执行。将其读作“总是,当 * 发生变化时,这样做”
    • @FrankDejay:在这种情况下,y0 和 y1 的声明并不重要,不要被“reg”所迷惑。这并不意味着 y0 和 y1 将是寄存器。它们是否是寄存器完全取决于如何编写代码来为它们分配值。
    【解决方案3】:

    这是一个糟糕的 FSM 示例。我对你感到困惑并不感到惊讶。据我了解,always 块计划仅在其敏感度列表中的输入发生变化时运行。

    所以对于第一个always 块,它被安排运行从0 到1 的每个时钟转换,并且reset 是异步的。

    第二个 always 块具有@* 符号,它基本上根据块​​内的逻辑为您创建一个敏感度列表。回想一下,只有 输入 在敏感度列表中很重要。因此,如果abstate_reg 发生变化,则会安排此always 块。

    在本例中,

        y1 = 1'b0;              // default output:  0
        y0 = 1'b0;              // default output:  0
    

    正在尝试模拟 IDLE 状态,即 FSM 输出 0 的状态。如果您快速研究 FSM 的运行方式,您会发现一旦它开始通过状态,(案例陈述)它不会回来。

    理想情况下,您希望您的 IDLE 信息处于自己的状态,而不是漂浮在状态逻辑之外,但我想这只是一个简单的例子。

    【讨论】:

    • 谢谢。我不确定它是否与您的答案相关,但 clk(clock) 也是输入线,所以它不会在 @* 的敏感度列表中吗?所以我在想,每次它得到一个时钟脉冲时,总是会再次评估这个块。此外,由于语句 y1 = 1'b0 和 y2 = 1'b0 在 case 语句之前,它似乎会被评估。这是作者在整本书中使用的风格,他在 NEXT STATE LOGIC 总是阻塞的开头将寄存器分配给它们的默认值,通常带有 @* 敏感度列表。我可以给你看其他例子吗?
    • @FrankDejay 正是 clk 是一个输入。但是,如果您查看 always @* 块,则 clk 从未被读取(或从未在表达式的右侧使用)。 @* in always @* 包括在编译/合成时在敏感度列表中读取的所有内容(RHS),所以基本上即使 clk 是输入,只是因为它从未在该块中被读取,它不会影响其中任何一个表达式。
    • @wisemonkey 哇。我对这个误会很久了。我的书似乎说 @* 包括模块中的所有信号。我没有意识到它只在 always 块中包含信号。更有意义。非常感谢大家!
    • 嗨@FrankDejay,wisemonkey 是对的。我还要补充一点,您可以了解 Verilog 如何安排事件。虽然 Verilog 是一种编程语言,但它对硬件进行建模,因此 Verilog 程序与过程式 C 程序非常不同。祝你好运。
    【解决方案4】:

    我不认为其他答案直接且正确地解决了 y0 和 y1 是否在每个时钟周期切换到 0 并返回的问题。

    假设状态机从 s0 变为 s1。在这两种状态下,y1 的最终值都是 1,但在重新评估 always 块时,首先将 y1 分配为 0。这种切换可能在每个时钟发生多次,或者根本不会在一个时钟周期发生,具体取决于 a、b 和state_reg 更改。这种切换是否传播到连接到输出 y1 的导线取决于模拟器。端口分配在 Verilog 中被视为连续分配,它们是单独运行的执行线程。在 y1=0 分配完成后,模拟器暂停执行 always 块是完全合法的,将 0 分配给连接到输出 y1 的线,然后恢复执行 always 块。实际上,如果采用良好的编码风格并不重要,因为直到下一个时钟周期,y1 的值才会被锁存到任何寄存器中,在所有切换完成并且 y1 的最终值可用之后很久。

    在模拟中,切换发生在零时间,但它也发生在真实硬件中,当多个输入发生变化时。构建不会像这样“故障”的逻辑需要特殊的设计实践。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-09-17
      • 1970-01-01
      • 2016-04-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多