# brup - Block Ram UPdate tool
Berke Durak <bd@exhrd.fr>
## IMPORTANT NOTICE
This tool does the same thing as `ecpbram` which is included in
`prjtrellis`, using the same approach. For iCE FPGAs you can also use
`icebram` which comes with `icestorm`. I was not aware of their
existence when I wrote this tool.
# 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
| ECP5 | PrjTrellis | Text configuration |
# Command-line interface
The general form is `brup CMD args...` where `CMD` is a subcommand.
Available subcommands follow.
| `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.
| `--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.
| `--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
| `--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
1. If you are using `make`, ensure that the configuration file
generated by NextPNR is marked precious, otherwise it will be deleted.
2. If your memory `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]`
# Compilation and installation
1. Install `cargo` from https://www.rust-lang.org/
2. From the `brup` source directory type `cargo install --path .`
3. 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
| 2025-09-23 | 0.1.0 | Initial release |
| 2025-09-24 | 0.1.1 | Fix bug affecting configs with multiple memory layouts |