ZPU Evo(lution)
Die ZPU ist ein 32-Bit-Stack-basierter Mikroprozessor, der urspruenglich von Øyvind Harboe von Zylin AS entworfen wurde. Die Originaldokumentation ist auf der Zylin/OpenCore-Website oder Wikipedia zu finden. Es handelt sich um einen Mikroprozessor fuer FPGA-Embedded-Anwendungen mit minimalem Verbrauch an Logikelementen und BRAM, allerdings auf Kosten der Ausfuehrungsgeschwindigkeit.
Zylin erstellte zwei Open-Source-Designs, naemlich die Small- und Medium-ZPU-Versionen. Weitere Designs wurden von externen Entwicklern erstellt, wie die Flex- und ZPUino-Varianten, die jeweils Verbesserungen am Originaldesign bieten, beispielsweise Wishbone-Interface, hoehere Leistung usw.
Dieses Dokument beschreibt ein weiteres Design, das ich als ZPU Evo(lution)-Modell bezeichne, dessen Schwerpunkt auf Leistung, Konnektivitaet und Befehlssatzerweiterung liegt. Dies entstand, weil ich eine CPU fuer einen Emulator eines Vintage-Computers benoetigte, die als IO-Prozessor zur Bereitstellung von Menue-, Peripherie- und SD-Diensten fungieren sollte.
Ein Beispiel fuer die Leistung der ZPU Evo laesst sich anhand von CoreMark ablesen, das einen Wert von 22,2 bei 100MHz auf Altera-Fabric unter Verwendung von BRAM zurueckgibt, und fuer Dhrystone 13,2 DMIPS. Vergleiche koennen mit den Original-ZPU-Designs in der Galerie unten angestellt werden, wobei auf den CoreMark-Score zu achten ist, der mittlerweile als De-facto-Standard gilt. Konnektivitaet zeigt sich durch die Implementierung sowohl eines System- als auch eines Wishbone-Busses, was den Anschluss vieler Open-Source-IP-Geraete ermoeglicht. Befehlssatzerweiterung zeigt sich durch die Einbindung eines eng gekoppelten L1-Caches, in dem mehrere Befehlsbytes bezogen und der CPU zur Verfuegung gestellt werden, was wiederum fuer Optimierungen (z.B. bis zu 5 IM-Befehle in einem Zyklus) oder fuer erweiterte Mehrbyte-Befehle (z.B. Implementierung eines LoaD Increment Repeat-Befehls) genutzt werden kann. Es gibt Raum fuer weitere Verbesserungen wie Stack-Cache, SDRAM-zu-L2-Burst-Modus, parallele Befehlsausfuehrung (z.B. and + neqbranch), die auf meiner Liste stehen.
Die CPU
Die ZPU Evo baut auf der ZPU Medium und Flex auf, und Teile des Codes sind aehnlich, beispielsweise die Befehlsdekodierung. Das Design unterscheidet sich jedoch durch Caching und die Implementierung eines Memory Transaction Processors, ueber den alle Speicher-/IO-Operationen (mit Ausnahme direkter Befehlslesevorgaenge bei aktiviertem Dual-Port-Befehlsbus) geleitet werden. Die Original-CPUs behandelten ihre Speicheranforderungen alle direkt oder als Teil der Zustandsmaschine, waehrend die Evo bei jeder benoetigten Speicheroperation eine Anfrage an den MXP sendet.
Die folgenden Abschnitte beschreiben einige der Funktionen und Aenderungen gegenueber den Original-ZPU-Designs.
Busstruktur
Die ZPU verfuegt ueber einen linearen Adressraum, in dem alle Speicher- und IO-Geraete direkt adressierbar sind. Bestehende ZPU-Designs bieten entweder einen Systembus oder einen Wishbone-Bus, waehrend die Evo beides bereitstellt. Die ZPU Evo erstellt je nach Konfiguration bis zu zwei verschiedene Regionen im Adressraum, um einen Systembus und einen Wishbone-Bus bereitzustellen.
Alle Modelle haben den Systembus implementiert, der bei CPU-Adresse 0 beginnt und sich bis zur Grenze des konfigurierbaren maximalen Adressbits erstreckt (z.B. 0x000000 - 0xFFFFFF fuer 24 Bit). Ein dedizierter Memory-Mapped-IO-Bereich ist am oberen Ende des Adressraums reserviert (obwohl er leicht an einer anderen Stelle platziert werden koennte), z.B. 0xFF0000 - 0xFFFFFF.
Falls konfiguriert, kann ein Wishbone-Bus instanziiert werden, der das maximale Adressbit um 1 erweitert (z.B. 0x1000000 - 0x1FFFFFF fuer das 24-Bit-Beispiel). Dies erzeugt effektiv 2 identische Regionen, wobei die untere ueber den Systembus und die obere ueber den Wishbone-Bus gesteuert wird. Wie beim Systembus ist der obere Bereich des Wishbone-Adressraums fuer IO-Geraete reserviert.
Ein dritter Bus kann konfiguriert werden, der nur fuer Befehlslesevorgaenge bestimmt ist. Dieser Bus spiegelt typischerweise den Systembus im Speicherbereich wider, gilt aber als an Schnellzugriffsspeicher zum Lesen von Befehlen ohne L2-Cache angeschlossen. Dies waere typischerweise der 2. Port eines Dual-Port-BRAM-Blocks, wobei der 1. Port mit dem Systembus verbunden ist.
L1-Cache
Um Leistung zu erzielen, aber insbesondere fuer Befehlsoptimierungen und erweiterte Befehle, ist ein L1-Cache unter Verwendung von Registern implementiert. Die Verwendung von Registern verbraucht Fabric-Platz und sollte daher sehr klein sein, ermoeglicht aber wahlfreien Zugriff in einem einzigen Zyklus, was beispielsweise beim Komprimieren eines 32-Bit-IM-Ladevorgangs (der 5 Befehle umfassen kann) in einen einzigen Zyklus erforderlich ist. Auch bei erweiterten Befehlen zeigt das erste Byte einen erweiterten Befehl an, und die folgenden 1-5 Bytes definieren den Befehl, der dann in einem einzigen Zyklus ausgefuehrt wird.
L2-Cache
Internes BRAM (On-Board Block RAM innerhalb des FPGA) benoetigt keinen L2-Cache, da die Zugriffszeit 1-2 Zyklen betraegt. Da BRAM eine begrenzte Ressource ist, wird angenommen, dass externes RAM oder SDRAM verwendet wird, das wesentlich langsamer ist und zwischengespeichert werden muss, um den Durchsatz zu erhoehen. Der L2-Cache wird fuer diesen Zweck verwendet, um einen Block externen RAMs vorab zu lesen und den L1-Cache bei Bedarf zu fuettern. Bei der Analyse zeigt sich, dass die von GCC generierten C-Programme typischerweise Schleifen und Aufrufe innerhalb eines lokalen Bereichs sind (es sei denn, grosse Bibliotheken werden verwendet), sodass die Implementierung eines einfachen Direct-Mapping-Caches zwischen externem RAM und BRAM (fuer den L2-Cache verwendet), relativ zum Programmzaehler indiziert, ausreicht, um ein Blockieren der CPU in den meisten Faellen zu verhindern.
Befehlssatz
Ein Merkmal der ZPU ist die Verwendung eines minimalen festen Satzes hardwareimplementierter Befehle und eines Soft-Satzes zusaetzlicher Befehle, die in Pseudo-Mikrocode (d.h. dem festen Befehlssatz) implementiert sind. Dies wird durch 32-Byte-Vektoren im Bereich 0x0000 - 0x0400 erreicht, und jeder Soft-Befehl verzweigt zum Vektor, wenn er nicht in Hardware implementiert ist. Der Vorteil ist die Reduzierung der FPGA-Ressourcen, der Nachteil ist die Leistung.
Die ZPU Evo implementiert alle Befehle in Hardware, dies kann jedoch in der Konfiguration angepasst werden, um bei Bedarf Soft-Befehle zu verwenden und so FPGA-Ressourcen zu schonen. Dies ermoeglicht ein Gleichgewicht zwischen Ressourcen und Leistung. Wenn die Ressourcen jedoch knapp sind, kann die Verwendung der Small/Flex-ZPU-Modelle die bessere Wahl sein.
Zusaetzlich zu den urspruenglichen Befehlen existiert ein Mechanismus zur Erweiterung des Befehlssatzes mittels Mehrbyte-Befehlen im Format:
Extend Instruction,<new insn[7:2]+ParamSize[1:0]>,[byte],[byte],[byte],[byte]
Wobei ParamSize =
- 00 - Keine Parameterbytes
- 01 - 8-Bit-Parameter
- 10 - 16-Bit-Parameter
- 11 - 32-Bit-Parameter
Einige erweiterte Befehle befinden sich in der Entwicklung (z.B. LDIR); ein exakter Opcode-Wert und erweiterter Befehlssatz wurden noch nicht vollstaendig definiert. Der GNU AS Assembler wird mit diesen Befehlen aktualisiert, damit sie in einem C-Programm aufgerufen werden koennen, und spaeter, wenn sie fuer C von Nutzen sind, in den GCC-Compiler migriert werden (z.B. ADD32/DIV32/MULT32/LDIR/LDDR).
Implementierter Befehlssatz
| Name | Opcode | Description |
|---|---|---|
| ADD | 00000101 | Pops two values on stack adds them and pushes the result |
| ADDSP | 0001xxxx | Add value of memory location SP+xxxx*4 to value on top of stack. |
| AND | 00000110 | Pops two values off the stack and does a bitwise-and & pushes the result onto the stack |
| ASHIFTLEFT * | 00101011 | arithmetic(signed) shift left. long shift; long valX; shift = ((long) popIntStack()) & INTMASK; valX = ((long) popIntStack()) & INTMASK; int t = (int) (valX « (shift & 0x3f)); pushIntStack(t); |
| ASHIFTRIGHT * | 00101100 | arithmetic(signed) shift left. long shift; int valX; shift = ((long) popIntStack()) & INTMASK; valX = popIntStack(); int t = valX » (shift & 0x3f); pushIntStack(t); |
| BREAKPOINT | 00000000 | The debugger sets a memory location to this value to set a breakpoint. Once a JTAG-like debugger interface is added, it will be convenient to be able to distinguish between a breakpoint and an illegal(possibly emulated) instruction. |
| CALL * | 00101101 | call procedure. int address = pop(); push(pc + 1); setPc(address); |
| CALLPCREL * | 00111111 | call procedure pc relative int address = pop(); push(pc + 1); setPc(address+pc); |
| DIV * | 00110101 | Signed 32 bit integer divide. a = popIntStack(); b = popIntStack(); if (b == 0) { // undefined } pushIntStack(a / b); |
| EMULATE | 001xxxxx | Push PC to stack and set PC to 0x0+xxxxx*32. This is used to emulate opcodes. Emulated Opcodes are marked with a star (*) in this table. |
| EQ * | 00101110 | pushIntStack((popIntStack() == popIntStack()) ? 1 : 0); |
| EQBRANCH * | 00110111 | int compare; int target; target = popIntStack() + pc; compare = popIntStack(); if (compare == 0) { setPc(target); } else { setPc(pc + 1); } |
| ESR | E00000000 | Copy Extended Status Register to TOS. Bit 31 : 1 = reserved. Bit 0 = Background Transfer in Progress (1). |
| EXTEND | 00001111 | Extended instruction set. Byte following this instruction represents the new instruction. |
| FIADD32 * | 00111010 | Fixed point (Q15) addition. TOS and NOS are added and the result placed in TOS. |
| FIDIV32 * | 00111011 | Fixed point (Q15) division. TOS is the dividend, NOS is the divisor, result is placed in TOS. |
| FIMULT32 * | 00111100 | Fixed point (Q15) multiplication. TOS is multiplied by NOS and the result is placed in TOS. |
| FLIP | 00001010 | Reverses the bit order of the value on the stack, i.e. abc->cba, 100->001, 110->011, etc. The raison d’etre for this instruction is mainly to emulate other instructions. |
| IM | 1xxxxxxx | Pushes 7 bit sign extended integer and sets the a instruction decode interrupt mask flag(IDIM). If the IDIM flag is already set, this instruction shifts the value on the stack left by 7 bits and stores the 7 bit immediate value into the lower 7 bits. Unless an instruction is listed as treating the IDIM flag specially, it should be assumed to clear the IDIM flag. To push a 14 bit integer onto the stack, use two consecutive IM instructions. If multiple immediate integers are to be pushed onto the stack, they must be interleaved with another instruction, typically NOP. |
| LDIR | E00001yxx | LoaD Increment Repeat, copies <n> words of memory from source to destination. TOS = Source Address, NOS = Destination Address, xx = bytes to transfer where ‘01’ = 8 bit parameter, ‘10’ = 16 bit parameter, ‘11’ = 32 bit parameter. y = mode of operation, ‘0’ = CPU waits for completion, ‘1’ = Transfer operates in background. If a previous transfer is operating in the background, CPU waits for completion prior to executing instruction. Consult ESR for current status of background execution. |
| LESSTHAN * | 00100100 | Signed comparison a = popIntStack(); b = popIntStack(); pushIntStack((a < b) ? 1 : 0); |
| LESSTHANOREQUAL * | 00100101 | Signed comparison a = popIntStack(); b = popIntStack(); pushIntStack((a <= b) ? 1 : 0); |
| LOAD | 00001000 | Pops address stored on stack and loads the value of that address onto stack. Bit 0 and 1 of address are always treated as 0(i.e. ignored) by the HDL implementations and C code is guaranteed by the programming model never to use 32 bit LOAD on non-32 bit aligned addresses(i.e. if a program does this, then it has a bug). |
| LOADB * | 00110011 | 8 bit load instruction. Really only here for compatibility with C programming model. Also it has a big impact on DMIPS test. pushIntStack(cpuReadByte(popIntStack())&0xff); |
| LOADH * | 00100010 | 16 bit load instruction. Really only here for compatibility with C programming model. pushIntStack(cpuReadWord(popIntStack())); |
| LOADSP | 011xxxxx | Push value of memory location SP+xxxxx*4, where xxxxx is a positive integer, onto stack. |
| LSHIFTRIGHT * | 00101010 | unsigned shift right. long shift; long valX; int t; shift = ((long) popIntStack()) & INTMASK; valX = ((long) popIntStack()) & INTMASK; t = (int) (valX » (shift & 0x3f)); pushIntStack(t); |
| MOD * | 00110110 | Signed 32 bit integer modulo. a = popIntStack(); b = popIntStack(); if (b == 0) { // undefined } pushIntStack(a % b); |
| MULT * | 00101001 | Signed 32 bit multiply pushIntStack(popIntStack() * popIntStack()); |
| NEG * | 00110000 | pushIntStack(-popIntStack()); |
| NEQ * | 00101111 | pushIntStack((popIntStack() != popIntStack()) ? 1 : 0); |
| NEQBRANCH * | 00111000 | int compare; int target; target = popIntStack() + pc; compare = popIntStack(); if (compare != 0) { setPc(target); } else { setPc(pc + 1); } |
| NOP | 00001011 | No operation, clears IDIM flag as side effect, i.e. used between two consecutive IM instructions to push two values onto the stack. |
| NOT | 00001001 | Bitwise inverse of value on stack |
| OR | 00000111 | Pops two integers, does a bitwise or and pushes result |
| POPPC | 00000100 | Pops address off stack and sets PC |
| POPPCREL * | 00111001 | setPc(popIntStack()+getPc()); |
| POPSP | 00001101 | Pops value off top of stack and sets SP to that value. Used to allocate/deallocate space on stack for variables or when changing threads. |
| PUSHPC | emulated | Pushes program counter onto the stack. |
| PUSHSP | 00000010 | Pushes stack pointer. |
| PUSHSPADD * | 00111101 | a=sp; b=popIntStack()*4; pushIntStack(a+b); |
| STORESP | 010xxxxx | Pop value off stack and store it in the SP+xxxxx*4 memory location, where xxxxx is a positive integer. |
| STORE | 00001100 | Pops address, then value from stack and stores the value into the memory location of the address. Bit 0 and 1 of address are always treated as 0 |
| STOREB * | 00110100 | 8 bit store instruction. Really only here for compatibility with C programming model. Also it has a big impact on DMIPS test. addr = popIntStack(); val = popIntStack(); cpuWriteByte(addr, val); |
| STOREH * | 00100011 | 16 bit store instruction. Really only here for compatibility with C programming model. addr = popIntStack(); val = popIntStack(); cpuWriteWord(addr, val); |
| SUB * | 00110001 | int a=popIntStack(); int b=popIntStack(); pushIntStack(b-a); |
| ULESSTHAN * | 00100110 | Unsigned comparison long a; long b; a = ((long) popIntStack()) & INTMASK; b = ((long) popIntStack()) & INTMASK; pushIntStack((a < b) ? 1 : 0); |
| ULESSTHANOREQUAL* | 00100111 | Unsigned comparison long a; long b; a = ((long) popIntStack()) & INTMASK; b = ((long) popIntStack()) & INTMASK; pushIntStack((a <= b) ? 1 : 0); |
| XOR * | 00110010 | pushIntStack(popIntStack() ^ popIntStack()); |
E = Extended instruction, prefixed by EXTEND opcode.
* = Emulated instruction if not implemented in hardware.
Vergleichstabelle implementierter Befehle

Hardware Variable Byte Write
In den Original-ZPU-Designs gab es zwar Spielraum, aber keine Implementierung fuer Byte-/Half-Word-/Full-Word-Schreiboperationen der ZPU. Die CPU musste entweder immer 32-Bit-wortausgerichtete Operationen durchfuehren oder die Operation in Mikrocode ausfuehren.
In der Evo wurde Hardware implementiert (zur Build-Zeit auswaehlbar), die Byte- und Half-Word-Schreiboperationen sowie Hardware-Read-Update-Write-Operationen ermoeglicht. Wenn die Hardware-Byte/Half-Word-Logik nicht aktiviert ist, wird auf die 32-Bit-Word-Read-Update-Write-Logik zurueckgegriffen. Beide Methoden haben Leistungsvorteile, wobei letztere 3 Zyklen laenger benoetigt.
Hardware Debug Serializer
Um die CPU zu debuggen oder einfach interne Betriebsinformationen auf niedrigem Niveau bereitzustellen, ist ein gepuffertes UART-Debug-Modul implementiert. Derzeit ist dies nur fuer die Ausgabe vorgesehen, soll aber an den IOCP fuer In-situ-Debugging angebunden werden, wenn Simulation/Signal-Tap nicht verfuegbar ist.
Eingebettet im CPU-RTL sind auswaehlbare pegelgetriggerte Anweisungen, die Snapshot-Informationen an den Serializer senden. Die Anweisungen werden erweitert, serialisiert und an ein angeschlossenes Terminal ausgegeben. Ein Snapshot der Ausgabeinformationen ist unten zu sehen (mit manuellen Kommentaren):
| 000477 01ffec 00001ae4 00000000 70.17 04770484 046c047c 08f0046c 0b848015 17700500 05000500 05001188 11ef2004 Break Point - Illegal instruction PC Stack TOS NOS Insn Signals Signals Signals Signals L1 Insn Q L1 Insn Q L1 Insn Q L1 Insn Q |
Alle kritischen Informationen wie der aktuell ausgefuehrte Befehl (oder nicht, wenn blockiert), Signale/Flags, L1/L2-Cache-Inhalte und Speicherinhalte koennen ausgegeben werden.
Timing-Constraints
Dies ist in Arbeit. Ich aktualisiere schrittweise das Design und/oder fuege Constraints hinzu, damit das Timing vollstaendig eingehalten wird. Derzeit gibt es bei 100MHz negativen Slack, obwohl das Design voll funktionsfaehig ist. Dies wird in Zukunft korrigiert, sodass das von TimeQuest analysierte Timing eingehalten wird.
System On a Chip
Um ein funktionierendes Framework bereitzustellen, in dem die ZPU Evo verwendet werden kann, wurde ein System-On-a-Chip-Wrapper erstellt, der die Instanziierung verschiedener Geraete (z.B. UART/SD-Karte) ermoeglicht.
Im Rahmen der Entwicklung wurden die ZPU Small/Medium/Flex-Modelle in das Framework integriert, was die Wahl der CPU ermoeglicht, wenn Fabric-Platz knapp ist oder CPUs verglichen werden, auch wenn Funktionen wie Wishbone bei den Original-ZPU-Modellen nicht verfuegbar sind. Ich habe ZPUino nicht einbezogen, da dieses Design bereits ueber ein sehr gutes Oekosystem verfuegt.
Das SoC implementiert derzeit (im Build-Tree):
| Component | Option | Comment |
|---|---|---|
| CPU | Yes | ZPU Small, Medium, Flex, Evo or Evo Minimal. |
| Wishbone Bus | Yes | 32 bit Wishbone bus. |
| (SB) BRAM | Yes | Implement a configurable block of BRAM as the boot loader and stack. |
| Instruction Bus BRAM | Yes | Enable a separate bus (or Dual-Port) to the boot code implemented in BRAM. |
| (SB) RAM | Yes | Implement a block of BRAM as RAM, separate from the BRAM used for the boot loader/stack. |
| (SB) SDRAM | Yes | Implement an SDRAM controller on the system bus. |
| (WB) SDRAM | Yes | Implement an SDRAM controller over the Wishbone bus. |
| (WB) RAM | Yes | Implement a block of BRAM as RAM over the Wishbone bus. |
| (WB) I2C | Yes | Implements an I2C Controller over the Wishbone bus. |
| (SB) Timer 0 | No | Implements a hardware 12bit Second, 18bit milliSec and 24bit uSec down counter with interrupt, a 32bit milliSec up counter with interrupt and a YMD HMS Real Time Clock. |
| (SB) Timer 1 | Yes | A selectable number of pre-scaled 32bit down counters. |
| (SB) UART 0 | No | A cached UART used for monitor output and command input/program load. |
| (SB) UART 1 | No | A cached UART used for software (C program)/hardware (ZPU debug serializer) output. |
| (SB) Interrupt Controller | Yes | A prioritized configurable (# of inputs) interrupt controller. |
| (SB) PS2 | Yes | A PS2 Keyboard and Mouse controller. |
| (SB) SPI | Yes | A configurable number of Serial Peripheral Interface controllers. |
| (SB) SD | Yes | A configurable number of hardware based SPI SD controllers. |
| (SB) IOCTL | Yes | An IOCTL bus controller for MiSTer HPS-to-FPGA communication. |
| (SB) SOCCFG | Yes | A set of registers to indicate configuration of the ZPU and SoC to the controlling program. |
Innerhalb der SoC-Konfiguration koennen Elemente wie Startadresse des Stacks, Reset-Vektor, IO-Start/Ende (SB) und (WB) festgelegt werden. Mit dem Wishbone-Bus lassen sich sehr einfach weitere OpenCore-IP-Geraete hinzufuegen; fuer den Systembus kann etwas Anpassungsarbeit erforderlich sein, da die OpenCore-IP-Geraete unterschiedliche Signale verwenden.
SDRAM
Das SoC unterstuetzt SDRAM ueber zwei unabhaengige Pfade: einen Systembus-(SB)-Controller (SOC_IMPL_SDRAM) und einen Wishbone-Bus-(WB)-Controller (SOC_IMPL_WB_SDRAM). Beide sind standardmaessig deaktiviert und koennen unabhaengig aktiviert werden. Die SDRAM-Timing-Parameter (tRCD, tRP, tRFC, tREF) und Geometrie (Rows, Columns, Banks, Datenbreite) sind alle in zpu_soc_pkg.vhd konfigurierbar. Der WB-SDRAM-Controller ist eine gecachte Variante mit Burst-Zugriff und wird empfohlen, wenn beide Busse aktiv sind und externe RAM-Bandbreite kritisch ist.
Software
Die mitgelieferte Software umfasst:
- Einen Bootloader, I/O Control Program (IOCP). Dies ist mehr als ein Bootloader; in seiner Grundform kann es eine Anwendung von einer SD-Karte bootstrappen, oder es kann Kommandozeilen-Monitor-Tools und eine serielle Upload-Funktion enthalten.
- Eine Anwendung, ZPUTA (ZPU Test Application). Dies ist eine Testsuite und kann als Einzelanwendung organisiert werden oder in ein Festplattenbetriebssystem aufgeteilt werden, in dem alle Funktionen auf der SD-Karte gespeichert sind. ZPUTA kann durch IOCP gebootstrapped oder standalone als einziges Programm im ROM/BRAM betrieben werden.
- Ein Festplattenbetriebssystem, zOS (ZPU Operating System). Eine Version von ZPUTA, die auf Produktionscode ausgerichtet ist, wobei alle Funktionen als Festplattenanwendungen vorliegen.
- Bibliotheksfunktionen in C zur Unterstuetzung bei der Anwendungsentwicklung, einschliesslich Bibliotheken von Drittanbietern, z.B. FatFS von El. Chan.
21/04/2020: Die Software fuer die ZPU wurde mit dem tranZPUter zusammengefuehrt und wird im zSoft-Repository gepflegt.
Konfiguration
Dieser Abschnitt zeigt, wie die ZPU und das SoC konfiguriert werden, entweder um die ZPU separat oder als Teil des mitgelieferten SoC zu verwenden.
CPU konfigurieren
Die CPU ist ueber die Konfigurationsdatei ‘cpu/zpu_pkg.vhd’ konfigurierbar. Sie legt im Wesentlichen die Groesse des Adressbusses fest und welche Hardware-Funktionen aktiviert werden sollen. Die folgende Tabelle beschreibt die konfigurierbaren Optionen.
| Configuration Variable | Model | Values | Description | |
|---|---|---|---|---|
| EVO_USE_INSN_BUS | Evo | true/false | Use a separate instruction bus to connect to the BRAM memory. All other Memory and I/O operations will go over the normal bus. This option is primarily used with Dual Port BRAM, one side connected to the Instruction Bus the other side to the standard bus and will give a significant performance boost when the executed code is in this memory. | |
| EVO_USE_HW_BYTE_WRITE | Evo | true/false | This option implements hardware writing of bytes, reads are always 32bit and aligned. | |
| EVO_USE_HW_WORD_WRITE | Evo | true/false | This option implements hardware writing of 16bit words, reads are always 32bit and aligned. | |
| EVO_USE_WB_BUS | Evo | true/false | Implement the wishbone interface in addition to the system bus. | |
| DEBUG_CPU | All | true/false | Enable CPU debugging output. | |
| DEBUG_LEVEL | All | 0 to 5 | Level of debugging output. 0 = Basic, such as Breakpoint, 1 =+ Executing Instructions, 2 =+ L1 Cache contents, 3 =+ L2 Cache contents, 4 =+ Memory contents, 5=+ Everything else. | |
| DEBUG_MAX_TX_FIFO_BITS | All | 2 .. ~16 | Size of UART TX Fifo for debug output. | |
| DEBUG_MAX_FIFO_BITS | All | 2 .. ~16 | Size of debug output data records fifo. | |
| DEBUG_TX_BAUD_RATE | All | Any Baud integer value | This option sets the output Baud rate of the debug serializer transmitter, ie. 115200 | |
| maxAddrBit | All | <16..31n> + WB_ACTIVE | This option sets the width of the address bus. |
SoC konfigurieren
Das System on a Chip ist ueber die Konfigurationsdatei ‘zpu_soc_pkg.vhd’ konfigurierbar. Die folgende Tabelle beschreibt die Optionen, die konfiguriert werden koennen, um das SoC an eine bestimmte Anwendung anzupassen.
Die vollstaendigen Konfigurationstabellen fuer CPU-Auswahl, Frequenzen, CPU-IDs, Cache-Konfiguration, IO-Geraete-Einstellungen, SoC-Optionen, BRAM, RAM, SDRAM, Wishbone-SDRAM, Instruction-BRAM, CPU-Initialisierung und Evo-spezifische Hardwarebefehle sind identisch mit der englischen Version und werden dort im Detail erlaeutert.
Build
Dieser Abschnitt zeigt, wie ein einfacher Build erstellt wird, und geht davon aus, dass das Ziel-Entwicklungsboard das QMTECH Cyclone V Board ist. Es gibt viele Konfigurationsoptionen, die jedoch separat behandelt werden.
Der empfohlene Weg, die ZPU zu bauen, ist das automatisierte Setup-Skript fuer Ihre Plattform (siehe Automatisiertes Setup und Build direkt unten) — es installiert die gesamte Toolchain (Intel Quartus Prime Lite 17.1 und, wo erforderlich, Docker), klont das Repository, laesst Sie das Zielboard und die CPU-Variante auswaehlen und baut den FPGA-Bitstream vollstaendig, ohne dass tiefgehende Kenntnisse der Tools erforderlich sind. Die manuellen Schritte Software-Build und RTL-Bitstream-Build weiter unten sind fuer fortgeschrittene Benutzer und Teil-Rebuilds gedacht.
Automatisiertes Setup und Build (empfohlen)
Wenn Sie lediglich das Repository ziehen und einen FPGA-Bitstream erzeugen moechten, ohne die gesamte Toolchain zu erlernen, verwenden Sie die mitgelieferten Setup-Skripte. Sie installieren alles Noetige (Intel Quartus Prime Lite 17.1 und, wo erforderlich, Docker), klonen bei Bedarf das Repository, lassen Sie Zielboard und CPU auswaehlen und bauen den Bitstream vollstaendig. Jedes Skript ist eigenstaendig: Kopieren Sie einfach die einzelne Datei fuer Ihre Plattform und fuehren Sie sie aus. Verifiziert auf Windows (nativ), macOS (Intel, via Docker) und Linux.
| Skript | Plattform | Hinweise |
|---|---|---|
setup_ZPU_windows.cmd |
Windows 10 / 11 | Empfohlen unter Windows — Starter zum Doppelklicken. Fuehrt das PowerShell-Setup aus, haelt das Fenster offen und protokolliert alles nach setup_ZPU_log.txt. Native Installation, kein WSL. |
setup_ZPU_windows_native.ps1 |
Windows 10 / 11 | Das zugrundeliegende PowerShell-Skript (bei Bedarf direkt aus einem offenen PowerShell-Fenster ausfuehrbar). Installiert Git for Windows + Quartus 17.1 via winget / Intel-Installer. |
setup_ZPU.sh |
Linux / macOS | Natives Quartus unter Linux (Standard); ein eigenstaendiges Headless-Quartus-Docker-Image unter macOS (kein natives macOS-Quartus vorhanden — nur Intel-Macs, da Quartus x86-64 ist). |
build_zpu.sh |
alle | Portabler Build-Wrapper, den die Setup-Skripte verwenden. Jederzeit zum Neubauen erneut ausfuehrbar: ./build_zpu.sh [BOARD] [CPU]. |
Windows 10 / 11 (nativ, kein WSL) — setup_ZPU_windows.cmd doppelklicken, oder aus einem offenen PowerShell-Fenster:
# optional: build a specific repo (default is the public https://git.eaw.app/eaw/zpu.git)
$env:ZPU_REPO_URL = "https://git.eaw.app/eaw/zpu.git"
Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass
.\setup_ZPU_windows_native.ps1
macOS / Linux — aus einem Terminal:
chmod +x setup_ZPU.sh
./setup_ZPU.sh
Jedes Skript fragt nach Entwicklungsboard und CPU-Variante (Standard QMV / EVO) und, sofern nicht aus einem Checkout heraus gestartet, wohin das Repository geklont werden soll. Nach Abschluss finden Sie im build/-Verzeichnis build/<BOARD>_<CPU>.sof (FPGA-Bitstream) und build/<BOARD>_<CPU>.rbf (rohes, komprimiertes Binaerformat).
| Boards | CPUs |
|---|---|
QMV (Standard, QMTECH Cyclone V), DE10_nano, DE0_nano, E115 (Cyclone IV E EP4CE115), CYC1000 (Cyclone 10 LP) |
EVO (Standard), EVO_MINIMAL, FLEX, MEDIUM, SMALL |
Zum spaeteren Neubauen (ohne das Setup erneut auszufuehren) rufen Sie nach installiertem Quartus direkt den portablen Wrapper auf, z.B. ./build_zpu.sh E115 EVO oder ./build_zpu.sh --list; unter Windows Git-Bash zuvor die Quartus-Tools auf den PATH legen: export PATH="/c/altera/17.1/quartus/bin64":$PATH. Optionale Umgebungsvariablen steuern das Verhalten, u.a. ZPU_REPO_URL (zu klonendes Repository), ZPU_BOARD / ZPU_CPU (ueberspringt die Abfragen), ZPU_RTL_METHOD (native oder docker), ZPU_DIR (in bestehendem Checkout bauen) und ZPU_QUARTUS_BIN (vorhandenes Quartus-bin/bin64), etwa ZPU_RTL_METHOD=docker ./build_zpu.sh E115 EVO.
Der Rest dieses Abschnitts beschreibt den zugrundeliegenden manuellen Build fuer alle, die die Tools lieber von Hand ausfuehren.
Software-Build
Jenkins kann fuer die Automatisierung des Builds verwendet werden, aber fuer eine einfache Kompilierung verwenden Sie das build.sh-Skript und das hierarchische Makefile-System gemaess den folgenden grundlegenden Anweisungen.
- Laden Sie die ZPU GCC ToolChain herunter und installieren Sie sie. Installieren Sie in /opt oder einem aehnlichen gemeinsamen Verzeichnis.
- Richten Sie den Umgebungsvariablenpfad ein.
export PATH=$PATH:/opt/zpu/bin - Klonen Sie das ZPU Evo-Repository.
- Bearbeiten Sie die Datei <zpu evo dir>/software/zputa/zputa.h und waehlen Sie die Funktionen aus, die in das ZPUTA-Core-Image eingebaut werden sollen (standardmaessig werden alle Funktionen als Applets erstellt, diese werden jedoch ignoriert, wenn sie in das ZPUTA-Core-Image eingebaut sind). Sie waehlen eine Funktion aus, indem Sie BUILTIN_
auf '1' setzen; setzen Sie auf '0', wenn Sie sie nicht einbauen moechten. - Entscheiden Sie, welche Memory-Map Sie moechten und ob ZPUTA eine Anwendung oder ein Bootloader sein soll, und geben Sie den Build-Befehl ein.
cd <zpu evo dir>/software ./build.sh -I 3 -O zputa -o 2 -B 0x1000 -A 0xC000 - Legen Sie eine SD-Karte in Ihr System ein, formatieren Sie sie als FAT32 und kopieren Sie die Dateien darauf.
cd build/SD cp -r * <abs path to SD card, ie. /media/psmart/ZPU>
RTL-Bitstream-Build
Um den FPGA-Bitstream zu erstellen (Umwandlung von HDL in eine Konfigurationskarte fuer das FPGA), gibt es zwei Methoden:
- Installieren Sie Intel Quartus Prime 17.1 oder neuer.
- Oeffnen Sie Quartus Prime und laden Sie das Projekt (File -> Open Project) und waehlen Sie <zpu evo dir>/build/QMV_zpu.qpf
- Kompilieren (Processing -> Start)
alternativ:-
- Installieren Sie Intel Quartus Prime 17.1 oder neuer.
- Verwenden Sie das Makefile-Build-System mit den folgenden Befehlen.
cd <zpu evo dir>/build make QMV_EVO
Die Build-Anweisungen fuer ZPU Small, Medium, Flex und Evo sowie Hinweise zur Einrichtung eines neuen Entwicklungsboards finden Sie in der englischen Version dieses Dokuments.