在Verilog中混合使用阻塞和非阻塞赋值(或者不混用!)

6
我正在Verilog中实现一个简单的序列化器,但我不理解阻塞赋值的细微差别。我特别难以理解这个答案的一部分。"然而,您永远不应该使用阻塞分配进行同步通信,因为这是不确定性的。"
我正在构建一个块,它以以下内容作为输入:
- 一个位时钟 - 一个5位并行数据输入(要序列化的值) - 一个“数据有效”信号,表示存在有效的5位数据
作为输出,我有:
- 串行数据输出 - 一个“完成”信号,表示已经到了新的5位值的时间 - 一个“传输”信号,当总线上有有效的串行数据时高电平
每当数据有效变为高电平时,该块就开始输出5位值,从下一个位时钟上升沿开始,每次输出一个位。当最后一个位在导线上时,该块发出“完成”信号,以便提供新的5位值。
省略一些重置逻辑,执行此操作的代码如下:
always @ (posedge clk) begin
    if(shiftIndex == 0) begin
        if(dataValid == 1) transmitting = 1; //Blocking assign
        else transmitting = 0; //Blocking assign
    end

   //Need the blocking assign up above to get this part to run
   //for the 1st bit
   if(transmitting == 1) begin
       shiftIndex <= shiftIndex + 1;
       dataOut <= data5b[shiftIndex];

       if(shiftIndex == 4) begin
           complete <= 1;
           shiftIndex <= 0;
       end
       else begin
           complete <= 0;
       end
   end
end

现在,我可以用所有非阻塞分配来编写块,但我觉得这会影响可读性。它看起来可能像这样:

always @ (posedge clk) begin
    if(shiftIndex == 0) begin
        if(dataValid == 1) begin
            transmitting <= 1; //Non-blocking now
            shiftIndex <= shiftIndex + 1;  //Duplicated code
            dataOut <= data5b[shiftIndex]; //Duplicated code
            complete <= 0;                 //Duplicated code
        end
        else transmitting <= 0;
    end

   //Now, this only runs for the 2nd, 3rd, 4th, and 5th bit.
   else if(transmitting == 1) begin
       shiftIndex <= shiftIndex + 1;
       dataOut <= data5b[shiftIndex];

       if(shiftIndex == 4) begin
           complete <= 1;
           shiftIndex <= 0;
       end
       else begin
           complete <= 0;
       end
   end
end

两者在模拟中都能实现我想要的效果,我更喜欢第一个,因为它对我来说更易于阅读。但由于我不明白为什么同步通信需要使用阻塞分配,这是不确定的,我担心我编写了一颗定时炸弹。

问题:在第一个代码中是否存在错误,当我尝试合成时会爆炸?第二个代码是否更可取,尽管有点难(对我而言)阅读?还有些第三件事我应该做吗?


1
如果您使用阻塞赋值=设置变量,并在同一周期内使用新值,则该值不能使用触发器生成,因此是组合逻辑,而不是同步逻辑。 - Morgan
@Morgan 是的,我一直在阅读这种智慧的变体。问题是,无论我在哪里读到它,它总是差一句话就结束了。接下来的“因此,x件糟糕的事情会发生”的句子是什么?如果“传输”具有组合特性,会发生什么不好的事情呢? - Pete Baughman
添加了更详细的回答作为答案,如果这不是问题的答案,请告诉我。 - Morgan
您IP地址为143.198.54.68,由于运营成本限制,当前对于免费用户的使用频率限制为每个IP每72小时10次对话,如需解除限制,请点击左下角设置图标按钮(手机用户先点击左上角菜单按钮)。 - EpicPandaForce
2个回答

