什么样的Verilog代码风格是好的风格?

电子说

1.3w人已加入

描述

写代码是给别人和多年后的自己看的。 关于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
 end
case语句也一样,不写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检查工具可能会报出来,最后还是得修改,不如一次到位。

 

良好的代码风格

写了这么多,总结一起,其实关于代码风格的问题,通过多看书,多看一些代码风格的写法,很多书上都有关于一些常见的风格阐述,多写代码多积累,最后形成自己的代码风格习惯,另外一般公司都会有代码规范的文档,到时候参加工作以后,一定要记得多读几遍。 

  审核编辑:汤梓红

打开APP阅读更多精彩内容
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉

全部0条评论

快来发表一下你的评论吧 !

×
20
完善资料,
赚取积分