当前位置:   article > 正文

学习笔记之FPGA的IP核及其应用_fpgaip核

fpgaip核

     

目录

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。

分类

  1. 处理器IP核:包括处理器核心(如ARM Cortex等)及其相关外设,用于实现通用计算功能。

  2. 通信IP核:用于实现各种通信协议和接口标准,如以太网MAC、USB控制器等。

  3. 存储IP核:包括各种存储控制器、缓存控制器等,用于实现数据存储和管理。

  4. 图像处理IP核:用于实现图像处理算法,如图像滤波器、图像压缩器等。

  5. 数字信号处理IP核:用于实现数字信号处理算法,如滤波器、FFT加速器等。

2. PLL锁相环 

2.1 PLL的基础知识

       PLL(Phase Locked Loop,即锁相环)是最常用的 IP 核之一,其性能强大,可以对输入到 FPGA 的时钟信号进行任意分频、倍频、相位调整、占空比调整,从而输出一个期望时钟,实际上,即使不想改变输入到 FPGA 时钟的任何参数,也常常会使用 PLL,因为经过PLL 后的时钟在抖动(Jitter)方面的性能更好一些。Altera 中的 PLL 是模拟锁相环,和数字锁相环不同的是模拟锁相环的优点是输出的稳定度高、相位连续可调、延时连续可调;缺点是当温度过高或者电磁辐射过强时会失锁(普通环境下不考虑该问题)。

PLL的功能:

  1. 时钟倍频器:可以将输入时钟信号倍频到更高的频率。

  2. 时钟分频器:可以将输入时钟信号分频得到更低的频率。

  3. 时钟延迟控制:可以对时钟信号进行精确的相位调整。

  4. 时钟频率合成:可以根据需要合成不同频率的时钟信号。

PLL的组成部分:

  1. 相频比较器:用于比较输入时钟和反馈时钟之间的相位差并生成控制信号。

  2. VCO(Voltage-Controlled Oscillator,电压控制振荡器):根据相频比较器的控制信号调整其输出频率。

  3. 分频器:用于将VCO输出的时钟信号分频得到所需的频率。

  4. 反馈路径:将输出时钟信号反馈回相频比较器,使得输出时钟与输入时钟保持稳定的相位关系。

使用场景:

  • 在FPGA中,PLL常用于生成各种需要精确时钟控制的信号,如高速接口通信、数据采样、时序逻辑控制等。

  • PLL还可以用于减小时钟抖动、降低时钟抖动对系统带来的影响,提高系统的稳定性和可靠性。

        如图所示为 PLL大体的一个结构模型示意图,我们可以看出这是一个闭环反馈系统,其工作原理和过程主要如下:

  1. 首先需要参考时钟(ref_clk)通过鉴频(FD)鉴相器(PD)和需要比较的时钟频率进行比较,我们以频率调整为例,如果参考时钟频率等于需要比较的时钟频率则鉴频鉴相器输出为 0,如果参考时钟频率大于需要比较的时钟频率则鉴频鉴相器输出一个变大的成正比的值,如果参考时钟频率小于需要比较的时钟频率则鉴频鉴相器输出一个变小的正比的值
  2. 鉴频鉴相器的输出连接到环路滤波器(LF)上,用于控制噪声的带宽,滤掉高频 噪声,使之稳定在一个值,起到将带有噪声的波形变平滑的作用。如果鉴频鉴相器之前的波形抖动比较大,经过环路滤波器后抖动就会变小,趋近于信号的平均值。
  3. 经过环路滤波器的输出连接到压控振荡器(VCO)上,环路滤波器输出的电压可以控制 VCO 输出频率的大小,环路滤波器输出的电压越大 VCO 输出的频率越高,然后将这个频率信号连接到鉴频鉴相器作为需要比较的频率。如果 ref_clk 参考时钟输入的频率和需要比较的时钟频率不相等,该系统最终实现的就是让它们逐渐相等并稳定下来。如果 ref_clk 参考时钟的频率是 50MHz,经过整个闭环反馈系统后,锁相环对外输出的时钟频率 pll_out 也是 50MHz

        倍频是在 VCO 后直接加一级分频器,我们知道 ref_clk 参考时钟输入的频率和需要比较的时钟频率经过闭环反馈系统后最终会保持频率相等,而在需要比较的时钟之前加入分频器,就会使进入分频器之前的信号频率为需要 比较的时钟频率的倍数,VCO 后输出的 pll_out 信号频率就是 ref_clk 参考时钟倍频后的结果

        分频是在 ref_clk 参考时钟后加一级分频器,这样需要比较的时钟频率就始终和 ref_clk 参考时钟分频后的频率相等,在 VCO 后输出的 pll_out 信号就是 ref_clk 参考时钟分频后的结果。


    以上部分来自于野火的官方资料,笔者认为写的很详细了。

