Generalizing the Boot ROM

With its tiny size the ZPU would seem an ideal candidate for a multi-core design.  Unfortunately, instantiating more than one of the traditional zpu_small cores in a single project is complicated by the ROM / Stack RAM entity being instantiated within the CPU core itself.  If you want multiple ZPUs, doing different jobs, then their ROMs must differ!  To solve this, I’ve removed the internal ROM and defined a new interface (using VHDL techniques I’ve picked up from the PACE project) for connecting the ZPU to a separate ROM / Stack RAM entity. The biggest advantage of this is that it makes it much simpler to swap between different firmwares.

The signals required for the ZPU’s ROM / Stack RAM entity are as follows:

      memAWriteEnable : in  std_logic;
      memAAddr        : in  std_logic_vector(maxAddrBitBRAM downto minAddrBit);
      memAWrite       : in  std_logic_vector(wordSize-1 downto 0);
      memARead        : out std_logic_vector(wordSize-1 downto 0);
      memBWriteEnable : in  std_logic;
      memBAddr        : in  std_logic_vector(maxAddrBitBRAM downto minAddrBit);
      memBWrite       : in  std_logic_vector(wordSize-1 downto 0);
      memBRead        : out std_logic_vector(wordSize-1 downto 0);

In order to avoid having to connect all these individually, and to create signals for them in the toplevel, I’ve defined some new types in zpupkg.vhd:

    type ZPU_ToROM is record
        memAWriteEnable : std_logic;
        memAAddr : std_logic_vector(maxAddrBitBRAM downto minAddrBit);
        memAWrite : std_logic_vector(wordSize-1 downto 0);
        memBWriteEnable : std_logic;
        memBAddr : std_logic_vector(maxAddrBitBRAM downto minAddrBit);
        memBWrite : std_logic_vector(wordSize-1 downto 0);
    end record;

    type ZPU_FromROM is record
        memARead : std_logic_vector(wordSize-1 downto 0);
        memBRead : std_logic_vector(wordSize-1 downto 0);
    end record;

This means the relevant part of the ZPU’s interface can be reduced to:

        from_rom : in ZPU_FromROM;
        to_rom : out ZPU_ToROM

and the toplevel merely has to do this:

signal zpu_to_rom : ZPU_ToROM; 
signal zpu_from_rom : ZPU_FromROM; 
...
myrom : entity work.SDBootstrap_ROM 
    port map ( 
        clk => clk, 
        from_zpu => zpu_to_rom, 
        to_zpu => zpu_from_rom 
    );

To simplify creating the ROM entities from compiled code, I’m doing some Makefile magic.  (Previously I’ve been using the 2-port RAM megafunction wizard to generate the ROMs – and using srec_cat to generate the required .mif files.)

Luckily the ZPU project includes a ROM generating program, which takes a raw binary file as input and outputs VHDL code like so:

     0 => x"a08087fe",
     1 => x"04000000",
     2 => x"800b810b",
     3 => x"ff8c0c04",
     4 => x"0b0b0ba0",
     5 => x"80809d0b",
     ...

With a bit of shell scripting and redirection it’s easy enough to combine this with a VHDL prologue and epilogue to build a complete VHDL file defining the ROM.  My prologue and epilogue use the name dualportram, and the makefile uses sed to change this to something more suitable, like so:

   make -C SDBootstrap/
   sed 's/dualportram/sdbootstrap_rom/' >sdbootstrap_rom.vhd <rom_prologue
   ./zpuromgen SDBootstrap/boot.bin >>sdbootstrap_rom.vhd
   cat >>sdbootstrap_rom.vhd rom_epilogue

If the directories, files and entities are named carefully, we can generalize this even further, and my toplevel firmware makefile now looks like this:

all: zpuromgen Dhrystone.gen SDBootstrap.gen RS232Bootstrap.gen

clean:
    make -C SDBootstrap clean
    make -C RS232Bootstrap clean
    make -C Dhrystone clean
    rm *.vhd

zpuromgen: zpuromgen.c
    gcc -o zpuromgen zpuromgen.c

%.gen:
    make -C $*/
    sed 's/dualportram/$*_ROM/' >$*_ROM.vhd <rom_prologue
    ./zpuromgen $*/$*.bin >>$*_ROM.vhd
    cat >>$*_ROM.vhd rom_epilogue

Leave a Reply

Your email address will not be published. Required fields are marked *