我的Verilog学习笔记1.docx
- 文档编号:24568448
- 上传时间:2023-05-28
- 格式:DOCX
- 页数:40
- 大小:392.80KB
我的Verilog学习笔记1.docx
《我的Verilog学习笔记1.docx》由会员分享,可在线阅读,更多相关《我的Verilog学习笔记1.docx(40页珍藏版)》请在冰豆网上搜索。
我的Verilog学习笔记1
Verilog学习笔记
1.我的第一个verilog程序:
三态门
modulethree_status_device(in,out,oe);
inputin,oe;
outputout;
assignout=(oe)?
in:
1'bz;
endmodule
其中oe为输出有效端,当oe置高则输入能顺利通过,否则输出高阻态。
查看TechnologySchematic后可知three_status_device模块使用的FPGA内部资源:
分别是输入缓冲器IBUF,非门INV和三态缓冲器OBUFT。
我们还可以通过ViewSynthesisReport
来观察到底使用了多少资源:
我们可以看出所选的芯片类型为V5系列的fx100,SPEED等级为-2,使用了1个查找表,1个FlipFlop触发器和3个IO口。
由于模块比较简单,我们直接进入后仿真阶段;
最后,我们可以通过
中的ViewHDLInstantiationTemplate看到生成的HDL模板供我们调用实例:
three_status_deviceinstance_name(
.in(in),
.out(out),
.oe(oe)
);
小结:
通过设计三态门,熟悉了verilog开发的主要流程和ise中的常用工具。
反思:
对于高阻态,一般FPGA内部是不支持判断的。
现在有些比较新的FPGA内部已经带有BUFT三态门让用户直接调用(在IOB中),而对于市面上常用的FPGA则无法做到,因为内部并没有BUFT三态门,所以就需要用到slice资源中的MUX复用器,用MUX除了多占用LC/LE的资源以外,受控信号(如数据总线等)会随着驱动源的增加而使延时加大。
也有说法是使用RAM或ROM的总线结构提供高阻态的输出。
在FPGA开发时,一般将不用的IO口设置为三态状态,如果IO口较多的时候既占用连线资源也占用slice资源,对系统产生延迟。
2.组合逻辑:
有毛刺怎么办?
引用《数字电路基础》的描述,当一个逻辑门的两个输入端的信号同时向相反方向变化,而变化的时间有差异的现象,称为竞争。
由竞争而可能产生的输出干扰脉冲的现象就叫做冒险,也就是通俗上说的毛刺。
书上还给出了常用的消除竞争冒险的方法:
消除互补相乘项:
通过人为优化逻辑表达式,消去同一信号的同反相同时存在项,降低竞争的发生几率。
增加乘积项避免互补项相加:
若组合逻辑表达式中,在某些信号取一定值的情况下,表达式可化为一个信号的同反相同时相乘或相加时,则需要人为加入相乘项以确保此时输出状态的稳定。
那么在verilog如何实现消除毛刺呢?
信号在fpga器件中通过逻辑单元连线时,一定存在延时。
延时的大小不仅和连线的长短和逻辑单元的数目有关,而且也和器件的制造工艺、工作环境等有关。
因此,信号在器件中传输的时候,所需要的时间是不能精确估计的,当多路信号同时发生跳变的瞬间,就产生了“竞争冒险”。
这时,往往会出现一些不正确的尖峰信号,这些尖峰信号就是“毛刺”。
另外,由于fpga以及其它的cpld器件内部的分布电容和电感对电路中的毛刺基本没有什么过滤作用,因此这些毛刺信号就会被“保留”并传递到后一级,从而使得毛刺问题更加突出。
尽管毛刺持续时间很短,但在高速电路中,这样的毛刺足以使后一级电路产生“误动作”。
要消除毛刺,我们先要了解FPGA内部毛刺的具体特点:
由于布线延迟,和器件延迟,取决于FPGA内部结构,这个涉及到约束问题,
FPGA中消除毛刺的常用方法是:
1.触发器输出
通过添加触发器,使输出信号在clk跳变沿进行读取,并输出,能有效地降低毛刺的发生几率。
但这样的话,延时也就增大。
但是,毛刺的产生是不定时的,如果毛刺在时钟跳变时期产生,则使用触发器的方法无法解决问题。
2.信号延时法
信号延时法,顾名思义,延时信号处理时期,等待信号稳定时再对数据进行处理。
它的具体做法有很多:
信号延时检测
信号延时方法很多,如使用门级电路延时,fpga的专用延时单元lcell,毛刺的产生随机性,单凭延时是无法解决问题的。
时钟延时
像使用触发器的原理类似,通过增加时钟计数器,对时钟进行分频,加大时钟间隔,来保证对信号进行处理的时候信号已经稳定;或者为防止在信号检测时钟跳变时,信号发生变化,延时对信号检测时间,比如加入标志位寄存器,信号跳变后的下一个检测时钟对其检测。
这针对检测时期瞬变信号导致检测错误的方法。
状态机检测
使用状态机对信号进行多次检测,首先第一次检测信号,进入下一状态,再次检测信号并与前面进行比较,如果不同则重新开始检测知道检测一定次数后确定信号不变动后,进行数据处理。
这种方法结合了上述方法,极好地消除了竞争冒险。
信号延时法缺点是用速度换取电路的稳定性,我们只能择优而取。
下面我们来看一些例子:
按照设想out=abc+bde;
其时序图要求如下:
(用TimingDesigner画图)
下面我们一一验证一下不同方法所实现的逻辑组合的效果。
//方法一,直接使用assign语句(数据流)
assignout=a&b&c|d&e&b;
类似的描述组合逻辑方法还有:
always@(aorborcordore)
begin
temp1=a&c|d&e;out=temp1&b;
end
wiretemp1,temp2;
andmyand1(temp1,a,b,c);
andmyand2(temp2,d,e,b);
ormyor(out,temp1,temp2);
由于表达式out=abc+bde可转换成out=b(ac+de),因此b的取值至关重要,当输入信号b变为0时,输出即变为0,所以上述语句一般写成:
wiretemp;
assigntemp=a&c|d&e;
assignout=temp&b;
这样一来,当b变为0,输出立即变化,这就是关键路径的选取。
关键路径所生成的RTL结构如下:
与没有选取关键路径相比:
由行为仿真波形可以看出,这样的逻辑设计与设想相同:
我们通过修改仿真文件.twf
对某些输入信号进行人为的延时,观察加入延时后出来的效果:
输入信号波形代码段均写在Initial段中:
(我们就在这里修改),为了保持毛刺持续时间尽量满足实际情况(一般为十几ns)所以这里设置延时均为10ns,即在`timescale1ns/1ps情况下,使用#10;语句进行延时动作。
initialbegin
//-------------CurrentTime:
490ns
#490;
a=1'b1;b=1'b1;c=1'b1;d=1'b1;
//-------------------------------------
//-------------CurrentTime:
1090ns
#600;a=1'b0;c=1'b0;
#10;e=1'b1;
#10;b=1'b0;
//-------------------------------------
//-------------CurrentTime:
1690ns
#600;b=1'b1;
#10;e=1'b0;
//-------------------------------------
//-------------CurrentTime:
2290ns
#600;e=1'b1;
//-------------------------------------
//-------------CurrentTime:
2690ns
#400;e=1'b0;
//-------------------------------------
//-------------CurrentTime:
3090ns
#400;e=1'b1;
#10;b=1'b0;c=1'b1;
//-------------------------------------
//-------------CurrentTime:
3690ns
#600;b=1'b1;
#10;d=1'b0;
#10;a=1'b1;
//-------------------------------------
end
仿真出来的效果(毛刺出现了)
可见,尽管做出了关键路径的选取,组合逻辑电路还是非常容易产生毛刺。
下面我们使用上面的说法,加入寄存器后观察结果。
regout;wiretemp;
assigntemp=a&b&c|d&e&b;
always@(posedgeclk)
out=temp;
从RTL结构图中可以直观看到加入D触发器(即寄存器)输出:
依旧采用改动后的仿真文件,所得出的仿真波形如下:
可见,上面的毛刺完全被消除了,而且不留一点痕迹。
由于使用触发器免不了使用时钟,详细的使用放到后面的时序电路学习一节中。
小结:
从本节中,实现了简单的逻辑组合,并透过阅读修改仿真文件,了解毛刺的产生;通过观察RTL结构图,了解到代码风格与生成的模型之间的联系,并通过使用D触发器消除毛刺。
反思:
如果外部情况比较恶劣,延时级别大于ns级,或者输入信号的跳变在时钟跳变时期发生,会产生什么效果,这需要在后面的学习中逐步体会。
3.使用组合逻辑:
加法器的设计
一个设计,其实现方案多样,而且不同的实现方案,电路结构是不一样的,这将导致速度或使用资源的差异。
所以,在学习过程中,需要对不同的方法进行比较,了解各种编码风格实现的差别,对将来的系统设计有极大的帮助。
下面我们以加法器的设计为引导,延伸本节的讨论。
首先,我们要知道一个加法器模型应该有的端口,他们分别为:
加数,被加数,上级进位,和,进位这几个端口。
注意verilog的编码规范,模块端口有高位至低位,从输出到输入进行描述:
moduleadder(cout,sum,clk,rst,a,b,cin);
(1)实现方法一
assign{cout,sum}=a+b+cin;
这样看来,加法器的实现非常简单,从结构上看,它直接调用了一个加法器(如图):
但是对于“+”号的综合,FPGA内部是采用查找表所实现的,下面是由上述语句生成的二级查找表结构:
(查找表的级数是延时的主要因素,降低查找表级数是代码优化的首要任务)
其中LUT3_E8表示3输入查找表,E8指查找表内部真值表的结果值。
由于使用查找表实现,容易出现毛刺,我们回顾下上一节所用到的方法,我们再使用一次:
再次出现了D触发器。
(仿真波形就不看了,大家可以自己试试)
(2)串行加法器
wire[1:
0]temp;
assign{temp[0],sum[0]}=a[0]+b[0];
assign{temp[1],sum[1]}=a[1]+b[1]+temp[0];
assigncout=temp[1];
虽然咋一看,与第一种方法相当类似,你确定是一样吗?
我们看看生成的
,如下图:
我们可以看出,这样的代码,只使用了一级的LUT,这样设计有助于降低延时,而且并没有增多LUT的使用量,反而少用一个LUT。
可见资源利用率是第二种略为节省些。
(3)查找法
reg[1:
0]sum;regcout;
always@(posedgeclkornegedgerst)
begin
if(!
rst)
{cout,sum}=3'bzzz;
else
case({a,b})
4'b00_00:
{cout,sum}=3'b000;4'b00_01:
{cout,sum}=3'b001;
4'b00_10:
{cout,sum}=3'b010;4'b00_11:
{cout,sum}=3'b011;
4'b01_00:
{cout,sum}=3'b001;4'b01_01:
{cout,sum}=3'b010;
4'b01_10:
{cout,sum}=3'b011;4'b01_11:
{cout,sum}=3'b100;
4'b10_00:
{cout,sum}=3'b010;4'b10_01:
{cout,sum}=3'b011;
4'b10_10:
{cout,sum}=3'b100;4'b10_11:
{cout,sum}=3'b101;
4'b11_00:
{cout,sum}=3'b011;4'b11_01:
{cout,sum}=3'b100;
4'b11_10:
{cout,sum}=3'b101;4'b11_11:
{cout,sum}=3'b110;
default:
{cout,sum}=3'bzzz;
endcase
end
我们看生成的RTL图如下:
可见编译器知道我们的用意,使用了ROM作为查找的存储,加数为取址信号。
通过观察.ngc文件可知,这样的描述仍然使用了一级的3个LUT,输出使用寄存器同步输出。
(4)卡诺图法
我们写出他的真值表,通过卡诺图进行简化,得出各输出的逻辑表达式:
assignsum[0]=(~a[0]&b[0])|(a[0]&~b[0]);
assignsum[1]=(~a[1]&~a[0]&b[1])|(~a[1]&a[0]&~b[1]&b[0])|(a[1]&a[0]&b[1]&b[0])|(~a[1]&b[1]&~b[0])|(a[1]&~b[1]&~b[0])|(a[1]&~a[0]&~b[1]);
assigncout=(a[0]&b[1]&b[0])|(a[1]&a[0]&b[0])|(a[1]&b[1]);
这种方法是用门电路数目较多,延时参次不齐,而且使用不方便,但速度相对会快些。
要注意,有时候适当的加上括号,除了有助于阅读,还可以减少逻辑门的级数,下面再举个例子:
assignout=(da+db)+(dc+dd);和assignout=da+db+dc+dd;
da+db+dc+dd生成的模块内部图
(da+db)+(dc+dd)所生成的模块内部图
尽管使用的加法器个数一样,但级数变少了,这样的编码风格,提高了模块运行的速度,但由于FPGA内部使用LUT实现,所以最终对提高速度影响不大,但对于ASIC(专用集成芯片)的模块设计,这样的考虑是很有必要的。
(5)超前加法器
assignsum[0]=a[0]^b[0]^cin;
assignsum[1]=a[1]^b[1]^((a[0]&b[0])|(a[0]^b[0])&cin);
assigncout=(a[1]&b[1])|((a[1]^b[1])&(a[0]&b[0]))|((a[0]^b[0])&(a[1]^b[1])&cin);
根据数电书上的解释,人为地观察进位与输入的关系,由输入直接导出进位是这种方法的核心思想。
但我们仍然可以看出,当位数增加的时候,电路的设计变得非常复杂,下面我们通过调用模块来简化多位加法器的设计。
(6)调用模块
moduleone_bit_adder(cout,sum,cin,ina,inb);
inputina,inb,cin;
outputcout,sum;
assign{cout,sum}=ina+inb+cin;
endmodule
//twobitadder(byusingonebitadder)
//Thisisatopmodule
moduleuse_adder(cout,sum,c,x,y);
input[1:
0]x,y;
inputc;
output[1:
0]sum;
outputcout;
wirec_temp;
one_bit_adderu1(.cout(c_temp),.sum(sum[0]),.cin(c),.ina(x[0]),.inb(y[0]));
one_bit_adderu2(.cout(cout),.sum(sum[1]),.cin(c_temp),.ina(x[1]),.inb(y[1]));
endmodule
这里先设计一位计数器(当然,计数器的设计可以使用上面的任何一个例子),然后通过对模块端口的连线,而模块的调用有以下两种方法:
模块端口的对齐:
即按照模块原型声明的端口顺序
模块端口对应连接:
使用.端口名(线名),.端口名(线名)……这样的方式进行模块调用,当某一端口需要悬空,必须加上“,”但可以不填写任何东西。
(7)流水线
input[3:
0]a,b;inputclk,cin;
output[3:
0]sum;outputcout;
reg[3:
0]tempa,tempb;regtempci;regcout;regfirstco;
reg[1:
0]firstsum;reg[2:
0]firsta,firstb;
reg[3:
0]sum;
always@(posedgeclk)
begin
tempa=a;tempb=b;tempci=cin;
end
always@(posedgeclk)
begin
{firstco,firstsum}=tempa[1:
0]+tempb[1:
0]+tempci;
firsta=tempa[3:
2];firstb=tempb[3:
2];
end
always@(posedgeclk)
begin
{cout,sum}={firsta[2:
0]+firstb[2:
0]+firstco,firstsum};
end
endmodule
使用流水线的方法,最重要的是将未用到的数据进行存储,这样做使得流水线不同级之间相互独立,这样做大大提高了速度(除了第一次运算需要额外的两个时钟周期),但是这样的设计使用大量资源,这需要在性能和资源中进行折衷。
使用的资源:
我们从图中可以看出,流水线极大提高了触发器的使用量,是典型的以面积换取速度的例子。
小结:
通过对加法器的各种设计以及编码风格,我们可以看出VerilogHDL——硬件描述语言的优势,不同的编码风格所产生的电路在速度与面积上都将产生不同的变化,要学好HDL,则需要经验的积累和对电路结构的了解,必须明白语言与电路之间的对应关系,这样,Verilog语言才能用得得心应手。
反思:
Verilog的编码与FPGA内部结构、内部器件特性有着很大关系,要编写一个好的代码,学习FPGA内部结构成为必不可少的一个阶段。
4.有限状态机
时序电路由组合电路和存储电路组成,时序电路是状态依赖的,所以称为状态机。
其中Melay状态机,其状态与当前状态,输入,输出均有关系,而Moore状态机则只跟当前状态有关,一下用两个实例对其进行探讨。
(1)Melay状态机
moduleserial_checkout(same,din,clk,rst);
inputdin;inputclk,rst;outputsame;
reg[2:
0]status;regsame_temp;
//Writedownyourserialwhichyouwanttocheckout
parameterserial_number=8'h11010011;//checkthelowestbitfirst
parameterbit1=1'b1;parameterbit2=1'b1;parameterbit3=1'b0;
parameterbit4=1'b0;parameterbit5=1'b1;parameterbit6=1'b0;
parameterbit7=1'b1;parameterbit8=1'b1;
assignsame=same_temp;
always@(posedgeclkornegedgerst)
if(rst==0)
begin
status<=3'b000;same_temp<=1'b0;
end
else
case(status)
3'b000:
begin
if(din==bit1)
status<=3'b001;
else
status<=3'b000;
end
3'b001:
begin
if(din==bit2)
status<=3'b010;
else
status<=3'b000;
end
3'b010:
begin
if(din==bit3)
status<=3'b011;
else
status<=3'b000;
end
3'b011:
begin
if(din==bit4)
status<=3'b100;
else
status<=3'b000;
end
3'b100:
begin
if(din==bit5)
status<=3'b101;
else
status<=3'b000;
end
3'b101:
begin
if(din==bit6)
status<=3'b110;
else
status<=3'b000;
end
3'b110:
begin
if(din==bit7)
status<=3'b111;
else
status<=3'b000;
end
3'b111:
begin
if(din==bit8)
same_temp<=1;
else
status<=3'b000;
end
endcase
endmodule
上述是使用Melay状态机实现的序列检测模块的完整代码,每一个状态都与输入有关,当所有
- 配套讲稿:
如PPT文件的首页显示word图标,表示该PPT已包含配套word讲稿。双击word图标可打开word文档。
- 特殊限制:
部分文档作品中含有的国旗、国徽等图片,仅作为作品整体效果示例展示,禁止商用。设计者仅对作品中独创性部分享有著作权。
- 关 键 词:
- Verilog 学习 笔记