0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

【ZYNQ Ultrascale+ MPSOC FPGA教程】第三章Verilog基础模块介绍

FPGA技术专栏 来源:芯驿电子科技 作者:芯驿电子科技 2021-01-21 13:25 次阅读

原创声明:

本原创教程由芯驿电子科技(上海)有限公司(ALINX)创作,版权归本公司所有,如需转载,需授权并注明出处。

适用于板卡型号:

AXU2CGA/AXU2CGB/AXU3EG/AXU4EV-E/AXU4EV-P/AXU5EV-E/AXU5EV-P /AXU9EG/AXU15EG

简介

本文主要介绍verilog基础模块,夯实基础,对深入学习FPGA会有很大帮助。

数据类型

常量

整数:整数可以用二进制b或B,八进制o或O,十进制d或D,十六进制h或H表示,例如, 8’b00001111表示8位位宽的二进制整数,4’ha表示4位位宽的十六进制整数。

X和Z:X代表不定值,z代表高阻值,例如,5’b00x11,第三位不定值,3’b00z表示最低位为高阻值。

下划线:在位数过长时可以用来分割位数,提高程序可读性,如8’b0000_1111

参数parameter: parameter可以用标识符定义常量,运用时只使用标识符即可,提高可读性及维护性,如定义parameter width = 8 ; 定义寄存器reg [width-1:0] a; 即定义了8位宽度的寄存器。

参数的传递:在一个模块中如果有定义参数,在其他模块调用此模块时可以传递参数,并可以修改参数,如下所示,在module后用#()表示。

例如定义模块如下调用模块

modulerom#(parameterdepth=15,parameterwidth=8)(input[depth-1:0]addr,input[width-1:0]data,outputresult);endmodulemoduletop();wire[31:0]addr;wire[15:0]data;wireresult;rom#(.depth(32),.width(16))r1(.addr(addr),.data(data),.result(result));endmodule

Parameter可以用于模块间的参数传递,而localparam仅用于本模块内使用,不能用于参数传递。Localparam多用于状态机状态的定义。

变量

变量是指程序运行时可以改变其值的量,下面主要介绍几个常用了变量类型

1.Wire 型

Wire 类型变量,也叫网络类型变量,用于结构实体之间的物理连接,如门与门之间,不能储存值,用连续赋值语句assign赋值,定义为wire [n-1:0] a ; 其中n代表位宽,如定义wire a ; assign a = b ; 是将b的结点连接到连线a上。如下图所示,两个实体之间的连线即是wire类型变量。

pIYBAGAJEDOATu7AAAAdYMTnkac433.jpg

2.Reg 型

Reg 类型变量,也称为寄存器变量,可用来储存值,必须在always语句里使用。其定义为

reg [n-1:0] a ; 表示n位位宽的寄存器,如reg [7:0] a; 表示定义8位位宽的寄存器a。如下所示定义了寄存器q,生成的电路为时序逻辑,右图为其结构,为D触发器。

moduletop(d,clk,q);inputd;inputclk;outputregq;always@(posedgeclk)begin
q<= d ;endendmodule

o4YBAGAJEDSAN2RJAAAiJabSDIs653.jpg

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

moduletop(a,b,c,d,sel,Mux);inputa;inputb;inputc;inputd;input[1:0]sel;outputregMux;always@(seloraorborcord)begincase(sel)2'b00:Mux=a;2'b01:Mux=b;2'b10:Mux=c;2'b11:Mux=d;endcaseendendmodule

pIYBAGAJEDWAVb_bAAAlhI0kTfs738.jpg

3.Memory型

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

运算符

运算符可分为以下几类:

  1. 算术运算符(+,-,*,/,%)

  2. 赋值运算符(=,<=)

  3. 关系运算符(>,<,>=,<=,==,!=)

  4. 逻辑运算符(&&,||,!)

  5. 条件运算符(?:)

  6. 位运算符(~,|,^,&,^~)

  7. 移位运算符(<<,>>)

  8. 拼接运算符({ })

算术运算符

“+”(加法运算符),”-“(减法运算符),”*”(乘法运算符),”/”(除法运算符,如7/3 =2),“%”(取模运算符,也即求余数,如7%3=1,余数为1)

赋值运算符

“=”阻塞赋值,”<=”非阻塞赋值。阻塞赋值为执行完一条赋值语句,再执行下一条,可理解为顺序执行,而且赋值是立即执行;非阻塞赋值可理解为并行执行,不考虑顺序,在always块语句执行完成后,才进行赋值。如下面的阻塞赋值:

代码如下:激励文件如下

