目录
1. FPGA的IP核定义和分类
2. PLL锁相环
2.1 PLL的基础知识
2.2 PLL核的调用
3. ROM IP核
4. RAM IP核
5. FIFO IP核
1. FPGA的IP核定义和分类
FPGA的IP核是在可编程逻辑器件(FPGA)中可以实现特定功能的可重用模块,它们以形式化的方式描述了硬件的功能和接口。如图所示为 PLL 大体的一个结构模型示意图,我们可以看出这是一个闭环反馈系统,其工作原理和过程主要如下:2、鉴频鉴相器的输出连接到环路滤波器(LF)上,用于控制噪声的带宽,滤掉高频噪声,使之稳定在一个值,起到将带有噪声的波形变平滑的作用。如果鉴频鉴相器之前的波形抖动比较大,经过环路滤波器后抖动就会变小,趋近于信号的平均值。 3、经过环路滤波器的输出连接到压控振荡器(VCO)上,环路滤波器输出的电压可以控制 VCO 输出频率的大小,环路滤波器输出的电压越大 VCO 输出的频率越高,然后将这个频率信号连接到鉴频鉴相器作为需要比较的频率。如果 ref_clk 参考时钟输入的频率和需要比较的时钟频率不相等,该系统最终实现的就是让它们逐渐相等并稳定下来。如果 ref_clk 参考时钟的频率是 50MHz,经过整个闭环反馈系统后,锁相环对外输出的时钟频率 pll_out 也是 50MHz。
分类:
-
处理器IP核:包括处理器核心(如ARM Cortex等)及其相关外设,用于实现通用计算功能。
-
通信IP核:用于实现各种通信协议和接口标准,如以太网MAC、USB控制器等。
-
存储IP核:包括各种存储控制器、缓存控制器等,用于实现数据存储和管理。
-
图像处理IP核:用于实现图像处理算法,如图像滤波器、图像压缩器等。
-
数字信号处理IP核:用于实现数字信号处理算法,如滤波器、FFT加速器等。
2. PLL锁相环
2.1 PLL的基础知识
PLL(Phase Locked Loop,即锁相环)是最常用的 IP 核之一,其性能强大,可以对输入到 FPGA 的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟,实际上,即使不想改变输入到 FPGA 时钟的任何参数,也常常会使用 PLL,因为经过PLL 后的时钟在抖动(Jitter)方面的性能更好一些。Altera 中的 PLL 是模拟锁相环,和数字锁相环不同的是模拟锁相环的优点是输出的稳定度高、相位连续可调、延时连续可调;缺点是当温度过高或者电磁辐射过强时会失锁(普通环境下不考虑该问题)。
PLL的功能:
-
时钟倍频器:可以将输入时钟信号倍频到更高的频率。
-
时钟分频器:可以将输入时钟信号分频得到更低的频率。
-
时钟延迟控制:可以对时钟信号进行精确的相位调整。
-
时钟频率合成:可以根据需要合成不同频率的时钟信号。
PLL的组成部分:
-
相频比较器:用于比较输入时钟和反馈时钟之间的相位差并生成控制信号。
-
VCO(Voltage-Controlled Oscillator,电压控制振荡器):根据相频比较器的控制信号调整其输出频率。
-
分频器:用于将VCO输出的时钟信号分频得到所需的频率。
-
反馈路径:将输出时钟信号反馈回相频比较器,使得输出时钟与输入时钟保持稳定的相位关系。
使用场景:
-
在FPGA中,PLL常用于生成各种需要精确时钟控制的信号,如高速接口通信、数据采样、时序逻辑控制等。
-
PLL还可以用于减小时钟抖动、降低时钟抖动对系统带来的影响,提高系统的稳定性和可靠性。
如图所示为 PLL大体的一个结构模型示意图,我们可以看出这是一个闭环反馈系统,其工作原理和过程主要如下:
- 首先需要参考时钟(ref_clk)通过鉴频(FD)鉴相器(PD)和需要比较的时钟频率进行比较,我们以频率调整为例,如果参考时钟频率等于需要比较的时钟频率则鉴频鉴相器输出为 0,如果参考时钟频率大于需要比较的时钟频率则鉴频鉴相器输出一个变大的成正比的值,如果参考时钟频率小于需要比较的时钟频率则鉴频鉴相器输出一个变小的正比的值。
- 鉴频鉴相器的输出连接到环路滤波器(LF)上,用于控制噪声的带宽,滤掉高频 噪声,使之稳定在一个值,起到将带有噪声的波形变平滑的作用。如果鉴频鉴相器之前的波形抖动比较大,经过环路滤波器后抖动就会变小,趋近于信号的平均值。
- 经过环路滤波器的输出连接到压控振荡器(VCO)上,环路滤波器输出的电压可以控制 VCO 输出频率的大小,环路滤波器输出的电压越大 VCO 输出的频率越高,然后将这个频率信号连接到鉴频鉴相器作为需要比较的频率。如果 ref_clk 参考时钟输入的频率和需要比较的时钟频率不相等,该系统最终实现的就是让它们逐渐相等并稳定下来。如果 ref_clk 参考时钟的频率是 50MHz,经过整个闭环反馈系统后,锁相环对外输出的时钟频率 pll_out 也是 50MHz。
倍频是在 VCO 后直接加一级分频器,我们知道 ref_clk 参考时钟输入的频率和需要比较的时钟频率经过闭环反馈系统后最终会保持频率相等,而在需要比较的时钟之前加入分频器,就会使进入分频器之前的信号频率为需要 比较的时钟频率的倍数,VCO 后输出的 pll_out 信号频率就是 ref_clk 参考时钟倍频后的结果。
以上部分来自于野火的官方资料,笔者认为写的很详细了。
2.2 PLL核的调用
实验代码
`timescale 1ns / 1ps
module pll_test(
input clk,
input rst_n,
output clkout1, //pll clock output
output clkout2, //pll clock output
output clkout3, //pll clock output
output clkout4 //pll clock output
);
wire locked;
pll pll_inst
(
// Clock in ports
.inclk0(clk), // IN 50Mhz
// Clock out ports
.c0(clkout1), // OUT 25Mhz
.c1(clkout2), // OUT 50Mhz
.c2(clkout3), // OUT 75Mhz
.c3(clkout4), // OUT 100Mhz
// Status and control signals
.areset(~rst_n), // IN
.locked(locked) //The signal of PLL normal operation
); // OUT
endmodule
测试文件
`timescale 1ns / 1ps //仿真单位/仿真精度
module pll_test_tb();
//parameter define
parameter CLK_PERIOD = 20; //时钟周期 20ns
//reg define
reg sys_clk;
reg sys_rst_n;
//wire define
wire clk_100m;
wire clk_100m_180deg;
wire clk_50m;
wire clk_25m;
//信号初始化
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#200
sys_rst_n = 1'b1;
end
//产生时钟
always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
pll_test u_pll_test(
.clk (sys_clk ),
.rst_n (sys_rst_n ),
.clkout1 (clk_25m ), //pll clock output
.clkout2 (clk_50m ), //pll clock output
.clkout3 (clk_75m ), //pll clock output
.clkout4 (clk_100m ) //pll clock output
);
endmodule
仿真时序
3. ROM IP核
ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事实上在 FPGA 中通过 IP 核生成的 ROM 或 RAM(RAM 将在下一节为大家讲解)调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释,FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.mif 或.hex 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。
Altera 推出的 ROM IP 核分为两种类型:单端口 ROM 和双端口 ROM。对于单端口ROM 提供一个读地址端口和一个读数据端口,只能进行读操作;双端口 ROM与单端口ROM 类似,区别是其提供两个读地址端口和两个读数据端口,基本上可以看做两个单口RAM 拼接而成。这里仅介绍单端ROM。
实验代码
module rom_wr(
input clk,
input rst_n,
//rom address
output reg [7:0] addr
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
addr <= 1'b0;
end
else
begin
if(addr == 8'd256 - 1'b1)
addr <= 0;
else
addr <= addr + 1'b1;
end
end
endmodule
module rom_wr(
input clk,
input rst_n,
//rom address
output reg [7:0] addr
);
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
begin
addr <= 1'b0;
end
else
begin
if(addr == 8'd256 - 1'b1)
addr <= 0;
else
addr <= addr + 1'b1;
end
end
endmodule
测试文件
`timescale 1ns/1ns //仿真的单位/仿真的精度
module ip_1port_rom_tb();
reg sys_clk;
reg sys_rst_n;
wire [7:0] q;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#100
sys_rst_n = 1'b1;
end
always #20 sys_clk = ~sys_clk;
rom_ip1 u_rom_ip1(
.clk (sys_clk),
.rst_n (sys_rst_n),
//rom data
.q (q)
);
endmodule
4. RAM IP核
以下是单端口ROM的接口和时序图:
module ram_1p_top(
input sys_clk , //系统时钟
input sys_rst_n //系统复位,低电平有效
);
//wire define
wire ram_wr_en ; //ram写使能
wire ram_rd_en ; //ram读使能
wire [4:0] ram_addr ; //ram读写地址
wire [7:0] ram_wr_data ; //ram写数据
wire [7:0] ram_rd_data ; //ram读数据
//*****************************************************
//** main code
//*****************************************************
//ram读写模块
ram_rw u_ram_rw(
.clk (sys_clk),
.rst_n (sys_rst_n),
.ram_wr_en (ram_wr_en ),
.ram_rd_en (ram_rd_en ),
.ram_addr (ram_addr ),
.ram_wr_data (ram_wr_data),
.ram_rd_data (ram_rd_data)
);
//ram ip核
ram_1port u_ram_1port(
.address (ram_addr),
.clock (sys_clk),
.data (ram_wr_data),
.rden (ram_rd_en),
.wren (ram_wr_en),
.q (ram_rd_data)
);
endmodule
module ram_rw(
input clk , //时钟信号
input rst_n , //复位信号,低电平有效
output ram_wr_en , //ram写使能
output ram_rd_en , //ram读使能
output reg [4:0] ram_addr , //ram读写地址
output reg [7:0] ram_wr_data, //ram写数据
input [7:0] ram_rd_data //ram读数据
);
//reg define
reg [5:0] rw_cnt ; //读写控制计数器
//*****************************************************
//** main code
//*****************************************************
//rw_cnt计数范围在0~31,ram_wr_en为高电平;32~63时,ram_wr_en为低电平
assign ram_wr_en = ((rw_cnt >= 6'd0) && (rw_cnt <= 6'd31) && rst_n) ? 1'b1 : 1'b0;
//rw_cnt计数范围在32~63,ram_rd_en为高电平;0~31时,ram_rd_en为低电平
assign ram_rd_en = ((rw_cnt >= 6'd32) && (rw_cnt <= 6'd63)) ? 1'b1 : 1'b0;
//读写控制计数器,计数器范围0~63
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
rw_cnt <= 6'd0;
else if(rw_cnt == 6'd63)
rw_cnt <= 6'd0;
else
rw_cnt <= rw_cnt + 6'd1;
end
//读写控制器计数范围:0~31 产生ram写使能信号和写数据信号
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
ram_wr_data <= 8'd0;
else if(rw_cnt >= 6'd0 && rw_cnt <= 6'd31)
ram_wr_data <= ram_wr_data + 8'd1;
else
ram_wr_data <= 8'd0;
end
//读写地址信号 范围:0~31
always @(posedge clk or negedge rst_n) begin
if(rst_n == 1'b0)
ram_addr <= 5'd0;
else if(ram_addr == 5'd31)
ram_addr <= 5'd0;
else
ram_addr <= ram_addr + 1'b1;
end
endmodule
//这部分代码在设置好IP核后自动生成
`timescale 1 ps / 1 ps
// synopsys translate_on
module ram_1port (
address,
clock,
data,
rden,
wren,
q);
input [4:0] address;
input clock;
input [7:0] data;
input rden;
input wren;
output [7:0] q;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_off
`endif
tri1 clock;
tri1 rden;
`ifndef ALTERA_RESERVED_QIS
// synopsys translate_on
`endif
wire [7:0] sub_wire0;
wire [7:0] q = sub_wire0[7:0];
altsyncram altsyncram_component (
.address_a (address),
.clock0 (clock),
.data_a (data),
.rden_a (rden),
.wren_a (wren),
.q_a (sub_wire0),
.aclr0 (1'b0),
.aclr1 (1'b0),
.address_b (1'b1),
.addressstall_a (1'b0),
.addressstall_b (1'b0),
.byteena_a (1'b1),
.byteena_b (1'b1),
.clock1 (1'b1),
.clocken0 (1'b1),
.clocken1 (1'b1),
.clocken2 (1'b1),
.clocken3 (1'b1),
.data_b (1'b1),
.eccstatus (),
.q_b (),
.rden_b (1'b1),
.wren_b (1'b0));
defparam
altsyncram_component.clock_enable_input_a = "BYPASS",
altsyncram_component.clock_enable_output_a = "BYPASS",
altsyncram_component.intended_device_family = "Cyclone IV E",
altsyncram_component.lpm_hint = "ENABLE_RUNTIME_MOD=NO",
altsyncram_component.lpm_type = "altsyncram",
altsyncram_component.numwords_a = 32,
altsyncram_component.operation_mode = "SINGLE_PORT",
altsyncram_component.outdata_aclr_a = "NONE",
altsyncram_component.outdata_reg_a = "UNREGISTERED",
altsyncram_component.power_up_uninitialized = "FALSE",
altsyncram_component.read_during_write_mode_port_a = "NEW_DATA_NO_NBE_READ",
altsyncram_component.widthad_a = 5,
altsyncram_component.width_a = 8,
altsyncram_component.width_byteena_a = 1;
endmodule
`timescale 1ns/1ns //仿真的单位/仿真的精度
module ip_1port_ram_tb();
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#50
sys_rst_n = 1'b1;
end
always #20 sys_clk = ~sys_clk;
ram_1p_top u_ram_1p_top(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n)
);
endmodule
5. FIFO IP核
module fifo_rd(
input clk,
input rst_n,
//fifo
input rd_full,
input rd_empty,
output rd_re,
input [7:0] rd_data
);
reg rd_flag;
assign rd_re = (~rd_empty) & rd_flag;//防止数据溢出
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rd_flag <= 1'b0;
else if(rd_full)
rd_flag <= 1'b1;
else if(rd_empty)
rd_flag <= 1'b0;
end
endmodule
module fifo_rd(
input clk,
input rst_n,
//fifo
input rd_full,
input rd_empty,
output rd_re,
input [7:0] rd_data
);
reg rd_flag;
assign rd_re = (~rd_empty) & rd_flag;//防止数据溢出
always@(posedge clk or negedge rst_n)
begin
if(!rst_n)
rd_flag <= 1'b0;
else if(rd_full)
rd_flag <= 1'b1;
else if(rd_empty)
rd_flag <= 1'b0;
end
endmodule
module fifo_test(
input sys_clk , //时钟信号
input sys_rst_n //复位信号
);
//wire define
wire clk_50m ; //50Mhz时钟
wire clk_25m ; //25Mhz时钟
wire locked ; //时钟稳定信号
wire rst_n ; //复位信号
wire [7:0] rd_usedw; //读侧FIFO中的数据量
wire [7:0] wr_usedw; //写侧FIFO中的数据量
wire wr_full ; //写侧满信号
wire wr_empty; //写侧空信号
wire wr_req ; //写请求信号
wire [7:0] wr_data ; //写入FIFO的数据
wire rd_full ; //读侧满信号
wire rd_empty; //读侧空信号
wire rd_req ; //读请求信号
wire [7:0] rd_data ; //读出FIFO的数据
//*****************************************************
//** main code
//*****************************************************
//待时钟输出稳定后,再拉高rst_n信号
assign rst_n = sys_rst_n & locked;
//例化锁相环模块
pll u_pll_clk (
.areset (~sys_rst_n ),
.inclk0 (sys_clk ),
.c0 (clk_25m ),
.c1 (clk_50m ),
.locked (locked )
);
//例化FIFO写模块
fifo_wr u_fifo_wr(
.clk (clk_50m),
.rst_n (rst_n),
.wr_full (wr_full ),
.wr_empty (wr_empty),
.wr_re (wr_re ),
.wr_data (wr_data )
);
//例化异步FIFO模块
fifo_ip u_async_fifo (
.aclr (~rst_n ),
.data (wr_data ),
.rdclk (clk_25m ),
.rdreq (rd_re ),
.wrclk (clk_50m ),
.wrreq (wr_re ),
.q (rd_data ),
.rdempty (rd_empty ),
.rdfull (rd_full ),
.rdusedw (rd_usedw ),
.wrempty (wr_empty ),
.wrfull (wr_full ),
.wrusedw (wr_usedw )
);
//例化FIFO读模块
fifo_rd u_fifo_rd(
.clk (clk_25m),
.rst_n (rst_n),
.rd_full (rd_full ),
.rd_empty (rd_empty),
.rd_re (rd_re ),
.rd_data (rd_data )
);
endmodule
测试文件
`timescale 1ns/1ns //仿真的单位/仿真的精度
module fifo_tb();
reg sys_clk;
reg sys_rst_n;
initial begin
sys_clk = 1'b0;
sys_rst_n = 1'b0;
#100
sys_rst_n = 1'b1;
end
always #20 sys_clk = ~sys_clk;
fifo_tset u_ip_fifo(
.sys_clk (sys_clk ),
.sys_rst_n (sys_rst_n)
);
endmodule
可以看到写入和读取出的数据无差异,由于信号种类繁多,所以简单参考即可。文章来源:https://www.toymoban.com/news/detail-842391.html
免责声明:本文所引用的各种资料均用于自己学习使用,这里感谢黑金、野火和正点原子官方的资料以及各位优秀的创作者。文章来源地址https://www.toymoban.com/news/detail-842391.html
到了这里,关于学习笔记之FPGA的IP核及其应用的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!