Verilog语法基础

程序模块

1、Verilog程序如同其他编程语言的积序二后有快宁的源文件格式。Veriloc的源代码扩展名为.v,每一个Verilog文件都是一个Vorilo档地诗老可以理解为编程语言中的的数。其基本格式如下:

 module模块名(模块输入/输出信号);
 模块内容
 endmodule

其中模块名通常和文件名相同,同一个文件只定义一个模块,比如adder.v里就应该只定义一个叫adder的模块。这个要求和Java对类的要求很相似。

2、输人、输出信号则类似函数的返回值和参数,只不过在Verilog里并不把参数和返回值放到不同的地方定义,而是都写在一起。所有的参数或者返回值,最终都只是导线而已。而导线根据驱动信号的方向,可以有输人和输出区别。至于需要多少个输人、多少个输出,那就取决于具体的程序了。

3、模块内容则是模块内部的逻辑,也许有代码块( always),也许只是一些简单的接线( assign )。不过别忘了,一切最后都会回归到硬件。

4、最后说说模块的实例化,或者说调用。如前面所说,模块类似于软件编程语言里而的函数,它也确实有对应的函数名、参数、返回值等类似的概念。那么要使用这个“函数”,自然也就需要一种调用的方法。只不过, Verilog里的调用,并不是像编程语言一样在特定位置执行特定代码(毕竟本身就没有“执行代码”这种操作),而是新复制一份这个模块所表示的硬件,然后连接对应的导线。这一点在参数的定义时其实也就有所体现,定义的输人、输出并非是变量,而是导线,也就是说,传递的内容并不是数值,而是连接。一旦连接被确定,传输就是时时进行的(因为被连接上了)。这和编程语言里的调用非常不一样,所以务必进行区分。具体的例子我们之后将提到。

模块参数

1、模块参数的格式其实很固定,就是方向、类型、宽度、名称。

2、方向可以是输入(Input)或者输出(Oulpul ),注意方向是对于模块而言的,从外界进入模块是Input,而从模块输出到外部则是Oulpul。

3、在Verilog里,基础类型只有两种:一种是wire,另一种是reg。需要说明的是,不要望文义,Verilog里的wire是指导线没错,但是reg并不说明这是一个寄存器。具体的区别会在后面单独解释。如果没有标明类型,通常会默认为wire,但是这个默认值是可以修改的。

4、宽度表示这个信号的位宽。在其他各种编程语言中,数据类型通常都会指定这个变量的位比如说C语言中char是8位,int通常是32位,double通常是64位,等等。而Verilog里类型并不能顺便指定这个变量的位宽,位宽需要单独指定。如果没有指定,通常就默认为1位。这其实是个十分常见的错误,多位的信号忘了指定宽度,默认成了1位。

5、名称和其他语言中的变量名称,概念是一样的。接下来举儿个例子中,具体的用途当然还是需要结合整个模块来理解。

 input wire a,//定义一个1位的wire型输入信号,名为a
 output wire b,/定义一个1位的wire型输出信号,名为b
 output reg c,1/定义一个			1位的reg型输出信号,名为c
 input wire[3:0] d,//定义一个4位(3~0)的wire型输入信号,名为 d
 output wire[9:0] e,1/定义一个10位(9~0)的wire型输出信号,名为e
 output reg[0:8] f,1/定义一个9位(0~8)的reg型输出信号,名为f

内部信号定义

内部信号定义类似于软件编程语言中的变量。只不过这里的变量,不一定具有存储数值的能力,有可能只是作为导线起到连接的作用。内部信号定义的语法格式几乎和模块的参数一样,只是去掉了方向、定义,毕竞不需要和外界通信.自然也就没有输入、输出的说法。以下是几个例子。

wire a;    //定义一个1位的wire型信号,名为a
reg[31:0] b;     //定义一个32位的reg型信号,名为b

表达式和运算符

Verilog作为一个类C语言,其表达式、运算符和C语言都非常接近。但是需要指出的是,在Verilog里进行数字运算时,综合器会根据操作产生需要的运算器(加法器、乘法器等),有时候默认的行为可能并不合适、需要特别意。

数值表示

1、Verilog的数值表示相比共他编程语言要复杂一些。原因很简单,前面在信号定义里提到了。一个信号的宽度可以是任意数值。而不是根据类型固定的几个数值,数值的表示方式中也得能够体现这个特点。Verilog中数值的格式为:宽度、‘ ’ '、进制、数字。

2、宽度就是位宽,是一个数字,和之前信号定义里的位宽对应;进制可以为二进制(b)、十进制(d)和十六进制(h);数字就是要表示的数字。

