brup - Block Ram UPdate tool
Berke Durak bd@exhrd.fr
Description
Tired of waiting after NextPNR just to get a bitstream with updated block RAM contents?
Suppose you have a design having a softcore CPU, say a RISCV core, with firmware in the initial contents of one or more memory blocks. These can be inferred by Yosys or explicitly instantiated in your RTL.
Once your RTL code is more or less stable you will be spending lots of time iterating on your firmware without any other changes to your RTL. During some or all the phases of your design you will want to test your changes on the hardware. You make a change to your firmware and recompile it. Compilation should be fast, as code that can fit in the block RAMs will be quite small. But you now need a new bitstream to actually run it on the FPGA.
Typically you type "make" and this re-runs Yosys, which is also pretty fast, but then NextPNR takes a while. It has to do placing and routing, which is a difficult combinatorial optimization problem. After that you can configure your FPGA over JTAG, which only takes one or two seconds.
Overall, if it were not for NextPNR, you would be able to test a change to your code in a couple of seconds. Instead, you have to wait tens of seconds if your design is small and up to many minutes if it is large. NextPNR wastes your time solving basically the same problem over and over again.
Using brup you can instantly update the already-routed ECP5
configuration file with your new BRAM contents without having to
re-run NextPNR. Your edit-compile-run cycle only takes seconds.
Usage
brup works by locating the known contents of your firmware (or other
memory) in the chip configuration produced by NextPNR, and replacing
them with the new version.
To allow successful location, the memory contents need to be such that each column of each BRAM is unique, according to the way Yosys lays them out.
Yosys has a complex memory layout strategy, and the Lattice ECP5 memory blocks can be configured to have different widths. Yosys can slice the memory contents in multiple ways. It may reorder bits and may even delete bit positions that are constant.
Therefore, for successful updates, you should synthesize your RTL
using (pseudo)-random contents for the memories that are to be updated
using brup, so that the tool can locate them unambiguosly.
Step 1. Generate random hex files
For each memory that you want to update using brup, you need
a unique pattern file pattern123.hex.
Suppose that you're using Verilog code and that your file foo.v
contains a declaration for a 32-bit wide, 512 word memory rom123:
reg [31:0] rom123[512-1:0];
initial $readmemh("pattern123.hex",rom123);
Generate a random pattern using brup
brup genpat --output pattern.hex --width 32 --count 512 --seed 123
where 123 is distinct arbitrary integer for each memory.
If you only have one memory you may omit --seed 123.
You need to give the width of your memory with --width.
Alternatively, you can use the following incantation:
od -t x4 -A null /dev/urandom |
tr ' ' '\n' | grep '^........$' | head -512 >pattern.hex
Step 2. Generate your initial configuration
Use Yosys then NextPNR for synthesis and placement as you usually do.
Make sure you keep the text configuration produced by nextpnr-ecp5:
yosys -p "synth_ecp5 -top top -abc9 -json foo.json" foo.v
nextpnr-ecp5 --json foo.json --lpf foo.lpf ... --textcfg foo.cfg
Step 3. Produce your updated hex file
Compile your firmware and produce your hex file as usual, e.g.
riscv64-linux-gnu-gcc-12 -Wall -c foo_fw.c
riscv64-linux-gnu-ld <ETC.>
python3 makehex.py foo 4096 >foo.hex
You obtain foo_fw.hex
Step 4. Update your configuration
You may now call brup to produce an updated text configuration. Do not overwrite
your initial foo.cfg!
brup update \
--config foo.cfg \
--mem pattern.hex \
--new-mem foo_fw.hex \
--width 32
--output foo_with_fw.cfg
Step 5. Use ecppack to obtain an SVF file and configure your FPGA
Something like:
ecppack foo_with_fw.cfg --svf foo_with_cfg.svf
openocd -f ecp5.cfg -c "transport select jtag;init;svf foo_with_cfg.svf;exit"
Step 6. Fix your code, and repeat from step 3
Note that you don't need to re-route again!
Supported hardware and file formats
So far, brup has been designed for and only works with Lattice ECP5
configuration files produced by NextPNR and Yosys.
Configuration file formats
| Arch | Tool | Format |
|---|---|---|
| ECP5 | PrjTrellis | Text configuration |
Command-line interface
The general form is brup CMD args... where CMD is a subcommand.
Available subcommands follow.
| Subcommand | Description |
|---|---|
update |
Main subcommand, update config with new contents |
genpat |
Generate pseudo-random pattern |
help |
Display help |
cpcfg |
Read and re-create a chip configuration |
brdump |
Dump BRAM contents |
brlayout |
Dump BRAM contents using a specific layout |
The update subcommand
Takes an input chip configuration, a hex file giving the current contents, a hex file with the desired (new) contents and produces an updated NextPNR configuration.
| Argument | Description |
|---|---|
--config PATH |
Chip configuration file produced by NextPNR |
--mem PATH |
Hex file with the current memory contents of the config |
--new-mem PATH |
Hex file with the desired contents |
--width N |
Width, in bits, of the memory |
--output PATH |
Output chip configuration file |
The genpat subcommand
This generates a pseudo-random hex file that can be used to locate the memory contents in the chip configuration files.
| Argument | Description |
|---|---|
--width N |
Width, in bits, of the memory |
--count M |
Number of words (addresses) of the memory |
--seed S |
Optional, different seeds produce different files |
--output PATH |
Output hex configuration file |
The cpcfg subcommand
| Argument | Description |
|---|---|
--input PATH |
Chip configuration file produced by NextPNR |
--output PATH |
Output chip configuration file |
This subcommand is only useful for testing the configuration parser. It reads
the configuration file and writes it back. The two files should be equivalent.
In the test bench, this is verified by running ecppack on the original
and copied configurations and comparing the resulting SVF files.
Other commands and options
Some other commands used for debugging are help, identify,
modify, brdump and brlayout. The update and identify
commands can take extra options --layout-clear, --layout-enable NAME and --layout-disable NAME to select the set of layouts brup
will try. Available layouts are 512x36, 1024x18, 2048x9,
4096x4, 8192x2 and 16384x1. By default brup will try the
layouts in that order until it finds one that allows identification.
Tips
- If you are using
make, ensure that the configuration file generated by NextPNR is marked precious, otherwise it will be deleted. - If your memory
foohas sizepand widthq, that is, it consists ofpwords tha areqbits wide, as inreg [q-1:0] foo[0:p-1]then the hex file should haveplines, each lineiwith1 <= i <= pshould contain the hexadecimal value for the wordfoo[i-1]
Compilation and installation
- Install
cargofrom https://www.rust-lang.org/ - From the
brupsource directory typecargo install --path . - The ECP5 sysMEM blocks have a two cycle latency between address presentation and data output, and Yosys will resort to LUTs if your design uses the memory output before it's ready.
Testbench
Under testbench/ you will find a Zsh script run.sh that will
synthesize a series of ECP5 designs having one memory with various
widths N and sizes M using Yosys and NextPNR. It will then test
the genpat, cpcfg and update command of brup on those.
Provided yosys, nextpnr-ecp5 and ecppack are in your
PATH and you have zsh installed you can call it as
testbench/run.sh all
Note that for some small values of M and N Yosys will not use
block RAMs (i.e. DP16K sysMEM primitives) in spite of the
nomem2reg attribute, and the testbench script will output "No
BRAM synthesized".
Author and links
Author: Berke Durak bd@exhrd.fr
- https://exhrd.fr/
- https://github.com/berke
- https://github.com/berke/brup
- https://crates.io/crates/brup
Keywords
- FPGA, bitstream, configuration, Lattice, ECP5, block ram, Yosys, NextPNR, DP16KD
Revision history
| Date | Version | Description |
|---|---|---|
| 2025-09-23 | 0.1.0 | Initial release |
| 2025-09-24 | 0.1.1 | Fix bug affecting configs with multiple memory layouts |