vivado常用IP调用配置——PLL、ROM、RAM

我对vivado一无所知,如果有更好的操作,希望评论我

1. PLL锁相环

1.1. PLL锁相环介绍

PPL(Phase Locked Loop),能够对输入的周期信号进行任意分频、倍频、相位调整、占空比调整,从而输出期望时钟。也可以对不进行分频倍频的时钟信号进行优化,使输出的时钟信号比输入的时钟信号在抖动方面性能更好。

模拟锁相环:输出的信号稳定性更高,相位连续可调,延时连续可调。温度过高或电磁辐射过强会导致失锁。

  • 基本工作原理:反馈系统
    ref_clk:参考时钟
    FD/PD:鉴频鉴相器。比较参考时钟和对比时钟的大小,若两者频率相同,输出0;若参考时钟大,输出一个变大的、成正比的值;若参考时钟小。输出一个变小的、成正比的值
    LF:环路滤波器。输出不同电压幅值的信号
    VCO:压控振荡器。电压越高,输出信号的频率越大
    pll_out:输出时钟
    在这里插入图片描述

    举例:参考时钟为50MHz,VCO基准震荡频率为10HMz
    两路信号传入FD/PD鉴频鉴相器,参考频率>对比频率,输出一个变大的、成正比的值,LF环路滤波器接收到后,输出电压变大,VCO接收到后将输出的频率变大,作为反馈信号又重新送给鉴频鉴相器。经过多次调整,最终对比时钟会等于参考时钟

  • PLL倍频
    在这里插入图片描述
    DIV为分频器,分频参数可调节
    举例:ref_clk为50MHz,DIV分频器送给FD/PD鉴频鉴相器的时钟也会是50MHz,反馈调节
    若DIV实现2分频,输出为50MHz,则VCO输出100MHz,pll_out = 100MHz
    若DIV实现3分频,输出为50MHz,则VCO输出150MHz,pll_out = 150MHz
    (疑问:都已经有分频器模块了,为啥还要pll)

  • PLL分频
    在这里插入图片描述
    先将输入的参考时钟进行分频,
    举例:若ref_clk为50MHz,分频器进行5分频,则VCO输出10MHz

1.2. IP调用

  1. 新建工程后,打开IP Catalog
    在这里插入图片描述

  2. 搜索找到Clocking Wizard,双击开始进行配置
    在这里插入图片描述

  3. 配置IP核

    输出时钟配置
    在这里插入图片描述

  4. 配置一直ok,完了继续generate
    在这里插入图片描述
    在这里插入图片描述

  5. 之后就能看到文件目录刷新,点击clk_wiz_o前面的展开
    在这里插入图片描述

  6. 新建源文件,实例化IP
    IP Sources选项卡中,打开.v文件,可以看到模块的端口信息,参考这个端口实例化
    在这里插入图片描述

    在这里插入图片描述

  7. 生成RTL
    在这里插入图片描述

1.3. 仿真

	// 生成50MHz的系统时钟
    parameter   clk_period_50   =   20;
    always #(clk_period_50 / 2) clk_in1 = ~clk_in1;

在这里插入图片描述

后面有规律的生成倍频和分频时钟可以理解,就是不太理解复为无效后隔一段时钟才能生成有效的输出时钟,locked信号可以作为信号稳定的标志

2. ROM

2.1 IP调用

  1. Vivado选择IP核,开始配置
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

  2. 选择添加.coe文件,因为没有写好的.coe文件,点击Edit,新建并存放在默认路径下
    ROM实现的是非易失性存储器,需要对其进行初始化
    在这里插入图片描述
    这里就可以编辑.coe文件,也可以先保存
    在这里插入图片描述
    可以看到文件路径是红色的,这就代表不可用
    在这里插入图片描述
    之后用编辑器打开.coe文件,写入一下信息
    (如何快速填充设置内容我还不太会,可以用C或者其他语言循环打印,然后粘贴过来)
    radix代表进制格式
    vector代表数据

    memory_initialization_radix		=	16	;
    memory_initialization_vector	=
    									00, 01, 02, 03, 04, 05, 06, 07,
    									08, 09, 0a, 0b, 0c, 0d, 0e, 0f,
    									10,	11, 12, 13,	14, 15, 16, 17,
    									18, 19, 1a, 1b, 1c, 1d, 1e, 1f	;
    

    重新保存.coe文件,可以看到上图文件路径变成黑色,就证明好了
    可以选中“Fill …”,将剩余没有指定数据的内存位置用“FF”填充,然后OK
    在这里插入图片描述
    可以看到文件目录更新,IP添加成了,系数文件也添加成了
    在这里插入图片描述

  3. 实例化IP
    打开IP Sources中的.v文件,查看端口信息
    (这里是可以看到用来实例化的端口名称,端口比较少,见名知意,如果是端口比较多的可能就不太清除怎么连线了,不知道那里可以查看到端口解释信息)
    在这里插入图片描述
    或者还可以打开RAM_IP\RAM_IP.ip_user_files\ip\ram_8x256_one这个路径里面的.veo文件,直接可以看到人家弄好的实例化,自己改一下实例化名就好了(这个看起来好操作一点)
    在这里插入图片描述

