Block RAMs (BRAM) are one of the key building blocks within our programmable logic designs. They are used for a range of applications from the ROM to FIFOs, and many stops in between.
Of course, one of the most popular applications of BRAMs is to act as data and instruction memories for our embedded processing solutions, e.g. MicroBlaze, or Arm Cortex-M1 and M3.
While it would be nice to get our software correct first time, unless it is very simple, it rarely happens. As such we need several design iterations, if each iterations needs to re-run the implementation to update the BRAM contents the time between tests is considerable. Although we can use JTAG debugging for some flows easily, e.g. MicroBlaze, there are use cases where we cannot, like when working with Arm cores without a DAP or separate JTAG.
There are two flows that we can use when working with Vivado to update the memory contents:
- MEMDATA — Used prior to implementation and is used to merge MicroBlaze programs with the bitstream.
- UpdateMem — Can be used after implementation provided the design meets specific criteria.
The criteria for using the UpdateMem flow is the design must contain a processor (either hard or softcore). If the design does not contain a processor, the memories must have been implemented using Xilinx Parameterised Macros. If you are unfamiliar with the XPM Macros, then please take a look here. I will outline in another blog how we can work around these constraints if our design does not meet this criteria.
Using the UpdateMem approach, we can update the BRAM with the contents of either a elf file (created by SDK etc) or a MEM file which can be hand created.
To demonstrate the flow, let’s take a look at creating a simple Zynq-based processor with a connected BRAM via a BRAM controller. In the initial design, the BRAM will not be initialised with any data.
Once the bit stream is completed, if we look in the implementation directory of the project, we will see there exists a <project_name>.MMI file. This MMI file contains the memory map information.
This file contains everything we need to be able to update the BRAM within the design, including the physical location, the endianness, data width and address range.
If the base project is downloaded and ran using SDK (create a simple hello world application and BSP), you can see the BRAM contents are all zero.
To update the BRAM without re-running the implementation, we need three files:
- The MMI File indicated above.
- A TCL script to configure and run the BRAM update.
- ELF or MEM file which contains the data to be updated in to BRAM.
The TCL script itself is very easy to create and can be implemented within a few lines of code. Within the TCL script, we need to define the location of the bit file and the MMI file, both of which are located in the projects implementation directory. Along with these files, we also need to provide a MEM or ELF file location.
My TCL script code can be seen below:
set source_bit_file “./bram_update.runs/impl_1/design_1_wrapper.bit”
set mmi_file “./bram_update.runs/impl_1/design_1_wrapper.mmi”
set mem_file “./update.mem”
set output_bit_file “./updated.bit”
exec updatemem — debug — force — meminfo $mmi_file — data $mem_file — bit $source_bit_file — proc design_1_i/processing_system7_0 — out $output_bit_file
The final file for this example is the MEM file, which takes a simple format of address and data. If we desire, we can just define an address and then also blocks of contiguous data to ease the flow.
For this example, I wrote two different values at different addresses — one little gotcha here is the address must be the same as the address range the BRAM is mapped in to for example my MEM File contents are.
With these three files, we are now able to run the TCL script. We can do this via a bat file or from the Vivado TCL console. I ran this example from the TCL console in Vivado. In the output, you can see the initialisation data being updated.
The updated bitstream will be output in the present working directory unless otherwise requested.
Once the updated file was available, I again used SDK to download the application and updated bitstream.
When this updated bitstream was run on the board as you will see the block RAM contents reflect the updated block RAM contents.
If you want to take a look at the project and scripts created and understand how this all works, I have uploaded the project to my GitHub and you can find it at the link below.