Vivado中用HDL定义BRAM存储器并用updatemem合成bit文件

最近被这么一个工作折腾了好几天:一个非IPI的Vivado工程(需要全部Verilog手写),在Artix-7 FPGA中实现了一个ARM Cortex-M0 DesignStart软核(老版,没有debug模块),存放ARM程序的存储器是实现在FPGA片上RAM上的;ARM程序用Keil MDK编写,我希望在测试这个程序时,不用每次都重新综合一遍FPGA。

总体来说这个需求是:

  • Vivado;
  • 全部HDL,非IPI;
  • 无处理器(在Vivado看来M0 DesignStart不是处理器);
  • 存储器放在BRAM上;
  • 替换Bitstream中的BRAM初始化,而不用重新综合整个逻辑。
  • 这件事看上去肯定是能做到的嘛。然而Vivado要用新的updatemem命令来替换以前ISE中的data2mem,我发现在无处理器的工程上,相关资料比较少……

    这里总结一下整个实现过程。使用环境是Vivado 2015.2。

    先把Vivado多线程开了……
    参考资料:
    [Xilinx AR# 53090] Vivado - Is it possible to run Tcl commands during initialization of the Vivado or PlanAhead tools?

    在Vivado安装文件夹\Vivado\version\scripts中新建一个init.tcl,里面写
    set_param general.maxThreads 8

    这样下次运行Vivado是自动开启8线程,综合实现比较快……

    将寄存器定义在BRAM上
    参考资料: 
    [Xilinx AR# 54778] Design Assistant for Vivado Synthesis - Help with Synthesis HDL Attribute Support - keep, keep_hierarchy, ram_style, rom_style

    这个页面的附件中给出了ram_style属性的例子,以verilog为例:
    module ram_inf_64x1d_2 (a, dpra, clk, din, we, spo, dpo);

    parameter ADDRESSWIDTH = 6;
    parameter BITWIDTH = 1;
    parameter DEPTH = 34;

    input clk, din, we;
    input [ADDRESSWIDTH-1:0] a, dpra;

    output spo, dpo;

    (* ram_style = "block" *)
    reg [BITWIDTH-1:0] ram [DEPTH-1:0];
    reg [ADDRESSWIDTH-1:0] read_dpra;
    reg [ADDRESSWIDTH-1:0] read_a;

    always @(posedge clk) begin
    if (we) begin
    ram [a] <= din;
    end
    read_a <= a;
    read_dpra <= dpra;
    end

    assign spo = ram [read_a];
    assign dpo = ram [read_dpra];

    endmodule

    上面代码展示了如何将寄存器数组reg ram强制定义在Block RAM上,并展示了两种访问方式:

  • 单口访问:地址a、输入数据din、输出数据spo;
  • 输入、输出分别访问:输入地址a、输入数据din、输出地址dpra、输出数据dpo。
  • 实际使用中可根据需要选择保留其中一组引脚。

    综合后可检查Project Summary和warning看是否成功综合成BRAM。建议这段module直接使用,不做逻辑上的修改。

    查看BRAM模块的实现情况
    参考资料: 
    [Xilinx AR# 59259] 2013.4 - Vivado IPI - write_bmm Support with non-processor Based Designs 

    这个帖子是针对bmm的,但其中提到了如何查看实现出的BRAM模块的位置,以及怎样知道每个模块分配的行、列地址范围。

    把工程Implement一下,然后打开Implemented Design。
    在打开的Implemented Design里面Ctrl+F搜索所有的BMEM:

    搜索结果长这样:

    每一行就是综合出的一个BRAM模块。选中其中一行,在它的属性里可以看到:

    其中比较重要的是模块的名字(比如xxxxx/ram_reg_0)、位置(比如X1Y7)、行和列的地址范围([0:16383]和[0:1])。

    为BRAM cell添加bmm属性
    参考资料: 
    [Xilinx论坛] A method to fix poor combination LBM of IPI Microblaze design 

    我们需要做的是在Vivado工程里添加一个xdc约束文件,包含类似下面的内容:
    create_property bmm_info_memory_device cell -type string

    set_property bmm_info_memory_device {[ 0: 1][0:16383]} [get_cells u_block_ram/ram_reg_0]
    set_property bmm_info_memory_device {[ 2: 3][0:16383]} [get_cells u_block_ram/ram_reg_1]
    set_property bmm_info_memory_device {[ 4: 5][0:16383]} [get_cells u_block_ram/ram_reg_2]
    set_property bmm_info_memory_device {[ 6: 7][0:16383]} [get_cells u_block_ram/ram_reg_3]

    # 后面还有好几个,最后是ram_reg_15

    除了第一行定义属性之外,后面每一行都对应上面搜索到的一个BRAM模块(当然不需要初始化的BRAM就不用写了)。
    这里有两个字段需要根据实际工程来改写:一个是类似[ 0: 1][0:16383]这样的地址范围,另一个是u_block_ram/ram_reg_0这样的cell名字。其他都是不用动的。
    我的工程里用到了64KBytes的RAM,总共使用了16个RAMB36模块,所以我的xdc文件中有16行,最后到{[30:31][0:16383]} [get_cells u_block_ram/ram_reg_15]。

    添加xdc文件之后把工程再次综合实现一遍。

    添加mmi文件

    参考资料:
    [Xilinx UG898] Vivado Design Suite User Guide: Embedded Processor Hardware Design 
    [Xilinx AR# 63041] 2015.1 - Vivado IP Integrator - write_mem_info does not create MMI file 

    上面一个文档里有一章专门讲mmi文件和updatemem命令。
    下面帖子里描述了如何在有Memory Controller IP的Block Design中生成mmi文件,并附了一个tcl脚本。虽然我的系统中既没有Memory Controller、也没有Block Design,但资料还是有很大的参考意义。

    实际上我把这个例子做了一遍,然后改写了它生成的mmi。

    mmi文件大概是这个模样:

    又是XML,现在不写几个XML都不好意思说自己用过Vivado……

    这段代码主要需要修改的是:

  • 里面:Name是自己随便起的;0和65535是分别是最小和最大地址,这里面地址的单位是字节,总共64KBytes,与总线是32位无关;另外这个地址应该是可以不从0起始的。
  • 每一个里面:MSB、LSB、Begin、End这些和上面xdc约束文件中的每一项是对应的,Placement根据Implemented Design中搜索的结果来定。
  • P.S. 在以前做bmm文件的时候,像Placement这种值是可以综合器自动帮助填写的,暂时还没发现如何在自定义bram时在mmi文件上实现类似功能。

    合成Bitstream
    参考资料: 
    [Xilinx AR# 63041] 2015.1 - Vivado IP Integrator - write_mem_info does not create MMI file 

    首先生成要往BRAM里烧写的数据,mem或elf格式。我这里是Keil MDK编译的可执行文件,直接把axf扩展名改成elf就能用。

    把mmi、elf文件都放在Vivado工程的.runs\impl_1文件夹下,在Vivado中运行tcl命令:
    cd {C:\non_processor_mmi\project_1.runs\impl_1}

    updatemem -force --meminfo mmi文件名.mmi --data elf文件名.elf --bit 原始的bit文件.bit --proc dummy --out download.bit

    其中C:\non_processor_mmi\project_1.runs\impl_1改成自己的工程文件夹。
    updatemem后面跟着的是一行命令,比较长。

    祈祷没有错误吧,然后可以看到impl_1文件夹中多了一个download.bit文件。
    这一段可以看到Vivado重新launch_runs了write_bitstream,生成了一个新的bit文件,但并没有再次综合实现。

    最后用Hardware Manager把Bitstream下载到FPGA中。

    如何更新数据

    比如说我用Keil MDK重新编译出了一个elf,那么只需要再次运行
    updatemem -force --meminfo xxx.mmi --data yyy.elf --bit zzz.bit --proc dummy --out download.bit

    就可以得到合成后的Bitstream,不需要再次综合。

    总结

  • 编写HDL文件,将寄存器强制定义在BRAM上;
  • 综合实现一遍;
  • 在xdc约束文件中添加bmm属性;
  • 编写mmi文件;
  • 运行updatemem合成Bitstream。
  • 关于data2mem

    这里记录一下在查找资料过程中查到的利用bmm合成bit的资料,待之后尝试。

    [Xilinx UG658] Data2MEM User Guide 
    Xilinx的data2mem使用手册,支持到ISE 14.7。

    [Xilinx Answer 46945] Data2Mem Usage and Debugging Guide 
    Xilinx整理的Data2Mem相关问答,相当于上面手册的补充说明,更实用一些的内容。

    Using Data2Mem in a Non-EDK design 
    Xilinx的另一篇文档,内容如其名。

    Loading BRAM data 
    这个wiki中作者记录了用data2mem合成AVR核的存储器数据的各种方法,使用的环境是ISE 12.4。

    spartan3 picoblaze how to make .bmm file work 
    这个帖子下面Walter Dvorak的回复提到了ISE可以自动生成bmm文件中的PLACED字段,使用的环境是ISE9.1.3。

    Using Xilinx Data2MEM to Patch Block RAMs 
    这个帖子把生成bmm到使用data2mem的过程总结的比较完整,其中也提到了上面那个Walter Dvorak的回复。

    文章来源:K_O_Carnivist的专栏