第3章

Verilog HDL 语法






视频讲解


3.1Verilog简介
在FPGA开发中使用最多的语言主要有两种: VHDL和Verilog。VHDL语法格式严谨,入门比较难。VHDL最初由美国国防部创建。Verilog对软件爱好者来说有种似曾相识的感觉,不易被爱好者排斥,初看起来还以为是C语言编程,其实它起源于C语言,语法格式灵活,容易掌握,因此被大多数FPGA工程师所喜爱。本章只对Verilog语法内容进行介绍。
3.2数据类型
3.2.1常量

(1) 整数: 整数可以用二进制b或B、八进制o或O、十进制d或D、十六进h或H表示,例如, 8’b00001111表示8位位宽的二进制整数,4’ha表示4位位宽的十六进制整数。
(2) x和z: x代表不定值,z代表高阻值,例如,5’b00x11,第三位为不定值,3’b00z表示最低位为高阻值。
(3) 下画线: 在位数过长时可以用来分割位数,提高程序可读性,如8’b0000_1111。
(4) 参数parameter: parameter可以用标识符定义常量,运用时只使用标识符即可,以提高程序可读性及维护性,如parameter width=8,寄存器reg [width-1:0] a,即定义了8位宽度的寄存器。
(5) 参数的传递: 在一个模块中如果有定义参数,那么在其他模块调用此模块时可以传递参数,并可以修改参数,如下面程序所示,在module后用#()表示。
例如,定义如下调用模块:

module rom module top(); 

#(wire[31:0] addr ; 

parameter depth =15,wire[15:0] data ; 

parameter width =8wire result ; 

)(rom 

input[depth-1:0] addr ,#(

input[width-1:0] data ,.depth(32),

output result .width(16)

); )

r1  

endmodule(

.addr(addr),

.data(data),

.result(result)

); 

endmodule





parameter可以用于模块间的参数传递,而localparam仅用于本模块内使用,不能用于参数传递,而localparam多用于状态机状态的定义。
3.2.2变量
变量是指程序运行时可以改变其值的量。下面主要介绍几种常用的变量。
1. wire类型
wire 类型变量也叫网络类型变量,用于结构实体之间的物理连接,如门与门之间,不能存储值,用连续赋值语句assign赋值,定义为wire [n-1:0] a,其中n代表位宽,如定义“wire a,assign a=b”,是将b节点连接到连线a上。如图31所示,两个实体之间的连线即是wire类型变量。
2. reg类型
reg 类型变量也称为寄存器变量,可用来存储值,必须在always语句里使用。其定义为reg [n-1:0] a ,表示n位位宽的寄存器,如reg [7:0] a表示定义8位位宽的寄存器a。如下所示定义了寄存器q,生成的电路为时序逻辑,如图32所示为D触发器。



图31wire连线




图32D触发器





module top(d, clk, q); 

input  d  ; 

input clk ; 

outputreg q ; 

always@(posedge clk)

begin

q <= d ; 

end

endmodule


也可以生成组合逻辑,如数据选择器,敏感信号没有时钟,定义了reg Mux,最终生成电路为组合逻辑,如图33所示。



图33数据选择器



module top(a, b, c, d, sel, Mux); 

inputa ; 

inputb ; 

inputc ; 

inputd ; 

input[1:0] sel ; 

outputreg Mux ; 

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

begin

case(sel)

2'b00: Mux = a ; 

2'b01: Mux = b ; 

2'b10: Mux = c ; 

2'b11: Mux = d ; 

endcase

end

endmodule


3. memory类型
可以用memory类型的变量来定义RAM和ROM等存储器,其结构为reg [n-1:0] 存储器名[m-1:0],含义为m个n位宽度的寄存器。例如,reg [7:0] ram [255:0]表示定义了256个8位寄存器,256是存储器的深度,8为数据宽度。


视频讲解


3.3运算符
3.3.1算术运算符

算术运算符包括“+”(加法运算符)、“-”(减法运算符)、“*”(乘法运算符)、“/”(除法运算符,如7/3 =2),“%”(取模运算符,也即求余数,如7%3=1,余数为1)。
3.3.2赋值运算符
赋值运算符分两种:“=”阻塞赋值和“<=”非阻塞赋值。阻塞赋值为执行完一条赋值语句,再执行下一条,可理解为顺序执行,而且赋值是立即执行; 非阻塞赋值可理解为并行执行,不考虑顺序,在always块语句执行完成后,变量才进行赋值。如下面的阻塞赋值: 
激励文件代码如下: 

