赞
踩
目录
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加速器等。
PLL(Phase Locked Loop,即锁相环)是最常用的 IP 核之一,其性能强大,可以对输入到 FPGA 的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟,实际上,即使不想改变输入到 FPGA 时钟的任何参数,也常常会使用 PLL,因为经过PLL 后的时钟在抖动(Jitter)方面的性能更好一些。Altera 中的 PLL 是模拟锁相环,和数字锁相环不同的是模拟锁相环的优点是输出的稳定度高、相位连续可调、延时连续可调;缺点是当温度过高或者电磁辐射过强时会失锁(普通环境下不考虑该问题)。
PLL的功能:
时钟倍频器:可以将输入时钟信号倍频到更高的频率。
时钟分频器:可以将输入时钟信号分频得到更低的频率。
时钟延迟控制:可以对时钟信号进行精确的相位调整。
时钟频率合成:可以根据需要合成不同频率的时钟信号。
PLL的组成部分:
相频比较器:用于比较输入时钟和反馈时钟之间的相位差并生成控制信号。
VCO(Voltage-Controlled Oscillator,电压控制振荡器):根据相频比较器的控制信号调整其输出频率。
分频器:用于将VCO输出的时钟信号分频得到所需的频率。
反馈路径:将输出时钟信号反馈回相频比较器,使得输出时钟与输入时钟保持稳定的相位关系。
使用场景:
在FPGA中,PLL常用于生成各种需要精确时钟控制的信号,如高速接口通信、数据采样、时序逻辑控制等。
PLL还可以用于减小时钟抖动、降低时钟抖动对系统带来的影响,提高系统的稳定性和可靠性。
如图所示为 PLL大体的一个结构模型示意图,我们可以看出这是一个闭环反馈系统,其工作原理和过程主要如下:
倍频是在 VCO 后直接加一级分频器,我们知道 ref_clk 参考时钟输入的频率和需要比较的时钟频率经过闭环反馈系统后最终会保持频率相等,而在需要比较的时钟之前加入分频器,就会使进入分频器之前的信号频率为需要 比较的时钟频率的倍数,VCO 后输出的 pll_out 信号频率就是 ref_clk 参考时钟倍频后的结果。
以上部分来自于野火的官方资料,笔者认为写的很详细了。
实验代码
- `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
仿真时序
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
以下是单端口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
- 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
可以看到写入和读取出的数据无差异,由于信号种类繁多,所以简单参考即可。
免责声明:本文所引用的各种资料均用于自己学习使用,这里感谢黑金、野火和正点原子官方的资料以及各位优秀的创作者。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。