5
使用阻塞(=)赋值时,该值可在下一行代码中使用。这意味着它是组合逻辑而不是来自触发器的驱动。
在仿真中,它看起来像是从触发器驱动的,因为块仅在正时钟边沿上评估,在现实中并非如此,可能会破坏接口。
我属于那种从不混合风格的派别,因为这可能在代码审查和重构中成为问题。如果模块需要输出新信号并且已经存在,则可以将其更改为输出。乍一看,它看起来像是一个触发器,因为它在一个always @(posedge clk块中。
因此,我建议不要混合使用风格,而是将组合部分拉出来放到自己的块中。这是否仍然符合您的要求?如果不符合,则可能会有问题。
我不知道数据有效性是如何控制的,但它可能会改变传输输出,潜在地传输也可能会产生故障,因为它是从组合解码而来的,而不是干净地从触发器驱动。接收接口可能是异步的,毛刺可能会导致死机等问题。
always @* begin
  if(shiftIndex == 0) begin
    if(dataValid == 1) transmitting = 1; //Blocking assign
    else transmitting = 0; //Blocking assign
  end
end

always @ (posedge clk) begin
  if(transmitting == 1) begin
    shiftIndex <= shiftIndex + 1;
    dataOut    <= data5b[shiftIndex];

   if(shiftIndex == 4) begin
       complete   <= 1;
       shiftIndex <= 0;
   end
   else begin
       complete   <= 0;
   end
  end
end

1
由于always @*中的transmitting未被始终赋值,因此会创建一个锁存器。 - Ari

2
就正确性而言,混合使用阻塞和非阻塞赋值没有问题,但是您需要清楚地了解哪个信号是顺序的,哪个信号是组合块(请注意,该组合块的输入要么来自其他顺序块,要么来自主要输入)。此外,您需要决定设计中是否需要任何锁存器。
如果您对不希望成为顺序的变量使用阻塞赋值,请确保始终将其分配给它,否则,它可能会被解释为顺序元素。
在您的第一个代码中,当(shiftIndex != 0)时,您没有分配给transmitting。这意味着应该使用transmitting的先前值当(shiftIndex != 0),因此它将成为一个顺序元素。但是您需要在当前时钟中使用它的值,因此您使用了阻塞赋值。
以下是另一种版本的代码,其中对于第一个比特使用firstBit_comb,并且始终被分配。
always @ (posedge clk) begin
    //Default value to avoid sequentials. Will be overwritten later if necessary
    firstBit_comb = 0;
    if(shiftIndex == 0) begin
        if(dataValid == 1) begin 
            transmitting_seq <= 1;
            firstBit_comb = 1;
        end
        else begin
         transmitting_seq <= 0; 
         firstBit_comb = 1;
        end
    end

   //Need the blocking assign up above to get this part to run
   //for the 1st bit
   if(firstBit_comb || transmitting_seq) begin
       shiftIndex <= shiftIndex + 1;
       dataOut <= data5b[shiftIndex];

       if(shiftIndex == 4) begin
           complete <= 1;
           shiftIndex <= 0;
       end
       else begin
           complete <= 0;
       end
   end
end

如果你将顺序和组合块分开,那么就更清晰明了。请注意,你的顺序元素的下一个状态通常是组合块的输出。

//combinational block
always_comb
begin
    //The default value of next state is the previous state
    transmitting_next = transmitting_seq;

    //The default value of firstBit_comb=0. It would be overwritten if necessary
    firstBit_comb = 0; 

    if(shiftIndex == 0 && dataValid == 1) begin
        firstBit_comb = 1;
        transmitting_next = 1;
    end
    else begin
        firstBit_comb = 0;
        transmitting_next = 0;
    end

end

//Sequential block
always @ (posedge clk) begin
    //update transmitting_seq with its next state
    transmitting_seq <= transmitting_next; 
   if(firstBit_comb || transmitting_seq) begin
       shiftIndex <= shiftIndex + 1;
       dataOut <= data5b[shiftIndex];

       if(shiftIndex == 4) begin
           complete <= 1;
           shiftIndex <= 0;
       end
       else begin
           complete <= 0;
       end
   end
end

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接