业界动态

零基础教你学FPGA之Verilog语法基础(中)

发布日期:2015-02-09

我们接着上篇文章继续学习,上次提到了两种赋值语句,让我们接着往下学。

  1、块语句

  块语句包括两种,一个是顺序块,一个是并行块。

  (1)顺序快

  顺序快就好比C语言里的大括号“{ }”,在语法中,用begin…end代替。这里只需要知道,在begin…end中间的语句是顺序执行的就行了。

  (2)并行块

  并行块可以算是一个新的知识点,与顺序块最大的不同就是并行块中的语句是同时开始执行的,要想控制语句的先后顺序,可以加延时语句控制。这种并行块是用fork…join语句描述。

  2、条件语句

  条件语句这里不说,和C语言一样。就说一点主意事项。

  在使用条件语句时,要注意语句的严整与封闭性。和C语言不同,举个例子

  always @(al or d)

  begin

  if(al==1) q=d;

  end

  这个例子是说,当al上升沿到来时,d的值赋给q,那么当al=0时又是什么情况呢,事实上,在always块中,如果没有给变量赋值,这个变量就会保持原值,也就是生成了一个锁存器。为了避免这种情况的发生,我们一般这样写

  always @(al or d)

  begin

  if(al==1) q=d;

  else q=0;

  end

  同样在case语句中也要加上default语句避免锁存器的生成,这样可以使设计者更加明确的设计目标,也提高了程序的可读性。

  3、case语句

  

360桌面截图20140226225540.jpg

 

  case语句要注意几点,只挑几点重要的,其他的不说了

  (1)case语句分项后的表达式的值必须相同,否则就会出现问题,例如上面图片上的result。

  (2)与C语言不同,与某一项case语句匹配后,就会跳出case语句,这里没有break语句。

  (3)case语句的所有表达式位宽必须相等,例如上图都是16位整型,如果不加以说明,系统会以默认值32位控制表达式位宽。

  下面是case,casez,case语句的真值表

  

360桌面截图20140226230246.jpg

 

  这个表其实还是很好记的

  case语句,只有匹配才出“1”

  casez语句除了匹配出“1”之外,另外只要遇到“z”就出“1”

  case语句除了匹配出“1”之外,另外只要遇到“z”或“x”就出“1”

  

360桌面截图20140226231114.jpg

 

  上图就用到了casez语句来处理不必要考虑的值,这样就可以灵活的设置对信号的默写位进行比较。

  下面写了一个小例子来练习一下

  使用case语句实现一个四选一多路选择器

  module xiaomo(a1,a2,a3,a4,out,s1,s2);

  input a1,a2,a3,a4;

  input s1,s2;

  reg out; //把输出变量声明为寄存器类型

  always @(s1 or s2 or a1 or a2 or a3 or a4 or out) //任何信号的变化都会引起输出变量的重新计算

  begin

  case ({s1,s2}) //位拼接运算

  2'b00: out=a1;

  2'b01: out=a2;

  2'b10: out=a3;

  2'b11: out=a4;

  default: out=1'bx; //保持语句的严整性

  endcase

  end

  endmodule

4、循环语句

  语法**有4中循环语句,这里只简单说一下C语言里没有的两种

  (1) forever语句

  连续执行语句,这种语句主要用在产生周期性的波形,用来做仿真信号。个人理解和always语句差不多的功能,但是,forever语句只能用在initial块中。

  (2)repeat 语句后面接常量表达式,可以指定循环次数,例如;

  repeat (8)

  begin

  …

  end

  表示循环8次相应语句。

  5、顺序块和并行块

  所谓顺序块就是前面说的begin…end,他的作用就是把多条语句组合到一起执行,在顺序块里面,语句是一条一条顺序执行的,如果遇到#10延迟语句,延迟也是相对于上一条语句的延迟,这一点比较重要。

  相对于顺序块的就是并行块,用fork…end语句表示,并行块里的语句是同时执行的。

  顺序块和并行块可以嵌套使用。

  如果在begin或者fork语句后面加上名字,这个块语句就成了命名块,例如

  begin :xiaomo

  ……

  end

  命名块有什么好处呢?有了命名块,我们就可以用verilog提供的disable语句来随时终止命名块,例如disable xiaomo;这样,当程序运行到此时,就会禁用命名块,就会直接跳出块语句,相当于C语言里面的break语句一样,看下面这个例子:

  

360桌面截图20140308132040.jpg

 

  6、生成块

  生成块语句可以动态的生成代码,这一声明语句方面了参数化模块的生成。党对矢量中的多个位进行重复操作时,或者进行多个模块的实力引用时,或者在根据参数的定义来确定程序中是否应该包括末端代码的时候,使用生成语句能够大大简化程序的编写过程。

  生成语句可以控制变量的声明、任务或函数的调用,还能对实力引用进行全面的控制。编写代码时必须在模块中说明生成块的实例范围,关键字 generate…endgenerate用来指定该范围。

  Verilog中有三种生成语句的方法,分别是循环生成,条件生成和case生成。

  (1)循环生成

  

360桌面截图20140308142337.jpg

 

  