moduletop(din,a,b,c,clk);inputdin;inputclk;outputrega,b,c;always@(posedgeclk)begin
a=din;
b=a;
c=b;endendmodule	`timescale1ns/1nsmoduletop_tb();regdin;regclk;wirea,b,c;initialbegin
din=0;
clk=0;foreverbegin#({$random}%100)
din=~din;endendalways#10clk=~clk;topt0(.din(din),.a(a),.b(b),.c(c),.clk(clk));endmodule

可以从仿真结果看到,在clk的上升沿,a的值等于din,并立即赋给b,b的值赋给c。

o4YBAGAJEDWAa1ZxAAAvAwbJUv4629.jpg

如果改为非阻塞赋值,仿真结果如下,在clk上升沿,a的值没有立即赋值给b,b为a原来的值,同样,c为b原来的值

pIYBAGAJEDeAOA1AAAAwQed_xzw608.jpg

可以从两者的RTL图看出明显不同:

o4YBAGAJEDeATqasAAAnWbFsG_Y582.jpgpIYBAGAJEDiAS-4gAAAuAZGKuRM445.jpg

阻塞赋值RTL图非阻塞赋值RTL图

一般情况下,在时序逻辑电路中使用非阻塞赋值,可避免仿真时出现竞争冒险现象;在组合逻辑中使用阻塞赋值,执行赋值语句后立即改变;在assign语句中必须用阻塞赋值。

  • 关系运算符

用于表示两个操作数之间的关系,如a>b,a

If (a>=b) q <=1’b1 ;
else q <= 1’b0 ;表示如果a的值大于等于b的值,则q的值为1,否则q的值为0
  • 逻辑运算符

“&&”(两个操作数逻辑与),”||”(两个操作数逻辑或),”!”(单个操作数逻辑非)例如:

If (a>b && c b并且c

  • 条件运算符

“?:”为条件判断,类似于if else,例如assign a = (i>8)?1’b1:1’b0 ;判断i的值是否大于8,如果大于8则a的值为1,否则为0。

  • 位运算符

“~”按位取反,”|”按位或,”^”按位异或,”&”按位与,”^”按位同或,除了”~”只需要一个操作数外,其他几个都需要两个操作数,如a&b,a|b。具体应用在后面的组合逻辑一节中有讲解。

  • 移位运算符

“<<”左移位运算符,”>>”右移位运算符,如a<<1表示,向左移1位,a>>2,向右移两位。

  • 拼接运算符

“{ }”拼接运算符,将多个信号按位拼接,如{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.

  • 优先级别

各种运算符的优先级别如下:

o4YBAGAJEDmAU0jwAAAqr45e88k829.jpg

组合逻辑

本节主要介绍组合逻辑,组合逻辑电路的特点是任意时刻的输出仅仅取决于输入信号,输入信号变化,输出立即变化,不依赖于时钟。

  • 与门

在verilog中以“&”表示按位与,如c=a&b,真值表如下,在a和b都等于1时结果才为1,RTL表示如右图

pIYBAGAJEDmAPO-kAAAOeXTjVtw603.jpgo4YBAGAJEDmAURKGAAAIZ9G2Azc508.jpg

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

moduletop(a,b,c);inputa;inputb;outputc;assignc=a&b;endmodule`timescale1ns/1nsmoduletop_tb();rega;regb;wirec;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.c(c));endmodule

仿真结果如下:

pIYBAGAJEDqAVXF_AAAT-T7c21U747.jpg

如果a和b的位宽大于1,例如定义input [3:0] a, input [3:0]b,那么a&b则指a与b的对应位相与。如a[0]&b[0],a[1]&b[1]。

  • 或门

在verilog中以“|”表示按位或,如c = a|b , 真值表如下,在a和b都为0时结果才为0。

o4YBAGAJEDqAPZDDAAAT4HoxqU8739.jpgpIYBAGAJEDuAWzpEAAA02MNKmUQ996.jpg

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

moduletop(a,b,c);inputa;inputb;outputc;assignc=a|b;endmodule	`timescale1ns/1nsmoduletop_tb();rega;regb;wirec;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.c(c));endmodule

仿真结果如下:

o4YBAGAJEDuAOMLaAAAUcqxF15A463.jpg

同理,位宽大于1,则是按位或。

  • 非门

在verilog中以“~”表示按位取反,如b=~a,真值表如下,b等于a的相反数。

pIYBAGAJEDuAfm9FAAAJggh4wQI042.jpgo4YBAGAJEDyAc7c6AAAxmdfXPB8792.jpg

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

moduletop(a,b);inputa;outputb;assignb=~a;endmodule	`timescale1ns/1nsmoduletop_tb();rega;wireb;initialbegin
a=0;foreverbegin#({$random}%100)
a=~a;endendtopt0(.a(a),.b(b));endmodule