2.2 PLL核的调用

        实验代码

  1. `timescale 1ns / 1ps
  2. module pll_test(
  3. input clk,
  4. input rst_n,
  5. output clkout1, //pll clock output
  6. output clkout2, //pll clock output
  7. output clkout3, //pll clock output
  8. output clkout4 //pll clock output
  9. );
  10. wire locked;
  11. pll pll_inst
  12. (
  13. // Clock in ports
  14. .inclk0(clk), // IN 50Mhz
  15. // Clock out ports
  16. .c0(clkout1), // OUT 25Mhz
  17. .c1(clkout2), // OUT 50Mhz
  18. .c2(clkout3), // OUT 75Mhz
  19. .c3(clkout4), // OUT 100Mhz
  20. // Status and control signals
  21. .areset(~rst_n), // IN
  22. .locked(locked) //The signal of PLL normal operation
  23. ); // OUT
  24. endmodule

        测试文件           

  1. `timescale 1ns / 1ps //仿真单位/仿真精度
  2. module pll_test_tb();
  3. //parameter define
  4. parameter CLK_PERIOD = 20; //时钟周期 20ns
  5. //reg define
  6. reg sys_clk;
  7. reg sys_rst_n;
  8. //wire define
  9. wire clk_100m;
  10. wire clk_100m_180deg;
  11. wire clk_50m;
  12. wire clk_25m;
  13. //信号初始化
  14. initial begin
  15. sys_clk = 1'b0;
  16. sys_rst_n = 1'b0;
  17. #200
  18. sys_rst_n = 1'b1;
  19. end
  20. //产生时钟
  21. always #(CLK_PERIOD/2) sys_clk = ~sys_clk;
  22. pll_test u_pll_test(
  23. .clk (sys_clk ),
  24. .rst_n (sys_rst_n ),
  25. .clkout1 (clk_25m ), //pll clock output
  26. .clkout2 (clk_50m ), //pll clock output
  27. .clkout3 (clk_75m ), //pll clock output
  28. .clkout4 (clk_100m ) //pll clock output
  29. );
  30. endmodule

        仿真时序 