module top(din,a,b,c,clk); `timescale 1ns/1ns

input din; module top_tb(); 

input clk; reg din ; 

outputreg a,b,c; reg clk ; 

always@(posedge clk)wire a,b,c ; 

begininitial

a = din; begin

b = a; din =0; 

c = b; clk =0; 

endforever

endmodulebegin

#({$random}%100)

din =~din ; 

end

end

always#10 clk =~clk ; 

top  t0(.din(din),.a(a),.b(b),.c(c),.clk(clk)); 

endmodule


仿真结果如图34所示,在clk的上升沿,a的值等于din,并立即赋给b,b的值赋给c。


图34阻塞赋值仿真波形


如果改为非阻塞赋值,则仿真结果如图35所示,在clk上升沿,a的值没有立即赋值给b,b为a原来的值,同样,c为b原来的值。


图35非阻塞赋值仿真波形


可以从两者的RTL视图看出明显不同,如图36和图37所示。


图36阻塞赋值RTL图




图37非阻塞赋值RTL图


注意: 一般情况下,在时序逻辑电路中使用非阻塞赋值,可避免仿真时出现竞争冒险现象; 在组合逻辑中使用阻塞赋值,执行赋值语句后立即改变; 在assign语句中必须用阻塞赋值。
3.3.3关系运算符
关系运算符用于表示两个操作数之间的关系,如a>b、a<b,多用于判断条件,例如:


If (a>=b) q <=1'b1 ; 

else q <= 1'b0 ; 


表示如果a的值大于或等于b的值,则q的值为1,否则q的值为0。

3.3.4逻辑运算符
逻辑运算符包括“&&”(两个操作数逻辑与)、“‖”(两个操作数逻辑或)和“!”(单个操作数逻辑非)。例如: if (a>b && c <d) 表示条件为a>b并且c<d,if (!a)表示条件为a的值不为1,也就是0。
3.3.5条件运算符
“?:”为条件判断,类似于if else,例如assign a = (i>8)?1’b1:1’b0 ,判断i的值是否大于8,如果大于8则a的值为1,否则为0。
3.3.6位运算符
位运算符包括“~”(按位取反)、“|”(按位或)、“^”(按位异或)、“&”(按位与)、“^~”(按位同或)。除了“~”只需要一个操作数外,其他几个都需要两个操作数,如a&b、a|b。具体应用3.4节中有讲解。
3.3.7移位运算符


图38优先级

移位运算符包括“<<”左移位运算符和“>>”右移位运算符,如a<<1表示向左移1位,a>>2表示向右移2位。
3.3.8拼接运算符
“{ }”为拼接运算符,用于将多个信号按位拼接起来,如{a[3:0],b[1:0]},将a的低4位和b的低2位拼接成6位数据。另外,{n{a[3:0]}}表示将n个a[3:0]拼接,{n{1’b0}}表示n位的0拼接,{8{1’b0}}表示8’b0000_0000。
3.3.9优先级
各种运算符的优先级如图38所示。


视频讲解


3.4组合逻辑
本节主要介绍组合逻辑,组合逻辑电路的特点是任意时刻的输出仅仅取决于输入信号,输入信号变化,输出立即变化,不依赖于时钟。
3.4.1与门
在Verilog中以“&”表示按位与,如c=a&b,表示在a和b都等于1时结果才为1,其真值表和RTL视图如图39所示。


图39与门真值表及RTL视图


代码实现和激励文件如下: 

module top(a, b, c); `timescale 1ns/1ns 

input  a ; module top_tb(); 

input  b ; reg a ; 

output c ; reg b ; 

wire c ; 

assign c = a & b ; initial

endmodulebegin

a =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),.c(c)); 

endmodule



仿真结果如图310所示。


图310与门仿真波形


如果a和b的位宽大于1,例如有定义“input [3:0] a,input [3:0]b”,那么a&b指a与b的对应位相与。如a[0]&b[0]、a[1]&b[1]。
3.4.2或门
在Verilog中以“|”表示按位或,如c = a|b ,表示在a和b都为0时结果才为0,其真值表和RTL视图如图311所示。


图311或门真值表与RTL视图


代码实现和激励文件如下: 

module top(a, b, c); `timescale 1ns/1ns 

input  a ; module top_tb(); 

input  b ; reg a ; 

output c ; reg b ; 

wire c ; 

assign c = a | b ; 

endmoduleinitial

begin

a =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),.c(c)); 

endmodule


仿真结果如图312所示。


图312或门仿真波形


3.4.3非门
在Verilog中以“~”表示按位取反,如b=~a,表示b等于a的相反数,其真值表和RTL视图如图313所示。


图313非门真值表及RTL视图


代码实现和激励文件如下: 

module top(a, b); `timescale 1ns/1ns 