仿真结果如如下:

pIYBAGAJEDyACfvZAAATRyvB80U398.jpg

  • 异或

在verilog中以“^”表示异或,如c= a^b ,真值表如下,当a和b相同时,输出为0。

o4YBAGAJED2AETY7AAAKk1iBfZg853.jpgpIYBAGAJED2AQtpaAAAp9KXhXz8610.jpg

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

moduletop(a,b,c);inputa;inputb;outputc;assignc=a^b;endmodule
	`timescale1ns/1nsmoduletop_tb();rega;regb;wirec;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.c(c));endmodule

仿真结果如下:

o4YBAGAJED6AbQ__AAApgT1Q7x0905.jpg

在verilog中以大于“>”,等于”==”,小于”<”,大于等于”>=”,小于等于”<=”,不等于”!=”表示,以大于举例,如c= a > b ;表示如果a大于b,那么c的值就为1,否则为0。真值表如下:

pIYBAGAJED6ACZadAAAKj3MoyNk511.jpgo4YBAGAJED-AGNh0AAAkkGvBjfU666.jpg

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

moduletop(a,b,c);inputa;inputb;outputc;assignc=a>b;endmodule	`timescale1ns/1nsmoduletop_tb();rega;regb;wirec;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.c(c));endmodule

仿真结果如下:

pIYBAGAJEECADiHvAAApldnbsqA845.jpg

  • 半加器

半加器和全加器是算术运算电路中的基本单元,由于半加器不考虑从低位来的进位,所以称之为半加器,sum表示相加结果,count表示进位,真值表可表示如下:

o4YBAGAJEECAYvXLAAALco5i8MI203.jpgpIYBAGAJEECANVDbAAAUmzcCxKw784.jpg

可根据真值表写出代码如下:激励文件如下:

moduletop(a,b,sum,count);inputa;inputb;outputsum;outputcount;assignsum=a^b;assigncount=a&b;endmodule	`timescale1ns/1nsmoduletop_tb();rega;regb;wiresum;wirecount;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.sum(sum),.count(count));endmodule

仿真结果如下:

o4YBAGAJEEGAFtQuAAAsKY8uAcI047.jpg

  • 全加器

而全加器需要加上低位来的进位信号cin,真值表如下:

pIYBAGAJEEGAIHJBAAANPVl-XAc692.jpgo4YBAGAJEEGAesNWAAAW0hLAwRY181.jpg

代码如下:激励文件如下:

moduletop(cin,a,b,sum,count);inputcin;inputa;inputb;outputsum;outputcount;assign{count,sum}=a+b+cin;endmodule	`timescale1ns/1nsmoduletop_tb();rega;regb;regcin;wiresum;wirecount;initialbegin
a=0;
b=0;
cin=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)b=~b;#({$random}%100)
cin=~cin;endendtopt0(.cin(cin),.a(a),.b(b),.sum(sum),.count(count));endmodule

仿真结果如下:

pIYBAGAJEEKAbaFvAAAWdbVI6MM917.jpg

  • 乘法器

乘法的表示也很简单,利用”*”即可,如a*b,举例代码如下:

moduletop(a,b,c);input[1:0]a;input[1:0]b;output[3:0]c;assignc=a*b;endmodule	`timescale1ns/1nsmoduletop_tb();reg[1:0]a;reg[1:0]b;wire[3:0]c;initialbegin
a=0;
b=0;foreverbegin#({$random}%100)
a=~a;#({$random}%100)
b=~b;endendtopt0(.a(a),.b(b),.c(c));
	endmodule

仿真结果如下:

o4YBAGAJEEKATa6DAAATtpUX-o8500.jpg

  • 数据选择器

在verilog中经常会用到数据选择器,通过选择信号,选择不同的输入信号输出到输出端,如下图真值表,四选一数据选择器,sel[1:0]为选择信号,a,b,c,d为输入信号,Mux为输出信号。

pIYBAGAJEEOAGr2HAAAN3odP1aw798.jpgo4YBAGAJEESAQR_DAAAycQ6wPhY618.jpg

代码如下:激励文件如下:

moduletop(a,b,c,d,sel,Mux);inputa;inputb;inputc;inputd;input[1:0]sel;outputregMux;always@(seloraorborcord)begincase(sel)2'b00:Mux=a;2'b01:Mux=b;2'b10:Mux=c;2'b11:Mux=d;endcaseendendmodule`timescale1ns/1nsmoduletop_tb();rega;regb;regc;regd;reg[1:0]sel;wireMux;initialbegin
a=0;
b=0;
c=0;
d=0;foreverbegin#({$random}%100)
a={$random}%3;#({$random}%100)
b={$random}%3;#({$random}%100)
c={$random}%3;#({$random}%100)
d={$random}%3;endendinitialbegin
sel=2'b00;#2000sel=2'b01;#2000sel=2'b10;#2000sel=2'b11;endtopt0(.a(a),.b(b),.c(c),.d(d),.sel(sel),.Mux(Mux));endmodule

