FPGA常用IP核
前言:
芯片行业中的IP,一般称为IP(Intellectual Property)核,是具有知识产权核的集成电路芯核的总称。说白了就是厂家实现的具有特定功能工具,然后我们可以直接调用,就相当于是函数库吧,如果要定制IP核的化是需要氪金的,对于学习来说,免费的就够用啦。
在FPGA里,我用的是zynq-7000系列,有一个时钟管理器,包括MMCM与PLL,两者功能类似,PLL可以看作是MMCM的的子集,网上说MMCM相对于PLL的优势就是相位可以动态调整。
这篇随笔就写一些最基本的IP的功能用法吧,省得以后自己忘了,高级功能可以看官网文档学习。
一、PLL锁相环
作用:产生高质量,低抖动的时钟信号,可以调频,调相,调占空比的功能。
具体原理不写了先,等以后再看,是一个反馈电路
使用方法:
1 选择IP Catalog,找到自己想要使用的IP核,然后双击需要的IP核,进行配置即可。
2 配置界面。
(1)基本设置
(2)配置输出
其他的没有配置,暂时用不到。
3 生成IP核。
配置好后点击OK,generate即可,生成后的IP在源文件目录中的样子如下:
点击源文件下的IP Sources,找到例化模板。
然后复制模板就行例化即可。
4 使用IP核。
在需要的位置直接例化IP核即可。
module pll(
input sys_clk,
output clk_100m,
output clk_25m,
output clk_s_90,
output clk_d_20,
output locked
);
pll_ip pll_ip_inst
(
// Clock out ports
.clk_100m(clk_100m), // output clk_100m
.clk_25m(clk_25m), // output clk_25m
.clk_s_90(clk_s_90), // output clk_s_90
.clk_d_20(clk_d_20), // output clk_d_20
// Status and control signals
.locked(locked), // output locked
// Clock in ports
.pll_clk(sys_clk)
); // input pll_clk
endmodule
5 测试IP核。
Testbench代码如下:
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2023/08/13 09:19:14
// Design Name:
// Module Name: tb_pll
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_pll();
reg sys_clk;
wire clk_100m;
wire clk_25m;
wire clk_s_90;
wire clk_d_20;
wire locked;
initial sys_clk = 1'b1;
always #10 sys_clk = ~sys_clk;
pll pll_inst(
.sys_clk (sys_clk ),
.clk_100m (clk_100m),
.clk_25m (clk_25m ),
.clk_s_90 (clk_s_90),
.clk_d_20 (clk_d_20),
.locked (locked )
);
endmodule
5 结果。
从locked置1开始,就说明时钟信号稳定了,其他四路信号经过测试结果也正确。
二、ROM
作用:只读存储器,存储的信息在制造时由厂家一次写入。只可以读取不可以写入,断电后也不会丢失信息。在这里我们就是厂家,我们可以规定存储的信息。
简单的原理在这
使用方法:
1 选择IP Catalog,搜索block,双击Block Memory Generator,进行配置即可。
2 配置界面
其中ECC Options是纠错类型的选择,这里用不到。
(1) 配置端口
(2) 初始化ROM,最初的数据。
这个coe文件可以用python来写,python代码如下:
file_name = "data.coe"
with open(file_name, 'w') as f:
f.write('memory_initialization_radix = 10;\n')
f.write('memory_initialization_vector = \n')
for i in range(256):
if i != 255:
f.write('{},\n'.format(i))
else:
f.write('{};'.format(i))
生成的文件内容格式:
生成好后放到 工程文件/工程名.srcs/sources_1/ip/rom_8x256,也就是生成的ip核目录下就行,然后在配置界面选择这个文件就ok,选择好后工程目录是这样的,出现了这个东西。
3 使用IP核
代码:
module rom
(
input wire sys_clk,
input wire [7:0]addr,
output wire [7:0]dout
);
rom_8x256 rom_8x256_inst (
.clka (sys_clk), // input wire clka
.addra(addr), // input wire [7 : 0] addra
.douta(dout) // output wire [7 : 0] douta
);
endmodule
4 测试IP核。
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2023/08/15 09:59:28
// Design Name:
// Module Name: tb_rom
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_rom(
);
reg sys_clk;
reg sys_rst_n;
reg [7:0]addr;
wire [7:0]dout;
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, negedge sys_rst_n)
if (sys_rst_n == 1'b0)
addr <= 8'd0;
else if (addr == 8'd255)
addr <= 8'd0;
else
addr <= addr + 1'b1;
rom rom_inst
(
.sys_clk(sys_clk),
.addr(addr),
.dout(dout)
);
endmodule
5 波形
读取结果与输入地址差一个周期,没有问题。
三、FFT
折腾了好久的东西,今天终于差不多搞明白了。博客参考:
教程
教程代码
上面是博客参考。
因为要注意的东西过于多,不是三言两语能写清楚的,直接看视频,我愿称之为神的视频教程:
Vivado快速傅里叶变换FFT端口篇;
Vivado快速傅里叶变换FFT端配置篇;
Vivado快速傅里叶变换FFT端细节篇。
视频挺长,按顺序耐心看完,收获一定会非常多。
几个比较重要地方,这些都是网上很多教程中忽视的点:
(1)一定要把Output Ordering改为Natural Order,这样输出顺序就是matlab中计算后输出的顺序。
(2)Optional Output Fields一定要把OVFLO打勾,这样使用的时候就会出现一个event_fft_overflow信号,能够监控你的fft ip运算过程是否发生了溢出,如果溢出就要一定要设置s_axis_config_tdata,对运算进行缩放,直到不发生溢出为止,然后输出结果乘相应的缩放倍数就是正确的结果。
(3)不要只关注输入输出,那几个event_...信号,也非常重要,上面视频里也都有讲各个信号的意义,用于监测异常的,可以帮助你快速锁定bug。
四、RAM
作用:随机存储器。
配置:
代码
示例代码就略了,很简单。
五、加法器Adder、乘法器Multiplier
这两个IP的用法很简单,甚至不用看教程。
主要的,乘法器的Pipeline Stages设置,加法器的Latency设置,就是延迟多少个时钟输出,一般根据需要设置即可。
代码
tb_adder.v
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2023/09/20 22:07:17
// Design Name:
// Module Name: tb_adder
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_adder();
reg [31:0] a;
reg [31:0] b;
reg sys_clk;
reg reset;
wire [31:0] out;
initial begin
reset = 1'b1;
#20
a = 31'd6;
b = 31'd0;
sys_clk = 1'b0;
reset = 1'b0;
end
always #10 sys_clk = ~sys_clk;
always @ (posedge sys_clk, posedge reset) begin
if (reset == 1'b1)
b <= 31'd0;
else
b <= b + 31'd1;
end
adder adder_inst (
.A(a), // input wire [31 : 0] A
.B(b), // input wire [31 : 0] B
.CLK(sys_clk), // input wire CLK
.SCLR(reset), // input wire SCLR
.S(out) // output wire [31 : 0] S
);
endmodule
tb_multiplier.v
`timescale 1ns / 1ns
//////////////////////////////////////////////////////////////////////////////////
// Company:
// Engineer:
//
// Create Date: 2023/09/20 21:49:42
// Design Name:
// Module Name: tb_multiplier
// Project Name:
// Target Devices:
// Tool Versions:
// Description:
//
// Dependencies:
//
// Revision:
// Revision 0.01 - File Created
// Additional Comments:
//
//////////////////////////////////////////////////////////////////////////////////
module tb_multiplier();
reg [15:0] a;
reg [15:0] b;
reg sys_clk;
reg reset;
wire [15:0] out;
initial begin
reset = 1'b1;
#20
a = 15'd6;
b = 15'd0;
sys_clk = 1'b0;
reset = 1'b0;
end
always #10 sys_clk = ~sys_clk;
always @ (posedge sys_clk, posedge reset) begin
if (reset == 1'b1)
b <= 15'd0;
else
b <= b + 15'd1;
end
multiplier multiplier_inst (
.CLK(sys_clk), // input wire CLK
.A(a), // input wire [15 : 0] A
.B(b), // input wire [15 : 0] B
.SCLR(reset), // input wire SCLR
.P(out) // output wire [31 : 0] P
);
endmodule