360桌面截图20140308142446.jpg

 

  注:genvar 是关键词,用于生成生成变量,生成变量只存在于生成块中,在确立后的方针代码中,生成变量是不存在的。

  xor_loop是赋予生成语句的名字,目的在于沟通它对循环生成语句之中的变量进行层次化引用。因此循环生成语句中的各个异或门的相对层次为:xor_loop[0].gl,xor_loop[1].gl…,xor_loop[31] 这句话什么意思啊

  这个例子中的 xor gl (out [ j ] , i0 [ j ], i1 [ j ] );什么意思??求大神指点

  当然这个异或门还可用always块实现

  生成块程序

  generate

  for (j=0;j

  begin :xiaomo

  always @(i0[ j ] or i1[ j ]) out [ j ]=i0[ j ]^i1[ j ];

  end

  endgenerate

  (2)条件生成

  

360桌面截图20140308145303.jpg

 

  下面是一段生成语句

  

360桌面截图20140308145416.jpg

 

  (3)case生成

  

360桌面截图20140308150117.jpg

 

  下面是一个例子使用case语句生成N位的加法器

  

360桌面截图20140308150759.jpg

 

  

360桌面截图20140308150809.jpg

 

  说实话上面这些东西我自己也是看的模模糊糊,好多地方不懂,只能先截个图放这儿了。唉...基础还是不行啊...

7、结构语句

  (1)initial语句比较简单,这里就不多说了。

  (2)always语句

  always语句在仿真过程中是不断活动的,always语句后面的语句是否执行,这要看always语句是否满足触发条件。因此,always语句只有和时序控制语句结合才能使用,否则就会被死锁。例如:always areg=~areg;

  这个always语句生成一个0延迟的无限跳变过程这时会发生死锁。但是一旦加上时序控制,这条语句就不一样了,例如:

  always #10 areg=~areg;

  这样的语句就描述的一个周期为20毫秒的跳变信号。所以我们常用这种方法来描述时钟信号,并作为激励信号来测试硬件电路。

  看下面这个例子

  reg [7:0] counter;

  reg tick;

  always @(posedge areg)

  begin

  tick=~tick;

  counter=counter+1;

  end

  这个例子就是说每当信号areg上升沿到来时,信号tick取反,计数器counter加一,这种时间控制是always语句最常用的。

  always语句的时间控制模板

  

360桌面截图20140308153256.jpg

 

  如果组合逻辑块语句的输入变量过多容易漏掉,例如:

  always @(a or b or c or d or e)

  这样的情况下可以用always@ (*)语句来代替,*号自动将所有输入变量默认为敏感信号。

  上面所讨论的都是等待信号的值发生变化或者触发时才执行相应语句,我们也可以用wait语句来用电平敏感来控制。例如

  always

  wait (count_enable) #20 count=count+1;

  意思就是说,当count-enable的值为1时,程序延迟20毫秒后计数。

  8、任务与函数

  书上写了关于任务与函数的区别,写了好多,我觉得区别这两个概念主要看一点就够了,就是看有没有返回值,函数有,任务没有。举个例子

  switch_bytes (old_bytes,new_bytes);这是个任务,没有返回值,功能是把新旧两个字节互换位置。

  new_bytes=switch_bytes(old_bytes);这是个函数,功能是把旧字节转换后赋值给新字节。有返回值。

  下面写一个交通信号灯的程序来学习一下任务这个概念

  moudle xiaomo_traffic;

  reg clock,red,green,amber;

  //定义时钟,红灯,黄灯,绿灯

  parameter on=1,off=0,red_tics=350.amber_tics=30,green_ics=200;

  //定义红灯等待350个时钟,黄灯等待30个时钟,绿灯等待200个时钟

  initial red=0;

  initial green=0;

  initial amber=0;

  //初始化,这里用initial语句保证三条语句同时执行

  always

  begin

  red=on; //红灯亮

  light (red,red_tics); //这里用到任务,功能是等待350个时钟的时间,声明看下面程序

  amber=on; //黄灯亮

  light (amber,amber_tics); //等待30个时钟

  green=on; //绿灯亮

  light (green,green_tics); //等待200个时钟

  end

  task:light; //命名任务

  output color;

  input [31:0] tics;

  //注意这里的两个变量要与上面的一一对应,也就是说上面的light(red,red_tics);中的red对应color,red_tics对应tics

  begin

  repeat(tics); //重复执行tics次下面的语句

  @(posedge clock); //等待上升沿,因为这里的tics对应上面的red_tics几个,所以这里要等待相应个上升沿

  color=off; //等到相应个上升沿结束时,相应颜色的灯关闭

  end

  endtask

  //下面就是写时钟函数了,用always块

  always

  begin

  #100 clock=0;

  #100 clock=1; //每100毫秒产生一次跳变

  end

  endmoudle

  注意:这个程序要找对一一对应关系,例如脉冲模块里的clock对应posedge clock中的clock,程序行与行之间是有联系的,不能随便声明

本站内容除特别声明的原创文章之外,转载内容只为传递更多信息,并不代表本网站赞同其观点。转载的所有的文章、图片、音/视频文件等资料的版权归版权所有权人所有。本站采用的非本站原创文章及图片等内容无法一一联系确认版权者。如涉及作品内容、版权和其它问题,请及时通过电子邮件或电话通知我们,以便迅速采取适当措施,避免给双方造成不必要的经济损失。联系电话:010-82306116;邮箱:aet@chinaaet.com。
FPGA Verilog 基础