inputa ; module top_tb(); 

output  b ; reg  a ; 

wire b ; 

assign b =~a ; 

endmoduleinitial

begin

a =0; 

forever

begin

#({$random}%100)

a =~a ; 

end

end

top  t0(.a(a),.b(b)); 

endmodule

仿真结果如图314所示。


图314非门仿真波形



3.4.4异或
在Verilog中以“^”表示异或,如c= a^b ,表示当a和b相同时,输出为0,其真值表和RTL视图如图315所示。


图315异或真值表及RTL视图


代码实现和激励文件如下: 

module top(a, b, c); `timescale 1ns/1ns 

input  a ; module top_tb(); 

input  b ; reg a ; 

output c ; reg b ; 

wire c ; 

assign c = a ^ b ; initial

endmodulebegin

a =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),.c(c)); 

endmodule



仿真结果如图316所示。


图316异或仿真波形


3.4.5比较器
在Verilog中,比较器以大于“>”、等于“==”、小于“<”、大于或等于“>=”、小于或等于“<=”和不等于“!=”表示。以大于举例,如c=a > b ,表示如果a大于b,那么c的值就为1,否则为0。其真值表和RTL视图如图317所示。


图317比较器真值表及RTL视图


代码实现和激励文件如下: 

module top(a, b, c); `timescale 1ns/1ns 

input  a ; module top_tb(); 

input  b ; reg a ; 

output c ; reg b ; 

wire c ; 

assign c = a > b ; initial

endmodulebegin

a =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),.c(c)); 

endmodule


仿真结果如图318所示。


图318比较器仿真波形


3.4.6半加器
半加器和全加器是算术运算电路中的基本单元,由于半加器不考虑从低位来的进位,所以称之为半加器,sum表示相加结果,count表示进位。其真值表和RTL视图如图319所示。


图319半加器真值表及RTL视图


代码实现和激励文件如下: 

module top(a, b, sum, count); `timescale 1ns/1ns 

input  a ; module top_tb(); 

input  b ; reg a ; 

output sum ; reg b ; 

output count ; wire sum ; 

wire count ; 

assign sum = a ^ b ; 

assign count = a & b ; initial

begin

endmodulea =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),

.sum(sum),.count(count)); 

endmodule


仿真结果如图320所示。



图320半加器仿真波形


3.4.7全加器
全加器需要加上低位来的进位信号cin,真值表和RTL视图如图321所示。


图321全加器真值表及RTL视图


代码实现和激励文件如下: 

module top(cin, a, b, sum, count); `timescale 1ns/1ns 

input cin ; module top_tb(); 

input  a ; reg a ; 

input  b ; reg b ; 

output sum ; reg cin ; 

output count ; wire sum ; 

wire count ; 

assign{count,sum}= a + b + cin ; initial

begin

endmodulea =0; 

b =0; 

cin =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

#({$random}%100)

cin =~cin ; 



end

end

top  t0(.cin(cin),.a(a),.b(b),

.sum(sum),.count(count)); 

endmodule


仿真结果如图322所示。


图322全加器仿真波形


3.4.8乘法器
乘法的表示也很简单,利用“*”即可,如a*b,乘法器的RTL视图如图323所示。



图323乘法器RTL视图


代码示例如下: 

module top(a, b, c); `timescale 1ns/1ns 

input[1:0] a ; module top_tb(); 

input[1:0] b ; reg[1:0]a ; 

output[3:0] c ; reg[1:0]b ; 

wire[3:0]c ; 

assign c = a * b ; 

endmoduleinitial

begin

a =0; 

b =0; 

forever

begin

#({$random}%100)

a =~a ; 

#({$random}%100)

b =~b ; 

end

end

top  t0(.a(a),.b(b),.c(c)); 

endmodule


仿真结果如图324所示。


图324乘法器仿真波形


3.4.9数据选择器
在Verilog中经常会用到数据选择器,通过选择信号,选择不同的输入信号输出到输出端,四选一数据选择器,sel[1:0]为选择信号,a、b、c、d为输入信号,Mux为输出信号。其真值表如表31所示,RTL视图如图325所示。


表31数据选择器真值表


选 择 信 号输 入 信 号输 出 信 号

sel[0]
sel[1]
a
b
c
d
Mux
0
0
x
x
x
x
a
0
1
x
x
x
x
b
1
0
x
x
x
x
c
1
1
x
x
x
x
d



图325数据选择器RTL视图


代码实现和激励文件如下: 

module top(a, b, c, d, sel, Mux); `timescale 1ns/1ns 

inputa ; module top_tb(); 

