Verilog描述⽅法与层次
Verilog描述⽅法与层次
Verilog语⾔有多种描述⽅法,这些⽅法也可以在多个层次上来描述硬件。
描述⽅式
在上⼀篇当中已经引⼊过数据流描述、⾏为描述、结构化描述这三种描述的⽅式的概念,本篇将继续深⼊说明这三种描述⽅式。
数据流描述
1.数据流 :组合逻辑电路的信号传输其实就类似于数据的流动,数据从中经过但是不会存储,⼀旦输⼊改变,输出随之在⼀定的延时(Tpd)之后发⽣改变。连续赋值语句
连续赋值语句具有以下的特点:
1.连续驱动:输⼊的改变将导致该语句重新计算
2.只有线⽹型变量可以⽤assign赋值:仿真器中不会储存assign赋值的变量,寄存器有存储要求显然不能⽤assign赋值,只有线⽹型可以。另外,assign语句允许多驱动即⼀个变量可以多个输⼊(实际上就是实现线与和线或),建议复习⼀下数字电路中线与和线或的实现。3.适⽤于组合逻辑建模:若要模拟延迟可以通过 # 来加延迟4.并⾏性:assign语句同其他语句块是并发的。以⼀个异或门为例:
module FullAdder(input x,input y,input c_in,output c_out,output sum);assign sum = x^y^c_in;
assign c_out = (x&y) || (x&c_in) || (y&c_in)//以上两条语句都是并发执⾏且连续赋值的。endmodule
延时
任何⼀个元器件都必然存在延时,⽽在数据流描述⽅式中,我们也可以选择对其进⾏建模。
延时具体也可以分为:上升延时即输出变为1时的延时,下降延时即输出变为0的延时,关闭延时即输出变为⾼阻态的延时,以及输出变为X即中间态的延时(通常取前⾯三种中最⼩的)。关于延时的建模也有多种⽅法,以下是⼀个例⼦:
assign #1 out = in1^in2;//`timescale 1ns/1ns输⼊到输出须1ns
assign #(1,2) out1 = in1^in2;//上升延时1ns,下降延时2ns,变x和关闭取最⼩为1nsassign #(1,2,3) out2 = in1^in2;//上升1ns,下降2ns,关闭3ns,变X1ns
assign #(4:5:6,3:4:5)out3 = in1^in2;
//(min:typ:max)描述的时延时最⼩典型最⼤三种情况,同样第⼀个是上升,第⼆个是下降
注意:连续赋值中的延时是惯性延时,即出现⼩于延时的信号(⽑刺信号)会被过虑掉。⽤户只能在逻辑综合中对时序加以控制,没办法直接操控器件本⾝的特性。多驱动线⽹
多驱动的意思实际上就是可以将⼏条线连到⼀起,实际上这样的情况主要集中在:线与、线或以及三态总线等实际应⽤当中。
wor wire_or;wand wire_and;tri wire_tri;
assign wire_or = in1 & in2;
assign wire_or = in3 & in4;//以上两个右边结果的或assign wire_and = in1 | in2;
assign wire_and = in3 | in4;//以上两个右边结果的与
assign wire_tri = (Write)? mem1 : 1'bz;
assign wire_tri = (read)? mem1 : 1'bz;//⾼阻态确保了能切断电⽓连接不会混乱
千万注意:⼀般的wire类型是绝对不允许多驱动的,数字电路中⼀旦这样接就会产⽣各种各样的竞争冒险,造成严重的输出混乱。
⾏为描述
⾏为描述使⽤语⾔描述电路的⾏为。⾏为描述的语句只有:always和initial。
⾏为描述的语句格式
1.initial与always语句块
主要把握两者的区别:initial只执⾏⼀次之后就会永久挂起,⽽always⼀旦符合敏感表的条件,就会反复执⾏。2.过程块中的语句种类
主要有四类:阻塞语句与⾮阻塞语句,连续过程赋值(是可以做连续过程赋值的)和⾼级编程语句。
always@(posedge Clock or negedge Rst_n)//always语句块begin
if(!Rst_n)//⾼级编程语句 begin
Reg_A <= 0;//⾮阻塞语句 Reg_b <= 0; end else begin
Reg_A = Input_A;//阻塞语句 Reg_b = Input_B; endend
3.时序控制(Timing Control)
时序控制主要通过三种⽅式进⾏控制:事件语句“@ ”,延时语句“#”,等待语句
`timescale 1ns/1ns
module Dff(input D,input Clk,input Rst,output Q);
always@(posedge Clk or negedge Rst)//事件发⽣即变量发⽣变化时执⾏ begin if(Rst) Q <= 0; else
Q <= D; end
endmodule
initial begin
#5 clk = 0;//表⽰5ns后clk赋值0end
wait()语句许多综合⼯具仍不⽀持,也主要⽤在仿真时,所以不赘述了。过程赋值语句
过程赋值语句即在initial块和always块中赋值的语句,主要分为:阻塞语句与⾮阻塞语句。
1.阻塞语句
常规形式 寄存器变量 = 表达式
所谓阻塞其实有两层含义:其⼀,计算表达式与赋值是统⼀的原⼦操作,不可再分,其中不能插⼊其他操作。其⼆,在begin...end代码块中必须把这个表达式赋值完才能做后续的操作,完成之前将完全阻塞后续操作,是顺序执⾏的。⽽阻塞往往也是⽤在组合逻辑电路当中的。
wire A;wire B;wire C;reg temp;reg D;
always@(A or B or C )begin
temp = A&B;
D = temp | C;//这样写可以使得逻辑⾮常清晰end
有意思的是,实际综合的过程中 D 与 temp 是不会被作为寄存器综合的,综合时仍然作为组合电路去处理。2.⾮阻塞赋值
常规形式 寄存器变量 <= 表达式
对应于阻塞赋值的情况:⾮阻塞意味着左右两边的操作不是作为⼀个原⼦操作进⾏的,执⾏时⾸先计算右边表达式,但并不马上赋值。同时在begin...end语句中出现的⾮阻塞赋值语句会默认优先级较低,计算后会在靠后的时间才赋值。这带给我们以良好的并发特性,在时序逻辑电路中⾮常实⽤。
always@(posedge Clk)begin
Q1 <= D; Q2 <= Q1;
Q3 <= Q2;//这很好地描述了⼀个三级流⽔寄存器电路end
3.过程连续赋值
在过程语句中是允许⽤assign对寄存器赋值的。但是需要注意这⼀赋值是强制性的,有需要时必须放开否则⽆法继续对该寄存器进⾏操作了。常⽤在测试中。这即称为连续过程赋值。关键字:assign 与 deassign;force 与 release;
module(input D,input Clk,input Clr,output Q);always@(Clr)begin
if(!Clr)//实现⼀个异步清零的操作 assign Q = 0; else
deassign;end
always@(posedge Clk) Q <= D;endmodule
语句组
语句组包含两类:顺序语句组begin...end...;并⾏语句组fork...join;
1.顺序语句组
其中的语句是⼀条⼀条的顺序执⾏的。当然,正如前⽂所述,对于⾮阻塞性的语句,是不会阻塞后续执⾏的。下⾯以⼀个典型的带延时的语句来说明这⼀点:
`timescale 1ns/1nsinitial begin
#2 Data = 0;#4 Data = 1;#8 Data = 2;end
//实际上如果观察波形图会发现:2ns时Data为0,6ns时Data为1,14ns时Data为2//三个语句是顺序的
2,并⾏语句组
⽤同样的例⼦来阐述这⼀情况
`timescale 1ns/1nsinitial fork
#2 Data = 0;#4 Data = 1;#8 Data = 2;join
//实际上如果观察波形图会发现:2ns时Data为0,4ns时Data为1,8ns时Data为2//三个语句是并发的
3.语句组的标识符
通过语句组的标识符可以实现类似C中局部变量的存在,即只在代码块内有效的变量。⾼级编程语句
Verilog的特点就是设计层次较⾼,不仅在晶体管级和门级,主要在RTL级和⾏为级描述,以及编写测试激励。为了提⾼抽象能⼒,Verilog从C中借鉴了⼀些语句,同时也创造了⼀些语句,统称为⾼级编程语句。注意:⾼级编程语句只能出现在initial和always过程块中,也可以互相嵌套。主要有三类:if...else语句,case语句,循环语句
1.if...else语句
正如C语⾔中那样,if...else表⽰的是条件判断。其中也允许继续嵌套语句组(begin..end或fork..join)。但是在硬件当中,则会体现⼀些新的特点。
⾸先,if...elif...else天然就具有优先级。
always@(a or b or c or sela or selb)begin if(sel_a) out = a; else if(sel_b) out = b; else out = c;end
//实际上实现了⼀个两级的多路选择器
//⽽选择sela的优先级最⾼,如果sela是关键路径则显然有利于电路实现
注意:应该避免使⽤if语句产⽣锁存器。尽量应该避免出现 a = a的情况导致锁存,因⽽有时需要遍历全部的条件,或者设置不关⼼值 x。
always@(sel or a or b or c)
begin
if(sel == 2'b00) q = a;
else if(sel == 2'b01) q = b;
else if(sel == 2'b10) q = c;
//这⾥没有说明sel = 2‘b11时的情况,因⽽系统默认此时q值不变从⽽出现锁存器 //锁存器(latch)是应该避免的器件,因为容易产⽣竞争冒险 //应该加上以下语句:
//else q = 1'bx 此时不关⼼q取什么。 end
2.case语句
case语句的语法仍然同C语⾔中没有⼤的区别。但是我们要注意同为条件判断语句在硬件实现中它与if...else的区别与联系。区别:case中所有判断是并发的,不存在优先级的概念。
联系:case中也容易产⽣锁存器,因⽽也需要格外注意default的设置。
always@(sel or a or b or c)begin
case(sel)
2'b00 : q = a; 2'b01 : q = b; 2'b10 : q = c;
default : q = 1'bx;//如果不写就会出现锁存。 endcaseend
case还有两个派⽣的语句casex(表⽰对x不关⼼)与casez(表⽰对⾼阻态不关⼼)
casez(encoder) 4'b1zzz : out1 = 3; 4'bz1zz : out1 = 2; 4'bzz1z : out1 = 1; 4'bzzz1 : out1 = 0;endcase
casex(encoder1) 4'b1xxx : out2 = 3; 4'bx1xx : out2 = 2; 4'bxx1x : out2 = 1; 4'bxxx1 : out2 = 0;endcase
实际上,在单热线(one-hot)中为了节省译码时间经常这样编码加快运⾏速度。3.循环语句
循环语句⽤于重复的操作。
在描述硬件的过程中,不是⾮常推荐写循环语句,因为在综合时,软件实际上时对循环从头到尾全部执⾏⼀遍。可能看起来接近软件程序的写法⽐较简洁,实际上对于硬件描述不是很好。通常循环语句都是⽤于后期对于电路的验证激励。循环语句主要有四种:forever, repeat, for(), while()
//===================单独的代码块 initial begin clk = 0; count = 0;
while(count<101) begin
$display(\"count = %d\ count = count + 1; end
forever # clk = ~clk;//forever常常⽤于⽣成时钟 end
//===================单独的代码块 integer i;
always@(posedge clk) begin
for(i = 0; i<100;i = i+1)//也是常常⽤在激励中做输⼊输出 q = i; end
//===================单独的代码块 if(in == 1)
repeat(8)//表⽰重复以下操作⼋次 a = a + 1;
结构化描述
结构化描述实际上就是在描述中使⽤已有的或者已经写好的功能模块。包括:门源语,⽤户⾃定义原语,其他模块。写好结构化描述对于代码的维护和扩展有很⼤的帮助。也容易理清各个模块之间的关系。
module half_add(
input a, input b, output c_out, output sum)xor u_xor(sum,a,b);//assign sum = a^b;and u_and(c_out,a,b);//assign c_out = a&b;//使⽤了门原语endmodule
实例化模块的⽅法
1.⾸先要再次强调输⼊输出的问题:input 输⼊在模块内是默认⼀个线⽹类型
output 输出在模块内默认为⼀个寄存器或者线⽹类型inout 在模块内默认是线⽹类型,双向信号应该是tri2.实例的⽅式
名称对应:module(.x(a),.y(b),.z(c) ...)通过名称做对应;
位置对应:module(a,b,c ...)外部的信号与对应端⼝信号在括号的位置⼀⼀对应,类似C语⾔的函数调⽤;参数化模块
1.参数定义
参数在硬件描述中有着重要的位置,例如上⼀篇提到过的,使⽤参数⽽不是编译指令去对状态机的状态进⾏描述等等。
实际上,当实例化模块是,⽤户是可以修改模块的参数来实现不同的特性。因此,⽤户可以定制⼀个模块具有缺省的参数值从⽽来改变参数做成不同的模块。2.参数的定制
有两种⽅法:主流的Altera和Xlinx各使⽤了⼀种。这⾥介绍简单介绍⼀下:通过defparam关键字对参数重新定义
在实例化模块中代⼊。类似于模块的位置对应。
系统层次
上⼀篇提到过,Verilog语⾔共有五个层次,⾃上到下:系统级,⾏为级,RTL级,门级,晶体管级。
我们设计中主要使⽤的都是RTL级。RTL级也是主流综合器最常⽤的级别,⾏为级虽然描述简单,但是实际综合中会出现很多错误。⽽对于验证中则常常使⽤⾏为级。
严格的说,描述层次之间没有严格的界限,根据⼯作特点和关注要素不同要合理选择。