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

     

目录

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核的调用

        实验代码

`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 RAMRAM 将在下一节为大家讲解)调用的都是 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核

        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慢一个周期以确保地址输入完整。
        实验代码
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

        可以看到这里设置address和data均为逐次加1,但时序中会产生一个周期的滞后。

5. FIFO IP核

        FIFO已经是我们的“老朋友”了,笔者在照相机实验的部分已经简单介绍过它的特性,简单来说就是一个对数据存储具有先进先出特性的缓存器。一般情况下,FIFO分为三种:同步、异步和异步混合位宽(异步+读写位宽不同)。
        本次的实验参考正点原子的异步FIFO IP核实验,正好可以联系前面学过的PLL核来产生写时钟和读时钟来对FIFO进行数据读写,基本框架如下图所示。
实验代码
        
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
写入时钟
读取时钟

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

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

  • 8
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值