1、第二一章Verilog的部分语法本节讲述部分的verilog语法主要包括位拼接“”的用法Parameter用法Begin end语句块同步与异步条件语句case,if等 阻塞赋值与非阻塞赋值 边沿检测电路(双边沿触发的计数器) 按键去抖动电路设计用法在C语言中,是复合语句,但是在verilog中,有着不同的含义-位拼接运算符。即,将几个1位或者是多位的变量拼接成位更宽的量。假设 a=1b0; b=3b101; c=2b11那么c,b,a=6b11_101_0。下划线可以使结果看起来更加清晰,别无他意。又如c0,b2:1,c1 = 4b1_10_1;实例:使用一位全加器的设计。全加器和半加器类似
2、,只是在输入端还有一个进位输入端Ci,电路符号如下:回顾前面的讲述,半加器中我们使用了2位的变量temp来保存运算结果,而实际上这个temp就是Co和S的拼接,即Co, S = temp。这里三个1位的数相加结果最大仍然是2位,使用Co, S代替半加器中temp的代码如下:代码注释:(1) A+B+Ci的和存入 Co,S 拼接符中,Co为高位,S为低位。有时候还用作复制运算符,例如:A=1b1;B=2A;则,B的值为A复制2次,即二进制的11(2b11)。注释:部分EDA工具可能不支持此复制运算符功能。Parameter关键字Verilog中使用parameter关键字来定义常量。例如: pa
3、rameter W=5那么在任何时候W就代表5。通常在可变宽度的设计中很有用。为可变宽度变量的设计提供了方便。接下来以4位全加器的设计演示parameter关键字的使用。例:4位全加器的设计根据要求,全加器要有两个4位的输入端,一个进位输入端Ci,一个4位的和输出端S和一个进位输出端Co。模块图如下:这里使用parameter关键字定义一个常量WIDTH为4,即parameter WIDTH=4;然后在位宽设计的时候采用这个符号WIDTH来定义,如:wire WIDTH-1:0 A;则A为4位宽的连线。完整代码如下:代码注释:可以在module和endmodule之间的任何位置使用parame
4、ter关键字定义常量。综合后的RTL视图如下,可以看出全加器的输入都是4位的。使用parameter的关键字的好处之一是,当我们需要另外一个8位的全加器的时候,我们不需要修改很多代码,只需要将parameter WIDTH=4;中的4换成8即可。然后重新综合,查看一下是否变为了8位全加器。begin end块语句begin end的功能相当于c语言中的复合语句“”的功能,即把好几条语句组合在一起作为一条语句执行。此处以模N计数器做实例讲解begin end的用法。模N计数器,即计数器的技术个数为N个,如果计数器从0开始计数,当记到N-1时候就开始下一轮的计数,即计数范围从0N-1。实例:模10
5、计数器设计。假设该计数器有两个端口,时钟输入端clk,计数器输出端cntr,技术范围从09。代码如下:代码注释:(1) Verilog HDL中允许将端口和类型放在一起声明。output reg 3:0 cntr;相当于output 3:0 cntr; output reg 3:0 cntr;两条语句的功能。输入的类型如果不声明,则默认为wire型。如:input clk;没有声明clk类型,则表示clk为wire型。cntr宽度为4位,因为9变为二进制是1001,占4位。(2)和(3) 这里判断的是cntr是否为9(二进制1001),而不是10。要特别注意!这里分析一下电路的动作,每个加1动
6、作的进行都需要posedge clk才能进行,计数器是从0开始计数的,当cntr变到9时就已经用了10个posedge clk了。如果让电路再次动作,就需要第11个posedge clk,而这时的计数值应该为0(模10已经完成了,下一个周期开始)。所以在第11个posedge clk到来之前计数值为9。这和C语言中的运行方式完全不同,不是顺序执行的,切记!另外,有的同学会问,为什么不这样写 begin cntr = cntr + 1; if(cntr = 4b1001) cntr = 0; end这种写法里面,begin和end之间有2条语句,原代码中是一条if语句。一定要记住硬件描述语言生成
7、的硬件电路,这两条语句都同时对cntr进行赋值操作(只看语句本身,不要带入语句的含义去看),这在电路上是不允许的,也就是说不可能有个电路的输出有两种同时给的驱动!尽管有些EDA软件工具会智能化的理解并生成正确的电路,但在学习中一定要避免这种方式的使用,切记!(4) 这两个(4)一定要成对使用。这个块还可以给起个名字,叫做块名。没有什么电气意义,只是使代码看起来更清晰明了。用法如下:begin :aaaa if(cntr = 4b1001) cntr = 0; else cntr = cntr + 1; end :aaaa就是begin end块的块名同步电路与异步电路从上一节的D触发器设计的R
8、TL视图中我们可以看出,D触发器本身的功能引脚较多,而我们使用的就CP和D、Q三个。下面我们尝试着使用其他的引脚。D触发器设计实例2:具有同步复位功能的D触发器设计。在最简单的D触发器基础上设计带复位端输入的D触发器。即使用RTL图中的CLR引脚。电路图如下。复位功能在数字电路中有同步复位和异步复位两类。所谓同步,指的是与时钟同步,也就是D触发器的任何输出都需要有时钟的上升沿才能产生输出。所谓异步是指即使没有时钟的上升沿也能够产生输出行为。这两种方式在Verilog HDL中都给予了支持。同步复位D触发器指当复位引脚端有效时(通常约定为高电平,也可以根据需要设计成低电平),在时钟上升沿出现时,
9、D触发器会输出(Q端)复位(即为0),否则,在时钟上升沿出现时,将D端的值传递至Q端。同步复位D触发器设计:代码注释: (1)如果条件成立,即clr为高电平时,进行(2)(2)q端口输出低电平,即复位(3)否则,如果clr端口不为高电平,则进行d触发器的正常工作行为。(4)语法,可以将相同类型的端口或者端口类型写在一个关键字的后面,中间用逗号隔开,最后放一个分号。类似c语言中的一次定义多个相同类型的变量方式。查看一下生成的RTL视图此时RTL图发生了变化,图中多了一个2选1的模块。根据此图分析,当clr为高电平时,二选一的数据选择器会选择0至D触发器的输入端,故,当D触发器的时钟上升沿出现时,
10、会将0送至输出q端;如果clr为低电平,则将d送至输出q端。这里,RTL视图中没有使用D触发器本身的CLR引脚,因此也可以推测D触发器本身的CLR引脚不是同步复位功能引脚,而是异步复位功能。可以在接下来的D触发器设计实例3中得到验证。这里使用了if else语句对clr的电平值进行判断,如果为高,就执行功能1,否则的话执行功能2。此if-else语句的功能和c语言中的if-else语句功能完全一致,不再详细论述。D触发器设计实例3:具有异步复位功能的D触发器设计。异步复位D触发器指当复位引脚端有效时(通常约定为高电平,也可以根据需要设计成低电平),D触发器就会立刻输出(Q端)复位(即为0),否
11、则,将D端的值传递至Q端。这个动作的过程没有时钟的参与!异步复位D触发器设计:代码注释: (1)和同步复位D触发器的区别在于always后面的括号中多了一个条件posedge clr。也就是说,这个always模块的动作可以在时钟cp的上升沿来触发,也可以由clr的上升沿来触发。两个条件之间用逗号“,”隔开,表示或的关系,也可以用关键字or代替。也就是说,或者posedge cp来触发电路动作,或者posedge clr来触发电路动作。这样,即使没有cp的上升沿,也可以产生动作,这就是异步的由来。生成的RTL视图如下这是大家看到,clr的端口直接连接到了D触发器的CLR端上,也充分说名了这个端
12、是异步端口,也可以从仿真的图上来说明。在25ns时刻,clr为低电平,这时候d触发器正常工作,将d端输出至q端,因d为高电平,故q端这时候变高。在60ns时刻,这个时刻,没有出现cp的上升沿,但是输出端发生了变化,很显然是由于clr的变化导致的q端输出。因此,这个D触发器是异步复位的D触发器。特别注意,在always块的(触发信号)中,有2个信号,为什么在生成电路的时候将cp连接到了D触发器的时钟端,而将clr端口连接到了D触发器的CLR端呢?为什么不将cp连接到CLR端,clr连接到时钟输入上呢?难道是因为名称的缘故?答案是否定的。这里注意到,尽管(触发信号)中有两个信号,但是其中一个信号c
13、lr在always块的内部做了判断if(clr = 1b1),而另一个没有判断。VerilogHDL综合器规定,在always块语句中被判断的量一定是复位或者是置位端口,always块中没有判断的量一定是时钟。换句话说,时序电路的时钟在always块中是不允许进行电平高低的判定和读取等操作的。小测试,将上述代码修改成以下代码并综合代码注释:(1)这里将if语句后面的clr换成了cp,编译后查看RTL视图如下:图中将cp连接到了CLR端,而将clr连接到了D触发器的时钟端,充分证明了上述观点。D触发器设计实例4:具有复位和置位功能的D触发器设计。此处为了节约篇幅,只进行异步置位、异步复位(复位优
14、先)的方式进行设计,其余方式的D触发器请自行设计。生成RTL视图如下:条件语句case,if等If语句和case语句是Verilog HDL的典型条件语句,其中if语句的用法和c语言中的用法完全一致,在此处,不在详细叙述。但注意,如果有多个if else语句同时使用的话,else总是和最近的if配对。以下以带有同步加载、同步清零的8位计数器为例示范if语句的使用。根据设计的要求,此模块图应该具有以下端口该计数器有5个端口,其中,clk为计数时钟,din为8为的数据置入端,dout为8位的计数数据输出端,load为初值置入使能端,假设高电平有效,clr为计数清零端,假设高电平有效。当load和c
15、lr均为低电平时,计数器正常计数。在功能部分的语句中,先判断clr是否有效(高电平),如果有效则给计数器清0;否则检查load是否有效,如果有效(高电平),则把初值din赋给计数器输出,如果load无效,则正常加1计数。读者试着使用if语句对该计数器进行增加功能,变为模60的计数器。If语句示例2:带有方向控制的加减法计数器设计。为了研究问题简便,这里不再采用清零端和初值加载功能,只有clk时钟端、updown方向控制端和计数输出端dout。模块图如下:功能定义如下:当updown为高电平时,dout对clk的上升沿进行加1计数,否则进行减1计数。代码如下:功能比较简单,这里不在注释。功能的正
16、确性可以从仿真图上进行验证出来从图中可以看出,当updown为高电平时,计数器的结果从023进行了加1计数,当updown变为低电平时,从23往下减计数,等等。VerilogHDL中的另一条件语句是case,它的语法格式如下:其中case 和 endcase为case语句的关键字。如果敏感信号表达式(通常是变量)和值n(分支值)相等,那么该分支值后面的语句就会执行,执行结束立刻退出case语句。它的语法非常类似于c语言中的switch-case语句,但是注意每个分支值后面没有break!下面用示例进行说明case语句的用法。 七(八)段数码管是电路系统中常见的外设,通常用于模拟显示数字和个别字
17、符。它的外形如下:标有abcdh的就是8个发光二极管,这8个二极管又有两种形式的电路连接方法,分别称共阴极接法和共阳极接法。共阴极的数码管内部电路如下图:将所有的二极管的阴极接到一起,通常连接到电路的GND上。共阳极的接法恰好相反,把所有二极管的阳极连接在一起,如下图以,共阴极为例,通常电路设计的连接如下图(图中串联了限流电阻,通常270R左右)若要显示如下字符0、1、2、3、4、5、6、7、8、9见下图 只需要将a、b、c、d、e、f、g、h等端口输出相应的高低电平即可。代码示例:设计一个八段数码管显示译码电路,将4为输入的二进制数在数码管上显示出来。模块图如下:din为二进制的输入,a、b
18、、c、d、e、f、g、h为8个输出端口,分别通过限流电阻连接到八段数码管相应的段上。代码如下:代码注释:(1) always(din)表示当din发生值得变化的时候,always块(从begin到end之间的代码)动作一次。always(din)也可以写成always(*),凡是括号中不是边沿信号的都可以写成*号,意义不变。(2)和(5),case语句的格式,必须成对使用。(3)分支值中的4d0也可以写成0(缺省为10进制)或者4b0000,在硬件上的含义的相同的。只是表现形式不同而已(十进制的和二进制的)。后面的分支同样适用。(4)default是case语句的缺省选项(和c语言中的swic
19、h-case一样),当输入的二进制值不在09范围内的时候,数码管不显示。这是一个很实用的例程,在很多数码管显示的系统中,可以直接使用。有兴趣的同学可以下载到带有数码管的FPGA/CPLD硬件系统上尝试一下。还可以对此程序进行扩展,让它能够显示十六进制的AbCdE和F等,这里不再在赘述。 小作业:设计一个加减法电路,2个4位数的加减法,结果用八段数码管显示出来阻塞赋值与非阻塞赋值 在以往的设计中,都是用了“=”这个赋值符号。通常称作阻塞赋值符号。那么它的意义和作用是什么呢?这里通过一个实例设计来进行“=”含义的诠释。欲设计一2个D触发器级联的电路,如下图:根据电路的构成,使用非阻塞赋值符号“=”
20、的代码如下:使用QuartusII综合后,发现生成的电路如下图:也就是说,实际上两个Dff根本没有级联起来?这是为什么呢?在Verilog中,“=”为非阻塞赋值,从语义上讲,begin 和end之间使用了阻塞赋值语句,那么这些语句的赋值是有先后顺序的(注意这里的说法,是语义上的先后顺序,实际硬件电路的动作都是在clk的上升沿处进行的)。也就是说,后一条语句的执行(q1=q0;)是在前一条语句q0=d;赋值结束才进行,而第一条语句中已经将q0的值变成了d,所以下一条语句中将q0的值付给q1就是将d的值赋给q1,所以综合后电路中出现的两个d触发器输入端都是d。要想使生成的电路符合要求,必须在(q1
21、=q0;)这条语句执行的时候,d的值还没有来得及赋给q0才可以。也就是说,让两条赋值语句同时执行就可以。这里就用到非阻塞赋值“=”符号,非阻塞赋值的含义就是指begin和end之间的各个语句执行时同时进行的,不必等上一条语句执行结束就立刻执行。在上面的实例中,将阻塞赋值符号变为非阻塞符号,代码如下:重新编译代码,查看RTL电路图如下:这时候生成的电路就是最开始要设计的电路了。注意事项:阻塞非阻塞赋值只有在时序电路中才会出现,所以只能在always语句中使用,换句话说,不可以在assign语句使用“=” 符号,因为assign电路生成的都是组合电路。非阻塞赋值符号“=”和比较小于等于“=” 是相
22、同的,在Verilog HDL靠语句来识别,如果出现在条件语句if等后面,则是“小于等于”,如果出现在赋值语句中就是赋值符号,要注意识别。多数情况下,为了得到更为精确的逻辑,往往使用“=”的场合比较多,“=”一般会在算法中出现。接着上例,如果说我就想使用“=”符号来实现两个D触发器的级联电路,可以吗?答案是肯定的,只需要将begin和end之间的两条语句调换一下顺序就可以了,读者可以自己去验证并体会其中的含义。当然也可写2个always块,每个always块中各使用一条赋值语句,因为多个always是并行执行的,所以没有语句执行先后之说。边沿检测电路两个相互级联的D触发器在实际的应用中有着很巧
23、妙的应用。有如下电路在级联的2个d触发器后面分别接入了非门和与门或者是异或门,那么这些门的输出端a、b、c会出现什么样的特点呢?首先分析一下a输出,因为两个d触发器是记忆电路,故假设q0和q1端都为低电平,d也是低电平,则a端当前t0时刻输出为低(即0),t1时刻,d变为高电平,t2时刻,时钟上升沿到来。q0端变为高电平,但q1端仍然为低电平,q1的非则为高电平,所以a端输出高电平。t3时刻,又一个clk时钟沿来到,这是q1输出也变为了高电平,故a端又变为低电平。t3时刻以后,例如t4,q0和q1的输出一直为高,a端一直为低电平。由此图得出,当d输入端出现一个上升沿的时候,在clk的作用下,系
24、统的a端会出现一个clk周期宽度的正脉中,这个特性可以在很多的应用中发挥关键的作用,这个电路也叫做上升沿检测电路,或者叫上升沿微分电路(卢毅赖杰编著的VHDL与数字电路设计)。同理可以得出,当d端出现下降沿的时候,b端会输出一个clk周期宽度的正脉中(下降沿检测电路或下降沿微分电路);不论d端产生的是上升沿还是下降沿,c端都会输出一个clk周期宽度的正脉冲(双边沿检测电路或双边沿微分电路)。该电路的完整描述与如下:注释:(1)” 为异或运算符,和c语言中的异或运算一样仿真波形如下图:由上图仿真结果来看,和分析的结果一样。在215ns时刻,d出现了上升沿,随后a端和c端均出现了一个周期的正脉冲,
25、在301ns时刻,d端变低,随后b端和c端口均出现了一个周期的正脉冲。 利用边沿检测电路可以实现一个电路看起来是由多个边沿触发的。而实际上多个时钟的电路时不存在的,因为在数字电路里面,任何触发器都只有一个时钟边沿触发。接下来以双边沿触发的计数器为例,查看电路设计的巧妙之处。两路边沿触发的计数器设计实例Up和down是两个输入端,当up端由低到高变化一次时,cntr进行加1动作(向上计数),当down端由低到高变化一次时cntr进行减加1动作(向下计数),clk为系统的时钟端,用来检测up和down的上升沿(微分电路)。根据要求,使用clk分别对up和down进行边沿检测,即设计两个微分电路。然
26、后根据微分电路的特征(只有一个周期的脉宽,即在这个周期内只有一个上升沿(下降沿),然后在计数电路中判读出现了哪一个脉冲即可进行相应的加或减1操作了。完整代码如下:产生up上升沿微分up_pulse产生down上升沿微分down_pulse由于up_pulse和down_pulse宽度为1个clk周期,故当up端出现上升沿时候,计数器只会进行1次加操作,同理,down端出现上升沿时候,计数器只会进行1次减操作这里的加减计数器和之前讲过的用一个端口控制加减方向的计数器有着本质的不同,注意区分,体会其中的硬件含义。注意:千万不要用posedge up,posedge down作为always的触发条件实现该实例按键去抖动电路设计继续研究两个D触发器级联的作用。在实际应用中,经常用到按键,常见的电路连接如下图:
copyright@ 2008-2022 冰豆网网站版权所有
经营许可证编号:鄂ICP备2022015515号-1