基于Xilinx的ROM IP核的使用

文章介绍了ROM的基本概念,特别是在FPGA中的应用,指出实际上FPGA中的ROM是基于内部RAM资源的。Xilinx提供了单端口和双端口ROM的IP核,文章详细展示了如何配置和使用这些IP核。实验部分涉及了ROM的初始化数据、数码管显示和按键消抖,通过ROMIP核进行数据传输。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.ROM的介绍

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

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

单端口ROM如下图所示:

双端口ROM:

2.IP核配置

关于IP核的配置如下图所示:

首先打开IP选型

然后搜索block

然后双击,我们选择单端口ROM

这里选择8位宽,深度256,总是使能。

定义了一个0-255的.coe文件,然后并添加

在Summary界面可以看到自己的配置

然后点击OK,我们的但端口ROM IP核就创建完成了。

3.IP核的使用

本次实验任务是参考野火征途Pro系列开发指南,设计一个ROM 的初始化数据是 0~255,也就是存入数据0~255。然后每隔 0.2s 我们从 0 地址开始往下读取数据显示在数码管上,我们再利用两个按键信号来读取指定地址的数据,每按一个按键就读取一个地址的数据显示在数码管上。再次按下按键后,以当前地址继续以 0.2s 的时间间隔往下读取数据并显示出来。

3.1实验分析

根据实验任务我们可以知道,本次实验需要使用到的外设有按键以及数码管。按键的话肯定需要消抖模块,数码管的话是需要数码管驱动模块。在按键跟数码管中间的数据传输时通过ROM IP进行传输,所以还需要设计一个rom控制模块。模块框图大致如下:

消抖模块、rom控制模块以及rom IP的系统时钟以及系统复位端口没有画出来,正常都应该有的,这里需要注意一下。

按键消抖模块代码如下:

`timescale 1ns / 1ps

module key_filter(
    input           sys_clk     ,
    input           sys_rst_n   ,
    input           key         ,

    output  reg     key_flag    
    );
    
parameter CNT_MAX = 20'd999_999;    
    
reg [19:0]  cnt_20ms;


//20ms cnt
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        cnt_20ms <= 20'd0;
    else if(key == 1'b1)
        cnt_20ms <= 20'd0;
    else if(cnt_20ms == CNT_MAX && key == 1'b0)
        cnt_20ms <= cnt_20ms;
    else
        cnt_20ms <= cnt_20ms + 20'd1;
end

//key_flag
always@(posedge sys_clk or negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        key_flag <= 1'd0;
    else if(cnt_20ms == (CNT_MAX - 20'd1))
        key_flag <= 1'd1;
    else
        key_flag <= 1'd0;
end

消抖设置了一个20ms的计数器,当按键按下时间小于20ms就认为按键在抖动时间,根据消抖模块可以看一下仿真代码及波形图如下:

`timescale 1ns / 1ns

module tb_key_filter(

    );
    
    
reg            sys_clk  ;
reg            sys_rst_n;
reg            key1     ;
reg            key2     ;
                        
wire           key1_flag;

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    key1 <= 1'b1;
    key2 <= 1'b1;
    #201
    sys_rst_n <= 1'b1;
    #200
//key1    
    key1 <= 1'b0;
    #20
    key1 <= 1'b1;
    #80
    key1 <= 1'b0;
    #120
    key1 <= 1'b1;
    #20
    key2 <= 1'b0;
    #20
    key2 <= 1'b1;
    #20
    key2 <= 1'b0;
    #100
    key1 <= 1'b0;
    #500
    key1 <= 1'b1;
    #500
    key1 <= 1'b0;
    #400
    key1 <= 1'b1;
end

always #10 sys_clk <= ~sys_clk;

defparam    key_filter_inst1.CNT_MAX = 19;
defparam    key_filter_inst2.CNT_MAX = 19;
    
key_filter key_filter_inst1(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key         (key1     ),
                           
    .key_flag    (key1_flag )
    );