仿真结果如下

pIYBAGAJEESAK-BQAAAbdAs15ew895.jpg

  • 3-8译码器

3-8译码器是一个很常用的器件,其真值表如下所示,根据A2,A1,A0的值,得出不同的结果。

o4YBAGAJEESAIlN4AAAVDKpH5fE429.jpg

pIYBAGAJEEWAa8WlAAAjRZ19Dko919.jpg

代码如下:激励文件如下:

moduletop(addr,decoder);input[2:0]addr;outputreg[7:0]decoder;always@(addr)begincase(addr)3'b000:decoder=8'b1111_1110;3'b001:decoder=8'b1111_1101;3'b010:decoder=8'b1111_1011;3'b011:decoder=8'b1111_0111;3'b100:decoder=8'b1110_1111;3'b101:decoder=8'b1101_1111;3'b110:decoder=8'b1011_1111;3'b111:decoder=8'b0111_1111;endcaseendendmodule
	`timescale1ns/1nsmoduletop_tb();reg[2:0]addr;wire[7:0]decoder;initialbegin
addr=3'b000;#2000addr=3'b001;#2000addr=3'b010;#2000addr=3'b011;#2000addr=3'b100;#2000addr=3'b101;#2000addr=3'b110;#2000addr=3'b111;endtopt0(.addr(addr),.decoder(decoder));endmodule

仿真结果如下:

o4YBAGAJEEWAKpkMAAApBhsiw8w051.jpg

  • 三态门

在FPGA使用中,经常会用到双向IO,需要用到三态门,如bio = en? din: 1’bz ;其中en为使能信号,用于打开关闭三态门,下面的RTL图即是实现了双向IO,可参考代码。激励文件实现两个双向IO的对接。

pIYBAGAJEEaAamNpAAARtuEcVo8429.jpg

moduletop(en,din,dout,bio);inputdin;inputen;outputdout;inoutbio;assignbio=en?din:1'bz;assigndout=bio;endmodule`timescale1ns/1nsmoduletop_tb();regen0;regdin0;wiredout0;regen1;regdin1;wiredout1;wirebio;initialbegin
din0=0;
din1=0;foreverbegin#({$random}%100)
din0=~din0;#({$random}%100)din1=~din1;endendinitialbegin
en0=0;
en1=1;#100000
en0=1;
en1=0;endtopt0(.en(en0),.din(din0),.dout(dout0),.bio(bio));topt1(.en(en1),.din(din1),.dout(dout1),.bio(bio));endmodule

激励文件结构如下图

o4YBAGAJEEaAGvvbAAAfwFq-shM522.jpg

仿真结果如下,en0为0,en1为1时,1通道打开,双向IO bio就等于1通道的din1,1通道向外发送数据,0通道接收数据,dout0等于bio;当en0为1,en1为0时,0通道打开,双向IO bio就等于0通道的din0,0通道向外发送数据,1通道接收数据,dout1等于bio

pIYBAGAJEEeAbl5tAAAdyT7pu00011.jpg

时序逻辑

组合逻辑电路在逻辑功能上特点是任意时刻的输出仅仅取决于当前时刻的输入,与电路原来的状态无关。而时序逻辑在逻辑功能上的特点是任意时刻的输出不仅仅取决于当前的输入信号,而且还取决于电路原来的状态。下面以典型的时序逻辑分析。

  • D触发器

D触发器在时钟的上升沿或下降沿存储数据,输出与时钟跳变之前输入信号的状态相同。

代码如下激励文件如下

moduletop(d,clk,q);inputd;inputclk;outputregq;always@(posedgeclk)begin
q<= d ;endendmodule






	`timescale1 ns/1 ns module top_tb();reg d ;reg clk ;wire q ;initialbegin
  d =0;
  clk =0;foreverbegin#({$random}%100)
    d =~d ;endendalways#10 clk =~clk ;top  t0(.d(d),.clk(clk),.q(q));endmodule

RTL图表示如下

o4YBAGAJEEeAOZRZAAAi8easSgk727.jpg

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

pIYBAGAJEEiANblIAAAVauy8dRg071.jpg

  • 两级D触发器

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

o4YBAGAJEEiAG2O8AAA1koNR9Vo348.jpg

代码如下:激励文件如下:

moduletop(d,clk,q,q1);inputd;inputclk;outputregq;outputregq1;always@(posedgeclk)begin
q<= d ;endalways@(posedge clk)begin
  q1 <= q ;endendmodule`timescale1 ns/1 ns module top_tb();reg d ;reg clk ;wire q ;wire q1 ;initialbegin
  d =0;
  clk =0;foreverbegin#({$random}%100)
    d =~d ;endendalways#10 clk =~clk ;top  t0(.d(d),.clk(clk),.q(q),.q1(q1));endmodule

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

