Posted on 8 Comments

ZPU next generation – pipelined

Working with various ZPU adaptations for quite a while, some feature requests came up for which there was no immediate solution except swapping out the ZPU core. In the past months, quite a number of SoC designs turned out to be workarounds which were sufficient for its purpose, but were prognosed to be a maintenance nightmare.

Long story short: I decided to give it a new go. This time in MyHDL. If you’re not aware of this developer’s gem, check out the official website. It has its quirks, but it is yet the best tool to design a CPU, due to Python’s extensive test benching features. Verification and validation of a CPU is way easier than using the classical VHDL/Verilog approach.

Pipelining the ZPU

The ZPU is a pure stack machine, therefore sorting out the register hazards like on the MIPS architecture is kinda simple. The first approach however does not use a separate fetch stage, therefore branch penalties are not too bad for the moment (although sacrificing some clock speed). This design introduced “shortcuts”: When a pop() instruction is following a push(), the value does not need to be fetched from the stack explicitely but can be bypassed by a ‘write register’. There are some minor but nasty exceptions┬á that need to be sorted out, but these turned out to require not too much logic.

Eventually, parts of the design were borrowed from another very basic VLIW concept I used on a MIPS16 clone (made a while ago) and ZPU instructions just translate to VLIW sequences. Operations like LOAD that don’t pipeline well due to possible I/O stalls are just implemented as VLIW ‘micro code’.

Differences to ‘classic’ ZPU4/Zealot ‘small’

In the original ZPU small, quite some traffic occured between core and the shared memory (program, data, stack). The ZPUng introduces some changes:

  • Separate stack memory: This allows a pure register (distributed RAM) synthesis for higher speed. Plus, the stack cannot trash the program code
  • Shared Program/Data: This is required to be compatible to the Zealot ‘Phi’ programs. However, traffic is reduced and the writes are delayed (writeback stage). ZPUng v0.2 implements instruction prefetching and DMA access to the program/data memory
  • Optional: Allow usage of pseudo dual port memory on very small FPGAs.
  • A read immediately following a write is a classic hazard scenario which is handled by bypass logic on the stack memory. On the prog/data memory, it is not relevant on the ZPU.

DMA access could already be implemented on the ZPU4 using a specific DMA capable memory block.

I had implemented ICE debugging for the Zealot, using our in-house “StdTAP” interface that is running on a few native JTAG primitives of various FPGA vendors. The new ZPUng should of course behave likewise. Since an ICE event is handled like an exception on high priority, a bit of logic had to be added.

Handling events: IRQ and emulation

This is the harder part: The existing ZPU and the same program code with IRQ handlers is required to work likewise on the new ‘ZPUng’ (working title). However, there are a few extras: By using an external System Interrupt Controller (SIC), we get more control over generating interrupt vectors. Remember, the standard ZPU4 or Zealot in its “phi” configuration has only one interrupt channel and vector. In the ZPUng, we take an external interrupt vector from the SIC which can be configured using the peripheral I/O (memory mapped registers; MMR). Because the ZPU is a stack machine, no specific “return from exception” command is required, therefore it is very simple to register an IRQ: Just set the interrupt vector register ‘n’ of the SIC to a C function address handler.

The very tricky part is, to make interrupt handling work together with the on chip debugger (In Circuit Emulation aka ICE). There a few boundary conditions:

  • IRQs don’t interrupt inside a “IM” (immediate load) sequence. Therefore, no fixed IRQ latency possible, but IM sequences are always atomic
  • IRQs can interrupt inside a single step ICE session

Another feature of the IRQ enhancements: Typically, an IRQ handler acknowledges the interrupt request to the SIC, allowing another interrupt to occur. If this happens before the IRQ routine is actually ended, it will re-enter itself and trash the stack, eventually. This is avoided on the ZPUng by clearing the corresponding IPEND flag just before the final return (POPPC). The logic sets the IRQACK flag (which prevents reentrance) to the IRQ state on every branch instruction. So interrupt routines are not reentrant when following this scheme. Reentrance could be enabled by nasty hacks messing with the SIC configuration.

