EDABOSS电子论坛

 找回密码
 立即注册
搜索
热搜: 活动 交友 discuz
查看: 568|回复: 0

[转帖] Verilog技巧

[复制链接]

29

主题

6

回帖

226

E币

技术员

Rank: 2

积分
65
发表于 2020-4-11 13:48:34 | 显示全部楼层 |阅读模式
本帖最后由 隔壁老王 于 2020-4-11 13:52 编辑

数字电路设计主要就是,选择器、全加器、比较器,几个常用逻辑门,再加个D触发器,电路基本都能实现了。写代码其实是个体力活,电路和时序图应该在设计阶段就已经到了你的文档里或在脑子里没来得及写出来。
组合逻辑+时序逻辑
assign或always@(*)
always@(posedge clk or negedge rst_n)
有人说掌握Verilog 20%的语法就可以描述 90%以上的电路,说的对。
casez

  1. always @(*)begin
  2.     casez(code)
  3.         8'b1???_???? : data[2:0] = 3'd7;
  4.         8'b01??_???? : data[2:0] = 3'd6;
  5.         8'b001?_???? : data[2:0] = 3'd5;
  6.         8'b0001_???? : data[2:0] = 3'd4;
  7.         8'b0000_1??? : data[2:0] = 3'd3;
  8.         8'b0000_01?? : data[2:0] = 3'd2;
  9.         8'b0000_001? : data[2:0] = 3'd1;
  10.         8'b0000_0001 : data[2:0] = 3'd0;
  11.       default : data[2:0] = 3'd0;
  12.     endcase
  13. end
复制代码

这样的case有优先级选择,可综合,实际项目可以使用,不过我个人习惯上还是,有优先用if-else,没有直接用case。
synopsys的EDA工具有关于full case与parallel case可以查看下面博客链接。
https://blog.csdn.net/li_hu/article/details/10336511
generate+for

合理使用generate+for循环可以提高编码效率,同样的赋值语句需要赋值多次。
  1. generate
  2.     genvar i;
  3.     for(i=0;i<16;i=i+1)
  4.         begin: neg_data
  5.             assign neg_data_out[i*DATA_WIDTH +:DATA_WIDTH] =
  6.                 -data_in[i*DATA_WIDTH +:DATA_WIDTH]
  7.         end
  8. endgenerate
复制代码

同一个模块需要实例化多次
  1. generate
  2.     genvar i;
  3.     for(i=0;i<16;i=i+1)
  4.         begin: mult_12x12
  5.             DW02_mult #(
  6.                 .A_WIDTH(12),
  7.                 .B_WIDTH(12)
  8.             ) u_DW02_mult0(
  9.                 .A(mult_a[i*12 +:12]),
  10.                 .B(mult_b[i*12 +:12]),
  11.                 .TC(1'b0),
  12.                 .PRODUCT(product[i*24 +:24])
  13.             );
  14.         end
  15. endgenerate
复制代码

当然这样写debug会有一些困扰,Verdi会显示每一个generate块,选中对应的块,加进去的波形就会是对应的bit信号。
generate if/case

做一些通用IP的方法,随便举个例子比如要做一个选择器通用IP,支持二选一,三选一,四选一。
  1. generate if(MUX_NUM == 0)begin : mux4_1
  2.     always@(*)begin
  3.         case(sel[1:0])
  4.              2'b00:data_out = data_in0;
  5.             2'b01:data_out = data_in1;
  6.             2'b10:data_out = data_in2;
  7.             default:data_out = data_in3;
  8.         endcase
  9.     end
  10. end else if(MUX_NUM = 1) begin : mux3_1
  11.     always@(*)begin
  12.         case(sel[1:0])
  13.              2'b00:data_out = data_in0;
  14.             2'b01:data_out = data_in1;
  15.             default:data_out = data_in2;
  16.         endcase
  17.     end
  18. end else begin : mux2_1
  19.     always@(*)begin
  20.         case(sel[1:0])
  21.              2'b00:data_out = data_in0;
  22.             default:data_out = data_in1;
  23.         endcase
  24.     end
  25. end endgenerate
  26. generate case可以写更多的分支
  27. generate
  28.     case(MUX_NUM)
  29.         0:begin:mux_2
  30.         end
  31.         1:begin: mux_3
  32.         end
  33.         2:begin: mux_4
  34.         end
  35.         default:begin
  36.         end
  37.     endcase
  38. end endgenerate
  39. 调用的时候只需要
  40. mux #(
  41.     .MUX_NUM(0)
  42. )
  43. u_mux(
  44.     ...
  45. );