inputb ; reg  a ; 

inputc ; reg  b ; 

inputd ; reg  c ; 

reg  d ; 

input[1:0] sel ; reg[1:0] sel ; 

wire  Mux ; 

outputreg Mux ; initial

begin

always@(sel or a or b or c or d)a =0; 

beginb =0; 

case(sel)c =0; 

2'b00: Mux = a ; d =0; 

2'b01: Mux = b ; forever

2'b10: Mux = c ; begin

2'b11: Mux = d ; #({$random}%100)

endcasea ={$random}%3; 

end#({$random}%100)

b ={$random}%3; 

endmodule#({$random}%100)

c ={$random}%3; 

#({$random}%100)

d ={$random}%3; 

end

end

initial

begin

sel =2'b00; 

#2000 sel =2'b01; 

#2000 sel =2'b10; 

#2000 sel =2'b11; 

end

top  

t0(.a(a),.b(b),.c(c),.d(d),.sel(sel),

.Mux(Mux)); 

endmodule


仿真结果如图326所示。


图326数据选择器仿真波形


3.4.1038译码器
38译码器是一个很常用的器件,其真值表如表32所示,根据A2、A1和A0的值,得出不同的结果。其RTL视图如图327所示。


表3238译码器真值表


输入
输出

A2
A1
A0
Y0
Y1
Y2
Y3
Y4
Y5
Y6
Y7
0
0
0
0
1
1
1
1
1
1
1
0
0
1
1
0
1
1
1
1
1
1
0
1
0
1
1
0
1
1
1
1
1
0
1
1
1
1
1
0
1
1
1
1
1
0
0
1
1
1
1
0
1
1
1
1
0
1
1
1
1
1
1
0
1
1
1
1
0
1
1
1
1
1
1
0
1
1
1
1
1
1
1
1
1
1
1
0



图32738译码器RTL视图


代码实现和激励文件如下: 

module top(addr, decoder); `timescale 1ns/1ns 

input[2:0] addr ; 
module top_tb(); 

outputreg[7:0] decoder ; reg[2:0]  addr ; 

wire[7:0] decoder ; 

always@(addr)

begininitial

case(addr)begin

3'b000: decoder =8'b1111_1110; addr =3'b000; 

3'b001: decoder =8'b1111_1101; #2000 addr =3'b001; 

3'b010: decoder =8'b1111_1011; #2000 addr =3'b010; 

3'b011: decoder =8'b1111_0111; #2000 addr =3'b011; 

3'b100: decoder =8'b1110_1111; #2000 addr =3'b100; 

3'b101: decoder =8'b1101_1111; #2000 addr =3'b101; 

3'b110: decoder =8'b1011_1111; #2000 addr =3'b110; 

3'b111: decoder =8'b0111_1111; #2000 addr =3'b111; 

endcaseend

endtop  

t0(.addr(addr),.decoder(decoder)); 

endmoduleendmodule

仿真结果如图328所示。


图32838译码器仿真结果


3.4.11三态门
在Verilog中,经常会用到双向I/O,需要用到三态输出电路,如bio = en? din: 1’bz;,其中en为使能信号,用于打开关闭三态输出电路,双向I/O电路RTL视图如图329所示,如下程序介绍了怎样实现两个双向I/O的对接。



图329三态输出电路 RTL视图



module top(en, din, dout, bio); `timescale 1ns/1ns 

input  din  ; module top_tb(); 

input  en ; reg en0 ; 

output dout ; reg din0 ; 

inout bio ; wire dout0 ; 

reg en1 ; 

assign bio = en? din :1'bz; reg din1 ; 

assign dout = bio ; wire dout1 ; 

wire bio ; 

endmoduleinitial

begin

din0 =0; 

din1 =0; 

forever

begin

#({$random}%100)

din0 =~din0 ; 

#({$random}%100)

din1 =~din1 ; 

end

end

initial

begin

en0 =0; 

en1 =1; 

#100000

en0 =1; 

en1 =0; 

end

top  

t0(.en(en0),.din(din0),.dout(dout0),.bi

o(bio)); 

top  

t1(.en(en1),.din(din1),.dout(dout1),.bi

o(bio)); 

endmodule


仿真结果如图330所示,当en0为0、en1为1时,1通道打开,双向I/O bio就等于1通道的din1,1通道向外发送数据,0通道接收数据,dout0等于bio; 当en0为1、en1为0时,0通道打开,双向I/O bio就等于0通道的din0,0通道向外发送数据,1通道接收数据,dout1等于bio。


图330三态门仿真波形


