Overview
I took a class last fall on Computer Engineering, where we delved into the intricacies of processor design and tradeoffs, from simple single-cycle processors to 5 stage pipelined processors and beyond. As part of the lab, we implemented a processor in VHDL for the LEGv8 ISA, and in the very first week learning about the basics of the ISA, I knew I wanted to make a simulator for it. Purely for fun (at first), but also to better learn the ISA and as a sanity-check for the results I was getting from my VHDL simulations. I wanted to scratch the engineering-itch.
I had already implemented a simulator for a different ISA previously, but the code was too inflexible to reuse for this (and was a little broken); and while there do exist other CPU simulators out there such as Unicorn, none of them had the simplicity or flexibility I was looking for, and none of them seemed like I could pick them up and just start instantly building out an ISA. Thus, I created ponyo.
Implementation
The original idea behind ponyo was to have a simulator that:
- took in assembly source code
- stepped through program execution
- could jump to break points in the code
- used a Harvard architecture (to match our processors in class and to make implementation easier)
- extendable enough to implement different ISAs, so the next time I want to implement a toy ISA, I don’t need to rewrite an entire simulator from scratch
The simulator was implemented and fleshed out over the course, with more instructions being added, bugs being fixed, and better functionality being implemented as needed. For each ISA, there are 3 files that are implemented:
decode.py
, which decodes the instructions into a format that’s easier to work with (the LEGv8 implementation uses a hash withop
anda1
-a4
to represent the operation and its args), and also defines the functionfindSymbols()
, which searches the code for symbols (i.e.,main:
)execr.py
, which defines the functionexecr(mem, instr)
, that takes in each instruction and memory space one at a time and modifies the memory space depending on the instruction - this is where what each instruction “does” is actually implementedmem.py
, which defines the memory space of the device, such as flags, data memory (RAM), registers, the sizes of the data at each address in data memory/registers, the program counter, and any other program state variables as necessary that completely define the state of the machine (for the LEGv8 implementation, this includes functions for reading and writing from/to both data memory and the registers)
Then there is a simple top-level file ponyo.py
that handles the
command-line interface and calling each of the ISA’s functions.
Usage
Here is a simple example of ponyo in action:
ADDI X16, XZR, 10 // Number of fib iterations
ADDI X15, XZR, 0 // Mem addr for storing
ADDI X5, XZR, 0 // Initial values
ADDI X6, XZR, 1
fib:
ADD X7, X6, X5 // Calc next fib number
ADDI X5, X6, 0
ADDI X6, X7, 0
SUBI X16, X16, 1 // Dec fib iterator
ADDI X0, X5, 0
STUR X0, [X15, 0] // Store fib value
ADDI X15, X15, 8
CBNZ X16, =fib
Performance
Throughout the project, I also investigated how fast the simulator was performing, and whether I could make it faster. One improvement I made that seems obvious now was to decode the source once instead of every instruction execution. Another improvement I made was to run the simulator with pypy3 instead of CPython, which often ran the simulation anywhere from 2x-10x faster, varying based on where I was in development of ponyo. Anyone looking to use ponyo should use pypy3 on performance grounds alone, if available.
Adding a new ISA
The 3 files mentioned above just need to be placed in a subfolder of the main
ponyo folder (/ponyo/ponyo/MY_ISA/{decode,execr,mem}.py
) along with
an __init__.py
, and the necessary imports and connections need to
be made in the main ponyo executable (/ponyo/ponyo.py
). Then
when executing ponyo, you just need to set the proper --isa
flag.
Future improvements
If I ever get around to messing with ISAs again in the future, there are definitely a lot of potential improvements I would want to make to this, including:
- finish implementing all of the LEGv8 ISA
- finally tackling GUIs/TUIs and creating a nice interface for visualizing the source code, memory space, and execution flow
- implementing more ISAs (such as Intel 4004 and EccCPU)
- Von Neumann architecture (instead of just Harvard) as this is what most computers use nowadays
Fishbowl icon created by Becris - Flaticon