1A、门电路的Verilog代码之与门。

1B、门电路的Verilog代码之或门。

1C、门电路的Verilog代码之非门----这次是一个4位数据的NOT操作!

2A、多路选择器的多种写法之一。

2B、多路选择器的多种写法之二。

2C、多路选择器的多种写法之三。

3、Mux的三种写法,RTL的认知大提升!
----Mux毫无疑问是一个组合逻辑电路,竟然可以使用Always来表达Mux的行为!!
----从根上破除了一个错误认知,以为always就是时序逻辑,不用always就是组合逻辑。
----实际上,用了always的电路可以是组合逻辑(如Mux)。不用always的电路也可能是时序逻辑!
----换一个说法,不适用always并不必然意味着组合逻辑电路。它仍然可以通过实例化其它时序模块来构成时序逻辑电路。
----判断一个电路时组合电路还是时序电路,最终要看它综合出的网表中是否包含存储元件(触发器/锁存器),而不是看代码中是否出现了always关键字。
----写RTL代码,不光要理解代码的含义,还要有一根弦就是它会被综合成实际的电路以及电路的行为,这就是所谓的硬件思维。
----Always电路可以时组合逻辑(如Mux)。但正如上面的2C中的RTL代码注释,如果always {begin...end}之间的case没有罗列完整且没有default的话,则代码会被综合为时序逻辑电路!罗列完整则被综合为组合逻辑电路!

----在不带时钟的always语句中,如果if-else的覆盖不完整,或者case的覆盖不完整且没有default兜底,则最终综合的结果是锁存器latch!!!
----在最终综合结果之前,也可以通过RTL Schematic来查看RTL对应的原理图,也是锁存器Latch!!
----从这个例子也可以看出,RTL代码不是纯逻辑,它是要综合成【标准单元(standard cell)】然后被制造出来的。RTL综合直到生成FPGA的位流文件,或者RTL综合直到生成ASIC的版图GDS文件,和纯RTL的仿真获取纯逻辑的结果,是不一样的。还是前面说的,要有所谓的硬件思维。硬件思维是怎么练出来呢,在芯片得知识里泡得久了就练出来了。
4、再总结提升:
使用always不必然意味着时序逻辑电路。 它也可以用来描述组合逻辑电路。
不使用always不必然意味着组合逻辑电路。 它仍然可以通过实例化其他时序模块来构成时序逻辑电路。
下面通过具体的例子详细解释这两个结论。
4-1. 使用 always 不一定是时序逻辑。
always本身只是一个过程块,它告诉综合工具:"这里的代码需要重复执行"。最终生成组合逻辑还是时序逻辑,关键在于敏感列表和块内的描述方式。
4-1-A. 用 always 描述组合逻辑。
当always块用于描述组合逻辑时,它必须是电平敏感的。这意味着每当输入信号的电平发生变化时,块内的逻辑就会重新计算。
现代推荐写法(Verilog-2001及以后):
// 使用 @(*) 自动推断所有输入信号,这是最安全、最推荐的组合逻辑写法
always @(*) begin
if (sel == 1'b1)
y = a;
else
y = b;
end
这里的 @(*) 告诉综合工具:"请自动找出这个always块中所有被读取的信号,并把它们都加入敏感列表"。这样,a, b, sel 中任何一个变化,y 的值都会被更新。这综合出来就是一个2选1的多路选择器,是纯粹的组合逻辑。
重要陷阱:不完整的条件赋值会产生锁存器
如果在用always描述组合逻辑时,if-else或case语句没有覆盖所有可能的分支,导致在某些条件下输出没有被赋值,综合工具就会认为你需要"记住"上一次的值,从而推断出一个锁存器。
// 错误示例:会产生锁存器
always @(*) begin
if (en) begin
y = d;
end
// 如果 en 为 0,y 没有被赋值,综合器会推断出锁存器来保持 y 的值
end
锁存器是时序元件,所以这个always块最终产生了时序逻辑,但这通常是设计失误。
4-1-B. 用 always 描述时序逻辑。
当always块用于描述时序逻辑时,它必须是边沿敏感的。这意味着它只在时钟信号的上升沿或下降沿(或复位信号的边沿)被触发。
// 典型的D触发器描述
always @(posedge clk) begin
q <= d; // 注意:时序逻辑推荐使用非阻塞赋值 <=
end
这里的 posedge clk 指定了只有在clk信号从0变到1的瞬间,q才会被赋值为d。在两个时钟沿之间,无论d如何变化,q都保持不变。这就是典型的时序逻辑,它具有"记忆"功能。
4-2. 不使用 always 不一定是组合逻辑
一个模块是否是时序电路,取决于它最终是否包含了存储元件(如触发器、锁存器)。always只是创建这些元件的一种方式,但不是唯一方式。
4-2-A. 通过实例化时序模块
你可以设计一个没有任何always语句的顶层模块,但它仍然是一个复杂的时序系统,因为它实例化了很多包含always的子模块。
// top_module.v - 这个模块里没有 always
module top_module (
input clk,
input rst_n,
input data_in,
output reg data_out
);
// 实例化一个已经存在的时序模块(例如,一个移位寄存器)
// my_shift_reg 内部肯定使用了 always @(posedge clk)
my_shift_reg u_shift_reg (
.clk (clk),
.rst_n (rst_n),
.din (data_in),
.dout (data_out)
);
endmodule
top_module本身没有always,但因为它包含了my_shift_reg这个时序模块,所以整个top_module的行为是时序的。
4-2-B. 使用门级原语构建
在非常底层的描述中,你可以用基本的门级原语(如nand, nor)来搭建一个锁存器或触发器,这同样不需要always语句。
// 用两个或非门搭建一个SR锁存器
module sr_latch_nor (output q, output q_bar, input s, input r);
nor #1 g1 (q, q_bar, s);
nor #1 g2 (q_bar, q, r);
endmodule
这个sr_latch_nor模块没有always,但它无疑是一个时序元件。
4-3. 再总结提升:
always是一个语法工具,而不是逻辑类型的定义符。 它既能生成组合逻辑,也能生成时序逻辑,关键在于你怎么用它(敏感列表和代码完整性)。
判断一个电路是组合还是时序,最终要看它综合出的网表中是否包含存储元件(触发器/锁存器),而不是看代码中是否出现了always关键字。
因此,理解Verilog代码背后的硬件意图,远比记住语法规则本身更重要。
<<<<<<<<完>>>>>>>>
文末附一副封面图。

摘要:本文系统阐述了Verilog硬件描述语言的关键概念,重点解析了组合逻辑与时序逻辑的本质区别。首先介绍了基本门电路(与门、或门、非门)的实现方法,然后通过多路选择器的三种写法展示了RTL设计的灵活性。核心观点指出:always语句既可描述组合逻辑(电平敏感),也可实现时序逻辑(边沿触发);而判断电路性质的关键在于综合后是否包含存储元件(触发器/锁存器),而非代码形式。特别强调硬件思维的重要性,提醒设计者注意不完整条件语句可能意外生成锁存器的问题,建议通过RTL原理图验证设计意图,避免常见的认知误区。