3、比如要表示一个8位的十进制数255,就写作8’ d255。同样的数字用二进制表示为8’bIII11IIl,用十六进制表示为8’hFF。这3种写法是完全等效的、只是看具体应用时哪种方便了。当然,宽度需要大于足够表示这个数字的最低宽度、比如还是255,最少需要8位来表示,所以并不能写作4’d255,但是可以写作20’d255。因为20位足够大。以下是一些例子:

5'd16   //5位十进制数16
7'h23   //7位十六进制数0x23
2'b10  //2位二进制数0b10

程序语句assign

1、Verilog 的程序语句基本可以分为两大类;一类是固定赋值语句(assign);另一类是代码块always)中使用的语句。之前说的il-else、for之类的语句都是配合语句块使用的。

2、先来训assign。assign的作用很简单,就是接线。比如说有两条线a和b,要把它们连接起来,通常来说语句如下:

assign a=b;或者assign b=a;

1、这两种写法有什么区别吗?如果只是接线,那么接上就是接上了,a和b接起来与b和a接起来真的有区别吗?答案是:有。虽然只是导线,但是导线最终还是会连接到输人/输出端口或者逻辑门上,这样导线也就有了驱动方和被驱动方的区别。比如a如果连接到了一个输入接口上(从外部进入FPGA),而b连接到了一个输出接口上(从FPGA输出到外部),合理的写法就是b=a,信号会从外部进入FPCGA连接到a上,随后从a连接到b,再输出到FPCA外部。当编写代码时,应该楚,虽然这是连线,但是右边永远应该是驱动方,左边应该是被驱动方,就像编程语言里面数据从右向左传输一样。

2、值得注意的是,assign能够做的不单单是简单的连线。assign已经足以实现很多组合逻辑了。举个例子,请看图2.42所示的半加器的原理图。
在这里插入图片描述

3、提示一下,这个电路的作用是输人两个1位的二进制数,计算它们的和,输出2位的结果。我们通过观察输入数字和输出数字的关系,发现这个电路其实只需要两个逻辑门就可以实现需要的功能,于是也就画出了这样的电路图。其中ICI和IC3就是两个逻辑门器件。这个电路可以很简单地用FPGA来实现:把两个按键的输入和两个LED的输出都连接上FPGA(通常开发板上都有),然后在FPCA内完成所需的逻辑。我们现在来看一下代码。

module lesson3(
input wire a,
input wire b,
output wire c,
output wire d);

assign c= a&b;
assign d= a^b;
endmodule

4、lesson3是模块名,括号里都是对于输人、输出信号的定义,而模块主体是两句assign语句。

5、首先来看参数部分,这里定义了4个信号,分别叫a、b、c、d,两个输入,两个输出,都没有定义宽度,所以默认为一位、类型为wire。这个代码就实现了图2.42所示的电路,而电路中有两个按键,是从外部输人模块的,所以定义了两个输入信号。同理,为了输出两个LED需要的信号定义了两个输出信号。

6、再来看模块主体。模块主体只有两条assign,内容也非常简明,一个是把a和b做与运算后连接上e,另一个是把a和b做异或运算后连接十d,这对应着图1。

7、注意这里出现了一个等号,具不具说明这个操作就类似于软件编程语言中的赋值呢?并不是。赋值所表示的是计算出右边的结果,复制保存到左边的变量当中。而Verilog的 assign,如之前所说,只是连接的作用,比如assign c= a&b;就是表示产生一个能计算a&b的电路(一个与门),并把结果和c连接起来。从效果来说它和赋值的区别就是,赋值是一瞬间发生的事情,获取a和b的值,计算a&b,存入c。赋值完成后,c和a、b就没有别的关系了,即使a和b的值在后面发生了变化,c也会保留先前的值。而使用assign,c是一条导线,没有记忆,并不能保留a&b的值,如果a、b发生变化,c也会随即发生变化。

8、总结一下,assign语句可以用于描述组合逻辑,assign的左值就是组合逻辑的输出,而右侧表达式则是组合逻辑的逻辑表达式。

程序语句always

1、讲完了assign,我们已经可以用Verilog来描述组合逻辑了,但是还不能描述时序逻辑。时序逻辑需要使用一种称为always语句块的东西。这里选取前面介绍过的按下按键让灯改变状态的例子(见图2.43)。
在这里插入图片描述

对应的Verilog代码如下。

module light(
	input key,
	output reg light);
	
alwayse(posedge key)
begin
	light<=~light;
end
endmodule

2、模块名称为light,有两个信号,一个是输入,叫作key,没有指定类型(默认为wire) ;
另一个是输出,叫作light,类型为reg。显然key就是按键输入,light 就是灯的输出。注意这里ligh被定义成了reg类型,确实表示灯应该连接到一个寄存器(触发器)上。

3、模块主体中只有一个always语句块,always语句块的格式如下。