IRQ redesign rev1

In order to re-enable IRQ reentrancy and allowing IRQ priorisation through the SIC, the interrupt handling was redesigned in ZPUng v1 such that higher prioritised IRQs can interrupt a current interrupt handler. Other implementations had made use of a POPINT opcode – the same is now happening here, with one exception: It just clears the flag, return is still done by a POPPC. This makes the code easier to handle. IPEND flags are now cleared at the beginning of the IRQ handler and a final IRQ_REARM() macro clears the internal IRQ acknowledge.

The SIC was changed such that recurring IRQs with lower priority don’t cause another “dingdong” on the IRQ pin.

Resource usage

For example, the ZPUng ‘small’ (compatible to the ‘phi’ config) alone was synthesized in two configurations for a MACHXO2 7000 with the following resource usage:

Speed optimized (max. 32 MHz as SoC) : LUT: 906 Registers: 153  SLICE: 453
Area optimized (max. 25 Mhz as SoC)  : LUT: 745 Registers: 152  SLICE: 361

This SoC configuration just uses a system interrupt controller plus a 2×16 GPIO bank as peripherals. Complex peripherals on the Wishbone bus would slow down the design further due to logic congestion of the current architecture.

Synthesis on the Papilio Spartan3-250k platform produces quite similar results, however the CPU is running a few MHz faster. This is very likely due to the Xilinx architecture being a little faster on the block RAM side.

Development Roadmap

  • Releases: The ZPUng is available as generated, synthesizable VHDL.
  • Full source release is not on the table for now, I’m afraid. One reason is that some modifications of MyHDL took place that will have to be merged back some day.
  • Speedup optimizations: There is not much in for it, and changes to the pipeline will increase logic elements. This is something where other CPU architectures perform better.
  • Update: The long term development is kinda unsure, as the GCC port of the ZPUng is not very much maintained. The future focus – I’m afraid – will be on compact RISC-V derivatives.

8 thoughts on “ZPU next generation – pipelined

  1. This is an excellent summary of how to approach pipelining the ZPU. What is the status of this work? Have you released it?
    Thank you,

    1. The ZPUng is part of the MaSoCist ‘beatrix’ reference design and is ready for evaluation by interested parties under NDA. See MaSoCist reference design for more information.

  2. How mature is this design? Could you provide it for ASIC synthesis? We are looking for a very tiny and power saving architecture. Just curious.

    1. Hi, it has passed quite a few verification tests and is technology invariant, however I never got into synth’ing it for an ASIC. Especially all the peripherals are very tricky (and those are in fact FPGA specific..). So I’m afraid this would be very hard to support without external help.

  3. Very interesting!

    Have you released the source yet?

    1. Sorry for the late reply..
      I’m afraid I haven’t made it past the OpenSource build system release ( with the legacy OpenSource Zealot ZPU.
      However, there’s real hardware:
      If someone wishes to synthesize the code for another platform, it is possible to find an individual source code sharing agreement.

      Edit: Tar outdated, see below

  4. Thanks anyway!

    I know It can easily become a big, fairly thankless job to clean up code sufficiently for a decent open-source release (especially where you’re using it for some specific application purpose which isn’t all that general, and may contain unrelated details to be cleaned out. Or just a mess of trivial git commits which ought to be combined together and cleaned out into a reasonable series of clear change sets before cluttering up the shared timeline for perpetuity!).

    BTW, that .tgz is actually just a .tar — must have forgotten the -z flag for tar to also compress it! Thankfully, it’s recognised as a valid tarball after renaming it.

  5. For the completeness of it:

    A ZPUng ‘VHDL’ build is found in this repo:

    Due to issues with current (flaky) MyHDL distributions the original python code is not released, it would not translate correctly. It’s on a low priority right now, so no promises made.

Leave a Reply

Your email address will not be published.

This site uses Akismet to reduce spam. Learn how your comment data is processed.