硬件描述语言Verilog设计经验总结

来源:本站
导读:目前正在解读《硬件描述语言Verilog设计经验总结》的相关信息,《硬件描述语言Verilog设计经验总结》是由用户自行发布的知识型内容!下面请观看由(电工技术网 - www.9ddd.net)用户发布《硬件描述语言Verilog设计经验总结》的详细说明。
简介:粗略地看Verilog与C语言有许多相似之处。分号用于结束每个语句,注释符也是相同的,运算符“==”也用来测试相等性。Verilog的if..then..else语法与C语言的也非常相似,只是Verilog用关键字 begin和end代替了C的大括号。事实上,关键字begin和end对于单语句块来说是可有可无的,就与C中的大括号用法一样。Verilog和C都对大小写敏感。

一、硬件描述语言Verilog

当然,硬件和软件的一个重要区别是它们的“运行”方式。硬件设计中用到的许多单元都是并行工作的。一旦设备电源开启,硬件的每个单元就会一直处于运行状态。虽然根据具体的控制逻辑和数据输入,设备的一些单元可能不会改变它们的输出信号,但它们还是一直在“运行”中。相反,在同一时刻整个软件设计中只有一小部分(即使是多软件任务也只有一个任务)在执行。如果只有一个处理器,同一时间点只能有一条指令在执行。软件的其它部分可以被认为处于休眠状态,这与硬件有很大的不同。变量可能以一个有效值而存在,但大多数时间里它们都不在使用状态。

软硬件的不同行为会直接导致硬件和软件代码编程方式的不同。软件是串行执行的,每一行代码的执行都要等到前一行代码执行完毕后才能进行(中断的非线性或操作系统的命令除外)。

二、模块(module)

一个Verilog模块的开头是关键字module,紧跟其后的是模块名称和端口列表,端口列表列出了该模块用到的所有输入输出名称。接下来是端口声明部分。注意:所有的输入输出既出现在模块第一行的端口列表中,也会出现在端口声明(declaration)部分中。

以下三个模块分别以∶ 结构式(structural)、数据流式(data-flow)及行为式(behavioral)来描述一个二输入与门。

//structural

module AND2 (in1,in2,out);

input in1;

input in2;

output out;

wire in1,in2,out;

and u1 (out,in1,in2);

endmodule

//data flow

module AND2 (in1,in2,out);

input in1;

input in2;

output out;

wire in1,in2,out;

assign out=in1&in2;

endmodule

//behavioral

module AND2 (in1,in2,out);

input in1;

input in2;

output out;

wire in1,in2;

reg out;

always@(in1 or in2);

out=in1 & in2;

endmodule

结构式的描述∶在这层次中模块是由逻辑闸(Gate Level)连接而成,在这层次的设计工作就好像以前用描绘逻辑闸来设计线路一样。例1中的AND是Verilog的基本元件 (primitive),是Verilog语言预先定义好的函式。

数据流式的描述∶ 它是一种模拟组合函式的方法。当任何输入有所改变时,输出会被重新计算而跟著改变。数据流式只能用来实践组合函式。在这个层次中,要说明数据如何在暂存器中储存与传送,如何处理数据。例2数据流式使用关键字assign进行描述。

行为式的描述∶它是一种使用高阶语言来描述硬件的方式。因Verilog提供很普遍及功能强大的描述方式。在这个层次的设计工作就好像写C语言一样,使用行为式所描述的模块其描述与真实的电路可能毫无关连。甚至某些行为式模块可能硬件实践。这种特性是Verilog的优点也是缺点。 例3中的行为式描述有一个使用关键字always进行描述。

三、Verilog中端口的描述

1、端口的位宽最好定义在I/O说明中,不要放在数据类型定义中;

Example1:

module test(addr,read,write,datain,dataout)

input[7:0] datain;

input[15:0] addr;

input read,write;

output[7:0] dataout; //要这样定义端口的位宽!

wire addr,read,write,datain;

reg dataout;

Example2:

module test(addr,read,write,datain,dataout)

input datain,addr,read,write;

output dataout;

wire[15:0] addr;

wire[7:0] datain;

wire read,write;

reg[7:0] dataout; // 不要这样定义端口的位宽!!

2、端口的I/O与数据类型的关系:

module内部 module外部