3. ROM IP核

        ROM 是只读存储器(Read-Only Memory)的简称,是一种只能读出事先所存数据的固态半导体存储器。其特性是一旦储存资料就无法再将之改变或删除,且资料不会因为电源关闭而消失。而事实上在 FPGA 中通过 IP 核生成的 ROM RAMRAM 将在下一节为大家讲解)调用的都是 FPGA 内部的 RAM 资源,掉电内容都会丢失(这也很容易解释,FPGA 芯片内部本来就没有掉电非易失存储器单元)。用 IP 核生成的 ROM 模块只是提前添加了数据文件(.mif .hex 格式),在 FPGA 运行时通过数据文件给 ROM 模块初始化,才使得 ROM 模块像个“真正”的掉电非易失存储器;也正是这个原因,ROM 模块的内容必须提前在数据文件中写死,无法在电路中修改。

        Altera 推出的 ROM IP 核分为两种类型:单端口 ROM 和双端口 ROM。对于单端口ROM 提供一个读地址端口和一个读数据端口,只能进行读操作;双端口 ROM与单端口ROM 类似,区别是其提供两个读地址端口和两个读数据端口,基本上可以看做两个单口RAM 拼接而成。这里仅介绍单端ROM。

        实验代码

  1. module rom_wr(
  2. input clk,
  3. input rst_n,
  4. //rom address
  5. output reg [7:0] addr
  6. );
  7. always@(posedge clk or negedge rst_n)
  8. begin
  9. if(!rst_n)
  10. begin
  11. addr <= 1'b0;
  12. end
  13. else
  14. begin
  15. if(addr == 8'd256 - 1'b1)
  16. addr <= 0;
  17. else
  18. addr <= addr + 1'b1;
  19. end
  20. end
  21. endmodule
  1. module rom_wr(
  2. input clk,
  3. input rst_n,
  4. //rom address
  5. output reg [7:0] addr
  6. );
  7. always@(posedge clk or negedge rst_n)
  8. begin
  9. if(!rst_n)
  10. begin
  11. addr <= 1'b0;
  12. end
  13. else
  14. begin
  15. if(addr == 8'd256 - 1'b1)
  16. addr <= 0;
  17. else
  18. addr <= addr + 1'b1;
  19. end
  20. end
  21. endmodule

        测试文件

  1. `timescale 1ns/1ns //仿真的单位/仿真的精度
  2. module ip_1port_rom_tb();
  3. reg sys_clk;
  4. reg sys_rst_n;
  5. wire [7:0] q;
  6. initial begin
  7. sys_clk = 1'b0;
  8. sys_rst_n = 1'b0;
  9. #100
  10. sys_rst_n = 1'b1;
  11. end
  12. always #20 sys_clk = ~sys_clk;
  13. rom_ip1 u_rom_ip1(
  14. .clk (sys_clk),
  15. .rst_n (sys_rst_n),
  16. //rom data
  17. .q (q)
  18. );
  19. endmodule

4. RAM IP核

        RAM 是随机存取存储器( Random Access Memory )的简称,是一个易失性存储器。 RAM 工作时可以随时从任何一个指定的地址写入或读出数据,同时我们还能修改其存储的数据,即写入新的数据,这是 ROM 所并不具备的功能。在 FPGA 中这也是其与 ROM 的最大区别。ROM 是只读存储器,而 RAM 是可写可读存储器,在我们 FPGA 中使用这两个存储器主要也是要区分这一点,因为这两个存储器使用的都是我们 FPGA 内部的 RAM 资源,不同的是 ROM 是只用到了 RAM 资源的读数据端口。 同样,Altera 推出的 RAM IP 核也分为两种类型:单端口 RAM 和双端口 RAM 。这里也是仅介绍单端RAM。

        以下是单端口ROM的接口和时序图:

         其中address(地址输入引脚)、data(数据)、使能(wren/rden)是必需要设计的,而字节控制位(byteena)则根据实际情况调整即可。在时序中,data会比address慢一个周期以确保地址输入完整。
        实验代码
  1. module ram_1p_top(
  2. input sys_clk , //系统时钟
  3. input sys_rst_n //系统复位,低电平有效
  4. );
  5. //wire define
  6. wire ram_wr_en ; //ram写使能
  7. wire ram_rd_en ; //ram读使能
  8. wire [4:0] ram_addr ; //ram读写地址
  9. wire [7:0] ram_wr_data ; //ram写数据
  10. wire [7:0] ram_rd_data ; //ram读数据
  11. //*****************************************************
  12. //** main code
  13. //*****************************************************
  14. //ram读写模块
  15. ram_rw u_ram_rw(
  16. .clk (sys_clk),
  17. .rst_n (sys_rst_n),
  18. .ram_wr_en (ram_wr_en ),
  19. .ram_rd_en (ram_rd_en ),
  20. .ram_addr (ram_addr ),
  21. .ram_wr_data (ram_wr_data),
  22. .ram_rd_data (ram_rd_data)
  23. );
  24. //ram ip核
  25. ram_1port u_ram_1port(
  26. .address (ram_addr),
  27. .clock (sys_clk),
  28. .data (ram_wr_data),
  29. .rden (ram_rd_en),
  30. .wren (ram_wr_en),
  31. .q (ram_rd_data)
  32. );
  33. endmodule
  1. module ram_rw(
  2. input clk , //时钟信号
  3. input rst_n , //复位信号,低电平有效
  4. output ram_wr_en , //ram写使能
  5. output ram_rd_en , //ram读使能
  6. output reg [4:0] ram_addr , //ram读写地址
  7. output reg [7:0] ram_wr_data, //ram写数据
  8. input [7:0] ram_rd_data //ram读数据
  9. );
  10. //reg define
  11. reg [5:0] rw_cnt ; //读写控制计数器
  12. //*****************************************************
  13. //** main code
  14. //*****************************************************
  15. //rw_cnt计数范围在0~31,ram_wr_en为高电平;32~63时,ram_wr_en为低电平
  16. assign ram_wr_en = ((rw_cnt >= 6'd0) && (rw_cnt <= 6'd31) && rst_n) ? 1'b1 : 1'b0;
  17. //rw_cnt计数范围在32~63,ram_rd_en为高电平;0~31时,ram_rd_en为低电平
  18. assign ram_rd_en = ((rw_cnt >= 6'd32) && (rw_cnt <= 6'd63)) ? 1'b1 : 1'b0;
  19. //读写控制计数器,计数器范围0~63
  20. always @(posedge clk or negedge rst_n) begin
  21. if(rst_n == 1'b0)
  22. rw_cnt <= 6'd0;
  23. else if(rw_cnt == 6'd63)
  24. rw_cnt <= 6'd0;
  25. else
  26. rw_cnt <= rw_cnt + 6'd1;
  27. end
  28. //读写控制器计数范围:0~31 产生ram写使能信号和写数据信号
  29. always @(posedge clk or negedge rst_n) begin
  30. if(rst_n == 1'b0)
  31. ram_wr_data <= 8'd0;
  32. else if(rw_cnt >= 6'd0 && rw_cnt <= 6'd31)
  33. ram_wr_data <= ram_wr_data + 8'd1;
  34. else
  35. ram_wr_data <= 8'd0;
  36. end
  37. //读写地址信号 范围:0~31
  38. always @(posedge clk or negedge rst_n) begin
  39. if(rst_n == 1'b0)
  40. ram_addr <= 5'd0;
  41. else if(ram_addr == 5'd31)
  42. ram_addr <= 5'd0;
  43. else
  44. ram_addr <= ram_addr + 1'b1;
  45. end
  46. endmodule
  1. //这部分代码在设置好IP核后自动生成
  2. `timescale 1 ps / 1 ps
  3. // synopsys translate_on
  4. module ram_1port (
  5. address,
  6. clock,
  7. data,
  8. rden,
  9. wren,
  10. q);
  11. input [4:0] address;
  12. input clock;
  13. input [7:0] data;
  14. input rden;
  15. input wren;
  16. output [7:0] q;
  17. `ifndef ALTERA_RESERVED_QIS
  18. // synopsys translate_off
  19. `endif
  20. tri1 clock;
  21. tri1 rden;
  22. `ifndef ALTERA_RESERVED_QIS
  23. // synopsys translate_on
  24. `endif
  25. wire [7:0] sub_wire0;
  26. wire [7:0] q = sub_wire0[7:0];
  27. altsyncram altsyncram_component (
  28. .address_a (address),
  29. .clock0 (clock),
  30. .data_a (data),
  31. .rden_a (rden),
  32. .wren_a (wren),
  33. .q_a (sub_wire0),
  34. .aclr0 (1'b0),
  35. .aclr1 (1'b0),
  36. .address_b (1'b1),
  37. .addressstall_a (1'b0),
  38. .addressstall_b (1'b0),
  39. .byteena_a (1'b1),
  40. .byteena_b (1'b1),
  41. .clock1 (1'b1),
  42. .clocken0 (1'b1),
  43. .clocken1 (1'b1),
  44. .clocken2 (1'b1),
  45. .clocken3 (1'b1),
  46. .data_b (1'b1),
  47. .eccstatus (),
  48. .q_b (),
  49. .rden_b (1'b1),
  50. .wren_b (1'b0));
  51. defparam
  52. altsyncram_component.clock_enable_input_a = "BYPASS",
  53. altsyncram_component.clock_enable_output_a = "BYPASS",
  54. altsyncram_component.intended_device_family = "Cyclone IV E",
  55. altsyncram_component.lpm_hint = "ENABLE_RUNTIME_MOD=NO",
  56. altsyncram_component.lpm_type = "altsyncram",
  57. altsyncram_component.numwords_a = 32,
  58. altsyncram_component.operation_mode = "SINGLE_PORT",
  59. altsyncram_component.outdata_aclr_a = "NONE",
  60. altsyncram_component.outdata_reg_a = "UNREGISTERED",
  61. altsyncram_component.power_up_uninitialized = "FALSE",
  62. altsyncram_component.read_during_write_mode_port_a = "NEW_DATA_NO_NBE_READ",
  63. altsyncram_component.widthad_a = 5,
  64. altsyncram_component.width_a = 8,
  65. altsyncram_component.width_byteena_a = 1;
  66. endmodule
        仿真文件
  1. `timescale 1ns/1ns //仿真的单位/仿真的精度
  2. module ip_1port_ram_tb();
  3. reg sys_clk;
  4. reg sys_rst_n;
  5. initial begin
  6. sys_clk = 1'b0;
  7. sys_rst_n = 1'b0;
  8. #50
  9. sys_rst_n = 1'b1;
  10. end
  11. always #20 sys_clk = ~sys_clk;
  12. ram_1p_top u_ram_1p_top(
  13. .sys_clk (sys_clk ),
  14. .sys_rst_n (sys_rst_n)
  15. );
  16. endmodule
        可以看到这里设置address和data均为逐次加1,但时序中会产生一个周期的滞后。

5. FIFO IP核

        FIFO已经是我们的“老朋友”了,笔者在照相机实验的部分已经简单介绍过它的特性,简单来说就是一个对数据存储具有先进先出特性的缓存器。一般情况下,FIFO分为三种:同步、异步和异步混合位宽(异步+读写位宽不同)。
        本次的实验参考正点原子的异步FIFO IP核实验,正好可以联系前面学过的PLL核来产生写时钟和读时钟来对FIFO进行数据读写,基本框架如下图所示。
实验代码
        
  1. module fifo_rd(
  2. input clk,
  3. input rst_n,
  4. //fifo
  5. input rd_full,
  6. input rd_empty,
  7. output rd_re,
  8. input [7:0] rd_data
  9. );
  10. reg rd_flag;
  11. assign rd_re = (~rd_empty) & rd_flag;//防止数据溢出
  12. always@(posedge clk or negedge rst_n)
  13. begin
  14. if(!rst_n)
  15. rd_flag <= 1'b0;
  16. else if(rd_full)
  17. rd_flag <= 1'b1;
  18. else if(rd_empty)
  19. rd_flag <= 1'b0;
  20. end
  21. endmodule
  1. module fifo_rd(
  2. input clk,
  3. input rst_n,
  4. //fifo
  5. input rd_full,
  6. input rd_empty,
  7. output rd_re,
  8. input [7:0] rd_data
  9. );
  10. reg rd_flag;
  11. assign rd_re = (~rd_empty) & rd_flag;//防止数据溢出
  12. always@(posedge clk or negedge rst_n)
  13. begin
  14. if(!rst_n)
  15. rd_flag <= 1'b0;
  16. else if(rd_full)
  17. rd_flag <= 1'b1;
  18. else if(rd_empty)
  19. rd_flag <= 1'b0;
  20. end
  21. endmodule
  1. module fifo_test(
  2. input sys_clk , //时钟信号
  3. input sys_rst_n //复位信号
  4. );
  5. //wire define
  6. wire clk_50m ; //50Mhz时钟
  7. wire clk_25m ; //25Mhz时钟
  8. wire locked ; //时钟稳定信号
  9. wire rst_n ; //复位信号
  10. wire [7:0] rd_usedw; //读侧FIFO中的数据量
  11. wire [7:0] wr_usedw; //写侧FIFO中的数据量
  12. wire wr_full ; //写侧满信号
  13. wire wr_empty; //写侧空信号
  14. wire wr_req ; //写请求信号
  15. wire [7:0] wr_data ; //写入FIFO的数据
  16. wire rd_full ; //读侧满信号
  17. wire rd_empty; //读侧空信号
  18. wire rd_req ; //读请求信号
  19. wire [7:0] rd_data ; //读出FIFO的数据
  20. //*****************************************************
  21. //** main code
  22. //*****************************************************
  23. //待时钟输出稳定后,再拉高rst_n信号
  24. assign rst_n = sys_rst_n & locked;
  25. //例化锁相环模块
  26. pll u_pll_clk (
  27. .areset (~sys_rst_n ),
  28. .inclk0 (sys_clk ),
  29. .c0 (clk_25m ),
  30. .c1 (clk_50m ),
  31. .locked (locked )
  32. );
  33. //例化FIFO写模块
  34. fifo_wr u_fifo_wr(
  35. .clk (clk_50m),
  36. .rst_n (rst_n),
  37. .wr_full (wr_full ),
  38. .wr_empty (wr_empty),
  39. .wr_re (wr_re ),
  40. .wr_data (wr_data )
  41. );
  42. //例化异步FIFO模块
  43. fifo_ip u_async_fifo (
  44. .aclr (~rst_n ),
  45. .data (wr_data ),
  46. .rdclk (clk_25m ),
  47. .rdreq (rd_re ),
  48. .wrclk (clk_50m ),
  49. .wrreq (wr_re ),
  50. .q (rd_data ),
  51. .rdempty (rd_empty ),
  52. .rdfull (rd_full ),
  53. .rdusedw (rd_usedw ),
  54. .wrempty (wr_empty ),
  55. .wrfull (wr_full ),
  56. .wrusedw (wr_usedw )
  57. );
  58. //例化FIFO读模块
  59. fifo_rd u_fifo_rd(
  60. .clk (clk_25m),
  61. .rst_n (rst_n),
  62. .rd_full (rd_full ),
  63. .rd_empty (rd_empty),
  64. .rd_re (rd_re ),
  65. .rd_data (rd_data )
  66. );
  67. endmodule

测试文件

  1. `timescale 1ns/1ns //仿真的单位/仿真的精度
  2. module fifo_tb();
  3. reg sys_clk;
  4. reg sys_rst_n;
  5. initial begin
  6. sys_clk = 1'b0;
  7. sys_rst_n = 1'b0;
  8. #100
  9. sys_rst_n = 1'b1;
  10. end
  11. always #20 sys_clk = ~sys_clk;
  12. fifo_tset u_ip_fifo(
  13. .sys_clk (sys_clk ),
  14. .sys_rst_n (sys_rst_n)
  15. );
  16. endmodule
写入时钟
读取时钟

        可以看到写入和读取出的数据无差异,由于信号种类繁多,所以简单参考即可。

免责声明:本文所引用的各种资料均用于自己学习使用,这里感谢黑金、野火和正点原子官方的资料以及各位优秀的创作者。

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/空白诗007/article/detail/947965
推荐阅读
相关标签
  

闽ICP备14008679号