A small (50 line) STUMP CPU emulator written in Python along with a quick-and-dirty example "test-bench" program for running STUMP binaries.
Take a look at the code on GitHub, if you're feeling a bit crazy.
The STUMP CPU
The STUMP is a simple 16-bit RISC microprocessor built as an example during the VLSI course at Manchester University. The specification for this can be found in the Lab Manual.
The following shows a demo execution of the emulator running "test program 1".
$ python test.py test1.bin R0 0000 R1 0000 R2 0000 R3 0000 R4 0000 R5 0000 R6 0000 PC 0000 CC=0000 > 000: memory== 1100100000000000 : st r0, [r0, r0] memory== c800 memory = 0000 R0 0000 R1 0000 R2 0000 R3 0000 R4 0000 R5 0000 R6 0000 PC 0001 CC=0000 > 001: memory== 0001000100011111 : add r1, r0, #-1 memory== 111f R0 0000 R1 ffff R2 0000 R3 0000 R4 0000 R5 0000 R6 0000 PC 0002 CC=0000 > r2=0xDEAD r2 =dead > r3=0xBEEF r3 =beef > 002: memory== 1101100100000001 : st r1, [r0, #1] memory== d901 memory = ffff R0 0000 R1 ffff R2 dead R3 beef R4 0000 R5 0000 R6 0000 PC 0003 CC=0000 > ...
The emulator is designed to be consistent with the specification however as it is not a simulator, it is stepped by full fetch-execute-writeback cycles rather than clock cycles.
Size and Style
This roject was an enterprise of code
golf and so the code is not
specifically optimised for speed and simply just for source code size. While the
emulator is correct and accurately emulates all external interfaces, it fits on
50 lines and just under 100 columns (assuming 2-character tabs). A commented
version can be found in
is laid out in a semi-readable way (or at least in the way I developed it) with
is the same file but with all comments and empty lines removed.
I have avoided using "cheats" like using semi-colons to concatenate code onto one line and using list expansion to place multiple assignments on one line (though in the latter case I do this where it is appropriate or intuative). Some areas are spaced very inconsistently and not at all in order to fit within a reasonable line length.
grep this for
eval and you will be disapointed to discover
one. This is used to help compact the representations of branch conditions and
while not strictly legit, it can be considered "safe" as it never executes user
code and most importantly, it does work!
I have run it against the supplied test programs which while broad are not exhaustive and it has passed. While I believe the implementation is correct to the specification, don't rely on it! If you do notice a bug, please feel free to submit a fix (please explain the bug, how to reproduce and explain your fix -- this is code-golf code!) or report it (with a suitable example case).
The emulator is just a library and doesn't do anything by itself. To use it:
- Create a
Memoryobject (passing the size in 16-bit words, usually 1<<16). This memory acts like a list indexable by memory address. Simply load your program into the memory.
- Create a
Stumpobject passing in the memory you created.
- To step the CPU one instruction at a time, use the
- Registers 0-7 can be accessed as
my_stumpis an instance of
ris the register number.
- The condition-codes can be accessed using
my_stumpis an instance of
xis a constant
cwhich are defined in
stumpy.py. Note that in order to change these you may need to set
cc_enin the stumpy instance to
- Memory is obviously accessible by reading the values in the
Memoryinstance you created.
Memory class may be modified to allow memory-mapped IO, for example, or
to print debugging information on accesses.
The Test Interface/Example Program
An example usage of the module is provided in the form of
test.py which is a
(very, very carelessly hacked-together) program which takes an augment of a
STUMP "binary" file (which is actually ASCII 0s and 1s separated in to word
blocks on individual lines) which is loaded and then executed by the emulator.
When you start the program, the STUMP's registers are printed and a prompt is
shown. The register values (
PC) are all in hexadecimal. The
condition code register, 'CC', is displayed in binary with the bits in-order-
Initially these will always be zeroed out on start (unlike real hardware which
may be undefined with the exception of the program counter and
R0 which is
tied to 0).
At the prompt, the following commands are available:
- [nothing]: Just press enter to step the CPU by one instruction.
- m addr: Print out the value in the memory address specified by addr. This
may be any valid Python integer format, e.g.
0b1101. The value printed will be in hexedecimal.
- m addr = val: Set the value at the address given to the specified value, again these numbers may be any valid Python integer.
- r num: Print out the value in the specified register. Registers may be given
by number, i.e. 0-7.
R7may be accessed as
pc. The 'CC' register's bits must be accessed individually by specifying the bit, i.e.,
- r num = val: Set the value of the given register where num and val are formatted as above.
- r: Dump all the registers on to the screen.
Tip: Whitespace is optional in the above arguments, e.g.
r 1 = 0xFFFF.
Note: And yes, numerical values are just passed straight into an eval with no sanity checking -- what-cha gonna' do about it, punk?
When the CPU is stepped, the time (in steps) is printed (in binary) followed by the instruction as loaded from the location indicated by the PC followed by a disassembly of the instruction.
Memory accesses are printed (where
memory[addr]== val indicates a read and
memory[addr] = val indicates a write. Addresses and values are printed in
hexadecimal. Register accesses are currently not printed.
Once the instruction has finished executing the registers are dumped onto the screen and the prompt is shown again.
Assembler and Example Binaries
An assembler and example binaries for the STUMP can be found on department
respectively. Unfortunately they are university property and so I cannot
distribute these -- sorry!