在 Verilog 中混合阻塞和非阻塞分配(或不!)
Mixing blocking and non-blocking assign in Verilog (or not!)
我正在 Verilog 中实现一个简单的序列化程序,但我不明白什么时候阻塞分配会导致问题的细微差别。我特别难以理解 this answer 的一部分。 "However, you should never use blocking assignments for synchronous communication, as this is nondeterministic."
我正在构建一个块,作为输入:
- 一个位时钟
- 一个5位并行数据输入(要序列化的值)
- 指示有效 5 位数据存在的 "Data valid" 信号
作为输出,我有:
- 串行数据输出
- 一个 "Complete" 信号,表明是时候输入新的 5 位值了
- 一个 "Transmitting" 信号,只要有有效的串行数据在总线上输出就为高电平
每当数据有效变高时,该块开始输出 5 位值,一次一位,从位时钟的下一个上升沿开始。当最后一位在线路上输出时,块发出信号 "complete" 因此可以提供一个新的 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
两者在模拟中似乎都能满足我的要求,我更喜欢第一个,因为它更容易阅读,但由于我不明白为什么使用阻塞分配进行同步通信是不确定的,所以我担心我编写了一个定时炸弹
问题:我是不是在第一个代码中做错了什么,当我尝试合成这个代码时会崩溃?尽管第二个代码更难阅读(无论如何对我而言),但第二个代码是否更可取?我应该做的第三件事是什么?
使用阻塞 (=
) 赋值时,该值可用于下一行代码。这意味着它是组合的,而不是由触发器驱动的。
在模拟中,它看起来像是从触发器驱动的,因为该块仅在正时钟边沿上进行评估,实际上并非如此,这可能会破坏接口。
我属于永远不要混合样式的派系,因为这可能是代码审查和重构中的问题。重构,如果一个模块需要输出一个新信号,并且可以看到它已经存在,那么它们只需更改为输出即可。乍一看像是触发器,因为它在 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
就正确性而言,混合阻塞和非阻塞赋值没有问题,但你需要清楚地了解哪个信号是顺序的,哪个信号是组合块(注意输入的此组合块来自其他顺序块或主要输入)。此外,您需要决定是否要在您的设计中使用任何锁存器。
如果你对一个你不想顺序的变量使用阻塞赋值,确保总是分配给它,否则,它可能被解释为顺序元素.
在您的第一个代码中,您没有在 (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
我正在 Verilog 中实现一个简单的序列化程序,但我不明白什么时候阻塞分配会导致问题的细微差别。我特别难以理解 this answer 的一部分。 "However, you should never use blocking assignments for synchronous communication, as this is nondeterministic."
我正在构建一个块,作为输入:
- 一个位时钟
- 一个5位并行数据输入(要序列化的值)
- 指示有效 5 位数据存在的 "Data valid" 信号
作为输出,我有:
- 串行数据输出
- 一个 "Complete" 信号,表明是时候输入新的 5 位值了
- 一个 "Transmitting" 信号,只要有有效的串行数据在总线上输出就为高电平
每当数据有效变高时,该块开始输出 5 位值,一次一位,从位时钟的下一个上升沿开始。当最后一位在线路上输出时,块发出信号 "complete" 因此可以提供一个新的 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
两者在模拟中似乎都能满足我的要求,我更喜欢第一个,因为它更容易阅读,但由于我不明白为什么使用阻塞分配进行同步通信是不确定的,所以我担心我编写了一个定时炸弹
问题:我是不是在第一个代码中做错了什么,当我尝试合成这个代码时会崩溃?尽管第二个代码更难阅读(无论如何对我而言),但第二个代码是否更可取?我应该做的第三件事是什么?
使用阻塞 (=
) 赋值时,该值可用于下一行代码。这意味着它是组合的,而不是由触发器驱动的。
在模拟中,它看起来像是从触发器驱动的,因为该块仅在正时钟边沿上进行评估,实际上并非如此,这可能会破坏接口。
我属于永远不要混合样式的派系,因为这可能是代码审查和重构中的问题。重构,如果一个模块需要输出一个新信号,并且可以看到它已经存在,那么它们只需更改为输出即可。乍一看像是触发器,因为它在 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
就正确性而言,混合阻塞和非阻塞赋值没有问题,但你需要清楚地了解哪个信号是顺序的,哪个信号是组合块(注意输入的此组合块来自其他顺序块或主要输入)。此外,您需要决定是否要在您的设计中使用任何锁存器。
如果你对一个你不想顺序的变量使用阻塞赋值,确保总是分配给它,否则,它可能被解释为顺序元素.
在您的第一个代码中,您没有在 (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