| Crates.io | brup |
| lib.rs | brup |
| version | 0.1.1 |
| created_at | 2025-09-23 16:37:14.840714+00 |
| updated_at | 2025-09-23 18:46:49.499573+00 |
| description | Tool for updating the BRAM contents of NextPNR Lattice ECP5 FPGA configuration files |
| homepage | |
| repository | https://github.com/berke/brup |
| max_upload_size | |
| id | 1851731 |
| size | 90,391 |
Berke Durak bd@exhrd.fr
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.
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.
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
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
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
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
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"
Note that you don't need to re-route again!
So far, brup has been designed for and only works with Lattice ECP5
configuration files produced by NextPNR and Yosys.
| Arch | Tool | Format |
|---|---|---|
| ECP5 | PrjTrellis | Text configuration |
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 |
update subcommandTakes 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 |
genpat subcommandThis 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 |
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.
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.
make, ensure that the configuration file
generated by NextPNR is marked precious, otherwise it will be deleted.foo has size p and width q, that is, it consists of
p words tha are q bits wide, as in reg [q-1:0] foo[0:p-1]
then the hex file should have p lines, each line i with 1 <= i <= p
should contain the hexadecimal value for the word foo[i-1]cargo from https://www.rust-lang.org/brup source directory type cargo install --path .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: Berke Durak bd@exhrd.fr
| 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 |