我对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
答案 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
最佳实践:
<=
)分配触发器和锁存器。
=
)分配组合逻辑。
always_comb
块)和预期的锁存器(always_latch
块)。 assign pwm = (compare >= ctr);
会在接收pwm
作为输入的下游模块中添加额外的延迟和噪声(合成后)。作为翻牌,输出信号是干净的。一些设计师将#3提升到更高的纯度,并将翻牌的所有计算移动到组合块中。顺序块(始终使用时钟块)简化为简单分配和复位(有时设置)逻辑。这样做的好处:
唯一的缺点是在小模块中多了几行。在典型项目中为统一编码风格支付的价格可以忽略不计。
我不希望直接将触发器分配给输出线(例如:assign pwm = pwm_q;
)。我喜欢使用它的唯一情况是它使连接更容易。当然,作者希望保持_d
(翻牌输入)_q
(翻牌输出)编码风格。我个人本可以使用简单的pwm
(和ctr
)作为翻牌输出名称,next
或ns
作为后缀或前缀。但这只是编码风格的差异。
线材输出方法确实有一个优点。如果两个模块碰巧驱动pwm
,那么当值发生冲突时,您将看到一个X.作为output reg
X仅在父模块中可见。 Linting和综合将为此提供警告或错误。它只是verilog模拟中的视觉检查。
如果你真的想在模拟时保证一个驱动器一个触发器输出,那么最好启用SystemVerilog并使用output logic pwm
并在pwm
块中分配always_ff
。 always_ff
,always_comb
和always_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_d
或ctr_q
发生变化,就会对compare
进行评估。这只是比较器输出,同样不需要存储元件。对于pwm_d
和ctr_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_d
和pwm_d
即可。在代码中,在时钟边缘,您开始计算ctr_q
。然后你比较,最后pwm_q
被改变了。因此在第二个代码中,在时钟边沿之后,在ctr = ctr+1'b1
改变之前将出现加法器延迟和比较器延迟。