作者: 潘伟涛 ,来源:网络交换FPGA
“要养成良好的Verilog代码风格,要先有硬件电路框图之后再写代码的习惯,设计出良好的时序,这样才能在FPGA开发或者ASIC设计中起到事半功倍的效果,否则会事倍功半。”
01、代码规范
一、概述
1、always\assign\reg\wire
2、文件名与module名要一致,一个文件一个module
3、统一的复位方式,异步复位上升沿有效(无论软复位或硬复位)
4、尽量避免用低电平有效的信号,尽量高电平有效
5、状态机一定要采用三段式
6、端口声明输入输出要分开,最好要有区分输入输出的标示
7、条件分支要写全。Case及if else等
8、信号名不要过长,不要超过32个字母
9、所有寄存器都要复位且有初始值
10、不允许使用门控时钟或门控的复位
11、组合逻辑阻塞赋值,时序逻辑非阻塞赋值
12、内部信号避免出现三态
13、避免出现latch
14、多使用parameter,增加修改的便利性
15、连接同一端口的同一组信号尽量有公共的符号表示,如dav\sop\eop等
另外:
不允许将多个寄存器写到一个always里面;
要为每一个寄存器单独写一个always,哪怕两个信号很相关;
要有写电路的意识,不能是写软件的风格(代码短);
按照横向思维,每个信号都要仔细考虑,考虑全。
刚开始时尽量避免同一个寄存器在多个模块里面都赋值(Multi Driver);
代码 - > 电路,写的时候一定明白什么样的代码产生什么样的电路;
设计流程:
设计目标分析 - > 功能模块划分 - > 确定关键电路时序和模块间时序 - > 具体电路设计
设计电路尤其是数字电路,最关键的一环是:设计各模块间的接口时序。这个工作必须在具体电路设计之前确定下来。
综合的TOP_DOWN流程是对整个芯片加约束;而综合的BOTTOM_UP流程是先把小模块做综合,然后把综合好的模块用一个顶层的模块包进去,再综合一次。电路较大时,用BOTTOM_UP流程。
时序是事先设计出来的,而不是事后测出来的,更不是凑出来的!
二、初学者注意的问题
对初学者一定要反复提醒自己注意:
1、避免把写软件的思想带入到写硬件电路中,对于verilog代码而言,常常是简单冗长的代码出来的电路反而高效;
2、写硬件电路代码的时候头脑中要有硬件结构,一定要弄明白什么样的代码能够综合出来什么样的电路。
3、要养成简单高效的写代码风格,写电路设计的硬件代码,关键的行为描述部分只允许用assing、always、if语句、case语句,其余的循环和函数之类的代码都不提倡使用。
一个典型的verilog模块的组成包括module,端口声明,输入输出定义,输出属性的声明,主要代码及endmodule。都有具体的格式要求,可查找资料查看详细的具体格式。另外,module的前面还有一个timescale及include和宏定义,端口声明之后还有一些参数定义等。
需要提示的规范的module写法是一个.v文件里只写一个module。这里面最重要的是主要代码部分,只需要掌握always、assign、wire、reg即可,assign语句后面尽量不要出现较为复杂的逻辑运算,复杂的逻辑运算需要修改成always的写法,以提高可读性。
数据的写法要注意规范性,每种类型的数据都要注明位宽及类型。
在芯片设计中,memory类型的数组变量一般用在深度小于64的寄存器堆定义中,对于FPGA中不涉及,都是用软件自动生成的RAM。不能对数组类型的变量中单独的几个bit进行操作,都是按照以“字”为单位进行操作。
运算符及表达式只需要注意区分单目和双目的运算符即可,简单来讲,单目的一般是用来进行计算的运算符,常常用来进行逻辑运算,写在赋值语句里面;而双目的运算符常常用来进行关系的判断,常常用在if语句的判断条件里面。需要注意的是,运算符是有优先级的,为了代码规范性及正确性,常常需要添加括号和空格进行隔离和区分。
对于语句而言,只允许用较为简单的assign赋值语句和always语句。在电路设计的可综合代码中,不提倡使用for、while等软件常用循环语句。always模块中敏感变量列表中有沿触发逻辑的是时序逻辑模块,综合出来的电路带有DFF,在赋值的时候要用非阻塞赋值;always模块中敏感变量列表中没有沿触发逻辑的是组合逻辑模块,综合出来的电路都是组合电路门的连接,在赋值的时候要用阻塞语句赋值。
if语句是有优先级的,同时满足多个分支的情况下优先执行最前面的分支,case语句是没有优先级的,可以同时执行多个满足条件的分支。if语句嵌套最多不能超过两级,否则会影响综合出来电路的性能。if语句要写全,一定要有else语句,并且组合逻辑中的else语句不能写自己等于自己,否则就会形成组合逻辑的反馈环,对电路产生很大的隐患。另外,if语句的条件判断语句不能过于冗长,如果条件判断太复杂,也会影响电路的性能,最好把时序逻辑里面较为冗长的判断逻辑单独拉出去写成组合逻辑,这样就可以提高电路的性能。
经常采用initial语句来写testbench.整个工程的宏定义可以写成一个文件,在每个文件的module前面include上,这样便于修改。
对于门级电路的描述也很重要,这常常是写出关键路径高效电路的一种最直接的方法。比如乘法器有很多种,如果用工具自动产生的电路,经常是不能满足性能需求,这个时候可以自己采用门级的描述方式来写booth编码的乘法器等来替换代码中的一个乘号,这样才能提高电路的性能。
三、工程实例
一个FPGA工程应该把电路设计代码和仿真代码分开成hdl文件夹和sim文件夹两个文件夹来存放,每个文件夹下都存放相应的文件,这样可以便于高效管理。详细例子可以参考从opencores网站上下载的工程。基本都是按照这样的思路来进行的存储。
四、代码的review
代码的review很重要,可以及早的发现问题,避免在后续调试阶段发现定位问题耗费更多的时间和精力。
02、基本技能
1、采沿
上升沿、下降沿。
适用于根据一些信号进行计数,比如多少个emac帧等,若根据某些信号来计数,无法保证这些信号是否持续一个时钟周期,所以需要进行取沿的操作。采沿时打一拍后,适用assign语句产生。
上升沿采样
下降沿采样
上升沿和下降沿采样
沿检测代码:
reg reg_ff1,reg_ff2; always@(posedge clk ) begin reg_ff1 <= reg_in; reg_ff2 <= reg_ff1; end
上升沿:if((reg_ff1) & (!reg_ff2 ) )
下降沿if(( ! reg_ff1) & (reg_ff2 ) )
双沿:if(reg_ff1 != reg_ff2)
2、“打拍”同步。
不同时钟域的信号进行交互时,需要进行“打两拍”的同步操作之后才能使用。主要是为了消除亚稳态问题。
具体代码如下:
reg bdat1,bdat2; always@(posedge clkb ) begin bdat1 <= adat; bdat2 <= bdat1; end
3、同步复位与异步复位
(1)同步复位,综合出来不带复位端,代码如下:
always @ (posedge clk) begin if (reset) q<= 1’b0 else q<= d; end
(2)异步复位,综合出来带复位端,代码如下:
always @ (posedge clk or posedge reset) begin if (reset) q<= 1’b0 else q<= d; end
4、三段式状态机
有限状态机(FSM)的写法,时序逻辑和组合逻辑分成两个模块写。决不允许把输出也写在里面。
时序部分:只能有当前信号和下一状态;
组合部分:组合内不能有输出,即任何输出都要经过寄存器才能输出;
module state4 (clock,reset,out); input reset, clock; output [1:0] out; parameter [1:0] stateA=2’b00; parameter [1:0] stateB=2’b01; parameter [1:0] stateC=2’b10; parameter [1:0] stateD=2’b11; reg [1:0] state, nextstate, out; //第一段,时序逻辑部分 always @ (posedge clock) begin if (reset ==1,0’b0) state <= stateA; else state <= nextstate; end //第二段,组合逻辑部分 always @ (state) begin case (state) stateA: nextstate = stateB; stateB: nextstate = stateC; stateC: nextstate = stateD; stateD: nextstate = stateA; endcase end //第三段,输出信号赋值部分,可能有多个always always@(postdge clock or negedge reset) begin if (reset==1’b0) out <= 2’b0; else … end endmodule
5、“One-hot” 编码
one-hot编码方式只用一个bit来表示一个状态,这大大缩小了状态译码的组合电路规模,使得路径延时更小,因此状态机的时钟可以运行在更高的频率上。
特例:不妨想象该状态机就是一个循环计数器,如果采用binary编码,则该计数器存在明显的组合电路;而如果采用one-hot编码,该计数器的综合结果就是一个移位寄存器序列,根本不存在任何组合门!
6、if条件判断不能过于复杂,若比较复杂,最好重新定义一个信号,用组合逻辑实现后再判断,否则将影响性能。
7、乒乓操作。
乒乓操作最忌讳两块RAM的区分信号向无限远处传播,导致跟很多模块纠缠,最终造成乒乓不起来。 因此,乒乓操作的一些RAM区分信号最好限制在模块的内部,对外不可见。这样才能准确的进行乒乓。
8、64Byte为存储单元存储问题
根据EMAC帧的帧长特点,选择64Byte作为以太网帧存储的基本单元,在进行流量等测试时测试帧长对吞吐率的影响会降至最小。
所用知识:C语言中队列管理,链表等。
了解队列管理模块、内存分配模块的基本功能。
9、RAM读出是否寄存问题
整个设计中用到RAM的地方若采用工具生产,最好要统一采用读出后寄存一拍再输出的RAM。
10、仿真环境-BFM模型
需要掌握以太网PHY模型、简单CPU模型(能够处理中断及配置寄存器功能)等简单行为模型的编写。此处不要求代码规范。
11、看时序图
会根据时序图,尤其是一些总线关系,如地址、数据及读写使能之间的相互关系,模拟出相应的Master或者Slaver应该满足的关系。
一种典型的应用是FPGA工具自动生成的FIFO或者RAM的时序图能够看明白,另外,一些较为常见的总线时序应牢记,如AMBA AHB总线。
12、了解脚本的含义
掌握简单的Tcl、Perl等语言的基本操作。如Modelism不用图形界面,而用命令行方式操作。
13、掌握仿真环境产生的方式
会使用Verilog语言熟练对文本进行操作。如从文件中读出若干个以太网帧作为激励,输出端的结果写成文件与正确结果文件进行对比。
14、掌握多种文本编辑工具
Ultra-Edit、Notepad++、GVIM等。会使用列操作等进行编辑,会使用BeyondCompare对文件夹或文件进行比较。
15、掌握版本管理工具的使用方法
会使用SVN等简单的版本管理工具。会服务器端及客户端的基本配置,会对代码进行更新、下载不同的版本、log信息上传等。养成良好的代码版本管理习惯。
16、掌握Debussy等工具的使用
能够看懂简单的Debussy跟Modelsim仿真结合的脚本语言的含义。会对代码中某些信号进行跟踪,会从波形定位到代码进行错误的检查等。
17、掌握nLint工具的使用
导入代码到工具中,能够自助设计规则对所写代码进行代码规范检查,并明白常见的多驱动、赋值错误、敏感变量不全等常见的错误修改。
养成写好代码就用nLint进行检查的良好习惯,尤其是对设计中原来以为要用到结果没有用到的一些变量甚至逻辑代码进行删除,避免最后造成资源的浪费。
如自己感觉这些工具使用起来麻烦,则可以自己手动写一个基于文本处理的代码规范检查脚本。
18、掌握SMART BITS等工具的使用
会以一定的速率产生自定义的以太网帧。能够结合SMART BITS及FPGA板能够完成回环实验。
掌握Wireshark等相应功能常见软件的使用方法,以便在没有硬件设备的情况下也可进行FPGA调试。
19、掌握Quartus\ISE等工具的使用
会对设计代码进行综合、布局布线。
会生产或利用工具生产RAM、FIFO、PLL等IP。
会利用工具进行管脚分配。
20、养成经常上论坛下载资料的好习惯
如:http://bbs.eetop.cn/index.php
https://opencores.org/
https://github.com/gotgit/gotgithub
本文转自:网络交换FPGA,转载此文目的在于传递更多信息,版权归原作者所有。
*本文由网络交换FPGA授权转发,如需转载请联系作者本人