key_filter key_filter_inst2(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key         (key2     ),
                           
    .key_flag    (key2_flag )
    );    

endmodule

接下来我们开始设计rom控制模块,代码如下:

`timescale 1ns / 1ps

module rom_ctrl(
    input               sys_clk     ,
    input               sys_rst_n   ,
    input               key1        ,
    input               key2        ,
    
    output  reg    [7:0]   addr      
    );
    

   
parameter   CNT_MAX = 9_999_999;    //0.2s计数器
parameter   ADDR_MAX = 8'd255;      //最大地址
reg         key1_en;
reg         key2_en;
reg [23:0]  cnt_200ms;

//key1_en
always@(posedge sys_clk or  negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        key1_en <= 1'b0;
    else if(key2 == 1'b1)
        key1_en <= 1'b0;
    else if(key1 == 1'b1)
        key1_en <= ~key1_en;
end

//key2_en
always@(posedge sys_clk or  negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0)
        key2_en <= 1'b0;
    else if(key1 == 1'b1)
        key2_en <= 1'b0;
    else if(key2 == 1'b1)
        key2_en <= ~key2_en;
end        

//0.2s cnt    
always@(posedge sys_clk or  negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) 
        cnt_200ms <= 24'd0;
    else if(key1_en == 1'b1 || key2_en == 1'b1 || cnt_200ms == CNT_MAX)
        cnt_200ms <= 24'd0;
    else
        cnt_200ms <= cnt_200ms + 24'd1;
end   
     
//地址
always@(posedge sys_clk or  negedge sys_rst_n) begin
    if(sys_rst_n == 1'b0) 
        addr <= 8'd0;
    else if(addr == ADDR_MAX && cnt_200ms == CNT_MAX)
        addr <= 8'd0;
    else if(key1_en == 1'b1)
        addr <= 8'd99;
    else if(key2_en == 1'b1)
        addr <= 8'd199;
    else if(cnt_200ms == CNT_MAX)
        addr <= addr + 8'd1;
end
        
endmodule

仿真代码:

`timescale 1ns / 1ns

module tb_rom_ctrl();

reg            sys_clk  ;
reg            sys_rst_n;
reg            key1     ;
reg            key2     ;
                        
wire  [7:0]    addr     ;

initial begin
    sys_clk = 1'b1;
    sys_rst_n <= 1'b0;
    key1 <= 1'b0;
    key2 <= 1'b0;
    #201
    sys_rst_n <= 1'b1;
    #2000
//key1    
    key1 <= 1'b1;
    #20
    key1 <= 1'b0;
    #2000
    key1 <= 1'b1;
    #20
    key1 <= 1'b0;
    #700
//key2
    key2 <= 1'b1;
    #20
    key2 <= 1'b0;
    #2500
    key2 <= 1'b1;
    #20
    key2 <= 1'b0;
    #500
//key1&key2
    key1 <= 1'b1;
    #20
    key1 <= 1'b0;
    #2000
    key2 <= 1'b1;
    #20
    key2 <= 1'b0;
    #2000
    key2 <= 1'b1;
    #20
    key2 <= 1'b0;
    
end

always #10 sys_clk <= ~sys_clk;

defparam    rom_ctrl_inst.CNT_MAX = 19;

rom_ctrl rom_ctrl_inst(
    .sys_clk     (sys_clk  ),
    .sys_rst_n   (sys_rst_n),
    .key1        (key1     ),
    .key2        (key2     ),
                           
    .addr        (addr     )
    );
endmodule

数码管例程就不展示了,大家可以随便使用一个数码管驱动模块,也可以参考野火的数码管例程

最后将这三个模块例化到一起,再进行一个仿真验证:

仿真结果跟我们要实现的任务要求是一致的,说明代码没有问题,这边就不展示上板验证的效果了,下一章介绍RAM的使用。

