关注、星标公众号,精彩内容每日送达
来源:网络素材
1.命名规则
用有意义而有效的名字
有效的命名有时并不是要求将功能描述出来,如:
for(i = 0;i < 1024;i = i + 1) Mem[i] <= #132'b0;
用连贯的缩写
采用缩写时应该主义同一信号在模块中的一致性.如:
Addr address Pntr pointer Clk clock Rst reset
用最右边的字符下划线代表低电平有效,高电平有效的信号不得以下划线表示,短暂的有效信号建议采用高电平有效.如:
Rst_ Trdy_ Irdy_
大小写原则
名字一般首字符大写,其余小写(但是parameter/integer定义的数值名可以全部大写),两个单词之间用下划线连接.如:
Data_in Mem_wr Rd_req Sensor_ctrl
全局信号名中应包含信号来源的信息.
如:D_addr[7:2],这里的"D"指明了地址是解码模块(Decoder module)的地址.
同一信号在不同层次应保持一致性
自己定义的常数和类型等用大写表示.如:
parameter CYCLE =100;
避免使用保留字
如:in,out,x,z等不能够作为变量,端口或模块名
添加有意义的后缀,使信号名更加明确,常用的后缀有:
_Clk 时钟信号 _next 寄存前的信号 _z 连到三态输出的信号 _f 下降沿有效的寄存器 _xi 芯片原始输入信号 _xo 芯片原始输出信号 _xod 芯片的漏极开路输出 _xz 芯片的三态输出 -xbio 芯片的双向信号
2.Modules
顶层模块应知识内部模块间的互联
Verilog设计一般都是层次性的设计,也就是在设计中会出现一个或多个模块,模块间的调用在所难免.可把设计比喻成树,被调用的模块就是输液,没被调用的模块就是树根,那么在这个树根模块中,除了内部的互联和模块的调用外,尽量避免再做逻辑,如不能再出现对reg变量赋值等.这样做的目的是为了更有效的综合,因为在顶层模块VS出现中间逻辑,Synopsys的Design Compiler就不能把子模块中的逻辑综合到最优.
每个模块应该在开始处注明文件名,功能描述,引用模块,设计者,设计时间,以及版权信息等.如:
/*==============================================================================================*/ Filename : RX_MUX.v Author : Dongyi Lin Description : Called by : Top module Revision History : 22-05-27 Revision : 1.0 Email : lindongyi@163.com Company : Hunan Institute of Advanced Sensing and Information Technology, Xiangtan Univeristy Copyright : 2022, Xiangtan University, All right reserved /*==============================================================================================*/
不要对Input进行驱动,在module内不要存在没有驱动的信号,更不能在端口模块出现没有驱动的输出信号,避免再仿真或综合时产生warning,干扰错误定位.
每行应限制在80个字符以内,以保持代码的清晰,没关和层次感.
一条语句占用一行,如果超过80个字符则要换行.
电路中调用的module名用Uxx表示.向量大小要表示清晰,采用基于名字的(name_based)调用,而非基于顺序的(order_based).
Instance Uinstance2( .DataOut (DOUT ), .DataIn (DIN ), .Cs_ (Cs_ ), );
时钟的上升沿或下降沿采样信号,不能一会上升沿,一会用下降沿.如果既要用上升沿又要用下降沿,应该分成两个模块设计.建议在顶层模块中对Clock做一个not门,
在层次模块中如果要用时钟下降沿就可以用not门产生的Posedge Clk_,这样的好处是在整个设计中采用同一种时钟出发,有利于综合.在模块中增加注释.
对信号,参量,引脚,模块,函数及进程等加以说明,便于阅读与维护.
Module名称要用大写表示,且应该与文件名保持一致.如:
module DFF_ADSYNC_RST( Reset, Clk, Data, Qout );
要严格芯片级模块的划分
只有顶层包括IO引脚(pads),中间层是时钟产生模块,JTAG,芯片的内核(CORE),这样便于对每个模块加以约束仿真,对时钟也可以仔细仿真.
模块输出寄存器化
对所有的模块的数据加以寄存,使得输出的驱动强度和输入的延迟可以预测,从而使得模块的综合过程更简单.
3.Net and Register
一个reg变量只能在一个always语句中赋值
向量有效位顺序一边为从大到小
推荐Data[4:0]这种格式的定义.对net和register类型的数据要做声明(在PORT中).
4.Expressions
用括号表示执行的优先级,对读者更情绪,更有意义,如:
if(alpha < beta && gamma >= delta)...就不如下面的更好 if((alpha < beta) && (gamma >= delta))
用函数(function)来代替表达式的多次重复
这样在以后的版本升级时更便利,而且经常使用的一组描述可以写到一个任务(task)中
5.IF语句
向量比较时,比较的向量要相等
在向量比较时,verilog将位数小的向量做0扩展以使得他们的长度相匹配,它的自动扩展是隐式的.建议采用显式扩展.如:
reg Abc [7:0]; reg Bca [3:0]; ...... if(Abc == {4'b0,Bca})begin ...... if(Abc == 8'b0)begin
每一个if都应该有一个else与之对应
没有else可能会使得综合出的逻辑和RTL级的逻辑不同,如果条件为假时不尽兴任何操作,则使用一条空语句,如:always@(Cond)begin if(Cond) DataOut <= DataIn; end //以上语句DataOut会综合出锁存器.
如果变量在if-else语句或case语句中没有被完全赋值,则应该提前给变量一个缺省值,即:
V1 = 2'b00; V2 = 2'b00; V3 = 2'b00; //给V1,V2,V3缺省值,在后面赋值变量时有一个默认值在 if(a == b)begin V1 = 2'b01; V2 = 2'b10; //V3 isnot assigned, so the default value of V3 is2'b00; end elseif(a == c)begin V2 = 2'b10; V3 = 2'b11; //V1 isnot assigned, so the default value of V1 is2'b00; end ...
6.case语句
case语句通常被综合为一级多路复用器,而if-then-else语句则综合为优先编码的串接的多个多路复用器.通常case语句比if语句快,有限编码结果在信号到达时有先后.case语句仿真比条件语句快.
所有的case语句都应该有一个default case,且允许空语句出现如:
default: ;
7.Function
在function的最后给function赋值,如:
function CompareVectors; input [199:0] Vector1; input [199:0] Vector2; input [31:0] Length; //local variables integer i; reg Equal; begin i = 0; Equal = 1; //给Equal赋初值 while((i < Length)&& Equal)begin if(Vector2[i] !== 1'bx)begin if(Vector1[i] !== Vector2[i]) Equal = 0; else; end i = i + 1; end CompareVectors = Equal; //赋值放在function的最后 endendfunction
在function中避免使用全局变量
否则容易引起HDL行为及仿真和门级仿真的差异.如:
function ByteCompare; input [15:0] Vector1; input [15:0] Vector2; input [7:0] Length; begin if(ByteSel) //ByteSel是全局变量,如果在其他位置无意修改了,可能导致函数结果错误, //所以最好在端口加以定义 ... else ... end endfunction
注意,函数与任务的调用均为静态调用.
8.Assignment
Verilog有两种赋值方式:过程赋值(procedural)和连续赋值(continuous).过程复制用于过程代码(initial,always,task,function)中给reg和integer变量,time\realtime\real复制,而连续赋值一般给wire变量赋值.
always@(敏感表),敏感表要完整,如果不完整,将会引起仿真和综合结果不一致,如:
always@(d or Clr) if(Clr) q = 1'b0; elseif(e) q = d; //以上语句在行为及仿真时e的变化不会使仿真器进入该always块,导致仿真结果错误.
assign/deassign仅用于仿真加速,仅对寄存器有用.
force/release仅用于debug,对寄存器和线网型都有用.
避免使用disable
对任何reg赋值,都用非阻塞赋值(<=)代替阻塞赋值(=),reg的非阻塞赋值要加单位延迟,但异步复位可加可不加.如:
always@(posedge Clk or negedge Rst_)begin if(!Rst_)begin Rega <= 0; //non_blocking assignment Regb <= 0; end elseif(Soft_rst_all)begin Rega <= #u_dly 0; //add unit delay Regb <= #u_dly 0; end elseif(Load_init)begin Rega <= #u_dly init_rega; Regb <= #u_dly init_rega; end elsebegin Rega <= #u_dly Rega << 1; Rega <= #u_dly St_1; end end //end Rega,Regb assignment
9.Combinatorial vs Sequential Logic
- 如果一个事件持续几个时钟周期,设计时就用时序逻辑代替组合逻辑.
如:
wire Ct_24_e4; //Ct_24_e4 last over several clock cycles assign Ct_24_e4 = (count8bit[7:0] >= 8'h24) & (count8bit[7:0] <= 8'he4);
这种设计将综合处两个8bit加法器,而且会产生毛刺,对于这种电路,要采用时序设计,代码如下:
reg Ct_24_e4; always@(posedge Clk ornegedge Rst_)begin if(!Rst_) Ct_24_e4 <= 1'b0; elseif(count8bit[7:0] > 8'he4) Ct_24_e4 <= #u_dly 1'b0; elseif(count8bit[7:0] > 8'h23) Ct_24_e4 <= #u_dly 1'b1; else ; end
内部总线不要悬空.在default状态,要把他上拉或下拉.
wire OE_default; assign OE_default = !(oe1 | oe2 | oe3); assign bus[31:0] = oe1 ? Data1[31:0]: oe2 ? Data2[31:0]: oe3 ? Data3[31:0]: OE_default ? 32'h0000_0000: //如果bus不等于oe1,oe2,oe3中的任何一个, //若等于OE_default,则bus为32'h0即拉低,否则拉高为高阻态. 32'hzzzz_zzzz;
10.Macros 宏指令
为了保持代码的可读性常用`define做常数声明
把`define放在一个独立的文件中
参数(parameter)必须在一个模块中定义,不要传递参数到模块(仿真测试向量例外);
define可以在任何地方定义,要把所有的
define定义在一个文件中(极少的一个两个define就不用了吧),在编译源代码时首先把这个文件读入.如果希望宏的作用于仅在一个模块中,就用参数来代替.
11.Comments
对更新的内容要做注释
在语法块的结尾做标记
每一个模块都应该在模块开始处做模块级的注释(参考前面的标准模块头)
在端口列表中出现的端口信号,都应该做简要的功能描述.
12.FSM 状态机
状态机的状态分配
Verilog描述状态机时必须有parameter分配好状态.
组合逻辑和时序逻辑分开用不同的进程
组合逻辑包括状态译码和输出,时序逻辑则是状态寄存器的切换
必须对所有状态都处理,不能出现无法处理的状态,使状态机时空
Mealy状态机输出不仅取决于当前状态,还与输入有关;Moore状态机输出仅与当前状态有关.
Mealy状态机的例子如下:
... reg CurrentState,NextState,Out1; parameter S0 = 0, S1 = 1; always@(posedge Clk ornegedge Rst_) if(!Rst_) CurrentState <= S0; else CurrentState <= #u_dly NextState; always@(In1 or In2 or CurrentState) case(CurrentState) S0:begin NextState <= #u_dly S1; Out1 <= #u_dly 1'b0; end S1:begin if(In1)begin NextState <= #u_dly S0; Out1 <= #u_dly !In2; end end endcase
13.Module 编写示例
/*==============================================================================================*/ Filename : module_name.v Author : Dongyi Lin Description : Called by : Top module Revision History : 22-05-27 Revision : 1.0 Email : lindongyi@163.com Company : Hunan Institute of Advanced Sensing and Information Technology, Xiangtan Univeristy Copyright : 2022, Xiangtan University, All right reserved/*==============================================================================================*/module module_name( Output_ports, //comment:port description Input_ports, //comment:port description Io_ports, //comment:port description Clk_port, //comment:port description Rst_port //comment:port description);//port declarations output [31:0] Dataout; input [31:0] Datain; inout Bi_dir_siginal; input input1; input2; //interrnal wire/reg declarations wire [31:0] internal_data; reg output_enable; //module instantiations, Self-build module module_name1 Uinstance_name1( .port1(...); .port2(...); ); module_name2 Uinstance_name2( .port1(...); .port2(...); ); //TSC4000 cell DTC12V1( .Clk(Clk), .CLRZ(Clr), .D(Data), .Q(Qout) );//always block always@(input2)begin ... end//function and task definitions function [function_type] function_name; declarations_of_inputs; [declarations_of_local_variables]; begin behavirol_statement; function_name = function_express; end endfunction endmodule
14.Testbench 编写示例
下面是一个格雷码的测试模块:
module TB_GRAY; reg Clock; reg Reset; wire [7:0] Qout; integer fout; //输出文件指针 parameter CYC = 20; GRAY DUT( .Clock(Clock), .Reset(Reset), .Qout(Qout) ); initial begin Clock = 1'b0; Reset = 1'b1; #(5 *CYC) Reset = 1'b0; #(5 *CYC) Reset = 1'b1; #(5000 *CYC); $fclose(fout); $finish; end initial begin $shm_open("GRAY.shm"); $shm_probe("AS"); fout = $fopen("gray.dat"); end always #(CYC) Clock = ~Clock; //输出数据到文件gray.dat always@(posedge Clock)begin $fwrite(fout,"%d %b\n", Qout, Qout); end endmodule
在testbench中避免使用绝对的时间,如#20,#15或#(CYC + 15)等,应该在文件前端使用parameter定义一些常量,使得时间的定义像#(CYC + OFF0)这样的形式,便于修改;
观测结果可以输出到波形文件GRAY.shm,或数据文件gray.dat.生成波形文件可以使用simwave或gtkwave观测结果,比较直观;而生成数据文件则既可以快速定位,也可以通过编写的小程序工具对它进行进一步的处理;
对大的设计的顶层方针,一般不要对所有信号进行跟踪,大的设计波形文件会很大,仿真时间也会延长,可以有选择的观测一些信号.
(全文完)
想要了解FPGA吗?这里有实例分享,ZYNQ设计,关注我们的公众号,探索