3.5时序逻辑
组合逻辑电路在逻辑功能上的特点是任意时刻的输出仅仅取决于当前时刻的输入,与电路原来的状态无关。而时序逻辑电路在逻辑功能上的特点是任意时刻的输出不仅取决于当前的输入信号,还取决于电路原来的状态。下面以一个典型的时序逻辑电路为例进行分析。
3.5.1D触发器
D触发器在时钟的上升沿或下降沿存储数据,输出与时钟跳变之前输入信号的状态相同。
代码实现和激励文件如下: 

module top(d, clk, q); `timescale 1ns/1ns 

input  d  ; module top_tb(); 

input clk ; reg d ; 	

outputreg q ; reg clk ; 

always@(posedge clk)wire q ; 

begininitial

q <= d ; begin

endd =0; 

endmoduleclk =0; 

forever

begin

#({$random}%100)

d =~d ; 

end

end

always#10 clk =~clk ; 

top  t0(.d(d),.clk(clk),.q(q)); 

endmodule


D触发器的RTL视图如图331所示。


图331D触发器的RTL视图


仿真结果如图332所示,可以看到在t0时刻时,d的值为0,则q的值也为0; 在t1时刻d发生了变化,值为1,那么q相应也发生了变化,值变为1。可以看到,在t0~t1的一个时钟周期内,无论输入信号d的值如何变化,q的值是保持不变的,也就是有存储的功能,保存的值为在时钟的跳变沿时d的值。


图332D触发器仿真波形


3.5.2两级D触发器
软件是按照两级D触发器的模型进行时序分析的,具体可以分析在同一时刻两个D触发器输出的数据有何不同,其RTL视图如图333所示。


图333两级D触发器的RTL视图


代码实现和激励文件如下: 

module top(d, clk, q, q1); `timescale 1ns/1ns 

input  d  ; module top_tb(); 

input clk ; reg d ; 

outputreg q ; reg clk ; 

outputreg q1 ; wire q ; 

always@(posedge clk)wire q1 ; 

begininitial

q <= d ; begin

endd =0; 

always@(posedge clk)clk =0; 

beginforever

q1 <= q ; begin

end#({$random}%100)

endmoduled =~d ; 

end

end

always#10 clk =~clk ; 

top  

t0(.d(d),.clk(clk),.q(q),.q1(q1)); 

endmodule


仿真结果如图334所示,可以看到,在t0时刻,d为0,q输出为0; 在t1时刻,q随着d的数据变化而变化,而此时钟跳变之前q的值仍为0,那么q1的值仍为0; 在t2时刻,时钟跳变前q的值为1,则q1的值相应为1,q1相对于q落后一个周期。


图334两级D触发器仿真波形


3.5.3带异步复位D触发器
异步复位是指独立于时钟,一旦异步复位信号有效,就触发复位操作。这个功能在写代码时会经常用到,用于给信号复位和初始化,其RTL视图如图335所示。


图335带异步复位D触发器的RTL视图


代码如下,注意要把异步复位信号放在敏感列表里。如果是低电平复位,即为negedge; 如果是高电平复位,则是posedge。

module top(d, rst, clk, q); `timescale 1ns/1ns 

input  d  ; module top_tb(); 

input rst ; reg d ; 

input clk ; reg rst ; 

outputreg q ; reg clk ; 

always@(posedge clk or negedge rst)wire q ; 

begin

if(rst ==1'b0)initial

q <=0; begin

elsed =0; 

q <= d ; clk =0; 

endforever

endmodulebegin

#({$random}%100)

d =~d ; 

end

end

initial

begin

rst =0; 

#200 rst =1; 

end

always#10 clk =~clk ; 

top  

t0(.d(d),.rst(rst),.clk(clk),.q(q)); 

endmodule


仿真结果如图336所示,可以看到,在复位信号之前,虽然输入信号d数据有变化,但由于正处于复位状态,输入信号q始终为0,在复位之后q的值就正常了。


图336带异步复位D触发器仿真波形


3.5.4带异步复位同步清零D触发器
前面讲到异步复位独立于时钟操作,而同步清零则是在同步时钟信号下操作的,当然也不仅限于同步清零,也可以是其他的同步操作,其RTL视图如图337所示。


图337带异步复位同步清零D触发器的RTL视图


不同于异步复位,同步操作不能把信号放到敏感列表里。代码如下: 

module top(d, rst, clr, clk, q); `timescale 1ns/1ns 

input  d  ; module top_tb(); 

input rst ; reg d ; 

input clr ; reg rst ; 

input clk ; reg clr ; 

outputreg q ; reg clk ; 

always@(posedge clk or negedge rst)wire q ; 

begininitial

