verilog 经验适合初学者.docx
《verilog 经验适合初学者.docx》由会员分享,可在线阅读,更多相关《verilog 经验适合初学者.docx(61页珍藏版)》请在冰豆网上搜索。
verilog经验适合初学者
先记下来:
1、不使用初始化语句;
2、不使用延时语句;
3、不使用循环次数不确定的语句,如:
forever,while等;
4、尽量采用同步方式设计电路;
5、尽量采用行为语句完成设计;
6、always过程块描述组合逻辑,应在敏感信号表中列出所有的输入信号;
7、所有的内部寄存器都应该可以被复位;
8、用户自定义原件(UDP元件)是不能被综合的。
一:
基本
Verilog中的变量有线网类型和寄存器类型。
线网型变量综合成wire,而寄存器可能综合成WIRE,锁存器和触发器,还有可能被优化掉。
二:
verilog语句结构到门级的映射
1、连续性赋值:
assign
连续性赋值语句逻辑结构上就是将等式右边的驱动左边的结点。
因此连续性赋值的目标结点总是综合成由组合逻辑驱动的结点。
Assign语句中的延时综合时都将忽视。
2、过程性赋值:
过程性赋值只出现在always语句中。
阻塞赋值和非阻塞赋值就该赋值本身是没有区别的,只是对后面的语句有不同的影响。
建议设计组合逻辑电路时用阻塞赋值,设计时序电路时用非阻塞赋值。
过程性赋值的赋值对象有可能综合成wire,latch,和flip-flop,取决于具体状况。
如,时钟控制下的非阻塞赋值综合成flip-flop。
过程性赋值语句中的任何延时在综合时都将忽略。
建议同一个变量单一地使用阻塞或者非阻塞赋值。
3、逻辑操作符:
逻辑操作符对应于硬件中已有的逻辑门,一些操作符不能被综合:
===、!
==。
4、算术操作符:
Verilog中将reg视为无符号数,而integer视为有符号数。
因此,进行有符号操作时使用integer,使用无符号操作时使用reg。
5、进位:
通常会将进行运算操作的结果比原操作数扩展一位,用来存放进位或者借位。
如:
Wire[3:
0]A,B;
Wire[4:
0]C;
AssignC=A+B;
C的最高位用来存放进位。
6、关系运算符:
关系运算符:
<,>,<=,>=
和算术操作符一样,可以进行有符号和无符号运算,取决于数据类型是reg,net还是integer。
7、相等运算符:
==,!
=
注意:
===和!
==是不可综合的。
可以进行有符号或无符号操作,取决于数据类型
8、移位运算符:
左移,右移,右边操作数可以是常数或者是变量,二者综合出来的结果不同。
9、部分选择:
部分选择索引必须是常量。
10、BIT选择:
BIT选择中的索引可以用变量,这样将综合成多路(复用)器。
11、敏感表:
Always过程中,所有被读取的数据,即等号右边的变量都要应放在敏感表中,不然,综合时不能正确地映射到所用的门。
12、IF:
如果变量没有在IF语句的每个分支中进行赋值,将会产生latch。
如果IF语句中产生了latch,则IF的条件中最好不要用到算术操作。
Case语句类似。
Case的条款可以是变量。
如果一个变量在同一个IF条件分支中先赎值然后读取,则不会产生latch。
如果先读取,后赎值,则会产生latch。
13、循环:
只有for-loop语句是可以综合的。
14、设计时序电路时,建议变量在always语句中赋值,而在该always语句外使用,使综合时能准确地匹配。
建议不要使用局部变量。
15、不能在多个always块中对同一个变量赎值
16、函数
函数代表一个组合逻辑,所有内部定义的变量都是临时的,这些变量综合后为wire。
17、任务:
任务可能是组合逻辑或者时序逻辑,取决于何种情况下调用任务。
18、Z:
Z会综合成一个三态门,必须在条件语句中赋值
19、参数化设计:
优点:
参数可重载,不需要多次定义模块
四:
模块优化
1、资源共享:
当进程涉及到共用ALU时,要考虑资源分配问题。
可以共享的操作符主要有:
关系操作符、加减乘除操作符。
通常乘和加不共用ALU,乘除通常在其内部共用。
2、共用表达式:
如:
C=A+B;
D=G+(A+B);
两者虽然有共用的A+B,但是有些综合工具不能识别.可以将第二句改为:
D=G+C;这样只需两个加法器.
3、转移代码:
如循环语句中没有发生变化的语句移出循环.
4、避免latch:
两种方法:
1、在每一个IF分支中对变量赋值。
2、在每一个IF语句中都对变量赋初值。
5:
模块:
综合生成的存储器如ROM或RAM不是一种好方法,只是成堆的寄存器,很费资源。
最好用库自带的存储器模块。
五、验证:
1、敏感表:
在always语句中,如果敏感表不含时钟,最好将所有的被读取的信号都放在敏感表中。
2、异步复位:
建议不要在异步时对变量读取,即异步复位时,对信号赋以常数值。
Averilog的流行,有两方面的原因;
Bverilog与VHDL相比的优点
C典型的verilog模块
Dverilog语法要点
A)verilog的流行,有两方面的原因:
1它是cadence的模拟器verilog-XL的基础,cadence的广泛流行使得verilog在90年代深入人心;
2它在硅谷获得广泛使用;
B)verilog与VHDL相比的优点二者的关系仿佛C与FORTRAN,具体而言:
1verilog的代码效率更高:
比较明显的对比:
VHDL在描述一个实体时采用entity/architecture模式,
verilog在描述一个实体时只需用一个"module/edumodule"语句块.
此外verilog的高效性还在很多地方体现出来;
2verilog支持二进制的加减运算:
VHDL在进行二进制的加减运算时使用conv_***函数或者进行其他的定义,总之必须通知编译器;verilog直接用形如"c=a+b"的表示二进制的加减运算;
3综合时可控制性好:
VHDL对信号不加区分地定义为"signal",
而verilog区分为register类型的和wire类型的;
但是也有人支持VHDL,认为verilog和VHDL的关系仿佛C和C++.C)典型的verilog模块
讨论以下典型电路的verilog描述:
*与非门;
*加法器;//即全加器
*D触发器;
*计数器;//**分频的counter
*latch;
*时序机;
*RAM;//用synopsys的
*模块引用;
*预编译;
*与非门的verilog描述如下:
//verilog使用和C语言相同的注释方法
modulend02(a1,a2,zn);//一个verilog模块总是以module开始,以endmodule结束,nd02是模块名,a1,a2,zn是模块的3个输入输出信号
inputa1,a2; //告诉编译器a1,a2对此模块而言是输入,并且数据类型是"bit"
outputzn; //告诉编译器zn对此模块而言是输出,数据类型也是"bit"
nand (zn,a1,a2);//我理解nand是运算符,我们不必深究verilog中的正式术语是什
么了吧,总之这种形式表示zn=~(a1&&a2);你一定已经想到类似的运算符还有"not","and","or","nor","xor"了吧;除了"not",括号里的信号数可以任意,例如or (z,f,g,h)表示z=f||g||h,并且延时是3个单位时间,#x表示延时x个单位时间;
endmodule
*加法器的verilog描述如下:
modulead03d1(A,B,CI,S,CO);
input[2:
0]A,B; //表示A,B是输入信号,并且是3位矢量,上界是2,下界是0
inputCI;
output[2:
0]S;
outputCO;
assign{CO,S}=A+B+CI;//一对"{"和"}"表示链接,即将CO和S合并成4位矢量
endmodule
*带异步清零端的D触发器的verilog描述如下:
moduledfctnb(d,cp,cdn,q,qn);
inputd,cp,cdn;
outputq,qn;
regq,qn; //关键字"reg"表示q和qn是"register"类型的信号;verilog中有两种类型的信号:
"register"类型和"wire"类型.你可以简单地把register类型的信号想象为某个D触发器的输出,而wire类型的的信号是组合逻辑的输出.二者的最大区别在于:
你可以对register类型的信号进行定时赋值(用wait语句在特定时刻的赋值,详见下面always语句),而对于wire类型的信号则不可.
alwayswait(cdn==0)//表示每当cdn=0时,将要对D触发器清零,"always"和"wait"嵌套,"wait"和"@"是verilog的两个关键字,表示一旦有某事发生;则执行下面的语句块,"always"有点象C语言中的"if...then...","wait"和"@"的区别:
请参考本模块.wait表示本语句块的进程停止,直到"cdn=0"的条件出现才继续;我理解在verilog中,每个最外层语句块都是一个***的进程;"@"(请看下个always语句)也表示本语句块的进程停止,直到后面定义"posedgecp"(即出现cp的上升沿)的事件出现才继续;也许wait和@可以合二为一吧,但至少到目前verilog中wait表示"条件",@表示"事件";具体运用中,wait总是用于类似"wait(xxx=1)"之类的场合,@总是用于类似"@(xxx)"或"@(posedge/negedgexxx)"之类的场合整句话的意思是"每当cdn等于0时,则作以下事情"
begin //begin...end结构的用法类似于pascal语言
q=0;
qn=1;
wait(cdn==1);
end
always@(posedgecp)//"@(posedgecp)"中有两个关键字:
"@(x)"表示"每当事件x发
生","posedgex"表示"x的上升沿,"negedgex"表示"x的下降沿",整句话的意思是"每当cp的上升沿,则作以下事情"
if(cdn) //如果cdn=1(意味着清零端无效)
begin
q=d;
qn=~q;//"~"表示反相
end
endmodule
*计数器的verilog描述如下:
modulecount(in,set,cp,out);//此计数器,在cp的上升沿将输入赋给输出,在cp的上升沿使输出加一
input[15:
0]in;
inputset,cp;
output[15:
0]out;
reg[15:
0]out;
always@(posedgeset)
out=in;
always@(posedgecp)
out=out+1; //verilog容许一个信号同时出现在等号两端,只要它是reg类型的
endmodule
*latch的描述如下:
always@(clkord)
if(clk)q=d;
*时序机的verilog描述如下:
always@(posedgeCLK) //D是下一个状态,Q是当前状态,e1,e2是输入,a,b是输出
Q=D;
always@(Qorothercase)begin//当Q变化或输入e1,e2变化时D要相应变化
D=Q;//note1
a=0;
b=0;
......
case(Q)
q1:
begin
q1action;
if(e1)D=d1;
if(e2)D=d2;
elseD=d3;
a=1;//note2
end
q2:
begin
b=1;
......
end
default:
begin
a=0;
b=0;
......
end
end
---annotations---
note1:
Thisisacustomexpression,afterreset,DshouldbeequaltoQ;
note2:
Inthisstatemachine,aisonlyequalto1atstateq1,in
otherstate,aisequalto0;
*RAM的verilog描述如下:
moduleram(din,ain,dout,aout,rd,wr);//这是一个双口RAM,分别有:
输入端:
输入地址ain;输入数据din;上升沿有效的写信号wr;/输出端:
输出地址aout;输出数据dout;高电平有效的读信号rd;
inout[7:
0]din;
input[7:
0]ain,aout;
inputrd,wr;
output[7:
0]dout;
reg[7:
0]memory[0:
255]; //请注意这是存储阵列的描述方法,描述了一个共有2
56个字的存储阵列,每个字是8位
assigndout=rd?
memory[aout]:
8'bz;//"assign"关键字表示并行赋值语句的
开始"?
"运算符的作用和在C语言中一样"8'bz"是一个常量,表示一个字节的高阻态,其中8表示长度是8bit,"'"是固定分割符,"b"表示后面的数据是以比特形式给出的,"z"表示高阻;举例:
4'ha表示长4bit的数"1010"。
类似的还可举出5'b10111,6'o33等等
always@(posedgewr)
memory[ain]=din;
endmodule
*模块引用
假设在前面(可以是别的模块)定义了moduleram(din,ain,dout,aout,rd,wr),则引用此
模块时只需写
rammyram(din_in_map,ain_in_map,dout_in_map,aout_in_map,rd_in_map,wr_in_map)
;
//其中"ram"是所引用的module名,"myram"是你起的instance名,"din_in_map"等等是图中的节点名,和器件(module)中的"din..."进行"虚实结合";
*预编译
类似C语言,只需写
`include"filename>",反上撇号"`"是verilog的预编译符,类似C中的"#".
D)verilog语法要点
*基本原则
设计时应该把你的系统划分为计数器,触发器,时序机,组合逻辑等等可综合的单元,对此不同的IC公司和EDA开发商可能根据自己的见解和经验提出不同的要求,并且对verilog程序的细节进行自己的规定,但有一点是对的:
即写硬件描述语言不象写C语言那样符合语法就行.单单符合verilog语法的程序可能被拒绝综合,甚至被拒绝模拟;
*最外层可以写什么?
这里所说的最外层是指module语句后的第一层,在这一层可以写这些可执行语句:
assign和nand等定义组合逻辑的语句,
always语句,
模块引用语句,
一些以"$"开头的系统定义语句.
特别注意不可以写if语句.if语句只能放在always内部.
不推荐写wait语句,因为不能综合.
*不可以在多个always语句中对一个信号赋值.
1.强烈建议用同步设计
2.在设计时总是记住时序问题
3.在一个设计开始就要考虑到地电平或高电平复位、同步或异步复位、上升沿或下降沿触发等问题,在所有模块中都要遵守它
4.在不同的情况下用if和case,最好少用if的多层嵌套(1层或2层比较合适,当在3层以上时,最好修改写法,因为这样不仅可以reducearea,而且可以获得好的timing)
5.在锁存一个信号或总线时要小心,对于整个design,尽量避免使用latch,因为在DFT时很难test。
6.确信所有的信号被复位,在DFT时,所有的FlipFlop都是controllable,
7.永远不要再写入之前读取任何内部存储器(如SRAM)
8.从一个时钟到另一个不同的时钟传输数据时用数据缓冲,他工作像一个双时钟FIFO(是异步的),可以用AsyncSRAM搭建AsyncFIFO。
9.在VHDL中二维数组可以使用,它是非常有用的。
在VERILOG中他仅仅可以使用在测试模块中,不能被综合
10.遵守register-inregister-out规则
11.像synopsys的DC的综合工具是非常稳定的,任何bugs都不会从综合工具中产生
12.确保FPGA版本与ASIC的版本尽可能的相似,特别是SRAM类型,若版本一致是最理想的,但是在工作中FPGA版本一般用FPGA自带的SRAM,ASIC版本一般用厂商提供的SRAM。
13.在嵌入式存储器中使用BIST
14.虚单元和一些修正电路是必需的
15.一些简单的测试电路也是需要的,经常在一个芯片中有许多测试模块
16.除非低功耗不要用门控时钟,强烈建议不要在design中使用gateclock
17.不要依靠脚本来保证设计。
但是在脚本中的一些好的约束能够起到更好的性能(例如前向加法器)
18.如果时间充裕,通过时钟做一个多锁存器来取代用MUX
19.不要用内部tri-state,ASIC需要总线保持器来处理内部tri-state,如IOcell。
20.在toplevel中作padinsertion
21.选择pad时要小心(如上拉能力,施密特触发器,5伏耐压等),选择合适的IOcell
22.小心由时钟偏差引起的问题
23.不要试着产生半周期信号
24.如果有很多函数要修正,请一个一个地作,修正一个函数检查一个函数
25.在一个计算等式中排列每个信号的位数是一个好习惯,即使综合工具能做
26.不要使用HDL提供的除法器
27.削减不必要的时钟。
它会在设计和布局中引起很多麻烦,大多数FPGA有1-4个专门的时钟通道
良好代码编写风格可以满足信、达、雅的要求。
在满足功能和性能目标的前提下,增强代码的可读性、可移植性,首要的工作是在项目开发之前为整个设计团队建立一个命名约定和缩略语清单,以文档的形式记录下来,并要求每位设计人员在代码编写过程中都要严格遵守。
良好代码编写风格的通则概括如下:
(1)对所有的信号名、变量名和端口名都用小写,这样做是为了和业界的习惯保持一致;对常量名和用户定义的类型用大写;
(2)使用有意义的信号名、端口名、函数名和参数名;
(3)信号名长度不要太长;
(4)对于时钟信号使用clk作为信号名,如果设计中存在多个时钟,使用clk作为时钟信号的前缀;
(5)对来自同一驱动源的信号在不同的子模块中采用相同的名字,这要求在芯片总体设计时就定义好顶层子模块间连线的名字,端口和连接端口的信号尽可能采用相同的名字;
(6)对于低电平有效的信号,应该以一个下划线跟一个小写字母b或n表示。
注意在同一个设计中要使用同一个小写字母表示低电平有效;
(7)对于复位信号使用rst作为信号名,如果复位信号是低电平有效,建议使用rst_n;
(8)当描述多比特总线时,使用一致的定义顺序,对于verilog建议采用bus_signal[x:
0]的表示;
(9)尽量遵循业界已经习惯的一些约定。
如*_r表示寄存器输出,*_a表示异步信号,*_pn表示多周期路径第n个周期使用的信号,*_nxt表示锁存前的信号,*_z表示三态信号等;
(10)在源文件、批处理文件的开始应该包含一个文件头、文件头一般包含的内容如下例所示:
文件名,作者,模块的实现功能概述和关键特性描述,文件创建和修改的记录,包括修改时间,修改的内容等;
(11)使用适当的注释来解释所有的always进程、函数、端口定义、信号含义、变量含义或信号组、变量组的意义等。
注释应该放在它所注释的代码附近,要求简明扼要,只要足够说明设计意图即可,避免过于复杂;
(12)每一行语句独立成行。
尽管VHDL和Verilog都允许一行可以写多个语句,当时每个语句独立成行可以增加可读性和可维护性。
同时保持每行小于或等于72个字符,这样做都是为了提高代码得可读性;
(13)建议采用缩进提高续行和嵌套语句得可读性。
缩进一般采用两个空格,如西安交通大学SOC设计中心2如果空格太多则在深层嵌套时限制行长。
同时缩进避免使用TAB键,这样可以避免不同机器TAB键得设置不同限制代码得可移植能力;
(14)在RTL源码的设计中任何元素包括端口、信号、变量、函数、任务、模块等的命名都不能取Verilog和VHDL语言的关键字;
(15)在进行模块的端口申明时,每行只申明一个端口,并建议采用以下顺序:
输入信号的clk、rst、enablesothercontrolsignals、dataandaddresssignals。
然后再申明输出信号的clk、rst、enalbesothercontrolsignals、datasignals;
(16)在例化模块时,使用名字相关的显式映射而不要采用位置相关的映射,这样可以提高代码的可读性和方便debug连线错误;
(17)如果同一段代码需要重复多次,尽可能使用函数,如果有可能,可以将函数通用化,以使得它可以复用。
注意,内部函数的定义一般要添加注释,这样可以提高代码的可读性;
(18)尽可能使用循环语句和寄存器组来提高源代码的可读性,这样可以有效地减少代码行数;
(19)对一些重要的always语句块定义一个有意义的标号,这样有助于调试。
注意标号名不要与信号名、变量名重复;
(20)代码编写时的数据类型只使用IEEE定义的标准类型,在VHDL语言中,设计者可以定义新的类型和子类型,但是所有这些都必须基于IEEE的标准;
(21)在设计中不要直接使用数字,作为例外,可以使用0和1。
建议采用参数定义代替直接的数字。
同时,在定义常量时,如果一个常量依赖于另一个常量,建议在定义该常量时用表达式表示出这种关系;
(22)不要在源代码中使用嵌入式的dc_shell综合命令。
这是因为其他的综合工具并不认得这些隐含命令,从而导致错误的或较差的综合结果。
即使使用DesignCompiler,当综合策略改变时,嵌入式的综合命令也不如放到批处理综合文件中易于维护。
这个规则有一个例外的综合命令,即编译开关的打开和关闭可以嵌入到代码中;
(23)在设计中避免实例化具体的门级电路。
门级电路可读性差,且难于理解和维护,如果使用特定工艺的门电路,设计将变得不可移植。
如果必须实例化门电路,我们建议采用独立于工艺库的门电路,如SYNOPSYS公司提供的GTECH库包含了高质量的常用的门级电路;
(24)避免冗长的逻辑和子表达式;
(25)避免采用内部三态电路,建议用多路选择电路代替内部三态电路。
规则 #1:
建立时序逻辑模型时,采用非阻塞赋值语句。
_z_C&q,s_F4R_E0规则 #2:
建立latch模型时,采用非阻塞