<think>好的,我现在要解决用户的问题,即如何在Xilinx平台下基于原语或IP添加FPGA的编译日期。首先,我需要回忆一下Xilinx FPGA开发的相关知识,尤其是关于在设计中嵌入元数据或时间戳的方法。 用户提到使用原语或IP,这可能涉及到直接在HDL代码中插入特定的模块或使用Xilinx提供的IP来生成编译日期。首先,我应该考虑在Verilog或VHDL中如何获取编译时间,并将其存储到寄存器或存储器中,以便在FPGA运行时可以读取。 记得在Verilog中,可以使用`$date`和`$time`系统函数来获取日期和时间,但这是仿真时的功能,综合时可能不可用。因此,这些函数可能无法直接生成编译日期到实际硬件中。这时候可能需要其他方法。 接下来,原语部分,Xilinx是否有相关的原语可以插入时间戳?比如,使用BUFG或LUT这样的原语可能不太相关。或者是否有像USR_ACCESS这样的原语,用于存储用户定义的数据。例如,USR_ACCESS_V2原语可能允许用户将数据烧写到某些寄存器中,这可能需要在生成bitstream时配置,但如何自动将编译日期写入呢? 另外,使用IP的话,比如使用AXI Quad SPI或BRAM,但这些可能不直接相关。有没有可能通过IP生成一个包含编译日期的ROM内容?例如,使用Vivado的IP集成器来生成一个ROM,其初始化文件在编译时自动生成,包含当前日期和时间。这种方法可能需要脚本支持,在综合前生成一个包含当前日期的文本文件,然后作为ROM的初始化文件。 或者,利用Vivado的tcl脚本功能,在综合过程中生成一个包含日期时间的变量,然后将其写入到某个寄存器或模块的参数中。例如,在综合前运行一个tcl脚本,将当前日期写入到HDL代码中的某个参数或常量中。 具体步骤可能包括: 1. 在设计中实例化一个ROM或寄存器模块,用于存储日期信息。 2. 使用tcl脚本在编译流程中生成日期字符串,并写入到某个文件(如.coe文件)作为ROM的内容。 3. 在Vivado项目中,将该文件作为ROM IP的初始化文件。 4. 这样,每次编译时,该文件会被更新,从而ROM中的内容即为最新的编译日期。 或者,使用Verilog的`宏定义`,在编译过程中通过tcl脚本替换宏定义的值。例如,在HDL代码中定义一个参数,如`parameter BUILD_DATE = "00000000";`,然后通过tcl脚本在综合前修改该参数值为当前日期,再触发综合和实现。 但如何在综合过程中动态修改参数呢?可能需要使用`define语句,然后通过tcl脚本修改对应的头文件。例如,在Verilog中使用` `define BUILD_DATE 20240601`,然后编写一个tcl脚本,在每次编译前生成该日期,并替换到对应的.vh文件中。 此外,Xilinx的USR_ACCESS原语可能允许用户嵌入自定义数据到bitstream中。例如,在7系列或Ultrascale器件中,USR_ACCESS可以配置为包含用户指定的数据,这些数据可以在运行时读取。用户需要在设计中使用该原语,并在生成bitstream时,通过tcl脚本将当前日期写入到USR_ACCESS的初始化值中。 具体的实现步骤可能如下: 1. 在HDL代码中实例化USR_ACCESS_V2原语。 2. 在Vivado的tcl脚本中,获取当前日期时间,并将其转换为合适的格式(如ASCII码的十六进制表示)。 3. 使用tcl命令将该日期时间字符串写入到原语的属性中,或者生成一个.xdc约束文件,设置USR_ACCESS的值。 4. 综合并生成bitstream,此时USR_ACCESS原语中包含了编译日期。 需要查阅Xilinx文档确认USR_ACCESS是否支持动态配置,或者是否需要在生成bitstream时通过属性设置其值。例如,原语可能有一个初始化值属性,可以通过tcl脚本在实现阶段修改该属性。 另外,另一种方法是使用EDK工具中的metadata,或者在SDK中生成软件可读取的日期信息。但用户希望基于原语或IP,所以可能更倾向于硬件层面的解决方案。 总结可能的步骤: - 方法一:使用tcl脚本生成日期,并嵌入到ROM IP的初始化文件中,实例化该ROM并读取日期。 - 方法二:利用USR_ACCESS原语,通过tcl脚本设置其初始值为编译日期。 - 方法三:在HDL中使用宏定义,并在编译前通过脚本替换该宏定义的值为当前日期。 需要进一步确认这些方法的可行性和具体操作步骤。例如,USR_ACCESS_V2原语的使用可能需要特定的属性和设置方式。此外,如何将日期字符串转换为合适的二进制格式,并确保其在综合过程中不被优化掉。 可能的挑战包括:确保日期信息在综合和实现过程中不被优化,正确设置原语的属性,以及日期格式的处理。例如,USR_ACCESS可能只能存储一定长度的数据,需要将日期转换为32位或64位的值。 最后,需要验证这些方法是否在实际项目中可行,可能需要参考Xilinx的文档或社区中的类似解决方案,以确保正确性。</think>在Xilinx FPGA设计中,通过原语或IP嵌入编译日期需结合Tcl脚本和硬件设计。以下是分步实现方法: --- ### **方法一:通过Tcl脚本+ROM IP** #### **步骤说明** 1. **生成日期文件** 在Vivado Tcl脚本中,生成包含当前日期/时间的文本文件(如`.coe`),格式示例: ```tcl set current_time [clock format [clock seconds] -format "%Y%m%d%H%M%S"] set fp [open "build_date.coe" w] puts $fp "MEMORY_INITIALIZATION_RADIX=16;" puts $fp "MEMORY_INITITIALIZATION_VECTOR=" puts $fp [format %08x [expr {[clock scan $current_time]}]] close $fp ``` 2. **实例化ROM IP** 在Vivado中创建Block Memory Generator IP,选择ROM模式,初始化文件指向`build_date.coe`,数据宽度匹配日期编码(如32位)。 3. **读取日期逻辑** 在HDL代码中实例化ROM,并通过地址读取数据: ```verilog rom_build_date rom_inst ( .clka(clk), .addra(0), // 固定地址 .douta(build_date) ); ``` --- ### **方法二:使用USR_ACCESS原语(UltraScale+系列)** #### **步骤说明** 1. **实例化原语** 在代码中添加`USR_ACCESS_V2`原语: ```verilog USR_ACCESS_V2 #( .SIM_DEVICE("ULTRASCALE_PLUS") ) usr_access_inst ( .CFGCLK(), .DATA(build_date), .DATAVALID() ); ``` 2. **Tcl脚本注入日期** 在综合后,通过Tcl修改`USR_ACCESS`的初始值: ```tcl set current_date [clock format [clock seconds] -format "%Y%m%d"] set_property BITSTREAM.CONFIG.USR_ACCESS_VALUE 0x[format %08x [expr {[clock scan $current_date]}]] [current_design] ``` --- ### **方法三:宏定义替换(需自动化脚本)** 1. **HDL代码定义宏** ```verilog `define BUILD_DATE 32'h00000000 ``` 2. **编译前脚本替换** 使用Python/Tcl脚本替换宏值为当前日期: ```tcl set build_date [clock format [clock seconds] -format "%Y%m%d"] exec sed -i "s/`define BUILD_DATE .*/`define BUILD_DATE 32'h${build_date}/" build_info.vh ``` --- ### **验证与注意事项** 1. **防优化处理** 添加`(* keep = "true" *)`或`MARK_DEBUG`属性,防止信号被优化。 2. **运行时读取** 通过JTAG或逻辑分析仪读取寄存器值,解析日期。 3. **格式兼容性** 日期需转换为二进制或ASCII编码(如UNIX时间戳)。 --- 以上方法均需在Vivado流程中集成Tcl脚本自动化操作,确保每次编译更新日期。优先推荐**方法一或二**,直接通过硬件资源实现,可靠性更高。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

伊藤诚诚诚诚

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值