电子说
写代码是给别人和多年后的自己看的。 关于Verilog代码设计的一些风格和方法之前也写过一些Verilog有什么奇技淫巧?
模块化设计
把所有的代码都写到一个模块里,也不是一个好的风格。 积累自己的小IP,在芯片设计阶段,就将功能模块划分仔细,划分清楚,可复用的功能做成一个可参数化IP。搭建起你的数字积木。
对齐
把编辑器设置成tap自动替换成2/4个空格。 用空格对齐代码,提高代码观赏性。
//case0 input clk; input rst_n; inout in; output [6:0] out; //case1 input clk ; input rst_n ; inout in ; output [6:0] out ;连分号也要对齐的对齐狂魔,大可不必,徒增功耗。 关于提高Verilog代码编写效率的Gvim插件本订阅号之前也分享过,I/O端口按如上所示风格编写好后,直接可以生成端口列表。直接写assign和always块语句,也可以直接生成定义。省去手动定义的麻烦。
括号
用括号将表达式的条件括起来,让层次关系更清晰,一些情况不括起来功能上也没有问题,但是会引起阅读者的歧义和可读性差。善用括号,避免阅读歧义和工具理解歧义。
assign flag = cnt == 2'd1 && (mode != 2'd1 && (|v_shift) || cnt2 < 8'd15); assign flag = (cnt == 2'd1) && ((mode != 2'd1) && (|v_shift) || (cnt2 < 8'd15));
能少写则少写
always @(posedge clk or negedge rst_n) begin if(!rst_n) begin out <= 0; end else if(en)begin out <= in; end else begin out <= out; end end always @(posedge clk or negedge rst_n) begin if(!rst_n) out <= 0; else if(en) out <= in; end只有单行语句可以省略去begin-end,一个always块只对一个变量操作。else分支如果是保持的话,可以省略。还有begin的位置...,能少写一行算一行。 注意,在组合逻辑中else分支不写会产生锁存器,写成了保持就是组合逻辑环,这一点要和时序逻辑分开。时序逻辑中else分支不写代表保持。
always @(*)begin case(in[1:0]) 2'd0 : data[1:0] = 2'd0; 2'd1 : data[1:0] = 2'd1; 2'd2 : data[1:0] = 2'd2; default : data[1:0] = 2'd3; endcase endcase语句也一样,不写default分支会产生锁存器,如果case中的所有情况都达到,就可以不用写default分支,但在ASIC设计中可能工具会报lint,所以这样的写法是最完美的。
FSM
状态机采用三段式
小Tips,在写状态机时,将第二段需要保持状态循环的状态写法,写成这样的写法,可以省去很多else的分支。 万物基于状态机——状态机大法好
assign语句慎用
assign语句+三目运算符/与或门逻辑慎用
assign data_out[5:0] = data_vld0 ? data0[5:0] : data_vld1 ? data1[5:0] : data_vld2 ? data2[5:0] : data_vld3 ? data3[5:0] : 6'b0; assign data_out[5:0] = ({6{data_vld0}} & data0[5:0]) | ({6{data_vld1}} & data1[5:0]) | ({6{data_vld2}} & data2[5:0]) | ({6{data_vld0}} & data3[5:0]);这两种写法一个带有优先级另一个不带优先级,本身没什么问题。不过覆盖率的expression中会将这个表达式中的所有条件满足与否列出来。 其实很多情况是不可能出现的,比如这几个vld同时1,data为0的情况组合,但是这就需要designer一个个去waive掉,然后写出原因,选择的语句少了还好,但是如果非常多,waive起来工作量还是很大的,徒增功耗。 这样的问题可在开发阶段就避免。
always @(*)begin if(data_vld0) data_out[5:0] = data0[5:0]; else if(data_vld1) data_out[5:0] = data1[5:0]; else if(data_vld2) data_out[5:0] = data2[5:0]; else if(data_vld3) data_out[5:0] = data3[5:0]; else data_out[5:0] = 6'd0; end写成这样,所有条件跑到就可以了。并行的语句用case。用与门和或门做的表达可能会省一些cell,但是比起后来waive时漫长的体力活,省这一毛两毛的干啥。 看过一本书上的写法,将所有的组合逻辑都用assign做,把D触发器都做成IP,直接需要打拍的时候再调用。这样的思想很好,准确的将组合逻辑和时序逻辑分开,从代码到电路。 作者提到另一个原因是由于if-else和case不能传播不定态,有的EDA工具有X态传播选项,可以强行传播,一般也需要license,但并不是所有的EDA工具都有这个功能。 但是我个人(浅薄的)认为这样的风格不适合所有人。比如上面提到的覆盖率问题,还有代码的可读性问题,assign式的写法可读性会变差。 直接在声明wire时就给变量赋值这样的写法,连spyglass都过不了。还是先声明再用吧。 关于工具的license问题,看看公司怎么给解决。
乘法器分时复用
一个设计要考虑PPA最优,就要考虑乘法器的数量多少以及复用能不能最大化,追求最好的设计是整个数据通路中乘法器空闲不下来。 乘法器调用方法,一般是在乘法器的输入保证寄存器输入,结果输出到各个复用模块时打一拍再使用。可以做成在进行完乘法运算后,就打拍,这样消耗的寄存器会少很多。画个图意思一下(单bit)。
修改前
修改后
修改完后的寄存器省了很多,但是乘法器的输出寄存器负载会变大,不过后端综合时约束了max_fan_out工具会自动插buffer和复制寄存器,经过实测还是会节省很多面积,把一些优化工作可以交给工具去做,了解它,信任它,使用它。
数据位宽写全
assign dout = din; always @(posedge clk or negedge rst_n)begin if(!rst_n) dout[255:0] <= 'd0; else dout[255:0] <= din; end写成这样
assign dout[3:0] = din[3:0]; always @(posedge clk or negedge rst_n)begin if(!rst_n) dout[255:0] <= 256'd0; else dout[255:0] <= din; end写代码时将数据位宽写全,方便编辑器插件自动生成定义,而且写上位宽再后续阅读代码时能一眼看变量的位宽,提高阅读效率。 寄存器赋初始值 'd0 'h0等等,功能应该没有问题,这样写工具会默认是最大32bit??lint检查工具可能会报出来,最后还是得修改,不如一次到位。
良好的代码风格
写了这么多,总结一起,其实关于代码风格的问题,通过多看书,多看一些代码风格的写法,很多书上都有关于一些常见的风格阐述,多写代码多积累,最后形成自己的代码风格习惯,另外一般公司都会有代码规范的文档,到时候参加工作以后,一定要记得多读几遍。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !