enjoy-digital / litex

Build your hardware, easily!
Other
2.81k stars 542 forks source link

Support patching ICE40 SRAM images #142

Closed xobs closed 5 years ago

xobs commented 5 years ago

The ICE40 toolchain has the ability to patch block ram in the emitted bitstream. However, because it uses heuristics to locate the memory, it requires that the ram contain random data.

It is possible to hack litex to generate a ROM image that fills the entire area with random data:

diff --git a/litex/soc/integration/soc_core.py b/litex/soc/integration/soc_core.py
index 09de869..a9fee3f 100644
--- a/litex/soc/integration/soc_core.py
+++ b/litex/soc/integration/soc_core.py
@@ -279,6 +279,10 @@ class SoCCore(Module):
         self.cpu_or_bridge = self.cpu

     def initialize_rom(self, data):
+        import random
+        data = []
+        for d in range(self.rom.mem.depth):
+            data.append(random.getrandbits(32))
         self.rom.mem.init = data

     def add_wb_master(self, wbm):

We can then patch the emitted rom file with the actual contents of bios.bin.

There are a few issues:

  1. migen does not emit zero-padded memory files, so we must pad them before we hand them to icebram
  2. the bios.bin file must be converted to 32-bit hex values and padded to fill the entire memory
  3. memory values may need to be byteswapped.

I'm not very good with python, so I hacked together a C program and shell script combination that fixes up mem.init and patches it with the contents of bios.bin.

C program:

#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>

#define SWAP_BYTES // Define for lm32, undefine for riscv

static int rom_to_hex(const char *src, const char *dst, uint32_t mem_size) {
    FILE *outfile;
    char bfr[4];
    uint32_t file_size = 0;

    int fd = open(src, O_RDONLY);
    if (fd == -1) {
        fprintf(stderr, "unable to open src rom file %s: %s\n", src, strerror(errno));
        return 1;
    }

    outfile = fopen(dst, "w");
    if (!outfile) {
        fprintf(stderr, "unable to open dest rom file %s: %s\n", dst, strerror(errno));
        return 1;
    }

    while (read(fd, bfr, 4) == 4) {
#ifdef SWAP_BYTES
        fprintf(outfile, "%02x%02x%02x%02x\n", 0xff & bfr[0], 0xff & bfr[1], 0xff & bfr[2], 0xff & bfr[3]);
#else
        fprintf(outfile, "%02x%02x%02x%02x\n", 0xff & bfr[3], 0xff & bfr[2], 0xff & bfr[1], 0xff & bfr[0]);
#endif
        file_size += 4;
    }

    memset(bfr, 0, sizeof(bfr));
    while (file_size < mem_size) {
        fprintf(outfile, "%02x%02x%02x%02x\n", 0xff & bfr[3], 0xff & bfr[2], 0xff & bfr[1], 0xff & bfr[0]);
        file_size += 4;
    }
    fclose(outfile);

    return 0;
}

static int patchup_mem(const char *src, const char *dst, uint32_t *mem_size) {
    *mem_size = 0;
    FILE *s = fopen(src, "r");
    if (!s) {
        fprintf(stderr, "unable to open source mem file %s: %s\n", src, strerror(errno));
        return 1;
    }

    FILE *d = fopen(dst, "w");
    if (!d) {
        fprintf(stderr, "unable to open dest mem file %s: %s\n", dst, strerror(errno));
        return 2;
    }

    char tmpline[128];
    while (fgets(tmpline, sizeof(tmpline)-1, s) != NULL) {
        uint32_t word = strtoul(tmpline, NULL, 16);
        fprintf(d, "%08x\n", word);
        *mem_size += 4;
    }

    fclose(d);
    fclose(s);
    return 0;
}

int main(int argc, char **argv) {
    uint32_t mem_size;
    if (argc != 5) {
        printf("Usage: %s [src-mem] [dst-mem] [src-bin] [dst-bin]\n", argv[0]);
        return 1;
    }
    if (patchup_mem(argv[1], argv[2], &mem_size))
        return 1;
    if (rom_to_hex(argv[3], argv[4], mem_size))
        return 1;
    return 0;
}

Shell script:

#!/bin/sh
set -e
builddir=../build/fomu_evt_usb_lm32.minimal
src_bs=${builddir}/gateware/top.bin
src_mem=${builddir}/gateware/mem.init
dst_bs=patched.bin
dst_mem=${builddir}/software/bios/bios.bin

iceunpack < "${src_bs}" > top.asc
./repack "${src_mem}" top.hex "${dst_mem}" patched.hex
icebram top.hex patched.hex < top.asc | icepack > "${dst_bs}"

echo "Patched file generated: ${dst_bs}"
xobs commented 5 years ago

It turns out this is already possible in litex.

Create the ROM this way:

from litex.soc.interconnect import wishbone
class RandomFirmwareROM(wishbone.SRAM):
    def __init__(self, size):
        import random
        # Seed the random data with a fixed number, so different bitstreams
        # can all share firmware.
        random.seed(2373)
        data = []
        for d in range(int(size / 4)):
            data.append(random.getrandbits(32))
        print("Firmware {} bytes of random data".format(size))
        wishbone.SRAM.__init__(self, size, read_only=True, init=data)

Then, register the rom:

class BaseSoC(SoCCore):
    def __init__(self, platform, **kwargs):
        SoCCore.__init__(self, platform, clk_freq, **kwargs)
        kwargs['integrated_rom_size']=0
        bios_size = 0x2c00
        self.submodules.firmware_ram = RandomFirmwareROM(bios_size)
        self.add_constant("ROM_DISABLE", 1)
        # self.add_memory_region("rom", kwargs['cpu_reset_address'], bios_size)
        self.register_rom(self.firmware_ram.bus, bios_size)

To repack the top.bin file, use ice40-repack

enjoy-digital commented 5 years ago

Thanks for sharing, this will be very useful.