小生才疏学浅,孤陋寡闻,下文若有不当之处,还请赐教
1 代码编写
1. 编写规范
- 排版
input,output,inout,reg,wire,parameter不缩进,且每次声明各占一行;
always,task,function不缩进;
输入输出信号的宽度定义与关键字之间,信号名与宽度间用空格分开;相同类型的定义对齐;
always中一般要用begin end区分,if else中仅有一个语句时,不使用begin end - 命名风格
端口,信号,变量名所用字母小写,若含义较复杂,可分段描述(三段),如tx_data_val;
函数名,宏定义,参数定义用大写;
低电平有效信号,后加"_n"
无条件寄存的寄存信号在原信号上加ff1,ff2...如data_in_ff1;
不能用reg作为后缀名,因为综合工具会给寄存器自动加_reg,此时会扰乱网表可读性 - 表达式书写
在长式子中适当添加括号;
赋值时需给常数注明比特宽度 - 条件判断语句
if else必须配合使用,case中也要添加default - FSM
状态机的时序逻辑部分和组合逻辑部分要分开写
推荐使用三段式写法 - 注释
最好每个always前都要加注释
2.Verilog硬件描述编写
- 数据类型
Verilog里面出来parameter,整形等类型外,硬件电路类型只有reg和wire两种。输出尽量设置成reg类型,采用寄存器输出方式。原因是组合逻辑输出存在毛刺,寄存器输出非常干净,毛刺较少 - 常量和数组
- 定义数字常量
由于verilog中没有定义大小(size)整数缺省为32位,因此定义数字最好用完整形式 如8’d50 - 定义数组
memory 型数据:在Verilog中通过对reg数据建立数组来对存储器进行建模,verilog中没有多维数组存在,memory型数据是通过扩展reg型数据的地址范围实现的。存储器的地址索引必须是常数表达式(n-1,m-1等必须是常量,符号常量也可以)。如:reg [n-1:0] array [0:m-1]; //包含了m个n位数据的数组array。
在这里[n-1:0]定义了存储器中每一个存储单元的大小,即n位寄存器。array名后面的[0:m-1],表示定义的存储器中有多少个这样的寄存器。
- 条件说明语句(if-else/case)
条件说明语句必须放在always和Initial过程块内部;同时语句必须完整if-else/case-default,否则可能会综合出锁存器。
if else和case的选择:
if else的综合是由与或非门构成的,也可能一组多路选择器;case结构的综合是由一条多路选择器组成。
对于要写出平行结构的条件,优先写成case结构,例如地址译码等;而条件之间由重复和嵌套的情况则写成if else结构
- 过程赋值语句:阻塞赋值(=)和非阻塞赋值(<=)
如果后面的赋值语句要等前面的赋值语句完成才执行,称为阻塞;如多条语句同时执行,则称为非阻塞。
(1) always块中时序电路建模时,用非阻塞赋值。时序电路中,对于阻塞赋值,硬件没有对应的电路,因而综合结果未知
(2) always块中建立时序和组合逻辑电路时,用非阻塞赋值。
(3) always块建立组合逻辑模型时,用阻塞赋值。
(4) 在同一个always块中不要既用非阻塞赋值又用阻塞赋值。
(5) 不要在一个以上的always块中为同一个reg变量赋值。
- 某些特殊符号
- {}大括号
(1){ }表示拼接,{第一位,第二位...};
(2)、{{ }}表示复制,{4{a}}等同于{a,a,a,a}; - $ 表示系统任务和函数
在系统任务名称前加$使之与用户定义的任务和函数相区分,比如常用的$display(显示信号值),$monitor(监视信号值),$time(返回当前仿真时间),$stop(暂停仿真),$finish(结束仿真)等 - #
(1) #(a,b,c.......)表示改变参数型常量(parameter)的值。
(2) #10 表示延时10个时间单位。 - ^ 异或
- === / !==
判断全等或不等,可包含x,z判断
3.testbench编写
在RTL代码编写结束后,需要对其编写testbench完成对待测设计的例化,测试代码的封装,提供测试激励,收集测试结果。如图为testbench结构。testbench是对DUT进行测试的方案描述文件,因此模块没有输入输出,用到的语句也是不可综合的
(1)语句块
- task
执行消耗时间的电路,测试文件中主要的测试任务内容。
task apb_write(input [31:0] addr, input [31:0] wdata );
... //无需begin-end
endtask
- function
函数功能,function和task都是描述功能块,但function无时序控制,即不消耗时间,通常做一些声明,创建的操作。
function 类型 name(); //int void等
...
return 类型值; //缺省的返回值是与函数名name同名的变量值;void无return
endfunction
- fork...join / join_any / join_none
fork引导的并行块,其特点:
(1)并行块中所有语句同时执行。join是当按时间时序排序在最后的语句执行完或者执行disable语句时,程序流程跳出程序块;join_any当任意语句执行完后,程序跳出;join_none是fork块和外部程序同时执行
(2)块内每条语句的执行时间是相对于程序流程进入到块内的仿真时间的
(3)延时时间是用来给赋值语句提供执行时序的
fork
块内声明语句 //块内声明语句可以是参数说明语句、 reg型变量声明语句、 integer型变量声明语句、
//real型变量声明语句、time型变量声明语句、事件(event)说明语句。
......
join
wait_fork; //等待所有fork进程执行完毕
disable_fork; //中断所有fork进程
(2)语句
- `timescale
描述时间精度
`timescale 10ns/1ns //模块test的时间单位为10ns、 时间精度为1ns
parameter d=1.55; //根据时间精度,参数d值被从1.55取整为1.6。
#10 creg = areg; //在两条赋值语句间延迟10个时间单位。
- $display()
打印信息 - $monitor($time, “a = %b, b = %h”, a, b)
当信号a或b的值发生变化时,系统任务$monitor显示当前仿真时间,信号a值(二进制), 信号b值(16进制)。 - readmenmh()
文件操作
reg [31:0] memory [1023:0]; //1K word字长的存储单元
$readmemh("file1", memory); //把file1中的内容读1K word到memory中去
- event 触发事件
event A, B; //声明
#10 ->A; //触发A
@(A); //等待A触发,触发信号为冲击信号,
wait( A.triggered ); //触发时会形成一小段电平信号
wait_order(A, B); //触发顺序A>B
2 verilog代码优化
- 计算方法
- 尽量使用移位操作或加法器替代乘法器
- 尽量用选择器替代加法器
- 路径
- 输入离输出尽可能近,避免关键信号路径过长
3 代码执行注意点
1. wire和reg型被综合的情况
区分在组合逻辑电路和时序逻辑电路综合情况,组合逻辑主要是由门级网表构成的,时序逻辑主要由门级网表和D触发器构成。
2. 冒险和竞争
一个逻辑门的两个输入端的信号同时向相反方向变化,而变化的时间有差异的现象称为竞争。
由竞争而可能产生输出干扰脉冲的现象称为冒险。
3.语句执行顺序
verilog是描述硬件的语言,因此对于代码的执行需要根据电路来理解。always之间均是并行执行;begin-end内是顺序执行,但如果里面是非阻塞赋值(<=),该赋值部分则是并行执行
4. 不可综合的verilog
描述事件的语句,无法用门来实现,如:
=== !== / % delay Initial Repeat forever wait fork join event
5.全局时钟资源语句
大型设计一般推荐使用同步时序电路。同步时序电路基于时钟触发沿设计,对时钟的周期、占空比、延时和抖动提出了更高的要求。为了满足同步时序设计的要求,一般在FPGA设计中采用全局时钟资源驱动设计的主时钟,以达到最低的时钟抖动和延迟。与全局时钟资源相关的原语常用的与全局时钟资源相关的Xilinx器件原语包括:IBUFG、IBUFGDS、BUFG、BUFGP、BUFGCE、BUFGMUX、BUFGDLL和DCM
IBUFG:输入全局缓冲,是与专用全局时钟输入管脚相连接的首级全局缓冲。所有从全局时钟管脚输入的信号必须经过IBUF元,否则在布局布线时会报错。 IBUFG支持AGP、CTT、GTL、GTLP、HSTL、LVCMOS、LVDCI、LVDS、LVPECL、LVTTL、PCI、PCIX和 SSTL等多种格式的IO标准。
IBUFGDS:IBUFG的差分形式,当信号从一对差分全局时钟管脚输入时,必须使用IBUFGDS作为全局时钟输入缓冲。
BUFG:全局缓冲,它的输入是IBUFG的输出,BUFG的输出到达FPGA内部的IOB、CLB、选择性块RAM的时钟延迟和抖动最小。
BUFGCE:带有时钟使能端的全局缓冲。它有一个输入I、一个使能端CE和一个输出端O。只有当BUFGCE的使能端CE有效(高电平)时,BUFGCE才有输出。
BUFGMUX:全局时钟选择缓冲,它有I0和I1两个输入,一个控制端S,一个输出端O。当S为低电平时输出时钟为I0,反之为I1。需要指出的是BUFGMUX的应用十分灵活,I0和I1两个输入时钟甚至可以为异步关系
BUFGP:相当于IBUG加上BUFG。
BUFGDLL:全局缓冲延迟锁相环,相当于BUFG与DLL的结合。BUFGDLL在早期设计中经常使用,用以完成全局时钟的同步和驱动等功能。随着数字时钟管理单元(DCM)的日益完善,目前BUFGDLL的应用已经逐渐被DCM所取代。
DCM:数字时钟管理单元,主要完成时钟的同步、移相、分频、倍频和去抖动等。DCM与全局时钟有着密不可分的联系,为了达到最小的延迟和抖动,几乎所有的DCM应用都要使用全局缓冲资源。DCM可以用Xilinx ISE软件中的Architecture Wizard直接生成。
4 Verilog编译及仿真
- verilog仿真器
仿真器不同,结果可能不同
(1)Verilog-XL: cadence开发的解释仿真器,“解释”即有一个运行时间的解释工具执行每一条Verilog指令并且与时间队列进行交流。该仿真器在cadence系统里是一个默认的verilog仿真器,但一直未被cadence更新,不具备verilog的新特点。
(2)NC-Verilog:cadence的编译仿真器,把Verilog转换成该程序的定制仿真器,即先转换成C程序,再编译成仿真器。与Verilog-2001大部分兼容。
(3)VCS:synopsys开发的编译仿真器,与该公司的其他工具集成在一起。兼容Verilog-2001 - Verilog生成电路的方法
(1)behavioral(行为级RTL):用高层次的Verilog描述所希望的系统行为,随后用综合工具综合成硬件电路。
(2)structural(结构级):Verilog完全由一个标准单元库基本门的实例构成。该Verilog可以完全是文本的,或是采用标准库中逻辑门符号的层次化原理图。