在这里插入图片描述
5. 生成RTL图
在这里插入图片描述

2.2. 仿真

分为两部分,前一部分地址由随机数种子产生,后一部分为顺序读取


module sim_rom(

    );
    
    reg             clk     ;
    reg             oe      ;
    reg     [7:0]   addr    ;
    
    wire    [7:0]   data    ;
    
    rom_useIP   rom_useIP0 (
            .clk    (clk)   ,
            .oe     (oe)    ,
            .addr   (addr)  ,
            
            .data   (data)
        );
        
    parameter   clk_period_50   =   20;
    always # (clk_period_50 / 2)    clk = ~clk;
    
    initial begin
        clk =   0;
        oe  =   0;
        #100
        read_rom;   #500
        oe  =   1;
        #100
        read_rom;   #500
        oe  =   0;
        #100
        oe  =   1;
        addr    =   0;
        read_rom_2;
    end
    
    task    read_rom;
        begin
            repeat (200) begin
                #20
                addr    =   {$random} % 256;
            end
        end
     endtask
     
     task    read_rom_2;
        begin
            repeat (200) begin
                #20
                addr    =   addr + 1'b1;
            end
        end
     endtask
     
endmodule

在这里插入图片描述

局部放大可以看到输入地址和输出数据中间差了两个两个节拍,这是因为默认有了寄存器
在这里插入图片描述
在这里插入图片描述

差不多是这个意思,可以试想一下,如果选择不添加时钟使能信号,输入地址和输出数据中间是不是差一个节拍?
我对vivado里面IP的调用一无所知
在这里插入图片描述
在Quartus里的IP是可以设置这个输出寄存器的有无(visio图画的是Quartus),我对veivadou一无所知,不太会设置

看了官方文档,说是对数据进行了流量控制,也就是打拍(底下的截图是Vivado文档说的,同样适用于单双端口ROM、RAM)
在这里插入图片描述在这里插入图片描述

3. RAM

3.1. RAM介绍

  • RAM是随机存取存储器(Random Access Memory),是一个 易失性存储器;可以对任意一个指定的地址写入或读出数据。这是ROM不具备的功能,它只能提前准备好数据,以后只能读出。
  • 双端口RAM,可能会存在在同一时间,两个端口对同一地址同时读写,就会引发冲突。简单双端口(Single Dual Port RAM),一个端口只负责读,另一个端口只负责写;真正双端口(True Dual Port RAM),两个端口都可以进行数据读写。
  • 单端口RAM(Single Port RAM),读写数据都用一个地址端口,读写操作不能同时进行。
  • 因为RAM实现的是易失性存储器,能实现读写操作,那么这里就会涉及到几种冲突:
    1. 读写冲突:影响有效数据输出
    2. 写写冲突:影响内存内容

3.2. IP调用

  1. 选择IP Core,和前面的ROM用的时同一个
    在这里插入图片描述

  2. 配置IP核
    在这里插入图片描述
    在这里插入图片描述
    补充:操作模式有一下三种,差别就在dout端口的输出上
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述
    关于可选择的寄存器:Primitives Output Register(原语输出寄存器,默认勾选)、Core Output Register(核输出寄存器)。两者都会增加最终输出的延迟,多打拍
    在这里插入图片描述
    在这里插入图片描述
    点击ok,配置,然后就能看见文件目录更新,IP添加成功
    这次给系数文件里面写这0-15的循环数据

    memory_initialization_radix		=	10	;
    memory_initialization_vector	=									
    									  0,   1,   2,   3,   4,   5,   6,   7,   8,   9,
                                         10,  11,  12,  13,  14,  15,   0,   1,   2,   3,
                                          4,   5,   6,   7,   8,  9,   10,  11,  12,  13,
                                         14,  15,   1,   2,   3,   4,   5,   6,   7,   8,
                                         ... ...   ;
    
    