if(rst ==1'b0)begin

q <=0; d =0; 

else if(clr ==1'b1)clk =0; 

q <=0; forever

elsebegin

q <= d ; #({$random}%100)

endd =~d ; 

endmoduleend

end

initial

begin

rst =0; 

clr =0; 

#200 rst =1; 

#200 clr =1; 

#100 clr =0; 

end

always#10 clk =~clk ; 

top  

t0(.d(d),.rst(rst),.clr(clr),.clk(clk),

.q(q)); 

endmodule


仿真结果如图338所示,可以看到clr信号拉高后,q没有立即清零,而是在下一个clk上升沿之后执行清零操作,也就是clr同步于clk。


图338带异步复位同步清零D触发器仿真波形


3.5.5移位寄存器
移位寄存器是指在每个时钟脉冲到来时,向左或向右移动一位,移位寄存器结构如图339所示,由于D触发器的特性,数据输出同步于时钟边沿,每个时钟来临,每个D触发器的输出Q等于前一个D触发器输出的值,从而实现移位的功能。


图339移位寄存器结构


代码实现和激励文件如下: 



module top(d, rst, clk, q); `timescale 1ns/1ns 

input  d  ; module top_tb(); 

input rst ; reg d ; 

input clk ; reg rst ; 

outputreg[7:0] q ; reg clk ; 

always@(posedge clk or negedge rst)wire[7:0] q ; 

begin

if(rst ==1'b0)initial

q <=0; begin

elsed =0; 

q <={q[6:0], d};  //向左移位clk =0; 

//q <= {d, q[7:1]} ;  //向右移位forever

endbegin

endmodule#({$random}%100)

d =~d ; 

end

end

initial

begin

rst =0; 

#200 rst =1; 

end

always#10 clk =~clk ; 

top 

t0(.d(d),.rst(rst),.clk(clk),.q(q)); 

endmodule


仿真结果如图340所示。可以看到,复位之后,数据随clk上升沿左移一位。


图340移位寄存器仿真波形


3.5.6单口RAM
单口RAM的写地址与读地址共用一个地址,代码如下,其中reg [7:0] ram [63:0]定义了64个8位宽度的数据,addr_reg可以将读地址延迟一周期之后将数据送出。


module top  `timescale 1ns/1ns 

(module top_tb(); 

input[7:0] data,reg[7:0] data ; 

input[5:0] addr,reg[5:0] addr ; 

input wr,reg wr ; 

input clk,reg clk ; 

output[7:0] q wire[7:0] q ; 

); initial

begin

reg[7:0] ram[63:0]; data =0; 

reg[5:0] addr_reg;  //地址寄存器addr =0; 

wr =1; 

always@(posedge clk)clk =0; 

beginend

if(wr)//写使能always#10 clk =~clk ; 

ram[addr]<= data; always@(posedge clk)

begin

addr_reg <= addr; data <= data +1'b1; 

endaddr <= addr +1'b1; 

assign q = ram[addr_reg];//读数据end

endmoduletop  t0(.data(data),

.addr(addr),

.clk(clk),

.wr(wr),

.q(q)); 

endmodule

仿真结果如图341所示。可以看到,q的输出与写入的数据一致。


图341单口RAM仿真波形


3.5.7伪双口RAM
伪双口RAM的读写地址是独立的,可以随机选择写或读地址,同时进行读写操作,代码如下: 


module top  `timescale 1ns/1ns 

(module top_tb(); 

input[7:0] data,reg[7:0] data ; 

input[5:0] write_addr,reg[5:0] write_addr ; 

input[5:0] read_addr,reg[5:0] read_addr ; 

input wr,reg wr ; 

input rd,reg clk ; 

input clk,reg rd ; 

outputreg[7:0] q wire[7:0] q ; 

); initial

begin

reg[7:0] ram[63:0]; data =0; 

reg[5:0] addr_reg;//地址寄存器write_addr =0; 

read_addr =0; 

always@(posedge clk)wr =0; 

beginrd =0; 

if(wr)//写使能clk =0; 

ram[write_addr]<= data; #100 wr =1; 

if(rd)//读使能#20 rd =1; 

q <= ram[read_addr]; end

endalways#10 clk =~clk ; 

always@(posedge clk)

endmodulebegin

if(wr)

begin

data <= data +1'b1; 

write_addr <= write_addr +1'b1; 

if(rd)

read_addr <= read_addr +1'b1; 

end

end

top  t0(.data(data),

.write_addr(write_addr),

.read_addr(read_addr),

.clk(clk),

.wr(wr),

.rd(rd),

.q(q)); 

endmodule


仿真结果如图342所示。可以看到,在rd有效时,对读地址进行操作,读出数据。


图342伪双口RAM仿真波形


3.5.8真双口RAM
真双口RAM有两套控制线和数据线,允许两个系统对其进行读写操作,代码如下: 

module top  `timescale 1ns/1ns 

(module top_tb(); 

input[7:0] data_a, data_b,reg[7:0] data_a, data_b ; 

input[5:0] addr_a, addr_b,reg[5:0] addr_a, addr_b ; 

input wr_a, wr_b,reg wr_a, wr_b ; 

input rd_a, rd_b,reg rd_a, rd_b ; 

input clk,reg clk ; 

outputreg[7:0] q_a, q_b wire[7:0] q_a, q_b ; 

); initial

begin

reg[7:0] ram[63:0];//声明 ram data_a =0; 

data_b =0; 

//端口 A addr_a =0; 

always@(posedge clk)addr_b =0; 

beginwr_a =0; 

if(wr_a)//写wr_b =0; 

beginrd_a =0; 

ram[addr_a]<= data_a; rd_b =0; 

q_a <= data_a ; clk =0; 

end#100 wr_a =1; 

if(rd_a)#100 rd_b =1; 

//读end

q_a <= ram[addr_a]; always#10 clk =~clk ; 

endalways@(posedge clk)

begin

if(wr_a)

//端口 B begin

always@(posedge clk)data_a <= data_a +1'b1; 

beginaddr_a <= addr_a +1'b1; 

if(wr_b)//写end

beginelse

ram[addr_b]<= data_b; begin

q_b <= data_b ; data_a <=0; 

endaddr_a <=0; 

if(rd_b)end

//读end

q_b <= ram[addr_b]; always@(posedge clk)

endbegin

if(rd_b)

begin

endmoduleaddr_b <= addr_b +1'b1; 

end

else addr_b <=0; 

end

top  

t0(.data_a(data_a),.data_b(data_b),

.addr_a(addr_a),.addr_b(addr_b

),

.wr_a(wr_a),.wr_b(wr_b),

.rd_a(rd_a),.rd_b(rd_b),

.clk(clk),

.q_a(q_a),.q_b(q_b)); 

endmodule



仿真结果如图343所示。


图343真双口RAM仿真波形


3.5.9单口ROM
ROM是用来存储数据的,可以按照下列代码形式初始化ROM,但用这种方法处理大容量的ROM时就比较麻烦,建议用FPGA自带的ROM  IP核实现,并添加初始化文件。
代码实现和激励文件如下: 

module top  `timescale 1ns/1ns 

(module top_tb(); 

input[3:0] addr,reg[3:0] addr ; 

input clk,reg clk ; 

outputreg[7:0] q  wire[7:0] q ; 

); initial

reg[7:0] rom [15:0];  //申明 rom begin

always@(addr)addr =0; 

beginclk =0; 

case(addr)end

4'd0: rom[addr]=8'd15; always#10 clk =~clk ; 

4'd1: rom[addr]=8'd24; always@(posedge clk)

4'd2: rom[addr]=8'd100; begin

4'd3: rom[addr]=8'd78; addr <= addr +1'b1; 

4'd4: rom[addr]=8'd98; 
end

4'd5: rom[addr]=8'd105; top  t0(.addr(addr),

4'd6: rom[addr]=8'd86; .clk(clk),

4'd7: rom[addr]=8'd254; .q(q)); 

4'd8: rom[addr]=8'd76; 
endmodule

4'd9: rom[addr]=8'd35; 

4'd10: rom[addr]=8'd120; 

4'd11: rom[addr]=8'd85; 

4'd12: rom[addr]=8'd37; 

4'd13: rom[addr]=8'd19; 

4'd14: rom[addr]=8'd22; 

4'd15: rom[addr]=8'd67; 

endcase

end

always@(posedge clk)

begin

q <= rom[addr]; 

end

endmodule


仿真结果如图344所示。


图344单口ROM仿真波形


3.5.10有限状态机
在Verilog中经常会用到有限状态机来处理相对复杂的逻辑,设定好不同的状态,根据触发条件跳转到对应的状态,在不同的状态下做相应的处理。有限状态机主要用到always及case语句。下面以一个四状态的有限状态机举例说明,如图345所示。



图345状态机跳转


在程序中设计了8位的移位寄存器。在Idle状态下,判断shift_start信号是否为高,如果为高,进入Start状态,在Start状态延迟100个周期,进入Run状态,进行移位处理,如果shift_stop信号有效,则进入Stop状态,在Stop状态,将q的值清零,再跳转到Idle状态。
Mealy有限状态机的输出不仅与当前状态有关,也与输入信号有关,在RTL视图中会与输入信号有连接。Mealy有限状态机的RTL视图如图346所示,其实现代码如下:



图346Mealy有限状态机的RTL视图






module top  

(

input shift_start,

input shift_stop,

input rst,

input clk,

input d,

outputreg[7:0] q  

); 

parameter Idle  =2'd0;//初始状态

parameter Start =2'd1;//开始状态

parameter Run=2'd2;//运行状态

parameter Stop  =2'd3;//停止状态

reg[1:0] state ; 

reg[4:0] delay_cnt ;//延迟计数

always@(posedge clk or negedge rst)

begin

if(!rst)begin

state <= Idle ; 

delay_cnt <=0; 

q <=0; end

else

case(state)

Idle  :begin

if(shift_start)

state <= Start ; end

Start :begin

if(delay_cnt ==5'd99)begin

delay_cnt <=0; 


 state <= Run ; end

else

delay_cnt <= delay_cnt +1'b1; end

Run:begin

if(shift_stop)

state <= Stop ; 

else

q <={q[6:0], d}; end

Stop  :begin

q <=0; 

state <= Idle ; end

default: state <= Idle ; 

endcase

end

endmodule

Moore有限状态机的输出只与当前状态有关,与输入信号无关,输入信号只影响状态的改变,不影响输出,比如对delay_cnt和q的处理,只与当前状态有关。Moore有限状态机的RTL视图如图347所示,其实现代码如下: 



图347Moore有限状态机的RTL视图



module top  

(

input shift_start,

input shift_stop,

input rst,

input clk,

input d,

outputreg[7:0] q  

); 

parameter Idle  =2'd0;//初始状态

parameter Start =2'd1;//开始状态

parameter Run=2'd2;//运行状态

parameter Stop  =2'd3;//停止状态

reg[1:0] current_state ; 

reg[1:0] next_state ; 

reg[4:0] delay_cnt ;//延迟计数

//第一部分:状态转换

always@(posedge clk or negedge rst)begin

if(!rst)

current_state <= Idle ; 

else

current_state <= next_state ; end

//第二部分:组合逻辑,判断语句转换条件

always@(*)begin

case(current_state)

Idle  :begin

if(shift_start)

next_state <= Start ; 

else

next_state <= Idle ; end

Start :begin

if(delay_cnt ==5'd99)

next_state <= Run ; 

else

next_state <= Start ; 

end

Run:begin

if(shift_stop)

next_state <= Stop ; 

else

next_state <= Run ; 

end

Stop  : next_state <= Idle ; 

default:next_state <= Idle ; 

endcase

end

//第三部分:输出数据

always@(posedge clk or negedge rst)

begin

if(!rst)

delay_cnt <=0; 

else if(current_state == Start)

delay_cnt <= delay_cnt +1'b1; 

else

delay_cnt <=0; 

end



always@(posedge clk or negedge rst)

begin

if(!rst)

q <=0; 

else if(current_state == Run)

q <={q[6:0], d}; 

else

q <=0; 

end

endmodule


上面两个程序中用到了两种方式的写法。第一种是Mealy状态机,采用了一段式的写法,只用了一个always语句,所有的状态转移,判断状态转移条件,数据输出都在一个always语句里,缺点是如果状态太多,则会使整段程序显得冗长。第二种是Moore状态机,采用了三段式的写法,状态转移用了一个always语句,判断状态转移条件是组合逻辑,采用了一个always语句,数据输出也是单独的 always语句,这样写起来比较直观清晰,状态很多时也不会显得烦琐。
激励文件如下: 

`timescale 1ns/1ns 

module top_tb(); 

reg shift_start ; 

reg shift_stop ; 

reg rst ; 

reg clk ; 

reg d ; 

wire[7:0] q ; 

initial

begin

rst =0; 

clk =0; 

d =0; 

#200 rst =1; 

forever

begin

#({$random}%100)

d =~d ; 

end

end



initial

begin

shift_start =0; 

shift_stop =0; 

#300 shift_start =1; 

#1000 shift_start =0; 

shift_stop  =1; 

#50 shift_stop =0; 

end

always#10 clk =~clk ; 

top  t0 

(

.shift_start(shift_start),

.shift_stop(shift_stop),

.rst(rst),

.clk(clk),

.d(d),

.q(q)

); 

Endmodule


仿真结果如图348所示。


图348状态机仿真波形


3.6总结
本章介绍了组合逻辑以及时序逻辑中常用的模块,其中有限状态机较为复杂,但经常用到,希望大家能够深入理解,在编写代码时多运用、多思考,有利于快速提升水平。