pIYBAGAJEEmAfINnAAAVqQDEpPI864.jpg

  • 带异步复位的D触发器

异步复位是指独立于时钟,一旦异步复位信号有效,就触发复位操作。这个功能在写代码时会经常用到,用于给信号复位,初始化。其RTL图如下:

o4YBAGAJEEmAS_j7AABQ4ZNWkQo576.jpg

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

moduletop(d,rst,clk,q);inputd;inputrst;inputclk;outputregq;always@(posedgeclkornegedgerst)beginif(rst==1'b0)
q<=0;else
    q <= d ;endendmodule`timescale1 ns/1 ns module top_tb();reg d ;reg rst ;reg clk ;wire q ;initialbegin
  d =0;
  clk =0;foreverbegin#({$random}%100)
    d =~d ;endendinitialbegin
  rst =0;#200 rst =1;endalways#10 clk =~clk ;top  t0(.d(d),.rst(rst),.clk(clk),.q(q));endmodule

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

pIYBAGAJEEqAGFVjAAApVGy8vj0512.jpg

  • 带异步复位同步清零的D触发器

前面讲到异步复位独立于时钟操作,而同步清零则是同步于时钟信号下操作的,当然也不仅限于同步清零,也可以是其他的同步操作,其RTL图如下:

o4YBAGAJEEqAYwBvAAAzKIal4ks813.jpg

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

moduletop(d,rst,clr,clk,q);inputd;inputrst;inputclr;inputclk;outputregq;always@(posedgeclkornegedgerst)beginif(rst==1'b0)
q<=0;elseif(clr ==1'b1)
    q <=0;else
    q <= d ;endendmodule`timescale1 ns/1 ns module top_tb();reg d ;reg rst ;reg clr ;reg clk ;wire q ;initialbegin
  d =0;
  clk =0;foreverbegin#({$random}%100)
    d =~d ;endendinitialbegin
  rst =0;
  clr =0;#200 rst =1;#200 clr =1;#100 clr =0;endalways#10 clk =~clk ;top  t0(.d(d),.rst(rst),.clr(clr),.clk(clk),.q(q));endmodule

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

pIYBAGAJEEuAe5q3AAAYHIA2jn4631.jpg

  • 移位寄存器

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

o4YBAGAJEEuASuO5AAAjsGcOS2c111.jpg

代码实现:

moduletop(d,rst,clk,q);inputd;inputrst;inputclk;outputreg[7:0]q;always@(posedgeclkornegedgerst)beginif(rst==1'b0)
q<=0;else
    q <={q[6:0], d};//向左移位//q <= {d, q[7:1]} ;  //向右移位endendmodule激励文件:`timescale1 ns/1 ns module top_tb();reg d ;reg rst ;reg clk ;wire[7:0] q ;initialbegin
  d =0;
  clk =0;foreverbegin#({$random}%100)
    d =~d ;endendinitialbegin
  rst =0;#200 rst =1;endalways#10 clk =~clk ;top t0(.d(d),.rst(rst),.clk(clk),.q(q));endmodule

仿真结果如下,可以看到复位之后,每个clk上升沿左移一位

pIYBAGAJEEyAMX5GAAAWn6PT2Fo115.jpg

  • 单口RAM

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

moduletop(input[7:0]data,input[5:0]addr,inputwr,inputclk,output[7:0]q);reg[7:0]ram[63:0];//declareram
reg[5:0]addr_reg;//addrregister
always@(posedgeclk)beginif(wr)//write
ram[addr]<= data;
	
  addr_reg <= addr;endassign q = ram[addr_reg];//read data 
endmodule	`timescale1 ns/1 ns module top_tb();reg[7:0] data ;reg[5:0] addr ;reg wr ;reg clk ;wire[7:0] q ;initialbegin
  data =0;
  addr =0;
  wr =1;
  clk =0;endalways#10 clk =~clk ;always@(posedge clk)begin
  data <= data +1'b1;
  addr <= addr +1'b1;endtop  t0(.data(data),.addr(addr),.clk(clk),.wr(wr),.q(q));endmodule

仿真结果如下,可以看到q的输出与写入的数据一致

o4YBAGAJEE2AUn-MAAAf2bQ6veM253.jpg

  • 伪双口RAM

伪双口RAM的读写地址是独立的,可以随机选择写或读地址,同时进行读写操作。代码如下,在激励文件中定义了en信号,在其有效时发送读地址。

moduletop(input[7:0]data,input[5:0]write_addr,input[5:0]read_addr,inputwr,inputrd,inputclk,outputreg[7:0]q);reg[7:0]ram[63:0];//declareram
reg[5:0]addr_reg;//addrregister
always@(posedgeclk)beginif(wr)//write
ram[write_addr]<= data;if(rd)//read 
     q <= ram[read_addr];endendmodule`timescale1 ns/1 ns module top_tb();reg[7:0] data ;reg[5:0] write_addr ;reg[5:0] read_addr ;reg wr ;reg clk ;reg rd ;wire[7:0] q ;initialbegin
  data =0;
  write_addr =0;
  read_addr =0;
  wr =0;
  rd =0;
  clk =0;#100 wr =1;#20 rd =1;endalways#10 clk =~clk ;always@(posedge clk)beginif(wr)begin
     data <= data +1'b1;
     write_addr <= write_addr +1'b1;if(rd)
       read_addr <= read_addr +1'b1;endendtop  t0(.data(data),.write_addr(write_addr),.read_addr(read_addr),.clk(clk),.wr(wr),.rd(rd),.q(q));endmodule

仿真结果如下,可以看到在rd有效时,对读地址进行操作,读出数据

pIYBAGAJEE2Af1XbAAAiVsGdQpc975.jpg

  • 真双口RAM

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

moduletop(input[7:0]data_a,data_b,input[5:0]addr_a,addr_b,inputwr_a,wr_b,inputrd_a,rd_b,inputclk,outputreg[7:0]q_a,q_b);reg[7:0]ram[63:0];//declareram
//PortA
always@(posedgeclk)beginif(wr_a)//write
begin
ram[addr_a]<= data_a;
     q_a <= data_a ;end
	if(rd_a)//read 
     q_a <= ram[addr_a];end//Port B 
always@(posedge clk)beginif(wr_b)//write 
begin
     ram[addr_b]<= data_b;
     q_b <= data_b ;endif(rd_b)//read 
     q_b <= ram[addr_b];endendmodule	`timescale1 ns/1 ns module top_tb();reg[7:0] data_a, data_b ;reg[5:0] addr_a, addr_b ;reg wr_a, wr_b ;reg rd_a, rd_b ;reg clk ;wire[7:0] q_a, q_b ;initialbegin
  data_a =0;
  data_b =0;
  addr_a =0;
  addr_b =0;
  wr_a =0;
  wr_b =0;
  rd_a =0;
  rd_b =0;
  clk =0;#100 wr_a =1;#100 rd_b =1;endalways#10 clk =~clk ;always@(posedge clk)beginif(wr_a)begin
    data_a <= data_a +1'b1;
    addr_a <= addr_a +1'b1;endelsebegin
     data_a <=0;
     addr_a <=0;endendalways@(posedge clk)beginif(rd_b)begin
     addr_b <= addr_b +1'b1;endelse addr_b <=0;endtop  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

仿真结果如下

o4YBAGAJEE6ASLVsAABhlKin5IM104.jpg

  • 单口ROM

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

代码实现

moduletop(input[3:0]addr,inputclk,outputreg[7:0]q);always@(posedgeclk)begincase(addr)4'd0:q<=8'd15;4'd1: q <=8'd24;4'd2: q <=8'd100;4'd3: q <=8'd78;4'd4: q <=8'd98;4'd5: q <=8'd105;4'd6: q <=8'd86;4'd7: q <=8'd254;4'd8: q <=8'd76;4'd9: q <=8'd35;4'd10: q <=8'd120;4'd11: q <=8'd85;4'd12: q <=8'd37;4'd13: q <=8'd19;4'd14: q <=8'd22;4'd15: q <=8'd67;default: q <=8'd0;endcaseendendmodule	`timescale1 ns/1 ns module top_tb();reg[3:0] addr ;reg clk ;wire[7:0] q ;initialbegin
  addr =0;
  clk =0;endalways#10 clk =~clk ;always@(posedge clk)begin
     addr <= addr +1'b1;endtop  t0(.addr(addr),.clk(clk),.q(q));endmodule

仿真结果如下

pIYBAGAJEE-AKp8PAAA0Nj7yUAY199.jpg

  • 有限状态机

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

o4YBAGAJEE-AZtCbAAAPlrXJRbg090.jpg

在程序中设计了8位的移位寄存器,在Idle状态下,判断shift_start信号是否为高,如果为高,进入Start状态,在Start状态延迟100个周期,进入Run状态,进行移位处理,如果shift_stop信号有效了,进入Stop状态,在Stop状态,清零q的值,再跳转到Idle状态。

Mealy有限状态机,输出不仅与当前状态有关,也与输入信号有关,在RTL中会与输入信号有连接。

moduletop(inputshift_start,inputshift_stop,inputrst,inputclk,inputd,outputreg[7:0]q);parameterIdle=2'd0;//Idlestate
parameterStart=2'd1;//Startstate
parameterRun=2'd2;//Runstate
parameterStop=2'd3;//Stopstate
reg[1:0]state;//statement
reg[4:0]delay_cnt;//delaycounter
always@(posedgeclkornegedgerst)beginif(!rst)begin
state<= Idle ;
   delay_cnt <=0;
   q <=0;endelsecase(state)
    Idle  :beginif(shift_start)state <= Start ;end
    Start :beginif(delay_cnt ==5'd99)begindelay_cnt <=0;
 state <= Run ;endelsedelay_cnt <= delay_cnt +1'b1;end
    Run   :beginif(shift_stop)state <= Stop ;elseq <={q[6:0], d};end
    Stop  :begin
              q <=0;state <= Idle ;enddefault: state <= Idle ;endcaseendendmodule

Moore有限状态机,输出只与当前状态有关,与输入信号无关,输入信号只影响状态的改变,不影响输出,比如对delay_cnt和q的处理,只与state状态有关。

moduletop(inputshift_start,inputshift_stop,inputrst,inputclk,inputd,outputreg[7:0]q);parameterIdle=2'd0;//Idlestate
parameterStart=2'd1;//Startstate
parameterRun=2'd2;//Runstate
parameterStop=2'd3;//Stopstate
reg[1:0]current_state;//statement
reg[1:0]next_state;reg[4:0]delay_cnt;//delaycounter
//Firstpart:statementtransition
always@(posedgeclkornegedgerst)beginif(!rst)
current_state<= Idle ;else
   current_state <= next_state ;end//Second part: combination logic, judge statement transition condition 
always@(*)begincase(current_state)
    Idle  :beginif(shift_start)next_state <= Start ;elsenext_state <= Idle ;end
    Start :beginif(delay_cnt ==5'd99)next_state <= Run ;else
                  next_state <= Start ;end
    Run   :beginif(shift_stop)next_state <= Stop ;else
                 next_state <= Run ;end
    Stop  : next_state <= Idle ;default:next_state <= Idle ;endcaseend//Last part: output data 
always@(posedge clk ornegedge rst)beginif(!rst)
    delay_cnt <=0;elseif(current_state == Start)
    delay_cnt <= delay_cnt +1'b1;else
    delay_cnt <=0;endalways@(posedge clk ornegedge rst)beginif(!rst)
    q <=0;elseif(current_state == Run)
    q <={q[6:0], d};else
    q <=0;endendmodule

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

pIYBAGAJEFCAdYMFAABtlCBJh7k434.jpg

Mealy有限状态机RTL图

o4YBAGAJEFGAILd0AAA0OJFe1kg142.jpg

Moore有限状态机RTL图

激励文件如下:

`timescale1ns/1nsmoduletop_tb();regshift_start;regshift_stop;regrst;regclk;regd;wire[7:0]q;initialbegin
rst=0;
clk=0;
d=0;#200rst=1;foreverbegin#({$random}%100)
d=~d;endendinitialbegin
shift_start=0;
shift_stop=0;#300shift_start=1;#1000shift_start=0;
shift_stop=1;#50shift_stop=0;endalways#10clk=~clk;topt0(.shift_start(shift_start),.shift_stop(shift_stop),.rst(rst),.clk(clk),.d(d),.q(q));endmodule

仿真结果如下:

pIYBAGAJEFGALXE4AABUvPqswiQ691.jpg

总结

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

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • FPGA
    +关注

    关注

    1603

    文章

    21337

    浏览量

    593753
  • Verilog
    +关注

    关注

    28

    文章

    1327

    浏览量

    109359
  • Zynq
    +关注

    关注

    9

    文章

    600

    浏览量

    46676
  • MPSoC
    +关注

    关注

    0

    文章

    194

    浏览量

    24122
收藏 人收藏

    评论

    相关推荐

    如何利用ZYNQ MPSoC玩DOOM?

    ,在详细介绍具体步骤之前,我们先来了解什么是管理程序,以及它们如何与Zynq UltraScale+ MPSoC 上的处理器协同工作。
    发表于 10-09 06:21

    如何调试Zynq UltraScale+ MPSoC VCU DDR控制器

      如何调试Zynq UltraScale+ MPSoC VCU DDR控制器  Zynq UltraScale+
    发表于 01-07 16:02

    如何调试Zynq UltraScale+ MPSoC VCU DDR控制器

    如何调试 Zynq UltraScale+ MPSoC VCU DDR 控制器?
    发表于 01-22 06:29

    ZYNQ Ultrascale+ MPSOC FPGA教程

    ZYNQ Ultrascale+ MPSOC FPGA教程
    发表于 02-02 07:53

    Zynq UltraScale+ MPSoC存储器接口系统的介绍

    该视频重点介绍UltraScale +产品系列的第一个成员Zynq®UltraScale+MPSoC,并展示了使用可编程逻辑中的DDR4
    的头像 发表于 11-29 06:36 3098次阅读

    Zynq UltraScale+ MPSoC的发售消息

    Zynq®UltraScale+MPSoC,现已开始发售。视频向您重点介绍了Xilinx UltraScale +产品组合的第一位成员
    的头像 发表于 11-27 06:47 3321次阅读

    米尔科技Zynq UltraScale+ MPSoC技术参考手册介绍

    Zynq UltraScale+ MPSoC是Xilinx推出的第二代多处理SoC系统,在第一代Zynq-7000的基础上做了全面升级,在单芯片上融合了功能强大的处理器系统(PS)和用
    的头像 发表于 11-18 11:03 2644次阅读
    米尔科技<b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b>技术参考手册<b class='flag-5'>介绍</b>

    ZYNQ Ultrascale+ MPSOC FPGA教程】第三章 Verilog基础模块介绍

    本文主要介绍verilog基础模块,夯实基础,对深入学习FPGA会有很大帮助。
    发表于 01-22 10:41 5次下载
    【<b class='flag-5'>ZYNQ</b> <b class='flag-5'>Ultrascale+</b> <b class='flag-5'>MPSOC</b> <b class='flag-5'>FPGA</b>教程】<b class='flag-5'>第三章</b> <b class='flag-5'>Verilog</b>基础<b class='flag-5'>模块</b><b class='flag-5'>介绍</b>

    如何调试 Zynq UltraScale+ MPSoC VCU DDR 控制器?

    Zynq UltraScale+ MPSoC VCU DDR 控制器是一款专用 DDR 控制器,只支持在 Zynq UltraScale+
    发表于 02-23 06:00 15次下载
    如何调试 <b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b> VCU DDR 控制器?

    米尔电子zynq ultrascale+ mpsoc底板外设资源清单分享

    米尔电子推出的国内首款zynq ultrascale+ mpsoc平台核心板(及开发板):MYC-CZU3EG吸引了人工智能、工业控制、嵌入式视觉、ADAS、算法加速、云计算、有线/无线通信
    发表于 01-07 15:20 3次下载
    米尔电子<b class='flag-5'>zynq</b> <b class='flag-5'>ultrascale+</b> <b class='flag-5'>mpsoc</b>底板外设资源清单分享

    ZYNQ Ultrascale+ MPSoC系列FPGA芯片设计

    基于 Xilinx 公司ZYNQ Ultrascale+ MPSoC系列 FPGA 芯片设计,应用于工厂自动化、机器视觉、工业质检等工业领域
    发表于 11-02 14:35 1196次阅读

    Zynq UltraScale+ MPSoC中的隔离方法

    电子发烧友网站提供《Zynq UltraScale+ MPSoC中的隔离方法.pdf》资料免费下载
    发表于 09-13 17:11 1次下载
    <b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b>中的隔离方法

    Zynq UltraScale+ MPSoC的隔离设计示例

    电子发烧友网站提供《Zynq UltraScale+ MPSoC的隔离设计示例.pdf》资料免费下载
    发表于 09-13 11:28 2次下载
    <b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b>的隔离设计示例

    Zynq UltraScale+ MPSoC生产勘误表

    电子发烧友网站提供《Zynq UltraScale+ MPSoC生产勘误表.pdf》资料免费下载
    发表于 09-15 10:35 0次下载
    <b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b>生产勘误表

    Zynq UltraScale+ MPSoC验证数据手册

    电子发烧友网站提供《Zynq UltraScale+ MPSoC验证数据手册.pdf》资料免费下载
    发表于 09-15 10:13 0次下载
    <b class='flag-5'>Zynq</b> <b class='flag-5'>UltraScale+</b> <b class='flag-5'>MPSoC</b>验证数据手册