文章目录
IP核介绍
IP核,又称知识产权。用在FPGA的学习中,有很方便的方式。主要是要懂得IP核的使用和方法,对于后序的学习有很方便的作用。
PLL使用介绍
介绍
在IP核中,有一种最基础的,叫做PLL,其实就是我们之前学过的分频器,在这个IP核中,我们可以设置最多五个输出时钟,我们可以设置不同的分频、相位、占空比等,由此我们可以通过简单的设置,实现不同的输出时钟,方便我们的后序学习。
设置IP核过程
首先,我们在右侧的搜索栏中输入pll,找到如下图的选项,双击点击开,找到自己提前设置好的存放位置,取好名字。
这里可以设置我们的系统时钟,也就是输入时钟,由于我们一般使用的开发板是50MHz的晶振,这里便使用相应的晶振。
接下来设置输出时钟,我们的PLLIP核最多可以设置5个输出时钟。
这里我设置了总共5个输出时钟,其中c0对应100MHz输出时钟,c1到c4都是25MHz输出时钟,c2有25的相位,c3的占空比为20%,c4则占空比有80%,同时也有25的相位差。
然后在此处选择上这个仿真示例。其他的部分都直接默认即可,这样我们就产生了一个PLL IP核,后续将继续分享产生的IP的使用方法。
PLL核使用
这里由于PLL是最简单的IP核,就直接给出顶层代码,仿真文件,并展示仿真效果。
另外,这里给出我们使用IP的快速方法,可找到刚才生成选择的_inst.v文件,将其复制出来,改括号里的参数。
顶层代码:
/**************************************功能介绍***********************************
Date :
Author : Rye
Description : PLL测试调用模块
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module pll_test(
input wire clk ,
input wire rst_n ,
output wire clk_0 ,
output wire clk_1 ,
output wire clk_2 ,
output wire clk_3 ,
output wire clk_4 ,
output wire locked
);
//---------<参数定义>---------------------------------------------------------
//---------<内部信号定义>-----------------------------------------------------
ip_test ip_test_inst (
.areset ( ~rst_n ),//pll IP核的异步复位引脚是高有效,系统是低有效
.inclk0 ( clk ),
.c0 ( clk_0 ),
.c1 ( clk_1 ),
.c2 ( clk_2 ),
.c3 ( clk_3 ),
.c4 ( clk_4 ),
.locked ( locked )
);
endmodule
仿真文件,由于PLL只是时钟IP核的使用,所以只需要拉长时间单位即可
`timescale 1ns/1ns
module pll_test_tb();
//激励信号定义
reg clk;
reg rst_n;
//输出信号定义
wire clk_0;
wire clk_1;
wire clk_2;
wire clk_3;
wire clk_4;
wire locked;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
pll_test u1_pll_test(
.clk (clk ),
.rst_n (rst_n ),
.clk_0 (clk_0 ),
.clk_1 (clk_1 ),
.clk_2 (clk_2 ),
.clk_3 (clk_3 ),
.clk_4 (clk_4 ),
.locked (locked)
);
//产生时钟
always #(CLOCK_CYCLE / 2) clk = ~clk;
//产生激励
initial begin
rst_n = 0;
clk = 0;
#5;
rst_n = 1;
#10;
#10000;
$stop;
end
endmodule
仿真效果
RAM使用介绍
介绍
RAM IP核,作为比较简单常用的寄存器IP核,也是我们需要着重学习的IP核之一。同时,RAM也是查找表LUT的重要组成部分,对于FPGA的学习有很重要的地位。
在RAM中,有一些非常重要的参数,分别有:写使能wren、读写地址address、读使能rden、写如数据DATA和读出数据q。
在这些参数中,对不同的参数进行配合使用,例如,拉高写使能wren,写入相应的地址address和数据DATA,就可以在相应的地址写入数据,之后如果在拉高读使能rden的同时输入地址address,q就会输出相应的地址的数据DATA。
注意事项:这里的RAM是在时序电路中使用,读使能拉高后,要两个时钟周期之后才开始找到数据的地址,并把数据进行输出。但是如果仅仅使用仿真却没有这个问题,很奇怪,各位可以研究研究。
设置IP核过程
搜索栏找到输入ram,找到如下框内的选项。
接下来跟着图走,这里既展示我们这次实验需要的选项,也尽量解释其中选项的具体内容。(英文真的很麻烦,可恶)
这里我在后续使用中使用了输入输出双时钟的IP核,与截图有些许出入,学习的时候要稍微注意一下。
RAM核使用
顶层代码
/**************************************功能介绍***********************************
Date :
Author : Rye
Description :
*********************************************************************************/
//---------<模块及端口声名>------------------------------------------------------
module ram_single(
input wire inclock,
input wire outclock,
input wire rst_n,
input wire [7:0] address,
input wire [7:0] data,
input wire rden,
input wire wren,
output wire [7:0] q
);
//---------<参数定义>---------------------------------------------------------
//---------<内部信号定义>-----------------------------------------------------
ram_test ram_test_inst (
.outclock ( outclock ),
.inclock ( inclock ),
.outaclr ( ~rst_n ),//RAM IP核的异步复位引脚是高有效,系统是低有效
.address ( address ),
.data ( data ),
.rden ( rden ),
.wren ( wren ),
.q ( q )
);
endmodule
仿真文件
`timescale 1ns/1ps
module ram_test_tb();
reg inclock ;//50MHz
reg outclock ;//25MHz
reg rst_n ;
reg [7:0] data ;
reg [7:0] address ;
reg rden ;
reg wren ;
wire [7:0] q ;
//例化要仿真的文件
ram_single u1_ram_single(
.inclock ( inclock ),
.outclock ( outclock ),
.rst_n ( rst_n ),
.address ( address ),
.data ( data ),
.rden ( rden ),
.wren ( wren ),
.q ( q )
);
always #10 inclock = ~inclock;//产生50M仿真时钟
always #20 outclock = ~outclock;//产生100M仿真时钟
integer i = 0,j = 0;//用于产生地址,写入数据
initial begin
inclock = 1'b1;
outclock = 1'b1;
rst_n = 1'b0;
wren = 1'b0;
rden = 1'b0;
address = 0;
#5;
rst_n = 1'b1;
#200;
for (i = 0;i < 256;i = i + 1) begin
wren = 1'b1;//拉高写使能
data = {$random} % 256;//输入数据
address = i;//找到地址
@(posedge inclock);
end
wren = 1'b0;
#2000;
for (i = 256;i > 0;i = i - 1) begin
rden = 1'b1;//拉高读使能
address = i;//找到地址
@(posedge outclock);
end
rden = 1'b0;
#2000;
$stop;
end
endmodule
仿真效果
首先是写入数据的观察
输出数据的观测,可以看到这些数据和地址都是随着周期更长的时钟变化的。
我们也可以使用memory list观测数据写入是否成功。
FIFO使用介绍
介绍
FIFO IP核,我们平时一般当做缓存器来用,因为这里FIFO核有先入先出的特性,和队列的使用有点像,不像RAM可以通过地址来控制输出的数据位置,但是由于不需要通过地址来找数据,用起来反而比较方便。
另外,由于FIFO没有地址位,其输出效果全靠时钟,所以的在FIFO的设置的时候,可以对设置其中的输入输出两个时钟,这里分别对两种时钟的使用给出示例。另外FIFO还有标准模式和前显模式,这里都简单的给出一点介绍。
设置IP核过程-单时钟
还是老地方,搜索FIFO
这里可以找到两种模式,我们此处两个模式都选了一遍,以便后续在的仿真当中做一些对比。注意这里的是前显模式,在截图的时候打字打错了,要注意一下阅读的时候分辨其中错误。
FIFO核使用-单时钟
在FIFO的使用当中,我们还是配合着写使能和和时钟改变存入的数据,同样根据时钟和读使能来输出数据。可以大致理解为读写使能和时钟上升沿同时发生的时候(wren && posedge clk)触发写和读,我们平时使用clk来找到读写的频率,用读写使能的脉冲来控制读写数据。
仿真代码示例
`timescale 1ns/1ns
module fifo_1_tb();
//激励信号定义
reg rst_n;
reg clk;
//标准
wire fifo_1_full;
wire fifo_1_empty;
reg [7:0] fifo_1_wr_data ;
reg fifo_1_wr_req ;
reg fifo_1_rd_req ;
//前显
wire fifo_1_h_full;
wire fifo_1_h_empty;
reg fifo_1_h_wr_req ;
reg fifo_1_h_rd_req ;
//输出信号定义
wire [7:0] q1;
wire [7:0] q2;
wire [3:0] usedw1;
wire [3:0] usedw2;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
fifo_1 fifo_1_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( fifo_1_wr_data ),
.rdreq ( fifo_1_rd_req ),
.wrreq ( fifo_1_wr_req ),
.empty ( fifo_1_empty ),
.full ( fifo_1_full ),
.q ( q1 ),
.usedw ( usedw1 )
);
fifo_1_h fifo_1_h_inst (
.aclr ( ~rst_n ),
.clock ( clk ),
.data ( fifo_1_wr_data ),
.rdreq ( fifo_1_h_rd_req ),
.wrreq ( fifo_1_h_wr_req ),
.empty ( fifo_1_h_empty ),
.full ( fifo_1_h_full ),
.q ( q2 ),
.usedw ( usedw2 )
);
//产生时钟
always #(CLOCK_CYCLE / 2) clk = ~clk;
initial begin
clk = 1'b0;
rst_n = 1'b0;
#(CLOCK_CYCLE * 2);
rst_n = 1'b1;
end
//产生激励
initial begin
fifo_1_wr_data = 0;
fifo_1_wr_req = 0;
fifo_1_rd_req = 0;
fifo_1_h_wr_req = 0;
fifo_1_h_rd_req = 0;
#(CLOCK_CYCLE * 4);
repeat(20) begin//拉高写使能,写进随机数,只要缓存器FIFO不空,就可以写如数据
fifo_1_wr_data = {$random}%256;
fifo_1_wr_req = ~fifo_1_full;
fifo_1_h_wr_req = ~fifo_1_h_full;
#20;
end
repeat(20) begin//拉高读使能,只要缓存器不空,就可以随着时钟向外输出数据
fifo_1_rd_req = ~fifo_1_empty;
fifo_1_h_rd_req = ~fifo_1_h_empty;
#20;
end
$stop;
end
endmodule
单时钟仿真效果
设置IP核过程-双时钟
双时钟的设置与单时钟的区别不会很大,这里给出有区别的部分
FIFO核使用-双时钟
仿真代码如下,这里关键注释还是直接写在代码中,各位可以注意一下
`timescale 1ns/1ns
module fifo_2_tb();
//激励信号定义
reg clk;
reg clk_100M;
reg rst_n;
wire fifo_2_full;
wire fifo_2_empty;
reg [7:0] fifo_2_wr_data ;
reg fifo_2_wr_req ;
reg fifo_2_rd_req ;
wire fifo_2_h_full;
wire fifo_2_h_empty;
reg fifo_2_h_wr_req ;
reg fifo_2_h_rd_req ;
//输出信号定义
wire [7:0] q1;
wire [15:0] q2;
//时钟周期参数定义
parameter CLOCK_CYCLE = 20;
//模块例化
//普通双时钟
fifo_2 fifo_2_inst (
.aclr ( ~rst_n ),
.wrclk ( clk ),
.wrreq ( fifo_2_wr_req ),
.data ( fifo_2_wr_data ),
.rdclk ( clk_100M ),
.rdreq ( ~fifo_2_empty ),
.q ( q1 ),
.rdempty ( fifo_2_empty ),
.rdusedw ( ),
.wrfull ( fifo_2_full ),
.wrusedw ( )
);
fifo_2_h fifo_2_h_inst (
.aclr ( ~rst_n ),
.wrclk ( clk ),//写使能是50MHz,每20ns写入一次数据
.wrreq ( fifo_2_h_wr_req ),
.data ( fifo_2_wr_data ),
.rdclk ( clk_100M ),//读使能100MHz,每10ns写入一次数据
.rdreq ( ~fifo_2_h_empty ),
.q ( q2 ),
.rdempty ( fifo_2_h_empty ),
.rdusedw ( ),
.wrfull ( fifo_2_h_full ),
.wrusedw ( )
);
//产生时钟
always #(CLOCK_CYCLE / 2) clk = ~clk;//50MHz时钟
always #(CLOCK_CYCLE / 4) clk_100M = ~clk_100M;//100MHz时钟
initial begin
clk = 1'b1;
clk_100M = 1'b1;
rst_n = 1'b0;
#(CLOCK_CYCLE * 2);
rst_n = 1'b1;
end
//产生激励
initial begin
fifo_2_wr_data = 0;
fifo_2_wr_req = 0;
fifo_2_rd_req = 0;
fifo_2_h_wr_req = 0;
fifo_2_h_rd_req = 0;
#(CLOCK_CYCLE * 4);
repeat(20) begin
fifo_2_wr_data = {$random}%256;
fifo_2_wr_req = ~fifo_2_full;
fifo_2_h_wr_req = ~fifo_2_h_full;
#20;
end
repeat(20) begin//拉高读使能,只要缓存器不空,就可以随着时钟向外输出数据
fifo_2_rd_req = ~fifo_2_empty;
fifo_2_h_rd_req = ~fifo_2_h_empty;
#20;
end
#100;
$stop;
end
endmodule
仿真效果:
我们这里依然是一个标准模式,一个前显模式这里可以相互对比
这里可以看见当写入数据的时候空信号拉低,表示数据成功写入,由于第二个FIFO核输出是16bit,所以空信号拉低的时序要晚一个数据。
而输出的频率间隔要比输入的时候更快,同时16位输出核也更快的把全部的数据输出来了。
可以看到,第一个8位输出按照顺序输出了之前的数据,而16位的输出数据是将后输入的数据拼在前8位,先输入的数据拼在后8位,拼接位整个16位数据输出出来。而c6之后的数据由于给出的数据已经存满,后续的数据便不能再输入到缓存器当中。
结语
IP核的三大基础就到此为止了,作为封装的IP核中,这些就是最基本的部分了。
虽然看起来和使用起来比较困难,但是如果能够更好的理解和学习这些IP核,对于整个FPGA的学习和理解也有很好的帮助,对于后续的读写数据也有不少的帮助。
对于IP核的学习,其实我不是很快乐,因为有点难,但是现在知道,确实很有用,所以各位还是好好研究研究吧,加油!