input wire wire或reg

output wire或reg wire

inout wire wire

3、assign语句的左端变量必须是wire;直接用"="给变量赋值时左端变量必须是reg!

Example:

assign a=b; //a必须被定义为wire!!

********

begin

a=b; //a必须被定义为reg!

end

在Verilog中有二种类型的内部信号用得比较多,它们是reg和wire。它们具有不同的功能。wire是线网形变量,它不能存储值,必须受到驱动器或者连续赋值语句的驱动。reg是数据存储单元的抽象,通过赋值语句可以改变寄存器存储的值,其作用与改变触发器存储的值相当。所有端口都有一个名称相同且声明为wire的信号。因此连线line被声明为wire不是必要的。reg会保持上次的赋值,因此不需要每次都进行驱动。wire型信号用于异步逻辑,有时也用来连接信号。因为 reg可以保持上次的值,因此输入不能被声明为reg类型。在Verilog模块中可以在任何时候异步地将输入改变为任何事件。reg和wire的主要区别是,reg类型的信号只能在过程块(后面会谈到)中赋值,而wire类型的信号只能在过程块外赋值。这两种信号类型都可以出现在过程块内部和外部的赋值运算符右边。 使用关键字reg并不一定意味着编译器会创建一个寄存器,理解这一点是非常重要的。

四、敏感变量的描述完备性

Verilog中,用always块设计组合逻辑电路时,在赋值表达式右端参与赋值的所有信号都必须在 always @(敏感电平列表)中列出,always中if语句的判断表达式必须在敏感电平列表中列出。如果在赋值表达式右端引用了敏感电平列表中没有列出的信号,在综合时将会为没有列出的信号隐含地产生一个透明锁存器。这是因为该信号的变化不会立刻引起所赋值的变化,而必须等到敏感电平列表中的某一个信号变化时,它的作用才表现出来,即相当于存在一个透明锁存器,把该信号的变化暂存起来,待敏感电平列表中的某一个信号变化时再起作用,纯组合逻辑电路不可能作到这一点。综合器会发出警告。

Example1:

input a,b,c;

reg e,d;

always @(a or b or c)

begin

e=d&a&b; /*d没有在敏感电平列表中,d变化时e不会立刻变化,直到a,b,c中某一个变化*/

d=e|c;

end

Example2:

input a,b,c;

reg e,d;

always @(a or b or c or d)

begin

e=d&a&b; /*d在敏感电平列表中,d变化时e立刻变化*/

d=e |c;

end

Verilog中用于上升沿和下降沿的关键字分别是posedge和negedge。这二个关键字经常被用于敏感列表。

五、条件的描述完备性

如果if语句和case语句的条件描述不完备,也会造成不必要的锁存器。

Example1:

if (a==1'b1)

q=1'b1;//如果a==1'b0,q=? q将保持原值不变, 生成锁存器!

Example2:

if (a==1'b1)

q=1'b1;

else

q=1'b0;//q有明确的值, 不会生成锁存器!

Example3:

reg[1:0] a,q;

....

case (a)

2'b00 : q=2'b00;

2'b01 : q=2'b11;//如果a==2'b10或a==2'b11,q=? q将保持原值不变, 锁存器!

endcase

Example4:

reg[1:0] a,q;

....

case (a)

2'b00 : q=2'b00;

2'b01 : q=2'b11;

default: q=2'b00;//q有明确的值. 不会生成锁存器!

endcase

六、描述的规范性

以触发器为例说明描述的规范性

1、无置位/清零的时序逻辑

always @( posedge CLK)

begin

Q<=D;

end

2、有异步置位/清零的时序逻辑

异步置位/清零是与时钟无关的,当异步置位/清零信号到来时,触发器的输出立即被置为1或0,不需要等到时钟沿到来才置位/清零。所以,必须要把置位/清零信号 列入always块的事件控制表达式。

always @( posedge CLK or negedge RESET)

begin

if (!RESET)

Q=0;

else

Q<=D;

end

3、有同步置位/清零的时序逻辑

同步置位/清零是指只有在时钟的有效跳变时刻置位/清零,才能使触发器的输出分别转换为1或0。所以,不要把置位/清零信号列入always块的事件控制表达式。但是必须在always块中首先检查置位/清零信号的电平。

always @( posedge CLK )

begin

if (!RESET)

Q=0;

else

Q<=D;

end

七、非阻塞赋值和阻塞赋值

always块中的赋值运算符与以关键字assign开头的连续赋值语句中用到的运算符不一样。"<="运算符用于非阻塞性(nonblocking)赋值,而"="运算符用于阻塞性(blocking)赋值。在一组阻塞性赋值语句中,在下一个阻塞性赋值语句执行前需要计算并赋值第一个赋值语句。这一过程就象C语言中语句的顺序执行。而非阻塞语句在执行时,所有赋值语句的右边被同时计算和赋值,在always块结束后才完成赋值操作。连续赋值语句必须使用阻塞赋值语句(否则编译器会报错)。

为了减少代码出错的概率,建议在顺序逻辑(例如希望以寄存器方式实现的逻辑)always块中的所有赋值语句使用非阻塞性赋值语句。大多数always块应该使用非阻塞性赋值语句。如果always块都是组合逻辑,那么就需要使用阻塞性赋值语句。

现列举八条“非阻塞赋值”和“阻塞赋值”指导方针,谨遵这些方针可以帮助Verilog设计者减少所遇到的90-100%的Verilog竞争:

1: 当为时序逻辑建模,使用“非阻塞赋值”。

2: 当为锁存器(latch)建模,使用“非阻塞赋值”。

3: 当用always块为组合逻辑建模,使用“阻塞赋值”

4: 当在同一个always块里面既为组合逻辑又为时序逻辑建模,使用“非阻塞赋值”。

5: 不要在同一个always块里面混合使用“阻塞赋值”和“非阻塞赋值”。

6: 不要在两个或两个以上always块里面对同一个变量进行赋值。

7: 使用$strobe以显示已被“非阻塞赋值”的值。

8: 不要使用#0延迟的赋值。

八、其他一些设计规范和原则

1:不使用初始化语句,用复位脉冲初始化信号和变量。

2:不使用延时语句;

3:不使用循环次数不确定的语句,如:forever,while等;

4:尽量采用同步方式设计电路;

5:尽量采用行为语句完成设计;

6:always过程块描述组合逻辑,应在敏感信号表中列出所有的输入信号;

7:所有的内部寄存器都应该可以被复位;避免使用内部生成的异步置位/清零信号,内部生成的置位/清零信号会引起测试问题。使某些输出信号被置位或清零,无法正常测试。

8:用户自定义原件(UDP元件)是不能被综合的。

9: if...else if ... else 语句是有优先级的,一般说来第一个if的优先级最高,最后一个else的优先级最低。 而case语句是"平行"的结构,所有的case的条件和执行都没有“优先级”。而建立优先级结构会消耗大量的组合逻辑,所以如果能够使用case语句的地方,尽量使用case替换if...else结构。

10: 状态机的一般设计原则,Biary, gray-code 编码使用最少的触发器,较多的组合逻辑。而one-hot编码反之。所以CPLD多使用GRAY-CODE, 而FPGA多使用ONE-HOT编码。另一方面,小型设计使用GRAY-CODE和BINARY编码更有效,而大型状态机使用ONE-HOT更有效。

11:fpga设计中不要使用门时钟,内部生成的时钟称为门生时钟(gated clock)。时钟信号必须连接到全局时钟管脚上。

12:不要使用内部三态信号,否则增加功耗。

13:避免使用负延触发的双稳态多谐振荡器(flip flop)。

14:不要在代码中使用buffer 类型的端口读取输出数据;要使用out 类型,再增加另外变量或信号,以获取输出值。这是因为buffer 类型的端口不能连接到其他类型的端口上,因此buffer 类型就会在整个设计的端口中传播下去。

15:对变量要先读后写;如果先写后读,就会产生长的组合逻辑和锁存器(或寄存器)。这是因为变量值是立即获取的。

提醒:《硬件描述语言Verilog设计经验总结》最后刷新时间 2024-03-14 01:15:55,本站为公益型个人网站,仅供个人学习和记录信息,不进行任何商业性质的盈利。如果内容、图片资源失效或内容涉及侵权,请反馈至,我们会及时处理。本站只保证内容的可读性,无法保证真实性,《硬件描述语言Verilog设计经验总结》该内容的真实性请自行鉴别。