电子说
写代码是给别人和多年后的自己看的。 关于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检查工具可能会报出来,最后还是得修改,不如一次到位。
良好的代码风格
写了这么多,总结一起,其实关于代码风格的问题,通过多看书,多看一些代码风格的写法,很多书上都有关于一些常见的风格阐述,多写代码多积累,最后形成自己的代码风格习惯,另外一般公司都会有代码规范的文档,到时候参加工作以后,一定要记得多读几遍。
审核编辑:汤梓红
全部0条评论
快来发表一下你的评论吧 !