来源:EETOP 论坛 作者:rosshardware 链接:http://bbs./thread-768668-1-1.html 以下是我过去10多年设计过的电路罗列总结,请大家补充,如果大家有兴趣(请反馈),后续可以针对每个点单独进行详细的阐述。 同步电路设计: 数学运算&数字信号处理类: 无符号运算-比较,加,减,乘,除 有符号运算符号-比较,加,减,乘,除,复数加法,复数乘法 绝对值,最大值,最小值 饱和,截位运算 NCO 滤波器 AGC 上变频,下变频 上采样,下采样 削波 DPD QMC FFT LDPC RS 维特比 控制类逻辑电路: 与或非,选择器,译码器 计数器 状态机 移位控制器 拼位设计 Leading One,Leading zero 握手控制 同步FIFO 仲裁调度(RR,WRR,WFQ) 流量整形(shaping) 报文头同步 BITMAP 乒乓流水设计 配置寄存器设计(RW,RO,RC,WC,W1_PULSE) 共享RAM的链表设计 异步电路设计 单bits异步处理 打三拍 异步握手 多bits异步处理 D-MUX 格雷码 异步FIFO SOC系统集成相关设计 系统级顶层设计: CRG设计 低功耗设计&Power Domain 规划 IO 复用(IO MAPPING)&排布 地址空间划分(Memory Maping) Paper Floorplan 系统控制器设计 核集成: ARM Cortex A系列 ARM Cortex R系列 ARM Cortex M系列 存储系统 SRAM,ROM,Flash DDR 总线 AMBA AXI,AHB,APB AMBA ACE 外设&加速器 DMA PCIE USB MPI NANDC NORC LOCAL BUS UART I2C SPI JTAG TIMER RTC WDT 模拟IP ADC,DAC TSENSOR USB PHY DDR PHY Serdes 知识结构地图-同步电路设计-运算类电路设计-无符号加法 学习加法运算之前,先谈几个概念: 知识结构地图-同步电路设计-运算类电路设计-无符号加法 学习加法运算之前,先谈几个概念: 1. 有符号和无符号 说到运算,我们首先介绍一下无符号和有符号数在数字电路的二进制表示方法,MSB(Most Significant Bit)代表最高位,LSB(Least Significant Bit)代表最低位。 在二进制运算里面,无符号数即所有bits位都代码实际的数据内容,dec代表十进制,计算公式: Value(dec)=(2^MSB)*bit(MSB)+(2^MSB-1)*bit(MSB-1) + ....+ (2^0)*bit0 有符号数通常会把MSB当作符号位,0代表正数,1代表负数,其余MSB-1 ~ 0 当作实际数据内容的补码,当符号位为0,实际值=补码值,当符号位为1,实际值=2^符号位bit位-补码值,计算公式: Value(dec) = (MSB == 1'b0) ? (2^MSB-1)*bit(MSB-1) + ....+ (2^0)*bit0 : -1* ((2^MSB)*bit(MSB)- ((2^MSB-1)*bit(MSB-1) + ....+ (2^0)*bit0)) 以3bits的二进制数为例,示意分别代表有符号数和无符号数的值: 小结一下,对于一个3bits的二级制数,如果代表无符号数,则表示范围为0~7, 如果表示有符号数,则表示范围为-4~3,即对于相同位宽的二进制数据,如果是无符号数,则能够表示范围为0~(2^MSB)-1, 如果是有符号数,则范围为-2^(MSB-1) ~ (2^(MSB-1)) -1, 由此可见,无符号数的范围是非对称的,即最小的复数值绝对值不等于最大整数的绝对值。 2. 定点数据和浮点数据 数字信号处理的输入源通常都是物理世界的模拟信号,其电平的表示是连续,数字处理会对其进行抽样,在算法阶段,会按照浮点运算的方式进行算法性能仿真,以便于评估最优性能边界。但是由于浮点运算硬件实现代价较大,且算法进行定点化以后的性能劣化通常也在实际使用可以接受范围,因此实际工程实现通常都采用定点化方式,实现算法链路。 浮点格式可以参考IEEE 754,由于实际工程使用不多,因此这里不做过多叙述,主要介绍定点的方法: 在定点数中,定义小数点的位置,把一个定点数分为两个部分,小数点左边部分的位宽为整数位宽,右边部分为小数位宽,小数点右边为0~1之间的小数,小数位宽则代表精度,比如(16,4)表示定点数位宽为16,整数位宽为4,小数位宽为12。当然可能不同公司会有不同定义,但是小数位宽和整数位宽的概念是相同的。 以(4,2) 为例,4位位宽,2位小数位 bit3 bit2 bit1 bit0 0 1 1 0 整数位 整数位 小数位 小数位 bit1 bit0 bit-1 bit-2 value(dec) = (2^1) * 0 + (2^0)* 1 + (2^-1) * 1 + (2^-2) * 0 = 1.5 3. 无符号二进制加法 无符号二进制加法,需要保证两个相加的加数均为无符号数,如果有一个位有符号数,则均为有符号运算,结果为有符号数,即对于减法来讲,不存在无符号减法。 无符号A+无符号B = 无符号C 无符号A+有符号B = 有符号C 有符号A+有符号B = 有符号C 有符号A+有符号B = 有符号C 二进制加法,动态范围会增加,精度保持不变,因此加法的结果需要扩一位,用于存放进位。 1011.1000 =》 8位 + 0101.1101 =》 8位 ------------------ 10001.0101 =》 9位 无符号加法Verilog 编码实现 localparam A_WIDTH = 16; localparam B_WIDTH = 8; // Sumation result width should be 1 bit more than biggest widht of adder factor localparam C_WIDTH = if (A_WIDTH > B_WIDTH) ? A_WIDTH + 1'b1 : B_WIDTH + 1'b1; reg [A_WIDTH-1 : 0] a; reg [B_WIDTH-1 : 0] b; reg [C_WIDTH-1 : 0] c; always @(*) begin c = {1'b0,a} + {1'b0,{A_WIDTH-B_WIDTH{1'b0}},b}; end 无符号加法编码要点: 1. 和c需要定位位宽比加数最大位宽大1位; 2. 加数a和b需要扩展位宽,扩展到与c位宽相等,且扩展位补0,否则有很多语法检查工具会报位宽不匹配错误,同时不同工具理解不一致,如果自动补1或者补最高位,就功能出问题了; 4. 无符号比较器 无符号比较器,需要比较两边信号均为无符号类型,同时如果位宽不匹配,需要扩展位宽进行匹配,对于无符号数,扩展位补0即可。当然,Synposys,推荐的时候,在定义信号时,把信号符号类型定义清楚,默认定位为无符号,可以不作位宽匹配,工具自动优化。不过本人还是建议,按照位宽扩位方式进行代码编写,一个是电路表达最清晰和可控,不依赖于工具的理解,因为可能synopsys综合最优,但可能FPGA综合就有问题,二个是,作工具语法检查,可以省去很多位宽不匹配的Warning的检查,防止Warning过多,检查疏忽,反而把真正有位宽匹配的问题漏掉。 个人推荐无符号比较器 Verilog写法: localparam A_WIDTH; localparam B_WIDTH; reg [A_WIDTH-1:0] a; // Default declaration type is unsigned reg [B_WIDTH-1:0] b; // Default declaration type is unsigned reg c; // A_WIDTH is bigger than B_WIDTH always @(*) begin if (a > {{(A_WIDTH-B_WIDTH){1'b0}},b}) begin c = 1'b1; end else begin c = 1'b0; end end sysnopsys 推荐写法: localparam A_WIDTH; localparam B_WIDTH; reg unsigned [A_WIDTH-1:0] a; // Default declaration type is unsigned reg unsigned [B_WIDTH-1:0] b; // Default declaration type is unsigned reg c; // A_WIDTH is bigger than B_WIDTH always @(*) begin if (a > b) begin c = 1'b1; end else begin c = 1'b0; end end 顺便再啰嗦一下,无不会单独讲Verilog编码规范和技巧,但是都会融入到我讲解的每个电路实现的列子里面。比如在无符号的加法和比较里面,我想传递给大家编码规范是: 1. 尽量参数化,这样便于代码的IP化,我们写的代码,后续如果有位宽变化的应用,只需要例化时更改参数即可,不需要大规模的修改代码,可以减少重复工作量,同时也减少犯错误,埋Bug的机会。 2. 代码要整洁,清晰易懂,行与行之间要有间隔,可以间隔4个Space,也可以2个Space,这个根据自己审美以及各个公司的要求来定。 3. 组合逻辑,采用Verilog 2001语法,即always @(*), Verilog 95 写组合语法,很多IP,尤其老外的IP,还用的这种语法,不推荐,因为需要把敏感信号列表写全,往往有时候笔误容易写漏,而且代码有修改,也可能忘记把新增信号加到信号敏感列表。 后续针对新的电路类型,给家讲新的代码编码规范和要求。 5. 无符号乘法器 与无符号加法类似,无符号乘法器也要求两边的乘数是无符号的,一旦有一方为有符号数,则整个结果为有符号数,否则综合会出现不可预知的结果。与无符号加法不同的是,无符号的乘法,乘积结果位宽为两个乘数位宽相加,而非乘数最大位宽+1,其实从原理上是比较容易理解的,因为二进制乘法,就是几组二进制加法移位的结果,例如: 1101 4位 * 110 3位 --------------------------------- 0000 + 1101 + 1101 ---------------------------------- 1001110 7位 乘法进行Verilog 编写,以前综合工具不是很优化,不能解析*,一般采用例会标准单元的方式,完成乘法运算: 传统古老方式Verilog 无符号乘法写法: localparam A_WIDTH; localparam B_WIDTH; localparam PRDCT_WIDTH = A_WIDTH + B_WIDTH; reg [A_WIDTH-1:0] a; // Default declaration type is unsigned reg [B_WIDTH-1:0] b; // Default declaration type is unsigned wire [PRDCT_WIDTH-1:0] prdct; DW02_MULT #( .A_WIDTH (A_WIDTH ), .B_WIDTH (B_WIDTH ) ) U_DW_MULT ( .TC (1'b0 ), // 0 for unsigned, 1 for signed .A (a ), .B (b ), .PRODUCT (prdct ) ); 随着工具不断优化,包括Synplify也被synopsys收购后,FPGA综合工具也支持*乘法识别,只需要代码中申明乘法参数的符号属性既可。 推荐乘法运算Verilog 代码: localparam A_WIDTH = 8; localparam B_WIDTH = 16; localparam PRDCT_WIDTH = A_WIDTH + B_WIDTH; reg unsigned [A_WIDTH-1:0] a; // Default declaration type is unsigned reg unsigned [B_WIDTH-1:0] b; // Default declaration type is unsigned reg unsigned [PRDCT_WIDTH-1:0] prdct; always@(*) begin prdct = a * b; end 乘法不用显示把a和b位宽扩位到A_WIDTH+W_WIDTH,只要prdct 定义位宽为A_WIDTH+B_WIDTH,工具就不会报错。 以上讲解的是乘法器两边都是变量信号的无符号乘法运算,对于一个变量,一个常量的无符号运算,需要注意一下几点: 1. 常数的位宽要定义清楚; 2. 常数的符号类型要显示定义为无符号; 3. 对于常数无论是是否2的整数次幂,均按照* 写,不需要自己优化移位,因为综合的优化效果,不会比手动移位差。 示例: localparam A_WIDTH = 8; localparam B_WIDTH = 8; localparam unsigned [B_WIDTH -1 : 0] B = 32; localparam PRDCT_WIDTH = A_WIDTH + B_WIDTH; reg unsigned [A_WIDTH-1:0] a; // Default declaration type is unsigned reg unsigned [PRDCT_WIDTH-1:0] prdct; always@(*) begin prdct = a * B; end 强烈不推荐: always@(*) begin prdct = a << 5; end 原因: 1. 代码可扩展性上讲,后续常数B的值变化不是2的5次方,或者说不是2的整数次幂,这个地方就需要修改为* 2. 代码可读性上讲,推荐的方式容易看懂,就是两个数相乘,不推荐的方式,还需要推敲一下,这行代码功能 3. 代码可控性上讲,a往作移位,低位补0,还是补1,还是补a的最低位,工具都可以有不同理解,所以不同工具可能理解会不一样 4. 两边位宽还不匹配,语法检查工具也会报Warning 顺便讲解一下这个章节代码规范一些细节: 1. 信号定义和申明,一行对应一个信号,不要多个信号定义在一行,否则修改其中一个信号,可能会影响其他信号,另外一行太长,也影响阅读,不建议定义方式: localparam A_WIDTH = 8,B_WIDTH = 16; 2. 对于模块例化,建议按照名字进行例化,不要按照位置进行例化,否则被例化模块端口有修改,例化的上层文件就要重新修改,即不建议这样的例化代码风格: DW_MULT #( A_WIDTH , B_WIDTH ) U_DW_MULT ( 1'b0 , a , b , prdct ); 甚至很多教科书上的这种写法,可维护性更差,就更不推荐了哈: DW_MULT #( A_WIDTH , B_WIDTH ) U_DW_MULT(1'b0,a,b,prdct); 原因很简答,如果a和b位置搞反了,a和b的位宽又不一样,就可能会报错,能够报错都算是不坏结果,就怕语法检查不跑错,最后仿真出错,定位问题会浪费较长时间。 3. 注意一下语法,例化赋值,或者用assign赋值的信号,我们定义为wire,在 always 块中的变量,无论是组合逻辑还是时序逻辑,都需要定义为reg。比如上面例子当中的prdct,第一个写法是通过例化模块得到值,所以定义为wire,第二个写法是在always块中得到,所以定义为reg。这个就是语法规定,没有什么理由,大家记住就行,否则工具就会报语法错误。 4. 在always 块中,组合逻辑采用非阻塞赋值 =, 时序逻辑采用阻塞赋值 <= ,具体原因,这里先不表述,前面章节主要讲组合逻辑,等讲到时序逻辑章节,会详细阐述原因,大家先有这个一个印象即可。 5.常数乘法给大家引申的一个写代码原则,尽量按照功能或者代码行为去写,只要是可综合风格即可,切忌自己觉得自己很聪明,对电路进行电路级的优化。这样会影响代码的可读性,扩展性以及可控性,同时现在综合工具优化功能很强,大家不必担心得不到最好的PPA(Power,Performance,Area),而且大家进行代码风格选择时,考虑的也不光是PPA这几个维度,也要从可以实现性,复杂度,可阅读星,开发周期多方面去考量。 对于除法的实现,相对于加减乘要麻烦一些。当然目前除法主要支持无符号数除法,我们分为两类进行介绍,一类是被除数是变量,即a/b这种,一类是被除数是常量,即a/B这种。 1. 被除数常量,方法一:长除法,即根据二进制手算除法,每次将被除数左移一位,每个周期得到一位商 比如 11/4 = 2 于 3 1011 --->11 - 100 ---->4 ------------------------------------ 0011 101 > 100 商最高位1, 余数 0011,将0011左移一位 011 - 100 011 < 100 商次高位为0, 余数为011 最终结果上为2'b10, 余数为011 需要注意一点,如果除数的高位为0,则需要对被除数高位补0,比如1111/001 (15/1) 由于001的bit2和bit1为0,因此1111需要补位为001111当作被除数,进行运算 001111 - 001 商的bit3为1 ------------------------------------ 0001 - 001 商的bit2为1 ------------------------------------- 0001 - 001 商的bit1为1 ------------------------------------- 0001 - 001 商的bit0为1 --------------------------------------- 000 余数为0 为了实现简便,我们对被除数的扩位进行归一化,统一扩位到被除数位宽+除数位宽,得到商取低位的被除数位宽即可。 根据这个思路,Verilog代码示意如下: module SHIFT_DIV #( parameter DIVIDEND_WIDTH = 16, parameter DIVISOR_WIDTH = 8, parameter QUOTIENT_WIDTH = DIVIDEND_WIDTH, parameter REMAINDER_WIDTH = DIVISOR_WIDTH - 1 ) ( input clk_sys, input rst_sys_n, input div_strt, input div_clr, input [DIVIDEND_WIDTH-1 : 0] dividend, input [DIVISOR_WIDTH-1 : 0] divisor, output reg div_end, output reg [QUOTIENT_WIDTH -1:0] quotient, output reg [REMAINDER_WIDTH-1:0] remainder ); localparam DIV_CNT_WIDTH = log2(QUOTIENT_WIDTH) + 1'b1; localparam LSF_REG_WIDTH = DIVIDEND_WIDTH+DIVISOR_WIDTH; ///////////////////////////////////////////////////////////////////////////////// reg div_cnt_en; reg [DIV_CNT_WIDTH-1 : 0] div_cnt; reg [LSF_REG_WIDTH-1 : 0] lsf_dividend; reg [DIVISOR_WIDTH-1 + 1 : 0] sub_dividend_divsor; ///////////////////////////////////////////////////////////////////////////////// wire [DIVISOR_WIDTH-1 : 0] divivend_cut; //////////////////////////////////////////////////////////////////////////////////// //Generate counter to control calculation cycle always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin div_cnt_en <= 1'b0; end else begin if ((div_clr == 1'b1) || (div_cnt >= (QUOTIENT_WIDTH))) begin div_cnt_en <= 1'b0; end else if (div_strt == 1'b1)begin div_cnt_en <= 1'b1; end end end always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin div_cnt <= {DIV_CNT_WIDTH{1'b0}}; end else begin if ((div_clr == 1'b1) || (div_strt == 1'b1) || (div_cnt >= (QUOTIENT_WIDTH))) begin div_cnt <= {DIV_CNT_WIDTH{1'b0}}; end else if (div_cnt_en == 1'b1)begin div_cnt <= div_cnt + 1'b1; end end end assign divivend_cut = lsf_divivend[DIVIDEND_WIDTH-1 -: DIVISOR_WIDTH]; always @(*) begin sub_dividend_divsor = {1'b0,divivend_cut } - {1'b0, divisor}; end always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin lsf_dividend <= {LSF_REG_WIDTH{1'b0}}; end else if ((div_strt == 1'b1) || (div_clr == 1'b1))begin lsf_dividend <= {{DIVISOR_WIDTH{1‘b0}},dividend}; end else if (div_cnt_en == 1'b1) begin if (sub_dividend_divsor[DIVISOR_WIDTH+1] == 1'b1 ) begin lsf_dividend <= {lsf_dividend[LSF_REG_WIDTH-2:0],1'b0}; end else begin lsf_dividend <= {sub_dividend_divsor[DIVISOR_WIDTH-2:0], lsf_dividend[LSF_REG_WIDTH-DIVIDEND_WIDTH-1:0],1'b0}; end end end always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin quotient <= {QUOTIENT_WIDTH{1'b0}}; end else if ((div_strt == 1'b1) || (div_clr == 1'b1))begin quotient <= {QUOTIENT_WIDTH{1‘b0}}; end else if (div_cnt_en == 1'b1) begin if (sub_dividend_divsor[DIVISOR_WIDTH+1] == 1'b1 ) begin quotient <= {quotient[QUOTIENT_WIDTH-2:0],1'b0}; end else begin quotient <= {quotient[QUOTIENT_WIDTH-2:0],1'b1}; end end end always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin remainder <= {REMAINDER_WIDTH{1'b0}}; end else if ((div_strt == 1'b1) || (div_clr == 1'b1))begin remainder <= {REMAINDER_WIDTH{1'b0}}; end else if (div_cnt_en == 1'b1 && (div_cnt >= (QUOTIENT_WIDTH)) begin remainder <= sub_dividend_divsor[REMAINDER_WIDTH-1:0] ; end end always @(posedge clk_sys or negedge rst_sys_n) begin if (rst_sys_n == 1'b0) begin div_end <= 1'b0; end else if ((div_strt == 1'b1) || (div_clr == 1'b1))begin div_end <= 1'b0; end else if (div_cnt_en == 1'b1 && (div_cnt >= (QUOTIENT_WIDTH)) begin div_end <= 1'b1 ; end else begin div_end <= 1'b0; end end endmodule 上述二进制移位除法,最大的问题就是运算的Cycle会随被除数位宽增加而增加,因此在除法运算延时要求较高场景,可以使用DW的除法器,DW的除法器包括支持流水插拍的版本,可以帮助提升工作时钟频率。 几种除法器在统一工艺,按照500MHz目标综合,52bits/25bits,数据对比: 除法器 面积(um2) 最大Slack 工作时钟周期 二级制移位除法 1000 -0.4 53 DW_div 43692 -15.42 2 DW_div_pipe 50008 -4.386 4 DW_div_seq 11980 -1.0348 10 综上,大家根据自己实际需求进行除法器实现策略。 接下来讲讲,除数为常数的除法实现,当然除数为变量的方式是兼容常数的运算,只是针对常数运算,通常也有几种方法,供大家参考。 方法一: 把除数转化为小数,采用乘法运算,比如: a[15:0] / 8'd24; 十进制小数转换成二进制小数采用"乘2取整,顺序排列"法。具体做法是:用2乘十进制小数,可以得到积,将积的整数部分取出,再用2乘余下的小数部分,又 得到一个积,再将积的整数部分取出,如此进行,直到积中的整数部分为零,或者整数部分为1,此时0或1为二进制的最后一位。或者达到所要求的精度为止。 然后把取出的整数部分按顺序排列起来,先取的整数作为二进制小数的高位有效位,后取的整数作为低位有效位。 例如:0.7=(0.1 0110 0110...)B 0.7*2=1.4========取出整数部分1 0.4*2=0.8========取出整数部分0 0.8*2=1.6========取出整数部分1 0.6*2=1.2========取出整数部分1 0.2*2=0.4========取出整数部分0 0.4*2=0.8========取出整数部分0 0.8*2=1.6========取出整数部分1 0.6*2=1.2========取出整数部分1 0.2*2=0.4========取出整数部分0 1/24 = 0.04167 = 00001010 0.04167*2 = 0.08334 ========取出整数部分0 0.08334*2 = 0.16698 ========取出整数部分0 0.16698*2 = 0.33336 ========取出整数部分0 0.33336*2 = 0.66672 ========取出整数部分0 0.66672*2 = 1.33344 ========取出整数部分1 0.33344*2 = 0.66688 ========取出整数部分0 0.66688*2 = 1.33376 ========取出整数部分1 0.33376*2 = 0.66752 ========取出整数部分0 a/8'd24 = (a * (00001010)) >> 8 方法二: 现在DC综合工具对于/ 在常数运算时,是可以识别,并且优化效果与方法一相当,所以可以采用 quotient = a/B的方式进行常数除法运算。 方法三: 对于被除数除数范围比较小的情况,可以采用查找表的方式,比如被除数为a[2:0] 除数为3,则通过查找表方式完成,Verilog示例如下: always @(*) begin case(a) 3'h0 : quotient = 3'h0; 3'h1 : quotient = 3'h0; 3'h2 : quotient = 3'h0; 3'h3 : quotient = 3'h1; 3'h4 : quotient = 3'h1; 3'h5 : quotient = 3'h1; 3'h6 : quotient = 3'h1; 3'h7 : quotient = 3'h1; default : quotient = 3'h0; endcase end 网友提问: 这个无符号加法在实现功能的时候,没有考虑a和b的那个数据位宽大,在进行功能实现的时候是不是应该考虑进去?不能直接默认a比较大把? 回答: 是要考虑进去,在我写的Demo 代码里面是考虑了的哈,参考第4行,当然实现的代码,是可以优化,作为兼容A和B任意的位宽为最大的情况,我更新一下,参见第12行: 今天来讲讲有符号数的加法,从无符号的加法章节就提及过,只要加数有一方为有符号数,则和一定是有符号数,重点强调一下,大家千万不要从场景上分析,认为C = A+B一定是>0,则及时A和B有一个是有符号数,那么和就是无符号数,我们只能从电路结构上决定C是无符号,还是有符号,原因是,大家场景分析,往往只是从正常功能场景分析,而忽略了异常场景。 比如 A[1:0]:作为无符号数 2 | . . 1 | . . . 0 —|.——————.———————— |0 1 2 3 4 5 6 B[1:0]:作为有符号数 2 | 1 |. . 0 —|——.————.———————— -1 | . 0 1 2 3 4 C正常为:正常场景,C被当做无符号数,没有问题,与有符号数值一样。 2 | 1 |. . . . . 0 —|—————————————— -1 | 0 1 2 3 4 异常场景,或者说未来B[1:0]信号相位和幅度发生了变化 B[1:0]:作为有符号数 2 | 1 | . 0 —|——.————.———————— -1 |. . 0 1 2 3 4 C[2:0]:作为有符号数(-4~3)波形 3 | . 2 | 1 | . . 0 —|—————————————— -1 |. . 0 1 2 3 4 C[2:0]:作为无符号数(0~8)波形, 0,1 两个坐标点,3,4两个坐标点,就存在很大幅度跳变 7 |. . 6 | 5 | 4 | 3 | . 2 | 1 | . . 0 —|—————————————— -1 | 0 1 2 3 4 所以C应该按照有符号处理,即便,从算法角度,希望C后续按照无符号进行后续计算处理, 也应该是做一个C的有符号到无符号转换,专访方式其实很简单就是,把C最高位取反,上面的里面即 C_UNSIGN = {~C[2],C[1:0]} 这样异常场景,C_UNSIGN的波形为,这样,只是增加直流分量,其幅度仍然没有变化: 7 | . 6 | 5 | . . 4 | 3 |. . 2 | 1 | 0 —|—————————————— -1 | 0 1 2 3 4 上面小节,主要跟大家强调,进行有符号运算,其和一定是有符号的,按照电路结构进行设计,如果根据场景需要把和作为无符号数使用,需要单独进行有符号到无符号转换,这个是电路结构的转换,不是简单定一个$signed去转换类型。有符号加法的Verilog实现形式,推荐两种方式: 方式二: Synopsys推荐,直接定义好符号类型,和的位宽按照运算法则定义好,实际+地方不作位宽匹配,工具自动识别 忘了介绍有符号的比较器的实现,这里给补充一下,有符号比较,两边一定是有符号数,需要统一处理,上一节讲了有符号到无符号的转换,因此,我们可以通过把有符号数,转换成无符号数,然后进行无符号的比较,结果应该是一致的,当然,目前Synopsys的工具也非常先进,我们自动把数据定义为有符号数,在比较时候,加上系统函数$signed就可以自动实现有符号数的比较。Verilog Demo: 下面在聊聊有符号减法,从电路结构上讲,只要涉及到减法,理论上其得到的结果就是应该是一个有符号数,所以大家按照这个原则进行设计就行,如果需要对结果作转换,进行有符号到无符号转换即可,Verilog代码也推荐两种风格: 另外补充一下有符号运算容出现的问题,这种写法是有问题的, |
|
来自: 毕杰lb7q1kq7pr > 《待分类》