前言
学习说明此文档为本人的学习笔记,注重实践,关于理论部分会给出相应的学习链接。
学习视频:是根据野火FPGA视频教程——第二十三讲 到 第二十七讲
https://www.bilibili.com/video/BV1nQ4y1Z7zN?p=3
这里进行常用的IP核简单的介绍,如果深度学习推荐数据:
《Xilinx系列FPGA芯片 IP核详解》
理论应用
IP(Intellectual Property)即知识产权。美国 Dataquest 咨询公司将半导体产业的 IP 定义为“用于 ASIC 或 FPGA 中的预先设计好的电路功能模块”。简而言之,这里的 IP 即电路功能模块。IP 核在数字电路中常用于比较复杂的功能模块(如 FIFO、RAM、FIR 滤波器、SDRAM 控制器、PCIE 接口等)设计成参数可修改的模块,让其他用户可以直接调用这些模块。随着设计规模增大,复杂度提高,使用 IP 核可以提高开发效率,减少设计和调试时间,加速开发进程,降低开发成本,是业界的发展趋势。利用 IP 核设计电子系统,引用方便,修改基本元件的功能容易。具有复杂功能和商业价值的 IP 核一般具有知识产权,尽管 IP 核的市场活动还不规范,但是仍有许多集成电路设计公司从事 IP 核的设计、开发和营销工作。
分类依据:产品交付方式
- HDL语言形式 --- 软核
硬件描述语言;可进行参数调整、复用性强;布局、布线灵活;设计周期短、设计投入少
- 网表形式 --- 固核
完成了综合的功能块;可预布线特定信号或分配特定的布线资源
- 版图形式 --- 硬核
硬核是完成提供设计的最终阶段产品—掩膜(Mask);
缺乏灵活性、可移植性差;更易于实现IP核的保护。
缺点:
IP核往往不能跨平台使用
IP核不透明,看不到内部核心代码
定制IP需额外收费
IP 核生成工具提供的 IP 核主要有以下几类:
1、数学运算模块,包括累加器、乘加器、乘累加器、计数器、加/减法器、实/复数乘法器、除法器、CORDIC 算法器、DSP48 宏和浮点数操作器。
2、存储器构造模块,包括块存储器和分布式存储器、先入先出存储器(FIFO)和移位寄存器。
3、DSP 功能,包括直接数字频率合成(DDS)编译器、数字上变频/下变频(DUC/DDC)编译器、有限冲激响应(FIR)滤波器、级联积分梳状(CIC)滤波器、离散傅里叶变换(DFT)和快速傅里叶变换(FFT)。
4、信道纠错码,包括 RS 码编码器和译码器、卷积码编码器、Viterbi 译码器、Turbo码编/译码器和低密度奇偶校验码(LDPC)编码器等。
5、网络应用,包括媒体访问控制器(MAC)、以太网物理编码子层/物理介质连接(PCS/PMA)、网络负载统计、以太网拓展连接单元接口(XAUI)、减少引脚使用的XAUI(RXAUI)、MAC 封装包和音/视频桥接(AVB)端点。
6、FPGA 结构属性,包括时钟向导、高速串行收发器(GTX/GTP)和系统监视向导。
7、连接器,包括标准总线接口(如 PCI/PCI-X、PCI Express、CAN)和数据接口(如以太网、RapidIO 等)。
8、调试和验证,包括逻辑调试内核(集成控制器核(ICON)、集成逻辑分析核(ILA)、虚拟输入/输出核(VIO)、Agilent 跟踪核(ATC2)、误比特率测试核(IBERT)和集成总线分析核(IBA)。
9、针对不同设计方法的特殊IP核,包括用工程导航工具进行逻辑设计的IP核、用Xilinx系统生成工具进行DSP算法设计的IP核,以及用Xilinx平台开发环境(XPS)或 PlanAhead进行嵌入式设计的IP核。
本章将重点介绍几个常用的 IP,如锁相环(PLL)、FIFO、RAM、ROM 等,详细说明各 IP 核的功能以及其使用方法,通过使用这些简单的 IP 核来掌握所有 IP 核的基本使用方法,起到抛砖引玉的效果。
实战演练
![](https://i-blog.csdnimg.cn/blog_migrate/e7e563e95c3e900c3a342d7c708b0944.png)
一、PLL(锁相环)IP核
1.1 PLL简介
PLL(Phase Locked LoopP,即锁相环)是最常用的 IP 核之一,其性能强大,可以对输入到 FPGA 的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟,实际上,即使不想改变输入到 FPGA 时钟的任何参数,也常常会使用 PLL,因为经过 PLL 后的时钟在抖动(Jitter)方面的性能更好一些。Xilinx 中的 PLL 是模拟锁相环,和数字锁相环不同的是模拟锁相环的优点是输出的稳定度高、相位连续可调、延时连续可调;缺点是当温度过高或者电磁辐射过强时会失锁(普通环境下不考虑该问题)。
1.2 基本工作原理
反馈系统,ref_clk:参考时钟,FD/PD:鉴频/相器。
1、参考时钟(ref_clk)通过鉴频(FD)鉴相器(PD)和需要比较的时钟频率进行比较,我们以频率调整为例,如果参考时钟频率等于需要比较的时钟频率则鉴频鉴相器输出为 0,如果参考时钟频率大于需要比较的时钟频率则鉴频鉴相器输出一个变大的成正比的值,如果参考时钟频率小于需要比较的时钟频率则鉴频鉴相器输出一个变小的正比的值。
2、鉴频鉴相器的输出连接到环路滤波器(LF)上,用于控制噪声的带宽,滤掉高频噪声,使之稳定在一个值,起到将带有噪声的波形变平滑的作用。如果鉴频鉴相器之前的波形抖动比较大,经过环路滤波器后抖动就会变小,趋近于信号的平均值。
3、经过环路滤波器的输出连接到压控振荡器(VCO)上,环路滤波器输出的电压可以控制 VCO 输出频率的大小,环路滤波器输出的电压越大 VCO 输出的频率越高,然后将这个频率信号连接到鉴频鉴相器作为需要比较的频率。
- 倍频实现
DIV(分频器),如果ref_clk为50MHz,假设DIV为1/2分频器,则最终分频输出为50MHz,那么之前的pll_out则为100MHz,实现了倍频的功能。
- 分频器实现
如果ref_clk为50MHz,假设DIV为1/5分频器,则最终DIV输出为10MHz,经过基本PLL结构,pll_out输出也为10MHz。
1.3 PLL IP核配置
建立一个PLL工程,在工程中找到IP核目录,并搜索时钟clk,找到时钟IP核选项。
双击打开时钟IP核,配置界面
1、框中我们输入 IP 核的命名,后面实例化 IP 核的时候都是使用的该名字,这里所取的名字最好是和该 IP 核相关,因为本节我们主要讲解 PLL,所以给该 IP 核取名为 pll_ip。
2、 框中我们选中 PLL。
3 框中是输入 IP 核的输入时钟,由于我们开发板上的时钟晶振为 50MHz,所以这里我们输入 50。
设置完之后切换到“Output Clocks”页面。
1、框中是对输出时钟频率、相位以及占空比的设置。为了让大家能够看到 PLL 核每种参数的设置的效果,我们生成四个输出时钟,输出的设置分别为输入时钟的 2 倍频、即100MHz;输入时钟的 2 分频,即 25MHz;输入时钟像移 90°;输入时钟占空比为 20%。
2、框中是对复位管脚以及锁定管脚的设置。其中 reset 为复位管脚,用来对 PLL IP 进行复位,locked 为锁定管脚,用来检测 PLL IP 核是否已经锁定,当输出时钟稳定时该信号会输出高电平表示输出稳定。对于一般的应用而言,可以不用添加这两个管脚,这里我们只添加上“locked”生成锁定管脚,以便在仿真时能够体现 PLL 的工作特点。
设置完之后切换到“Port Renaming”页面。
- 如图 25-10 所示,在“Summary”页面,从“IP Symbol”窗口可以看到我们最终创建
的 PLL 核的端口信号。点击“ OK ”完成 PLL 的创建设置。
生成IP核
- 添加已经有的 IP核
首先使用的芯片、工具及版本相同,这里为了演示,先把上面建立好的IP核从Vivado中移除,然后进行添加演示。
打开IP核文件夹,添加IP核
1.4 PLL-IP核调用
实例化模板:从1、2文件中均可以找到实例化模板。
- 调用IP核代码
module pll(
input wire sys_clk,
output wire clk_mul_2,
output wire clk_div_2,
output wire clk_phase_90,
output wire clk_ducle_20,
output wire locked
);
//PLL IP核的调用
pll_ip pll_ip_inst
(
// Clock out ports
.clk_100m(clk_mul_2), // output clk_100m
.clk_25m(clk_div_2), // output clk_25m
.clk_p_90(clk_phase_90), // output clk_p_90
.clk_d_20(clk_ducle_20), // output clk_d_20
// Status and control signals
.locked(locked), // output locked
// Clock in ports
.pll_clk(sys_clk)); // input pll_clk
endmodule
- 仿真代码
`timescale 1ns / 1ns
//
// Company: 追逐者-桥的小作坊
// Create Date: 2022/05/26 17:50:56
// Design Name: PLL IP核
// Module Name: tb_pll
module tb_pll();
reg sys_clk;
wire clk_mul_2;
wire clk_div_2;
wire clk_phase_90;
wire clk_ducle_20;
wire locked;
initial sys_clk = 1'b1;
always #10 sys_clk = ~sys_clk;
pll pll_inst(
. sys_clk (sys_clk ),
. clk_mul_2 (clk_mul_2 ),
. clk_div_2 (clk_div_2 ),
. clk_phase_90(clk_phase_90),
. clk_ducle_20(clk_ducle_20),
. locked (locked )
);
endmodule
- 仿真波形图
二、ROM IP核
2.1 ROM简介
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事 实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM(RAM 将在下一节为大家讲解)调用的都是FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释,FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.coe 格式)(.mf/.nex格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。
最简单的使用有效时钟CLKA、有效地址ADDRA和有效使能EA,就可以输出DOUTA
2.2 创建ROM初始化文件
ROM 作为只读存储器,在进行 IP 核设置时需要指定初始化文件,即写入存储器中的数据,数据要以规定的格式才能正确写入 ROM,这种格式就是 coe 文件。coe 是 Vivado 规定的一种文件格式,文件格式示意图,具体见图 25-24。
比较复杂时可以使用python和matlab自动生成文件。
2.3 ROM IP核配置
打开IP核目录并搜索找到ROM IP核:RAM & ROM 核“Block Memory Generator”双击打开配置。
- Basic界面
1、框中我们输入 IP 核的命名。
2、类型,普通和接口形式。
3、框中选择存储器类型,可供选择的类型有:Single Port RAM(单端口 RAM)、Simple Dual Port RAM(简单双口 RAM)、True Dual Port RAM(真双口 RAM)、Singl Port ROM(单端口 ROM)、Doul Port ROM(双端口 ROM)。这里我们选择“Single Port Rom”单端口 ROM。
4、框在 Algorithm 一栏中可选择用于实现内存的算法,其中 Minimum Area 为最小面积算法;Low Power 为低功耗算法;Fixed Primitives 为固定单元算法。这里我们按默认选择Minimum Area 即可。
5、校验选项,只有RAM中才会使用到。
6、设置使能位数,也是只有在RAM中才会用到。
- 设置完之后切换到“Potr A Options”页面。
- 设置完之后切换到“Other Options”页面。
框中选项是加载数据文件,即我们前面讲到的 ROM 初始化文件,由于 ROM 是只读存储器,所以我们必须添加 ROM 初始化文件才行。勾选上“Load Init File”点击 Browse 进行添加.coe 初始化文件,文件格式我们之前已经讲解,大家可根据自己想存入的数据进行生成该文件。该页面其余按默认设置即可。
- 设置完之后切换到“Summary”页面。
在“Summary”页面,从“IP Symbol”窗口可以看到我们最终创建的 ROM 核的端口信号。点击“OK”完成 ROM 的创建设置。
- 将编好的coe文件复制到IP核文件中
- 在IP核中添加该初始化.coe文件
- 双端口ROM配置
该窗口是对端口 B 的设置,这也是双端口 ROM 与单端口 ROM 最大的区别,多了一组端口。这里我们只需对 B 端口的数据位宽设置即可,数据深度会根据端口 A 的设置自动设置。例如我们端口 A 设置的数据位宽为 8bit,深度为 256;而我们 B 端口设置的数据位宽为 16bit,则其深度即为 128,其数据总量是一样的。其余设置与端口 A 设置一样即可。
2.4 ROM IP核调用
与之前的PLL类似调用类似,打开IP Sources找到.veo文件,内有实例化模型之间复制
- 程序代码
module rom(
input wire sys_clk,
input wire [7:0] addra,
output wire [7:0] douta
);
rom_8x256 rom_ip (
.clka(sys_clk), // input wire clka
.addra(addra), // input wire [7 : 0] addra
.douta(douta) // output wire [7 : 0] douta
);
endmodule
- 仿真代码
`timescale 1ns / 1ns
//
// Company: 追逐者-桥的小作坊
// Create Date: 2022/05/27 15:05:26
// Design Name: ROM IP核
// Module Name: tb_rom_ip
//
module tb_rom_ip();
reg sys_clk;
reg sys_rst_n;
reg [7:0] addra;
wire [7:0] douta; //将数据引出因此设置为网线型
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~ sys_clk;
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
addra <= 8'd0;
else if(addra == 8'd255)
addra <= 8'd0;
else
addra <= addra + 1'b1;
rom rom_inst(
.sys_clk(sys_clk),
.addra (addra ),
.douta (douta )
);
endmodule
- 仿真后的波形图
有波形图可以看出可以通过输入相应的地址返回相应的数据,但是两者的时钟差2个时钟。
下图框中是否在输出增加一个寄存器,如果加入寄存器会在增加一个时钟周期,不增加仅滞后一个时钟周期。
将其取消选取时,可以看出只延迟了1个时钟周期:
三、RAM IP核
3.1 RAM IP核简介
RAM 是随机存取存储器(Random Access Memory)的简称,是一个易失性存储器。 RAM 工作时可以随时从任何一个指定的地址写入或读出数据,同时我们还能修改其存储的数据,即写入新的数据,这是 ROM 所并不具备的功能。在 FPGA 中这也是其与 ROM 的最大区别。ROM 是只读存储器,而 RAM 是可写可读存储器,在我们 FPGA 中使用这两个存储器主要也是要区分这一点,因为这两个存储器使用的都是我们 FPGA 内部的 RAM 资源,不同的是 ROM 是只用到了 RAM 资源的读数据端口。
- 对于单端口 RAM,读写操作共用一组地址线,读写操作不能同时进行;
- 对于简单双端口 RAM,读操作和写操作有专用地址端口(一个读端口和一个写端口),即写端口只能写不能读,而读端口只能读不能写;
- 对于真正双端口 RAM,有两个地址端口用于读写操作(两个读/写端口),即两个端口都可以进行读写。
3.2 RAM IP核的配置
-
单端口配置
操作方式:
Write First:读写在同一始终下,先执行写操作,读出的是写入的值。
Read First:读写在同一始终下,先执行读操作,读出的是之前写入的值。
NO:读和写不能在同一始终下进行
后面两栏基本不用配置,直接生成IP核即可。
- 简单双端楼RAM IP核
- 真正双端口IP核配置
3.3 RAM IP核的调用
module ram(
input wire sys_clk,
input wire [7:0] addra,
input wire [7:0] dina,
input wire wea, //写使能
output wire [7:0] douta
);
s_ram_gen_8x256 s_ram_gen_8x256_inst (
.clka(sys_clk), // input wire clka
.wea(wea), // input wire [0 : 0] wea
.addra(addra), // input wire [7 : 0] addra
.dina(dina), // input wire [7 : 0] dina
.douta(douta) // output wire [7 : 0] douta
);
endmodule
四、FIFO IP核
4.1 FIFO IP核简介
FIFO(First In First Out,即先入先出),是一种数据缓冲器,用来实现数据先入先出的读写方式。与 ROM 或 RAM 的按地址读写方式不同,FIFO 的读写遵循“先进先出”的原则,即数据按顺序写入 FIFO,先被写入的数据同样在读取的时候先被读出,所以 FIFO存储器没有地址线。FIFO 有一个写端口和一个读端口外部无需使用者控制地址,使用方便。
FIFO 存储器主要是作为缓存,应用在同步时钟系统和异步时钟系统中,在很多的设计中都会使用,后面实例中如:多比特数据做跨时钟域的转换、前后带宽不同步等都用到了FIFO。FIFO 根据读写时钟是否相同,分为 SCFIFO(同步 FIFO)和 DCFIFO(异步FIFO),SCFIFO 的读写为同一时钟,应用在同步时钟系统中;DCFIFO 的读写时钟不同,应用在异步时钟系统中。
数据的产生模块与数据使用模块不对应时就会使用到FIFO,如两者的时钟频率不同无法在同一时钟下进行传输(多比特数据做跨时钟域的转换) ;两者的数据带宽不同下传输(前后带宽不同步)。
4.2 FIFO IP核配置
- scfifo IP核
在IP Catalog中搜索FIFO,并选择fifo核“FIFO Generator”,双击打开
- dcfifo IP核
4.3 FIFO IP核调用
- scfifo IP核
RTL 代码顶层的输入信号有:50MHz 的写时钟 wr_clk、输入 256 个 8bit 的数据 pi_data(值为十进制 0~255)和伴随该输入数据有效的标志信号 pi_flag、25MHz 的读时钟rd_clk、FIFO 的写请求信号 rd_en。这些输入信号需要在 Testbench 中产生激励。
RTL 代码顶层的输出信号有:FIFO 空标志信号 empty、FIFO 满标志信号 full、同步于wr_clk 指示 FIFO 中存在数据个数的信号 wr_data_count、从 FIFO 中读取的数据 po_data、同步于 rd_clk 指示 FIFO 中存在数据个数的信号 rd_data_count。这些信号也是我们需要通过仿真 DCFIFO IP 核主要观察的信号,这些信号通过 Testbench 中给输入信号激励后产生输出。
- 程序代码
module scfifo(
input wire sys_clk,
input wire sys_rst_n,
input wire [7:0] pi_data, //写数据
input wire pi_flag, //写请求信号
input wire rd_en, //FIFO读请求信号
output wire [7:0] po_data, //读数据
output wire full, //FIFO满标志,高有效
output wire empty, //FIFO空标志,高有效
output wire [7:0] data_count //FIFO中存在的数据个数
);
scfifo_8x256 scfifo_8x256_inst (
.clk(sys_clk), // input wire clk
.srst(~sys_rst_n), // input wire srst
.din(pi_data), // input wire [7 : 0] din
.wr_en(pi_flag), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(po_data), // output wire [7 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.data_count(data_count) // output wire [7 : 0] data_count
);
endmodule
- 仿真代码
`timescale 1ns / 1ns
module tb_scfifo();
reg sys_clk;
reg sys_rst_n;
reg [7:0] pi_data;
reg pi_flag;
reg rd_en;
reg [1:0] cnt_baud;
wire [7:0] po_data;
wire full;
wire empty;
wire [7:0] data_count;
initial begin
sys_clk = 1'b1;
sys_rst_n <= 1'b0;
#20
sys_rst_n <= 1'b1;
end
always #10 sys_clk = ~sys_clk;
//计数器,产生数据间的间隔
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//pi_flag:输入标志信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_flag <= 1'b0;
else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
pi_flag <= 1'b1;
else
pi_flag <= 1'b0;
//pi_data:数据写入
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
pi_data <= 8'b0;
else if((pi_data == 8'd255) && (pi_flag == 1'b1))
pi_data <= 8'b0;
else if(pi_flag == 1'b1)
pi_data <= pi_data + 1'b1;
//rd_en:读请求信号
always@(posedge sys_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(full == 1'b1)
rd_en <= 1'b1;
else if(empty == 1'b1)
rd_en <= 1'b0;
scfifo scfifo_inst(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n ),
.pi_data (pi_data ), //写数据
.pi_flag (pi_flag ), //写请求信号
.rd_en (rd_en ), //FIFO读请求信号
.po_data (po_data ), //读数据
.full (full ), //FIFO满标志,高有效
.empty (empty ), //FIFO空标志,高有效
.data_count(data_count) //FIFO中存在的数据个数
);
endmodule
- 仿真波形
虽然使用的是同步时钟,但是写的时钟间隔做了改变,而读的时钟依然使用的是系统时钟。
- dcfifo IP核
RTL 代码顶层的输入信号有:50MHz 的写时钟 wr_clk、输入 256 个 8bit 的数据 pi_data(值为十进制 0~255)和伴随该输入数据有效的标志信号 pi_flag、25MHz 的读时钟 rd_clk、FIFO 的写请求信号 rd_en。这些输入信号需要在 Testbench 中产生激励。
RTL 代码顶层的输出信号有:FIFO 空标志信号 empty、FIFO 满标志信号 full、同步于wr_clk 指示 FIFO 中存在数据个数的信号 wr_data_count、从 FIFO 中读取的数据 po_data、同步于 rd_clk 指示 FIFO 中存在数据个数的信号 rd_data_count。这些信号也是我们需要通过仿真 DCFIFO IP 核主要观察的信号,这些信号通过 Testbench 中给输入信号激励后产生输出。
- 程序代码
module dcfifo(
input wire wr_clk, rd_clk,
input wire [7:0] din,
input wire wr_en, rd_en,
output wire full, empty,
output wire [15:0] dout,
output wire [6:0] rd_data_count,
output wire [7:0] wr_data_count
);
dcfifo_gen dcfifo_gen_inst (
.wr_clk(wr_clk), // input wire wr_clk
.rd_clk(rd_clk), // input wire rd_clk
.din(din), // input wire [7 : 0] din
.wr_en(wr_en), // input wire wr_en
.rd_en(rd_en), // input wire rd_en
.dout(dout), // output wire [15 : 0] dout
.full(full), // output wire full
.empty(empty), // output wire empty
.rd_data_count(rd_data_count), // output wire [6 : 0] rd_data_count
.wr_data_count(wr_data_count) // output wire [7 : 0] wr_data_count
);
endmodule
- 仿真代码
`timescale 1ns / 1ns
module tb_dcfifo();
reg wr_clk;
reg rd_clk;
reg [7:0] din;
reg wr_en;
reg rd_en;
reg sys_rst_n;
reg [1:0] cnt_baud;
reg full_reg0;
reg full_reg1;
wire empty;
wire full;
wire [15:0] dout;
wire [6:0] rd_data_count;
wire [7:0] wr_data_count;
initial begin
wr_clk = 1'b1;
rd_clk = 1'b1;
sys_rst_n <= 1'b0;
#100;
sys_rst_n <= 1'b1;
#100000
sys_rst_n <= 1'b0;
end
always #10 wr_clk = ~wr_clk;
always #20 rd_clk = ~rd_clk;
//计数器,产生数据间的间隔
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
cnt_baud <= 2'b0;
else if(&cnt_baud == 1'b1)
cnt_baud <= 2'b0;
else
cnt_baud <= cnt_baud + 1'b1;
//wr_en:输入标志信号
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
wr_en <= 1'b0;
else if((cnt_baud == 2'd0) && (rd_en == 1'b0))
wr_en <= 1'b1;
else
wr_en <= 1'b0;
//din:数据写入
always@(posedge wr_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
din <= 8'b0;
else if((din == 8'd255) && (wr_en == 1'b1))
din <= 8'b0;
else if(wr_en == 1'b1)
din <= din + 1'b1;
//将同步于rd_clk时钟的写满标志信号full在rd_clk时钟下打两拍
always@(posedge rd_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
begin
full_reg0 <= 1'b0;
full_reg1 <= 1'b0;
end
else
begin
full_reg0 <= full;
full_reg1 <= full_reg0;
end
//rd_en:读请求信号
always@(posedge rd_clk or negedge sys_rst_n)
if(sys_rst_n == 1'b0)
rd_en <= 1'b0;
else if(full_reg1 == 1'b1)
rd_en <= 1'b1;
else if(empty == 1'b1)
rd_en <= 1'b0;
dcfifo dcfifo_inst(
.wr_clk (wr_clk ),
.rd_clk (rd_clk ),
.din (din ),
.wr_en (wr_en ),
.rd_en (rd_en ),
.full (full ),
.empty (empty ),
.dout (dout ),
.rd_data_count(rd_data_count),
.wr_data_count(wr_data_count)
);
endmodule
- 仿真波形图