复制代码

参数化定义

模块化设计,功能模块的划分尽可能细,差别不大的代码通过参数化达到重复使用的目的。
  1. always @(*)begin
  2.     case(sel)
  3.         CASE0:data_out = data_in0;
  4.         CASE1:data_out = data_in1;
  5.         CASE2。。。
  6.         default:;
  7.     endcase   
  8. end
复制代码

实例化
  1. mux #(
  2.     .CASE0(8'd11),
  3.     .CASE1(8'd44)
  4.     ...
  5. )
  6. u_mux(
  7.   ...
  8. );
复制代码

移位操作

对于移位操作直接用位拼接
  1. assign data_shift[6:0] = data[4:0] << 2;
  2. assign data_shift[7:0] = data[4:0] << shift[1:0];
复制代码

写成
  1. assign data_shift[6:0] = {data[4:0], 2'b0};
  2. always @(*)begin
  3.     case(shift[1:0])
  4.         2'b00: data_shift[7:0] = {3'b0, data[4:0]};
  5.         2'b01: data_shift[7:0] = {2'b0, data[4:0], 1'b0};
  6.         2'b10: data_shift[7:0] = {1'b0, data[4:0], 2'b0};
  7.         default:data_shift[7:0] = {data[4:0], 3'b0};
  8.     endcase
  9. end
复制代码

如果是有符号数,高位要补符号位。也就是算术移位。
  1. always @(*)begin
  2.     case(shift[1:0])
  3.         2'b00: data_shift[7:0] = {{3{data[4]}}, data[4:0]};
  4.         2'b01: data_shift[7:0] = {{2{data[4]}}, data[4:0], 1'b0};
  5.         2'b10: data_shift[7:0] = {data[4], data[4:0], 2'b0};
  6.         default:data_shift[7:0] = {data[4:0], 3'b0};
  7.     endcase
  8. end
复制代码

shift也可能是有符号数,正数左移,负数右移。右移方法同理。
$clog2系统函数

Verilog-2005引入了$clog2系统函数,为了方便计算数据位宽,避免位浪费。(这个是拿来凑字数的)
  1. parameter   DATA_WIDTH = 4,
  2. parameter   CNT_WIDTH  = log2(DATA_WIDTH)
  3. parameter   CNT_WIDTH  = clog2(DATA_WIDTH-1)
  4. parameter   CNT_WIDTH  = $clog2(DATA_WIDTH)

  5. reg   [DATA_WIDTH-1:0]   data_r0;

  6. reg     [CNT_WIDTH-1:0]   cnt;

  7. //-------------------------------------------------------
  8. //以下两个函数任用一个
  9. //求2的对数函数
  10. function integer log2;
  11.   input integer value;
  12.   begin
  13.     value = value-1;
  14.     for (log2=0; value>0; log2=log2+1)
  15.       value = value>>1;
  16.   end
  17. endfunction

  18. //求2的对数函数
  19. function integer clogb2 (input integer bit_depth);
  20. begin
  21.     for(clogb2=0; bit_depth>0; clogb2=clogb2+1)
  22.         bit_depth = bit_depth>>1;
  23. end
  24. endfunction
复制代码

对齐

tab键还是空格键?留言区说出你的故事。我把编辑器设置成tab自动替换成4个空格。
用空格对齐代码,提高代码观赏性。
  1. assign signal_b = signal_a;
  2. assign data_b = data_a;
  3. assign cs_en = 1'b1;

  4. assign signal_b = signal_a;
  5. assign data_b   = data_a;
  6. assign cs_en    = 1'b1;
复制代码

第二种写法更美观,always块里面的语句也应该对齐。
命名

给模块起名字,给信号起名字,真的很难,但是不管怎样都不要用拼音,会遭人鄙视。是的,我见过!

阶梯式assign

  1. assign data_out[5:0] = data_vld0 ? data0[5:0] :
  2.     data_vld1 ? data1[5:0] :
  3.     data_vld2 ? data2[5:0] :
  4.     data_vld3 ? data3[5:0] : 6'b0;
复制代码

由于if-else和case不能传播不定态,有的EDA工具有X态传播选项,可以强行传播,但是并不是所有的EDA工具都有这个功能,所以有些书上建议都用组合逻辑用assign。
这种写法没什么问题,但是有一点,覆盖率不好收,如果一些情况没跑到需要一个个分析。覆盖率会把数据信号当作一个情况列出来,比如数据信号data没出现过0 的情况,实际上数据信号没出现0的情况是正常的,这就要你一个一个的exclude掉。
所以不要写很长的assign做选择器,有优先级用if-else,或根据具体情况用case。这样哪一行哪一种情况没跑到会一目了然。当然if中的条件太多,覆盖率也不好收,条件太多组合的情况多,分析起来繁琐。如果上述信号的vld不同时出现也可以采用这种写法,减少cell的使用数量。这样也是有覆盖率的问题,这只是一种特殊情况,很长的assign选择器尽量不要写。
  1. assign data_out[5:0] = ({6{data_vld0}} & data0[5:0])
  2.     | ({6{data_vld1}} & data1[5:0])
  3.     | ({6{data_vld2}} & data2[5:0])
  4.     | ({6{data_vld0}} & data3[5:0]);
复制代码

关于X态传播,一定要注意,带有reset的寄存器面积和时序会稍微差一些,控制通路的寄存器必须带有复位,数据通路的寄存器可以不带复位,但是要注意使用时如果使用数据通路的数据去做了控制条件,就必须要复位,否则如果X态没有查出来,事情就大了。
布线太密的原因

寄存器位宽太大。
reg [10000-1:0] data;
这样写在功能上没什么问题,但是如果你之后有对这个数据做了很多逻辑,可能会造成后端布线太密,从后端的角度看到其实cell数量并不多,就是线比较密,比如说这个数据后面再放个选择器,或者输出给其他模块,就相当于一万根线连到很多地方,布线很紧张,如果时序有问题需要绕线,或者需要ECO,做成的可能性很小。
尽量不要这样做逻辑,除非对面积没限制,要么最后只能改架构。
第二个原因是负载太大。同一个信号在很多地方使用,布线也会变复杂,比如最常见的是参数信号,在很多模块都会有用到的情况,用寄存器复制的方法。
  1. always @(posedge clk or negedge rst_n)begin
  2.     if(!rst_n)begin
  3.         data_para0 <= 4'b0;
  4.         data_para1 <= 4'b0;
  5.         data_para2 <= 4'b0;
  6.     end
  7.     else begin
  8.         data_para0 <= data_para;
  9.         data_para1 <= data_para;
  10.         data_para2 <= data_para;            
  11.     end
  12. end
复制代码

画俩图大概意思一下。这样每个寄存器的驱动变少。

1.png
2.png

有网友提到这样子写被综合掉的概率也是很大。所以就只能在设计时尽量注意负载的问题。
加比选

面积:加法器 > 比较器 > 选择器
乘法器本质上也是全加器。
所以就有先选后比,先选后加,先选后乘。
  1. assign sum[4:0] = enable ? (data_a + data_b) : (data_c + data_d);

  2. assign add_a[3:0] = enable ? data_a : data_c;
  3. assign add_b[3:0] = enable ? data_b : data_d;
  4. assign sum[4:0]   = add_a + add_b;
复制代码

画个图意思一下。

3.png
4.png


数据通路与控制通路

数据通路打拍可以不带复位,带着使能信号去打拍,减少信号翻转,减少功耗。保证数据用的时候不是X态,
组合逻辑路径是否需要插入pipeline,插入pipeline的位置需要注意。寄存器能少用就少用。
尽量不要用除法,首先除法器面积更大,除法也会有余数,余数是否需要保留就很麻烦。除以常数可以做成乘以定点常数的方法。
乘以常数用移位加,也可直接用*号。例如a * 2‘d3,工具会帮你优化成 a << 2’d1 + a。甚至可能优化得更好。(杠:不要过度依赖工具)。关于用移位加还是*号的问题,博主做过综合后的面积对比,相对来说,工具还是优化那么一点点。直接用 * 号吧。
尽量不要用减法,减法要考虑到减翻的问题,尽量用加法。
方案设计

方案最重要,一个好的方案往往事半功倍。
状态机设计要状态明确,一个状态尽量只做一件事情。状态机大法好。
做成IP化设计,功能分割尽量独立并可复用性,相同的功能用同一块IP,保证IP的没问题,最后像搭积木一样,搭建起数字系统。多积累些常用IP,常用的一些写法的代码。
积分规则
回复

使用道具 举报

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

Archiver|手机版|小黑屋|EDABOSS电子论坛

GMT+8, 2024-4-25 08:13 , Processed in 0.044394 second(s), 23 queries .

Powered by Discuz! X3.4

© 2001-2023 Discuz! Team.

快速回复 返回顶部 返回列表