为什么不直接将更改写入输出寄存器?

时间:2015-12-19 01:25:43

标签: verilog

我对verilog很新。我找到了这个pwm代码:

module pwm #(parameter CTR_LEN = 8) (
 input clk,
 input rst,
 input [CTR_LEN - 1 : 0] compare,
 output pwm
);

reg pwm_d, pwm_q;
reg [CTR_LEN - 1: 0] ctr_d, ctr_q;

assign pwm = pwm_q;

always @(*) begin
 ctr_d = ctr_q + 1'b1;

 if (compare > ctr_q)
  pwm_d = 1'b1;
 else
  pwm_d = 1'b0;
end

always @(posedge clk) begin
 if (rst) begin
  ctr_q <= 1'b0;
 end else begin
  ctr_q <= ctr_d;
 end

 pwm_q <= pwm_d;
end

endmodule

(来源:embeddedmicro.com)

我不明白为什么总有两个街区。为什么加倍注册? 为什么不只有一个计数器和pwm寄存器,只有posedge块并直接操作寄存器?像那样:

module pwm #(parameter CTR_LEN = 8) (
 input clk,
 input rst,
 input [CTR_LEN - 1 : 0] compare,
 output pwm
);

reg pwm;
reg [CTR_LEN - 1: 0] ctr;

always @(posedge clk) begin
 if (rst) begin
  ctr = 1'b0;
 end else begin
  ctr = ctr + 1'b1;
 end

 if (compare > ctr)
  pwm = 1'b1;
 else
  pwm = 1'b0;
end

endmodule

2 个答案:

答案 0 :(得分:4)

它是部分编码风格偏好,部分编码风格最佳实践,以及合成器将如何优化的一些方式 你可以把它写成一个总是阻塞如下(与你提供的不同):

module pwm #(parameter CTR_LEN = 8) (
 input clk,
 input rst,
 input [CTR_LEN - 1 : 0] compare,
 output reg pwm // Note the 'reg'
);

//reg pwm; // You cannot define pwm on separate lines as output and reg with ANSI style
reg [CTR_LEN - 1: 0] ctr;

always @(posedge clk) begin
 if (rst) begin
  ctr <= 1'b0; // use non-blocking ('<='), instead of blocking ('=')
 end else begin
  ctr <= ctr + 1'b1; // use non-blocking ('<='), instead of blocking ('=')
 end

 if (compare > ctr) 
  pwm <= 1'b1; // use non-blocking
 else
  pwm <= 1'b0; // use non-blocking
end

endmodule

最佳实践:

  1. 使用非阻塞(<=)分配触发器和锁存器。
    • 这消除了Verilog仿真调度程序中的竞争条件,对合成没有影响。如果不使用非阻塞,那么仿真和电路之间的功能行为可能会有所不同。立即评估非阻塞,但是在相同戳记中完成所有操作之后才会应用新值。这意味着每次对翻牌进行采样时,它将始终是不在时钟之前的值,而不是新值。
  2. 使用阻止(=)分配组合逻辑。
    • 需要立即评估和更新组合逻辑。
  3. 纯粹的组合逻辑应该与触发器分开
    • 如果总是阻塞,则不完整的组合逻辑将转换为同步逻辑。这浪费了区域和翻牌,很难找到。
    • 如果两个总是阻塞,则不完整的组合逻辑将推断锁存器;通常是复杂的锁存器。锁存器不如触发器理想,但是linting工具,综合工具和逻辑等效检查工具通常会发出关于锁存器的警告。这些警告是一种寻找意外推断锁存器的方法 注意:SystemVerilog添加了一个关键字来标识预期的组合逻辑(always_comb块)和预期的锁存器(always_latch块)。
  4. 建议不要将具有重置/设置(特别是异步重置/设置)的触发器放在没有重置/设置的同一块块中。
    • 有些合成器将使用相同的触发器类型,并且set / reset定时为常量,这可能是浪费区域,因为这个触发器通常更大。
  5. 具有顺序逻辑(触发器)的模块的输出应该是触发器。
    • 这条规则与时间有关。预测模块中的组合延迟更容易 例如,assign pwm = (compare >= ctr);会在接收pwm作为输入的下游模块中添加额外的延迟和噪声(合成后)。作为翻牌,输出信号是干净的。
  6. 一些设计师将#3提升到更高的纯度,并将翻牌的所有计算移动到组合块中。顺序块(始终使用时钟块)简化为简单分配和复位(有时设置)逻辑。这样做的好处:

    1. 对于大型代码,这会减少代码总数。
    2. 它允许一种方式查看触发器当前值和下一个值,因为它们是单独的信号。
    3. 一些ASIC设计人员发现这种风格更容易将手动纠错应用于硅掩模。
    4. 进行逻辑优化的合成器倾向于使用两个始终块结构生成更好的结果。在尝试满足时间尺寸要求时,这一点至关重要。
    5. 唯一的缺点是在小模块中多了几行。在典型项目中为统一编码风格支付的价格可以忽略不计。

      我不希望直接将触发器分配给输出线(例如:assign pwm = pwm_q;)。我喜欢使用它的唯一情况是它使连接更容易。当然,作者希望保持_d(翻牌输入)_q(翻牌输出)编码风格。我个人本可以使用简单的pwm(和ctr)作为翻牌输出名称,nextns作为后缀或前缀。但这只是编码风格的差异。

      线材输出方法确实有一个优点。如果两个模块碰巧驱动pwm,那么当值发生冲突时,您将看到一个X.作为output reg X仅在父模块中可见。 Linting和综合将为此提供警告或错误。它只是verilog模拟中的视觉检查。

      如果你真的想在模拟时保证一个驱动器一个触发器输出,那么最好启用SystemVerilog并使用output logic pwm并在pwm块中分配always_ffalways_ffalways_combalways_latch在编译/精化时抛出错误,如果任何其他块也分配了那些受尊重的左侧值。

答案 1 :(得分:2)

为什么要双重注册?

首先,当你将某些东西声明为reg时,它不需要像硬件中的触发器或锁存器一样生成存储元件。如果可能,合成工具将使用组合登录。

always @(*) begin
 ctr_d = ctr_q + 1'b1;

 if (compare > ctr_q)
  pwm_d = 1'b1;
 else
  pwm_d = 1'b0;
end

在上面的代码中,always@(*)表示只要封闭表达式的任何输入发生更改,就立即对块进行求值。只要ctr_d更改,就会立即计算ctr_q。因此ctr_d只是加法器输出,不需要存储元素。同样,只要pwm_dctr_q发生变化,就会对compare进行评估。这只是比较器输出,同样不需要存储元件。对于pwm_dctr_d,合成工具将仅生成组合逻辑。

现在,如果您没有else的{​​{1}}部分,该工具将为其创建“推断锁存器”,以便在pwm_d语句为false时{ {1}}将保留其旧值。

如果您想要更明确,可以使用以下内容替换if块。

pwm_d

第一个代码更好。

在第一个代码中,在时钟边缘,always @(*)wire [CTR_LEN - 1: 0] ctr_d; wire pwd_d; assign ctr_d = ctr_q + 1'b1; assign pwm_d = (compare > ctr_q)? 1'b1 : 1'b0; // Ternary operator 都准备就绪,您只需将它们推送到ctr_dpwm_d即可。在代码中,在时钟边缘,您开始计算ctr_q。然后你比较,最后pwm_q被改变了。因此在第二个代码中,在时钟边沿之后,在ctr = ctr+1'b1改变之前将出现加法器延迟和比较器延迟。