3.3. 仿真

	// 根据输入的读写信号,产生控制ramIP核的一些控制信息
	module ram_ctrl(
		input	wire			sys_clk		,
		input	wire			sys_rst_n	,
		input	wire			wr_flag		,
		input	wire			rd_flag		,
		
		output	reg				wr_en		,
		output	reg 	[7:0]	addr		,
		output	wire	[7:0]	wr_data		,
		output	reg				rd_en
	    );
	    
	    parameter	CNT_MAX	=	24'd9_999_999;
	    
		reg		[23:0]	cnt_200ms	;	// 让变化的地址保持200ms,也就是200_000_000ns
	    								//输入时钟为50MHz(每一个周期20ns),可以确定计数器的计数范围为0~9_999_999
	    
	// cnt_200ms:读出时间间隔
		always @ (posedge sys_clk or negedge sys_rst_n)
			if (!sys_rst_n)
				cnt_200ms	<=	24'd0;		// 不工作
			else	if ((cnt_200ms == CNT_MAX) || (wr_flag == 1'b1) || (rd_flag == 1'b1))
				cnt_200ms	<=	24'd0;		// 计数器清零
	    	else	if (rd_en == 1'b1)
	    		cnt_200ms	<=	cnt_200ms + 1'b1;
	    	else
	    		cnt_200ms	<=	cnt_200ms;
	
	// wr_en:写使能信号
		always @ (posedge sys_clk or negedge sys_rst_n)
			if (!sys_rst_n)
				wr_en	<=	1'b0;
			else	if (addr == 8'd255)
				wr_en	<=	1'b0;
			else	if (wr_flag == 1'b1)
				wr_en	<=	1'b1;
	
	// addr:读写地址
		always @ (posedge sys_clk or negedge sys_rst_n)
			if (!sys_rst_n)
				addr	<=	8'd0;	// 不工作
			else	if (((addr == 8'd255) && (wr_en == 1'b1))			// 进行到写操作的最后一步
						|| ((addr == 8'd255) && (cnt_200ms == CNT_MAX))	// 读操作读到最后一个地址
						|| (wr_flag == 1'b1)							// 读操作过程中按下写操作按键
						|| (rd_flag == 1'b1))							// 读操作过程中按下大操作按键
				addr	<=	8'd0;	// 清零
			else	if ((wr_en == 1'b1) || ((rd_en == 1'b1) && (cnt_200ms == CNT_MAX)))
				addr	<=	addr + 1'b1;
			else
				addr	<=	addr;
	
	// wr_data:写数据,写操作过程中一直等于地址
		assign	wr_data	=	(wr_en == 1'b1) ? addr : 8'd0;
	
	// rd_en: 读使能信号
		always @ (posedge sys_clk or negedge sys_rst_n)
			if (!sys_rst_n)
				rd_en	<=	1'b0;
			else	if (wr_flag == 1'b1)
				rd_en	<=	1'b0;
			else	if (rd_flag == 1'b1)
				rd_en	<=	1'b1;
			else
				rd_en	<=	rd_en;
	
	endmodule

然后对ram_ctrl模块进行仿真,在sim文件中需要实例化RAM-IP,将ctrl模块生成的控制信号送给IP核,将IP核的读出信息用连线引出以便观察

module sim_ram ( );
	reg				sys_clk		;
	reg				sys_rst_n	;
	reg				wr_flag		;
	reg				rd_flag		;
	
	wire			wr_en		;
	wire	[7:0]	addr		;
	wire	[7:0]	wr_data		;
	wire			rd_en		;
	wire   [7:0]   data_out    ;

	ram_ctrl	ram_ctrl_inst (
			.sys_clk	(sys_clk	),
			.sys_rst_n	(sys_rst_n	),
			.wr_flag	(wr_flag	),
			.rd_flag	(rd_flag	),
			
			.wr_en		(wr_en		),
			.addr		(addr		),
			.wr_data	(wr_data	),
			.rd_en		(rd_en		)
		);
	// 为了能实现读写数据,这里还需要例化RMA-IP核
	ram_8x256_one ram_8x256_one_name (
			.clka	(sys_clk),    	// 时钟信号
			.ena	(sys_rst_n),   // 使能信号
			.wea	(wr_en),      		// 读写使能信号
			.addra	(addr),  		// 读写地址
			.dina	(wr_data),    	// 写入数据
			
			.douta	(data_out) 		// 读出数据
		);
	
	// 参数重定义
	defparam   ram_ctrl_inst.CNT_MAX = 10;	// 这里可以将ctrl模块中的参数定义为10,变小有利于仿真
	
	// 50MHz
	parameter	clk_period_50M	=	20;
	always # (clk_period_50M / 2)	sys_clk	=	~sys_clk;
	
	initial	begin
		sys_clk		=	1;
		sys_rst_n	=	0;
		wr_flag		=	0;
		rd_flag		=	0;
		
		#20
		sys_rst_n	=	1;
		#1000
	// rd_flag	先读出原始数据
		rd_flag	=	1;
		#20
		rd_flag	=	0;
		# 60_000			// 256个数据,每个等待11个时钟周期,一个时钟周期20ns
							// 256 * 11 * 20 == 56320
	// wr_flag	写入一组新数据
		wr_flag	=	1;
		#20
		wr_flag	=	0;
		#6_000				// 256个数据,每个等待一个时钟周期,一个时钟周期20ns
							// 256 * 20 = 5120
	// rd_flag	读出新数据
		rd_flag	=	1;
		#20
		rd_flag	=	0;
		# 60_000			// 256个数据,每个等待11个时钟周期,一个时钟周期20ns
							// 256 * 11 * 20 == 56320
	// rd_flag	读操作中重新开始读操作
		rd_flag	=	1;
		#20
		rd_flag	=	0;
		# 60_000			// 256个数据,每个等待11个时钟周期,一个时钟周期20ns
							// 256 * 11 * 20 == 56320
	   ;
	
	end

endmodule

在这里插入图片描述
第一次,读取原始数据
在这里插入图片描述
第二次,写入新数据
在这里插入图片描述
第三次,读出新数据

在这里插入图片描述
第四次,读操作被读操作打断

在这里插入图片描述

  • 10
    点赞
  • 100
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值