通过spyglass等工具修正rtl lint问题过程中,位宽不匹配问题是一大类很让人头疼的error和warning。很多时候可以根据经验和项目组的要求进行review和waive,但这些操作还是要基于比较清晰和正确的综合工具对这些问题处理方式的了解。于是借此机会,对补位和截位的规则通过实验再明确一番。
case0:宽 = 窄自动补位的场景,比如如下这段代码:
module test #(parameter INP0 = 4, INP1 = 4, OUTP = 32)
(
input clk,
input rst_n,
input [INP0 -1:0]i_data0,
input [INP1 -1:0]i_data1,
output reg [OUTP -1:0]o_data
);
wire[OUTP -1:0]o_data_d = i_data1 * ((i_data0 + 1'b1) * 2'd3);
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
o_data {OUTP{1'b0}};
end
else begin
o_data o_data_d;
end
end
endmodule
输入信号是是两个4bit信号,输出为32bit,一定会报lint error的逻辑是这句:wire[OUTP -1:0]o_data_d = i_data1 * ((i_data0 + 1'b1) * 2'd3);
位宽是32bit = 4bit * ((4bit + 1bit) * 2bit) = 4bit * 7bit,还是通过DC+TSMC90 fast库进行一下综合://///////////////////////////////////////////////////////////
// Created by: Synopsys DC Expert(TM) in wire load mode
// Version : O-2018.06-SP1
// Date : Wed Jul 24 15:00:53 2024
/////////////////////////////////////////////////////////////
而后我想看一下综合后的门电路是如何处理,当然谨慎的讲综合结果只供参考,毕竟这和工具以及库应该是有一定的关系的,对于后端我涉及不够深入所以只摆一下结果。综合结果中通过门电路综合了一个乘法器,输入分别为7bit和4bit,输出为10bit:
module test_DW02_mult_0 ( A, B, PRODUCT );
input [6:0] A;
input [3:0] B;
output [10:0] PRODUCT;
...
XOR2X8 U63 ( .A(n29), .B(n28), .Y(PRODUCT[9]) );
endmodule
分析代码可以看到,输出的10个bit都是有驱动的。后面在重映射的门级test模块中,调用的情况如下:module test ( clk, rst_n, i_data0, i_data1, o_data );
input [3:0] i_data0;
input [3:0] i_data1;
output [31:0] o_data;
input clk, rst_n;
wire N8, N7, N5, N10, n43, n44, n45, n46, n47, n48, n49, n50, n51, n52,
n53, n54, n55, n56, n57, n58, n59, n60, n61, n62, n63, n64, n65, n66,
n67, n68, n69;
wire [10:0] o_data_d;
test_DW02_mult_0 mult_9_2 ( .A({1'b0, N10, n69, N8, N7, n46, N5}), .B({
i_data1[3], n50, n49, i_data1[0]}), .PRODUCT(o_data_d) );
DFFRQX2 o_data_reg_0_ ( .D(o_data_d[0]), .CK(clk), .RN(rst_n), .Q(o_data[0])
);
...
DFFRHQX4 o_data_reg_9_ ( .D(o_data_d[9]), .CK(clk), .RN(rst_n), .Q(o_data[9]) );
INVX2 U24 ( .A(1'b1), .Y(o_data[11]) );
...
INVX2 U64 ( .A(1'b1), .Y(o_data[31]) );
...
XOR2X8 U96 ( .A(n65), .B(n66), .Y(n69) );
endmodule
test_DW02_mult_0的A端口对应的是((i_data0 + 1'b1) * 2'd3),i_data0在逻辑电路中被映射为{1'b0, N10, n69, N8, N7, n46, N5},比如简单看一个逻辑链:
BUFX12 U89 ( .A(i_data0[3]), .Y(n54) );
CLKINVX6 U73 ( .A(n54), .Y(n64) );
CLKAND2X12 U85 ( .A(n45), .B(n54), .Y(N10) );
而B端口对应的就是i_data1:
.B({i_data1[3], n50, n49, i_data1[0]})
BUFX8 U88 ( .A(i_data1[1]), .Y(n49) );
BUFX12 U90 ( .A(i_data1[2]), .Y(n50) );
而输出则是连接到中间信号o_data_d,在原始代码里定义o_data_d的位宽为32bit,但在综合后的信号只有11bit。
wire [10:0] o_data_d;
这是非常合理当然也是符合我们的预期的,而后11bit的o_data_d驱动了32bit的o_data低11bit:
DFFRQX2 o_data_reg_0_ ( .D(o_data_d[0]), .CK(clk), .RN(rst_n), .Q(o_data[0])
);
DFFRQX1 o_data_reg_5_ ( .D(o_data_d[5]), .CK(clk), .RN(rst_n), .Q(o_data[5])
);
DFFRQX1 o_data_reg_4_ ( .D(o_data_d[4]), .CK(clk), .RN(rst_n), .Q(o_data[4])
);
...
而o_data高21比特则被驱动为常0:
INVX2 U24 ( .A(1'b1), .Y(o_data[11]) );
INVX2 U26 ( .A(1'b1), .Y(o_data[12]) );
INVX2 U28 ( .A(1'b1), .Y(o_data[13]) );
case1:窄 = 宽自动截位的场景,使用的代码如下:
module test #(parameter INP0 = 7, INP1 = 16, OUTP = 16)
(
input clk,
input rst_n,
input [INP0 -1:0]i_data0,
input [INP1 -1:0]i_data1,
output reg [OUTP -1:0]o_data
);
wire[OUTP -1:0]o_data_d = i_data1 * i_data0;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
o_data {OUTP{1'b0}};
end
else begin
o_data o_data_d;
end
end
endmodule
16bit = 16bit * 7bit,综合后有一个乘法器test_DW02_mult_0,其实里面还调用了综合出的一个加法器test_DW01_add_1,不过其内部代码不影响分析就不放在这了。
module test_DW02_mult_0 ( A, B, PRODUCT );
input [6:0] A;
input [15:0] B;
output [22:0] PRODUCT;
test_DW01_add_1 FS_1 ( .A({1'b0, A1_19_, A1_18_, A1_17_, A1_16_, A1_15_,
A1_14_, A1_13_, A1_12_, A1_11_, A1_10_, A1_9_, A1_8_, A1_7_, A1_6_,
A1_5_, SUMB_6__0_, A1_3_, A1_2_, A1_1_, A1_0_}), .B({n11, n16, n15,
n14, n13, n12, A2_14_, n10, n39, n43, n40, n42, A2_8_, n41, n38, 1'b0,
1'b0, 1'b0, 1'b0, 1'b0, 1'b0}), .SUM({SYNOPSYS_UNCONNECTED_1,
SYNOPSYS_UNCONNECTED_2, SYNOPSYS_UNCONNECTED_3, SYNOPSYS_UNCONNECTED_4,
SYNOPSYS_UNCONNECTED_5, SYNOPSYS_UNCONNECTED_6, SYNOPSYS_UNCONNECTED_7,
PRODUCT[15:2]}) );
...
XOR2XL U165 ( .A(ab_1__0_), .B(ab_0__1_), .Y(PRODUCT[1]) );
NOR2BXL U216 ( .AN(B[0]), .B(n8), .Y(PRODUCT[0]) );
endmodule
PRODUCT的位宽定义虽然是23bit,但是只有PRODUCT[15:0]被赋值因为最后的输出是16bit,所以这里进行了截断。从代码里也能看到是计算结果时进行截断的,并没有提前截断信号i_data1:
NOR2BXL U82 ( .AN(B[15]), .B(n49), .Y(ab_0__15_) );
case2:窄 = 宽 * 窄module test #(parameter INP0 = 7, INP1 = 8, OUTP = 12)
(
input clk,
input rst_n,
input [INP0 -1:0]i_data0,
input [INP1 -1:0]i_data1,
output reg [OUTP -1:0]o_data
);
wire[2*INP0 -1:0]i_data0_db = i_data0 * i_data0;
wire[OUTP -1:0]o_data_d = i_data1 * i_data0_db;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
o_data {OUTP{1'b0}};
end
else begin
o_data o_data_d;
end
end
endmodule
o_data_d = i_data1 * i_data0_db这句的位宽是12bit = 8bit * 14bit,综合是还是以门级电路综合出了模块test_DW02_mult_1:
module test_DW02_mult_1 ( B, PRODUCT );
input [6:0] B;
output [13:0] PRODUCT;
...
XOR3X4 U48 ( .A(A1_9_), .B(n57), .C(n84), .Y(PRODUCT[11]) );
...
endmodule
观察代码还是会发现,乘法模块输出最高赋值到bit[11],显然这是对齐到后面的第二步乘法操作输出值位宽的。也就是说跟之前的经验一样,不管在这里定义了多大的位宽都会根据最后需要进行中间截位的。
综合出的第二个乘法器已经把输入位宽限定在了12bit,但是仍然给了一个位宽充分的输出:
module test_DW02_mult_0 ( A, B, PRODUCT );
input [11:0] A;
input [7:0] B;
output [19:0] PRODUCT;
...
XOR3X4 S4_0 ( .A(ab_11__0_), .B(CARRYB_10__0_), .C(SUMB_10__1_), .Y(
PRODUCT[11]) );
...
endmodule
跟前面一样,乘法器虽然定义了输出位宽为20bit,但是只驱动了12bit。最后整体的综合后test简单看下就好了,和之前没有太大区别: test_DW02_mult_1 mult_9 ( .B({n15, n14, n13, n12, n11, n10, n9}), .PRODUCT({
SYNOPSYS_UNCONNECTED_1, SYNOPSYS_UNCONNECTED_2, i_data0_db_11_,
i_data0_db_10_, i_data0_db_9_, i_data0_db_8_, i_data0_db_7_,
i_data0_db_6_, i_data0_db_5_, i_data0_db_4_, i_data0_db_3_,
i_data0_db_2_, SYNOPSYS_UNCONNECTED_3, i_data0_db_0_}) );
test_DW02_mult_0 mult_10 ( .A({i_data0_db_11_, i_data0_db_10_, i_data0_db_9_,
i_data0_db_8_, i_data0_db_7_, n1, n2, n3, i_data0_db_3_, i_data0_db_2_,
1'b0, i_data0_db_0_}), .B({i_data1[7], n8, n7, n6, n5, n4,
i_data1[1:0]}), .PRODUCT({SYNOPSYS_UNCONNECTED_4,
SYNOPSYS_UNCONNECTED_5, SYNOPSYS_UNCONNECTED_6, SYNOPSYS_UNCONNECTED_7,
SYNOPSYS_UNCONNECTED_8, SYNOPSYS_UNCONNECTED_9,
SYNOPSYS_UNCONNECTED_10, SYNOPSYS_UNCONNECTED_11, o_data_d}) );
然后我又顺便测试了一下把两步计算合成一步是否会有区别(因为修lint时总会有类似的操作,为了避免位宽error会拆分计算式进行位宽对齐):
wire[OUTP -1:0]o_data_d = (i_data0 * i_data0) * i_data1;
然后综合看了下,和拆分写果然是没有区别的。第一个乘法器i_data0自己乘自己的这个,只驱动12bit:
module test_DW02_mult_1 ( B, PRODUCT );
input [6:0] B;
output [13:0] PRODUCT;
...
XOR3X4 U48 ( .A(A1_9_), .B(n58), .C(n85), .Y(PRODUCT[11]) );
...
endmodule
第二个乘法器也只驱动12bit,和预期一致:
module test_DW02_mult_0 ( A, B, PRODUCT );
input [11:0] A;
input [7:0] B;
output [19:0] PRODUCT;
...
XOR3X4 S4_0 ( .A(ab_11__0_), .B(CARRYB_10__0_), .C(SUMB_10__1_), .Y(
PRODUCT[11]) );
...
endmodule
可见这种拆分或者合并的逻辑表达式,除了规避lint之外对电路的最终实现应当是没有区别的。
case3:宽 + 窄不同位宽的信号进行操作根据刚刚的实验其实已经比较明确了:会综合出一个满位宽的加法器/乘法器/...,但是根据最终的结果位宽进行截断。不过这个补充实验我想进一步明确下,当宽窄信号进行操作时的确不会出现很多人担心的宽信号被截断到窄位宽的情况。
module test #(parameter INP0 = 7, INP1 = 10, OUTP = 9)
(
input clk,
input rst_n,
input [INP0 -1:0]i_data0,
input [INP1 -1:0]i_data1,
output reg [OUTP -1:0]o_data
);
wire[OUTP -1:0]o_data_d = i_data0 + i_data1;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
o_data {OUTP{1'b0}};
end
else begin
o_data o_data_d;
end
end
endmodule
9bit = 7bit+10bit,综合之后是一个9bit的加法器:
module test_DW01_add_1 ( A, B, SUM );
input [8:0] A;
input [8:0] B;
output [8:0] SUM;
...
endmodule
而后在顶层调用时可以明确:
test_DW01_add_1 add_9 ( .A({1'b0, 1'b0, i_data0[6:1], n3}), .B({i_data1[8:1],
n2}), .SUM(o_data_d) );
位宽7bit的输入信号i_data0补了2bit的0作为9bit传递给test_DW01_add_1,而位宽10bit的输入信号i_data1则是截断为i_data1[8:0]的9bit信号传递给test_DW01_add_1。因此可以确定的是,信号的截断(以及补0)确实是根据最终的输出位宽来决定的,而不会根据参与运算的窄信号截断。
case4:有符号数最后这一部分是对之前文章的一个确认验证。看下同样的代码,对于有符号数综合时是如何处理的。对刚刚的代码做一点修改,输入输出都设置为signed:
module test #(parameter INP0 = 7, INP1 = 10, OUTP = 9)
(
input clk,
input rst_n,
input signed[INP0 -1:0]i_data0,
input signed[INP1 -1:0]i_data1,
output reg signed[OUTP -1:0]o_data
);
wire[OUTP -1:0]o_data_d = i_data0 + i_data1;
always @(posedge clk or negedge rst_n)begin
if(~rst_n)begin
o_data 而后在综合后调用test_DW01_add_1时就能看到符号位拓展了:
test_DW01_add_1 add_9 ( .A({n5, n5, n5, i_data0[5:2], n2, n4}), .B({
i_data1[8:1], n3}), .SUM(o_data_d) );
BUFX2 U6 ( .A(i_data0[6]), .Y(n5) );而且还能看到的是,综合工具以及实际电路里哪有什么signed和unsigned:
module test ( clk, rst_n, i_data0, i_data1, o_data );
input [6:0] i_data0;
input [9:0] i_data1;
output [8:0] o_data;
input clk, rst_n;
wire n2, n3, n4, n5;
wire [8:0] o_data_d;
...
endmodule
系列文章入口——
【芯片设计】SoC 101(一):绪论 | 【芯片设计】FIFO漫谈(零)从无处不在的FIFO开始说起 | 【芯片设计】计算机体系结构(一)虚拟内存 | 【芯片设计】深入理解AMBA总线(零)绪论
| 【芯片设计】握手协议的介绍与时序说明 | 【芯片设计】复位那些小事 —— 复位消抖 | 【芯片设计】快速入门数字芯片设计(一)Introduction | 【芯片验证】UVM源码计划(零)下定决心读源码前的自测环节
| 【芯片设计】异步电路碎碎念(一) 到底什么是异步电路
| 【芯片设计】从RTL到GDS(一):Introduction
| 其他文章链接——
【芯片验证】sva_assertion: 15道助力飞升的断言练习 | 【芯片验证】可能是RTL定向验证的巅峰之作 | 【芯片验证】RTL仿真中X态行为的传播 —— 从xprop说起 | 【芯片验证】年轻人的第一个systemVerilog验证环境全工程与解析 |
【芯片设计】verilog中有符号数和无符号数的本质探究
| 【芯片设计】论RTL中always语法的消失术 | 【芯片设计】代码即注释,注释即代码 | 【芯片设计】700行代码的risc处理器你确实不能要求太多了 |
入职芯片开发部门后,每天摸鱼之外的时间我们要做些什么呢 | 如何计算系统的outstanding 和 burst length? | 芯片搬砖日常·逼死强迫症的关键词不对齐事件 | 熟人社会里,一群没有社会价值的局外人 |
|