always@(触发条件)
begin
	语句1
	语句2
	......
end

4、always语句块的含义就是:当触发条件满足时执行语句。begin和end的作用就类似于C语言中的( )、只是把多个语句并在一起而已。

5、这个例子中的触发条件只有一个,就是posedge key,表示key输入信号的上升沿(posedge)触发。如果需要下降沿则是negelge。而语句只有一条lighl <= ~light,这确实是一个赋值语句,表示计算右边的值,存人左边的寄存器。当然,实际上发生的事情就是,产生一个能够计算右边结果的电路(非门),接人保存左边信号(reg light)的触发器的输人端,并且把always的触发条件接入触发器的时钟输人。这样,当满足触发条件时,也就是给触发器产生了时钟,触发器的输入端,也就是赋值语句右边的结果,会被存入寄存器。刚刚的解释请对照着原理图再理解一次。以上就是“满足触发条件,执行语句”这个听起来很“软件”的操作的硬件实现过程。

6、上面的这个解释,其实暗示了一个限制:因为时钟永远是几乎同时到达各个触发器的,所以各个赋值只能同时发生。如果你写以下的代码:

a<= b;
c<= a;

7、会发生什么呢?如果是软件编程语言中的赋值,不难看出b的值会被存入c。而这里,因为赋值是同时发生的,所以a原先的值会被存人c,而b的值会被存人a。所以这个赋值操作(<=)的名字也就是非阻塞赋值,表示上一条赋值语句的执行并不会阻塞下一条赋值语句的执行,也就是说所有赋值同时发生。这也是符合硬件实际情况的设计。

8、以上可以看出, always语句块可以用来实现时序逻辑的触发器部分。不过, always语句块还能实现组合逻辑。组合逻辑不需要等待任何时钟就会发生,或者说一旦输人变化,输出就会发生化。为此, Verilog中,用always语句块来实现组合逻辑的写法就是:

always @(*)
begin
	语句1
	语句2
end

9、星号(*)就表示了任何输入发生变化都会触发,也就是像组合逻辑的表现一样。如果用always来重写前面的那个组合逻辑的例子,就会是以下这样:

alwayse(*)
begin
	c=a&b;
	d=a^b;
end

10、注意这里的赋值用的不再是之前的<=了,而是变成了=。这个赋值在Verilog里称为阻塞式赋值,也就是和传统编程语言一样的赋值方式,前面的先执行,后面的后执行。所以你可以写类做这样的代码:

always (*)
begin
	c= 1'b0;
	if((a=1'b1)||(b=1'b0))
		c=1'b1;
end

11、这段代码完全可以按照传统编程语言的思路来理解,首先将c赋值为0,随后,如果a为1,或者b为0,那么就将c赋值为1,否则就保持不变。但是事情真的是这样的吗?并不是。不如来考虑一下,如果代码真的是这么执行的,会发生什么事情。当模块中有任何信号发生变化时,c都会先赋值为0,随后按照a、b的输人判断是不是要赋值为1。这就是这个代码所表示的意思。那么,假设现在a为1,b为0,c应该为1吧?如果b从0变成了1,会发生什么呢?c还是会先变成0,然后发现条件成立,再变成1。

12、对吗?并不对。c会保持为1,条件仍然成立,c会一直保持为1。阻塞式赋值只是Verilog中的一个“语法糖”(编者按:语法糖就是对功能并没有影响,但是更方便程序员使用的某种语法)。真实的硬件并不能够实现赋值的先后顺序,设计一个阻塞式赋值的语法只是为了方便描述逻辑。如刚刚那个always块,其实就等效于如下的assign语句:

assign c=a|~b;

13、对应的硬件无非就是一个非门加上一个或门,并没有什么特殊的“赋值电路”、这个电路就可以实现,在任何输入发生变化时产生相应的输出。至 于Verlog中关于“阻塞式赋值”的设定,只是为了方便描述逻辑而存在的。有了阻基式赋值的语法,我们在写代码的时候就可以在一个always语句块里多次对同一个变量赋值,只有最后一次结果才会被保留。通常的做法就是先给信号设定一个初始值,随后再使用不同的语句按照情况赋新的值。

14、做一个简单的总结,alwaysi语句有两种写法。第一是用于描述时序逻辑的,形式为always@(posedge 时钟信号),代码块中只使用非阻塞式赋值(<= )。第二种则是用于描述组合逻辑的。形式为always@(*),代码块中只使用阻塞式赋值(=)。前面讲了那么多原理上的东西,是为了让大家明白Verilog中代码和实际硬件的对应关系,帮助大家理解为什么在Verilog中不能像C一样代码。

以上文章出自《PPGA入门指南》--------张文挺著

  • 42
    点赞
  • 36
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值