从真值表到硅片:一位芯片工程师的4位数值比较器门级设计实战手记
最近在带几个刚入行的新人做Verilog项目,发现一个挺普遍的现象:很多朋友对RTL行为级描述已经比较熟练了,但一提到门级设计,就有点发怵。他们总觉得门级描述是“老古董”,是综合工具该操心的事。这种想法其实忽略了一个关键点——真正理解门级设计,是你从“写代码的”变成“设计电路”的重要分水岭。
今天我就拿一个经典的4位数值比较器作为例子,完整走一遍从真值表分析、逻辑表达式推导,到最终门级实现和验证的全过程。这不仅仅是完成一道题目,更是想分享一种工程化的思维方式。你会发现,当你能用最基本的门电路搭出一个功能模块,并且清楚地知道每一个门在电路中的位置和作用时,你对数字电路的理解会完全不一样。
1. 理解需求:不只是功能,更是约束
拿到一个设计需求,第一步永远是仔细读题。题目要求我们用Verilog的门级描述方式实现一个4位数值比较器。这意味着什么?
注意:这里的“门级描述”是一个硬性约束。你不能使用
assign Y2 = (A > B)这样的行为级语句,也不能用always @(*)这样的过程块。你只能使用Verilog内置的基本门级原语,比如and、or、not、nand、nor、xor、xnor。这就像给你一盒乐高基础颗粒,让你拼出一辆汽车,不能用现成的轮子或车门模块。
端口定义很清晰:
- 输入:两个4位无符号数
A[3:0]和B[3:0] - 输出:三个1位信号
Y2(A>B)、Y1(A=B)、Y0(A<B)
功能表是设计的“圣经”。对于比较器,其逻辑是逐位比较的,从最高位(MSB)开始:
| A[3] vs B[3] | A[2] vs B[2] | A[1] vs B[1] | A[0] vs B[0] | Y2 (A>B) | Y1 (A=B) | Y0 (A<B) |
|---|---|---|---|---|---|---|
| A[3] > B[3] | x | x | x | 1 | 0 | 0 |
| A[3] < B[3] | x | x | x | 0 | 0 | 1 |
| A[3] = B[3] | A[2] > B[2] | x | x | 1 | 0 | 0 |
| A[3] = B[3] | A[2] < B[2] | x | x | 0 | 0 | 1 |
| A[3] = B[3] | A[2] = B[2] | A[1] > B[1] | x | 1 | 0 | 0 |
| ... | ... | ... | ... | ... | ... | ... |
| 全相等 | 全相等 | 全相等 | 全相等 | 0 | 1 | 0 |
这个表告诉我们,比较是“优先级”的。只要高位能分出大小,低位就不用看了。只有高位全部相等,才需要看下一位。这是设计的关键。
2. 从真值表到逻辑门:一次思维的体操
很多教程会直接给出最终的逻辑表达式,但我想带你一步步推出来。这个过程本身,就是锻炼你数字电路设计肌肉的最好方式。
我们先从最简单的1位比较器开始。设两个1位输入为 a 和 b,输出为 gt (a>b), eq (a=b), lt (a<b)。列出真值表:
| a | b | gt | eq | lt |
|---|---|---|---|---|
| 0 | 0 | 0 | 1 | 0 |
| 0 | 1 | 0 | 0 | 1 |
| 1 | 0 | 1 | 0 | 0 |
| 1 | 1 | 0 | 1 | 0 |
直接写出最小项表达式:
gt = a & ~beq = ~a & ~b | a & b = ~(a ^ b)(这就是同或XNOR)lt = ~a & b
看,1位比较器很简单。但问题来了,4位比较器不是四个1位比较器结果的简单组合。因为高位优先,我们需要把高位的比较结果“传递”下来。
让我们换个思路,用比较器最经典的“递归”或“级联”结构来思考。
想象一下,我们有一个模块,它能比较两个数,并且还能接收一个“低位相等”的信号。如果“低位相等”为真,它就需要认真比较当前位;如果“低位相等”为假,说明高位已经分出胜负,它直接“躺平”,把结果传下去就行。
定义三个中间信号,对于第 i 位 (i=3,2,1,0):
eq_i:到第 i 位为止(包括第 i 位),所有高位都相等吗?gt_i:到第 i 位为止,是否已经能判断 A > B?lt_i:到第 i 位为止,是否已经能判断 A < B?
那么,递推关系是:
eq_i = eq_{i+1} & (A[i] ~^ B[i])。意思是,要想截止到第i位都相等,必须之前位都相等(eq_{i+1}为真),并且当前位也相等(A[i]XNORB[i]为真)。注意,我们定义eq_4 = 1'b1(开始比较前,默认“之前位”相等)。gt_i = gt_{i+1} | (eq_{i+1} & (A[i] & ~B[i]))。意思是,截止到第i位能判断A>B,要么是之前位已经判断出A>B了(gt_{i+1}为真),要么是之前位都相等(eq_{i+1}为真)但当前位A[i]=1且B[i]=0。lt_i = lt_{i+1} | (eq_{i+1} & (~A[i] & B[i]))。逻辑同上。
最终,Y2 = gt_0, Y1 = eq_0, Y0 = lt_0。
这个递推结构非常清晰,但它用到了与、或、非、同或门。题目允许这些基本门,所以理论上我们可以用这个结构。但这里我想引入一个更工程化的考量:门级优化。
在实际的芯片设计中,我们不仅关心功能正确,还关心面积、速度和功耗。在CMOS工艺中,不同类型的门其性能(速度)和代价(晶体管数量、面积)是不同的。
- 非门 (INV):1个pMOS + 1个nMOS。
- 2输入与非门 (NAND2):2个pMOS + 2个nMOS。pMOS并联,nMOS串联。
- 2输入或非门 (NOR2):2个pMOS + 2个nMOS。pMOS串联,nMOS并联。
- 2输入与门 (AND2):一个NAND2加一个INV,共3个pMOS + 3个nMOS。
- 2输入或门 (OR2):一个NOR2加一个INV,也是3个pMOS + 3个nMOS。
由于pMOS的空穴迁移率比nMOS的电子迁移率低,pMOS的“驱动能力”通常弱于尺寸相同的nMOS。在NAND2中,两个pMOS并联去驱动高电平,对抗两个串联的nMOS拉低电平,结构上比较平衡。而在NOR2中,是两个串联的pMOS对抗两个并联的nMOS,这对驱动能力弱的pMOS非常不利,往往需要增大pMOS的宽度来补偿,导致面积和寄生电容增加。
所以,一个常见的经验法则是:在可能的情况下,优先使用与非门(NAND)结构,而不是或非门(NOR)结构。当然,这不是绝对的,最终要看综合工具在特定工艺库下的优化结果。但对于我们手动进行门级设计,这是一个很好的指导思想。
那么,我们的逻辑表达式能不能尽量用与非门来实现呢?这就是下一步:逻辑变换。
3. 逻辑变换与门级结构设计
我们回到1位比较器的输出表达式:
gt = a & ~beq = ~(a ^ b)(这是一个XNOR,可以用基本门搭出来:~(a ^ b) = (a & b) | (~a & ~b))lt = ~a & b
如果我们想多用NAND,可以利用德摩根定律:a & b = ~(~a | ~b),但更直接的是,a & b = ~(~(a & b)),即一个NAND加一个INV。但INV也是门,我们看看有没有更紧凑的写法。
实际上,对于4位比较器,我们可以直接从功能表写出Y2, Y1, Y0的表达式,然后尝试用与非-与非形式表达。这是一个有点繁琐但极其锻炼人的过程。为了节省篇幅,我直接给出一种经过优化的、以NAND和NOR为主的门级网络结构思路。
我们可以将4位比较看作是对4个“位对”的比较。定义:
EQ_i = ~(A[i] ^ B[i]),即第i位相等。GT_i = A[i] & ~B[i],即仅在第i位A>B。LT_i = ~A[i] & B[i],即仅在第i位A<B。
那么:
Y1 (A=B) = EQ_3 & EQ_2 & EQ_1 & EQ_0。这是一个4输入与门。Y2 (A>B) = GT_3 | (EQ_3 & GT_2) | (EQ_3 & EQ_2 & GT_1) | (EQ_3 & EQ_2 & EQ_1 & GT_0)。Y0 (A<B) = LT_3 | (EQ_3 & LT_2) | (EQ_3 & EQ_2 & LT_1) | (EQ_3 & EQ_2 & EQ_1 & LT_0)。
Y2和Y0的表达式是对称的。现在,我们的目标是把这些AND和OR操作,用NAND和NOR(以及必要的NOT)来实现。
一个技巧:a & b & c & d = ~(~(a & b & c & d))。我们可以先用一个多输入NAND门实现~(a&b&c&d),再取反。但Verilog内置门原语通常只支持2输入(有些仿真器支持多输入,但为了可移植性和对应真实门电路,最好用2输入门搭建)。所以EQ_3 & EQ_2 & EQ_1 & EQ_0需要三个2输入与门级联。
但我们可以做得更优化。观察Y2的表达式,它是一系列“与-或”项的和。这正好是**“与或非-与非”**结构可以实现的。回想一下,一个“与或”表达式(a&b) | (c&d),可以写成~(~(a&b) & ~(c&d)),也就是先对各个与项取非(用NAND),再对这些结果进行“与非”操作。这就是“与非-与非”形式。
让我们以Y2为例,动手变换一下。为了清晰,我们定义:
P3 = GT_3P2 = EQ_3 & GT_2P1 = EQ_3 & EQ_2 & GT_1P0 = EQ_3 & EQ_2 & EQ_1 & GT_0
则 Y2 = P3 | P2 | P1 | P0。
根据德摩根定律:Y2 = ~(~P3 & ~P2 & ~P1 & ~P0)。
看,如果我们能先得到~P3, ~P2, ~P1, ~P0,再用一个4输入NAND门,就能得到Y2。而~P3 = ~GT_3 = ~(A[3] & ~B[3])。这本身就是一个与非门的形式吗?~(A[3] & ~B[3]) 不是标准的NAND2,因为第二个输入是~B[3]。我们需要先得到B[3]的反相。
所以,一个可行的设计步骤是:
- 为每一位的
A[i]和B[i]生成其反相信号A_n[i]和B_n[i]。这需要8个非门。 - 计算每一位的
EQ_i = ~(A[i] ^ B[i])。异或非可以用基本门搭:EQ_i = (A[i] & B[i]) | (A_n[i] & B_n[i])。这可以进一步转换为与非形式。 - 计算每一位的
GT_i = A[i] & B_n[i]和LT_i = A_n[i] & B[i]。 - 计算各级的“相等传递”信号:
E3 = EQ_3,E32 = EQ_3 & EQ_2,E321 = EQ_3 & EQ_2 & EQ_1。这些是产生P1,P0等项需要的。 - 利用步骤4的信号与
GT_i相与,得到P2,P1,P0(P3就是GT_3)。 - 对各个
P项取非,然后进行“与非”操作,得到最终输出。
这个过程画成电路图会有点复杂,但用Verilog门级原语来描述,却是非常直接和机械的。下面,我们就进入代码实现环节。
4. Verilog门级实现:像搭积木一样构建电路
理解了上面的推导,写代码就是按图施工。这里我提供一种实现方案,它可能不是晶体管数最少的,但结构清晰,严格使用基本门,并且易于理解。
首先,我们需要生成所有输入信号的反相版本。在门级描述中,这必须显式地用 not 门实现。
// 生成A和B的反相信号
not inv_A3 (A3_n, A[3]);
not inv_A2 (A2_n, A[2]);
not inv_A1 (A1_n, A[1]);
not inv_A0 (A0_n, A[0]);
not inv_B3 (B3_n, B[3]);
not inv_B2 (B2_n, B[2]);
not inv_B1 (B1_n, B[1]);
not inv_B0 (B0_n, B[0]);
接下来,计算每一位的 EQ_i。我们用与门和或门来实现 EQ_i = (A[i] & B[i]) | (A_n[i] & B_n[i]),然后再考虑转换。
// 计算 EQ_i = A[i] & B[i]
and and_eq_A3_B3 (and_A3_B3, A[3], B[3]);
and and_eq_A2_B2 (and_A2_B2, A[2], B[2]);
and and_eq_A1_B1 (and_A1_B1, A[1], B[1]);
and and_eq_A0_B0 (and_A0_B0, A[0], B[0]);
// 计算 EQ_i = A_n[i] & B_n[i]
and and_eq_An3_Bn3 (and_An3_Bn3, A3_n, B3_n);
and and_eq_An2_Bn2 (and_An2_Bn2, A2_n, B2_n);
and and_eq_An1_Bn1 (and_An1_Bn1, A1_n, B1_n);
and and_eq_An0_Bn0 (and_An0_Bn0, A0_n, B0_n);
// 计算 EQ_i = (A[i]&B[i]) | (A_n[i]&B_n[i])
or or_eq3 (EQ3, and_A3_B3, and_An3_Bn3);
or or_eq2 (EQ2, and_A2_B2, and_An2_Bn2);
or or_eq1 (EQ1, and_A1_B1, and_An1_Bn1);
or or_eq0 (EQ0, and_A0_B0, and_An0_Bn0);
然后计算 GT_i 和 LT_i:
// 计算 GT_i = A[i] & B_n[i]
and and_gt3 (GT3, A[3], B3_n);
and and_gt2 (GT2, A[2], B2_n);
and and_gt1 (GT1, A[1], B1_n);
and and_gt0 (GT0, A[0], B0_n);
// 计算 LT_i = A_n[i] & B[i]
and and_lt3 (LT3, A3_n, B[3]);
and and_lt2 (LT2, A2_n, B[2]);
and and_lt1 (LT1, A1_n, B[1]);
and and_lt0 (LT0, A0_n, B[0]);
现在计算“相等传递”信号 E32, E321 等:
// E32 = EQ3 & EQ2
and and_E32 (E32, EQ3, EQ2);
// E321 = EQ3 & EQ2 & EQ1
and and_E321_temp (E321_temp, E32, EQ1); // 先算 E32 & EQ1
// 实际上,E321 就是 E321_temp,我们给它一个线网连接即可
wire E321;
assign E321 = E321_temp; // 注意:门级描述中通常用连续赋值或更多门,这里为清晰先用assign,也可用and门直接驱动。
// 同理,E3210 = EQ3 & EQ2 & EQ1 & EQ0
and and_E3210 (E3210, E321_temp, EQ0);
有了这些,我们就可以构造 Y2 的各个“与项” P:
P3 = GT3P2 = EQ3 & GT2-> 需要and(EQ3, GT2)P1 = E32 & GT1-> 需要and(E32, GT1)P0 = E321 & GT0-> 需要and(E321, GT0)
and and_P2 (P2, EQ3, GT2);
and and_P1 (P1, E32, GT1);
and and_P0 (P0, E321, GT0);
// P3 就是 GT3
现在,Y2 = P3 | P2 | P1 | P0 = ~(~P3 & ~P2 & ~P1 & ~P0)。我们需要先得到各个 ~P 信号。我们可以通过与非门来同时实现“与”和“非”,但这里我们已经用与门得到了 P,所以需要额外的非门。或者,我们重新设计,直接用与非门生成 ~P 信号。让我们选择后者,这样更符合“与非-与非”的思路。
我们可以这样定义:
nP3 = ~(A[3] & B3_n)这本身就是一个与非门!nand(A[3], B3_n)得到的就是~(A[3] & B3_n),而GT3 = A[3] & B3_n,所以nP3 = ~GT3。完美。nP2 = ~(EQ3 & GT2)。我们可以用nand(EQ3, GT2)直接得到。nP1 = ~(E32 & GT1)->nand(E32, GT1)nP0 = ~(E321 & GT0)->nand(E321, GT0)
那么 Y2 = ~(nP3 & nP2 & nP1 & nP0),这又是一个4输入与非门。但是Verilog内置的 nand 原语是2输入的,所以我们需要用三个2输入与非门来构建一个4输入与非功能。
让我们调整一下代码,采用更直接的与非门实现:
// 重新用与非门计算 ~GT_i 和 ~(EQ & GT) 等
// 对于 P3 = GT3 = A[3] & B3_n, ~P3 = nand(A[3], B3_n)
nand nand_nP3 (nP3, A[3], B3_n);
// 对于 P2 = EQ3 & GT2, ~P2 = nand(EQ3, GT2)。但GT2本身是与门输出,我们需要先得到GT2。
// 我们可以直接用 nand 来表达 ~(EQ3 & (A[2] & B2_n)),但这是三层逻辑了。不如先计算 GT2。
and and_GT2 (GT2, A[2], B2_n); // 复用之前的GT2计算
nand nand_nP2 (nP2, EQ3, GT2);
// 同理计算 ~P1 和 ~P0
and and_GT1 (GT1, A[1], B1_n);
nand nand_nP1 (nP1, E32, GT1);
and and_GT0 (GT0, A[0], B0_n);
nand nand_nP0 (nP0, E321, GT0);
// 现在 Y2 = ~(nP3 & nP2 & nP1 & nP0)
// 用两级与非门实现4输入与非:
wire temp_Y2_1, temp_Y2_2;
nand nand_Y2_1 (temp_Y2_1, nP3, nP2);
nand nand_Y2_2 (temp_Y2_2, nP1, nP0);
nand nand_Y2_final (Y2, temp_Y2_1, temp_Y2_2);
Y0 的计算是完全对称的,只需把 GT_i 换成 LT_i。
Y1 最简单,就是 E3210,也就是 EQ3 & EQ2 & EQ1 & EQ0。我们可以用三级2输入与门实现,也可以用与非门加反相器实现。这里用与门:
// Y1 = EQ3 & EQ2 & EQ1 & EQ0
wire temp_Y1_1, temp_Y1_2;
and and_Y1_1 (temp_Y1_1, EQ3, EQ2);
and and_Y1_2 (temp_Y1_2, EQ1, EQ0);
and and_Y1_final (Y1, temp_Y1_1, temp_Y1_2);
把所有的代码片段组合起来,就构成了完整的门级描述模块。注意,我们需要将所有的线网(wire)声明好。完整的模块代码结构如下:
module comparator_gate (
input [3:0] A,
input [3:0] B,
output Y2, // A > B
output Y1, // A == B
output Y0 // A < B
);
// 声明所有内部线网
wire A3_n, A2_n, A1_n, A0_n;
wire B3_n, B2_n, B1_n, B0_n;
wire and_A3_B3, and_A2_B2, and_A1_B1, and_A0_B0;
wire and_An3_Bn3, and_An2_Bn2, and_An1_Bn1, and_An0_Bn0;
wire EQ3, EQ2, EQ1, EQ0;
wire GT3, GT2, GT1, GT0;
wire LT3, LT2, LT1, LT0;
wire E32, E321_temp, E321, E3210;
wire nP3, nP2, nP1, nP0;
wire temp_Y2_1, temp_Y2_2;
wire temp_Y1_1, temp_Y1_2;
// 对于Y0,也需要类似的信号
wire nL3, nL2, nL1, nL0; // ~LT 相关的信号
wire temp_Y0_1, temp_Y0_2;
// 此处插入上述所有门实例化语句
// ... (所有 not, and, nand, or 门的代码)
endmodule
这个代码看起来很长,但每一行都对应着一个实实在在的逻辑门。当你用综合工具把它映射到标准单元库时,这些 and、nand、not 门都会被替换成库里实际的物理单元。
5. 验证的艺术:如何构建一个“聪明”的Testbench
代码写完了,但工作只完成了一半。没有经过充分验证的硬件代码,就像没经过测试的软件,随时可能崩溃。对于数字设计,验证的工作量往往远大于设计本身。
一个常见的误区是,只做简单的随机测试,或者只测几个边界值。对于这个4位比较器,输入空间是16x16=256种组合,全部遍历是可行的。但我想介绍一种更有工程价值的验证方法:受约束的随机测试结合断言和覆盖率收集。
我们的Testbench要完成以下几件事:
- 功能验证:检查在所有可能的输入组合下,输出是否符合预期。
- 时序验证(如果有时钟):检查建立保持时间。
- 覆盖率收集:确保我们的测试用例充分锻炼了代码的每一个分支、每一条语句、每一个条件。
由于本题是纯组合逻辑,我们重点看功能验证和代码覆盖率。
首先,一个基础的Testbench会例化设计,施加激励,并检查输出。我们可以用 $display 在出错时打印信息。
`timescale 1ns/1ps
module tb_comparator;
reg [3:0] A, B;
wire Y2, Y1, Y0;
// 例化设计
comparator_gate uut (
.A(A),
.B(B),
.Y2(Y2),
.Y1(Y1),
.Y0(Y0)
);
integer i, j;
integer error_count = 0;
initial begin
// 方法1:穷举所有输入组合
for (i = 0; i < 16; i = i + 1) begin
for (j = 0; j < 16; j = j + 1) begin
A = i;
B = j;
#10; // 等待组合逻辑稳定
check_result(i, j, Y2, Y1, Y0);
end
end
// 方法2:随机测试(作为补充)
repeat(1000) begin
A = $random % 16;
B = $random % 16;
#10;
check_result(A, B, Y2, Y1, Y0);
end
if (error_count == 0)
$display("*** TEST PASSED! All %0d cases checked.", 16*16+1000);
else
$display("*** TEST FAILED! %0d errors found.", error_count);
$finish;
end
task check_result;
input [3:0] a, b;
input y2, y1, y0;
reg exp_y2, exp_y1, exp_y0;
begin
// 计算期望值
exp_y2 = (a > b);
exp_y1 = (a == b);
exp_y0 = (a < b);
if ( !( (y2 === exp_y2) && (y1 === exp_y1) && (y0 === exp_y0) ) ) begin
$display("[ERROR] @ time %0t: A=%0d, B=%0d, Expected (Y2,Y1,Y0)=(%b,%b,%b), Got (%b,%b,%b)",
$time, a, b, exp_y2, exp_y1, exp_y0, y2, y1, y0);
error_count = error_count + 1;
end
end
endtask
endmodule
这个Testbench做了穷举测试,应该能覆盖所有情况。但如何证明呢?我们需要收集代码覆盖率。在仿真工具中(如VCS、ModelSim、QuestaSim等),可以编译时加入覆盖率选项,仿真后会生成覆盖率报告。
覆盖率通常包括:
- 行覆盖率:代码的每一行是否都被执行过。
- 分支覆盖率:条件语句(如if-else)的每个分支是否都被执行过。
- 条件覆盖率:对于布尔表达式,每个子条件真假组合是否都被覆盖到。
- 翻转覆盖率:寄存器或信号的0->1和1->0翻转是否都发生过。
对于我们的门级描述,由于全是连续赋值和门实例化,没有过程语句,所以主要关注行覆盖率和翻转覆盖率。一个优秀的验证应该达到100%的行覆盖。
在仿真脚本中,我们可能需要这样编译和运行:
# 以VCS为例
vcs -full64 -sverilog -debug_all -cm line+cond+fsm+tgl comparator_gate.v tb_comparator.v
./simv -cm line+cond+fsm+tgl
urg -dir simv.vdb -report coverage_report
仿真后,打开覆盖率报告,我们会看到每个文件、每行代码的覆盖情况。对于这个比较器,如果穷举了所有256种输入,并且A和B的每一位都经历了0和1的变化,那么理论上应该能达到100%的行覆盖和翻转覆盖。
提示:在实际项目中,100%的覆盖率是一个理想目标,但并非总能轻易达到。有些代码在特定条件下才可能执行(比如错误处理逻辑)。这时需要分析覆盖漏洞,编写针对性的测试用例。这个过程本身就能帮你发现设计中的潜在问题。
6. 超越功能:思考面积、时序与可测性
当我们确保功能正确后,作为工程师,还需要思考更多。这个门级实现是“最优”的吗?我们可以从几个维度评估:
1. 面积(晶体管数量) 我们可以粗略估算一下上面设计的门数量:
- 非门:8个 (用于A[3:0]和B[3:0]取反)
- 与门:计算EQ_i、GT_i、LT_i、E32等,大约有20多个。
- 或门:4个 (用于计算EQ_i)
- 与非门:用于计算nP和最终输出,大约10个。
总共大约40-50个门。每个2输入门大约对应4-6个晶体管(CMOS工艺)。所以整个模块大约在200-300个晶体管。有没有可能更省?如果我们从一开始就坚持全部用与非门和非门来实现,可能会减少门的种类,但数量不一定更少。实际上,综合工具在映射到标准单元库时,会进行大量的优化,它可能把我们的一些与门-或门结构转换成更优化的复合门(如AND-OR-INV)。
2. 关键路径延迟
组合逻辑的延迟取决于信号从输入到输出经过的门级数。对于Y2和Y0,最长的路径可能是:A[0] -> 非门 -> 与门(计算GT0) -> 与门(计算E321) -> 与非门(计算nP0) -> 两级与非门(计算最终Y2)。这大概有6级门延迟。对于Y1,路径是:A[0] -> 非门 -> 与门(计算and_An0_Bn0) -> 或门(计算EQ0) -> 三级与门(计算Y1)。也是6级左右。
在高速电路中,级数越多,延迟越大,最高工作频率越低。有没有办法减少级数?一种思路是并行化。例如,我们可以直接写出Y1的乘积项表达式,然后用多级与非门实现,可能减少级数。但面积可能会增加。这是一个典型的面积-速度折衷。
3. 可测性设计 如果这个模块是更大芯片的一部分,我们还需要考虑测试。比如,是否容易检查制造缺陷?常见的可测性设计技术包括扫描链(Scan Chain)。对于纯组合逻辑,我们可以通过控制所有输入,观察所有输出来进行测试。只要输入可控、输出可观,其可测性就是高的。我们的设计满足这一点。
7. 门级描述的意义:连接抽象与物理
走完这一趟,我们再回头看“门级描述”的意义。它强迫我们思考:
- 布尔逻辑如何用最基本的门电路实现?
- 不同的逻辑表达式,对应的电路结构有何不同?哪种更优?
- 我们写的每一行代码,最终会变成硅片上的哪些晶体管?
当你用行为级描述 assign Y2 = (A > B) 时,综合工具会帮你完成所有这些转换和优化。但如果你不知道门级电路是什么样子,你就很难理解综合报告里的时序、面积数据,更难以在代码层面指导综合工具产生更好的结果。
例如,如果你知道比较器是这种高位优先的链式结构,其关键路径较长,你可能会在行为级代码中尝试不同的写法,或者添加流水线寄存器来提升频率。这种洞察力,就来自于对底层电路的深刻理解。
所以,下次当你写Verilog时,不妨在脑海中多问一句:我这段代码,综合出来会是什么样子?养成这个习惯,你会更快地成长为一名优秀的数字芯片设计师。
最后,分享一个我自己的小习惯:在完成一个模块的门级描述后,我会用仿真工具画出它的原理图(Schematic)。看着那些与门、或门、非门按照你的设计连接在一起,形成一条条数据通路,那种感觉,就像建筑师看着自己设计的蓝图变成高楼大厦,是一种实实在在的创造者的快乐。这份快乐,希望你也能体验到。
454



被折叠的 条评论
为什么被折叠?



