Currently when testing our simpleCPU code we have two choices ISim (figure 1) or CPUSim (figure 2). Both have their advantages and disadvantages. ISim is a low level simulator, allowing cycle accurate simulations, allowing a user to judge what will happen on the hardware if they were to upload their design into an FPGA. The downside of cycle accurate simulations are that they are slowwwwww, and the green squiggles are sometimes a bit overwhelming :). At the other end of the spectrum we have CPUSim that abstracts away from the hardware i.e. removes the AND / OR gates, defining the processor in terms of a Register Transfer Level (RTL) description. The advantage of this approach is that it will be faster, as we are not simulating the state of raw bits, signal on "wires" etc. The downside of CPUSim is that it is optimised around the simulations of simple instruction-sets, therefore, its a bit tricky to simulate the SimpleCPU_v1d using this software i.e. the register addressing mode instruction where the opcode is split across the low and high nibble. For more information on how the RTL model of the SimpleCPU_v1a was implement in CPUSim have a look here: (Link).
Figure 1 : ISim simulation
Figure 2 : CPUSim simulation
Therefore, i decided to write some simple python based instruction-set simulators to go with the simple python based assembler. Note, i'm using the word "simple" a lot here to lower expectations :). The primary aim of these simulators is to allow students to test out ideas, to test out fragments of code, rather than big programs, or programs that interact with GPIO, or process large amounts of data stored in memory etc.
This processor is a simple accumulator based architecture, having a very limited 1-operand instruction-set:
MOVE KK : 0000 XXXX KKKKKKKK : ACC <- ACC
ADD KK : 0001 XXXX KKKKKKKK : ACC <- ACC + KK
SUB KK : 0010 XXXX KKKKKKKK : ACC <- ACC - KK
AND KK : 0011 XXXX KKKKKKKK : ACC <- ACC & KK
LOAD AA : 0100 XXXX AAAAAAAA : ACC <- M[AA]
STORE AA : 0101 XXXX AAAAAAAA : M[AA] <- ACC
JUMPU AA : 1000 XXXX AAAAAAAA : PC <- AA
JUMPZ AA : 1001 XXXX AAAAAAAA : IF ACC==0 THEN PC <- AA ELSE PC <- PC + 1
JUMPNZ AA : 1010 XXXX AAAAAAAA : IF ACC!=0 THEN PC <- AA ELSE PC <- PC + 1
where X=Not-used, K=Constant and A=Address. For more information on the SimpleCPU_v1a have a look here: (Link). In addition to having a simple instruction-set this processor also has a very small memory, having only 256 addresses, thats not to say you can't write long looping programs, but in general the type of programs written for this processor will be small, therefore, making it ideally suited for a simple instruction-set simulator. Before writing this simulator we first need to decide how it will be tested, decided on the test code below. Testing a processor's instruction-set is always compromise i.e. testing normal usage and edge cases, but its "impossible" to test all possible combinations of instructions and data. However, i think this code is a good compromise. You can download a copy of this code here :(Link).
###################
# INSTRUCTION-SET #
###################
# INSTR IR15 IR14 IR13 IR12 IR11 IR10 IR09 IR08 IR07 IR06 IR05 IR04 IR03 IR02 IR01 IR00
# MOVE 0 0 0 0 X X X X K K K K K K K K
# ADD 0 0 0 1 X X X X K K K K K K K K
# SUB 0 0 1 0 X X X X K K K K K K K K
# AND 0 0 1 1 X X X X K K K K K K K K
# LOAD 0 1 0 0 X X X X A A A A A A A A
# STORE 0 1 0 1 X X X X A A A A A A A A
# ADDM 0 1 1 0 X X X X A A A A A A A A
# SUBM 0 1 1 1 X X X X A A A A A A A A
# JUMPU 1 0 0 0 X X X X A A A A A A A A
# JUMPZ 1 0 0 1 X X X X A A A A A A A A
# JUMPNZ 1 0 1 0 X X X X A A A A A A A A
# JUMPC 1 0 1 1 X X X X A A A A A A A A -- NOT IMPLEMENTED
########
# CODE #
########
start:
move 1 # acc = 1
move 3 # acc = 3
move 7 # acc = 7
move 15 # acc = 15
move 31 # acc = 31
move 63 # acc = 63
move 127 # acc = 127
move 255 # acc = 255
add 1 # acc = 0
add 3 # acc = 3
add 7 # acc = 10
add 15 # acc = 25
add 31 # acc = 56
add 63 # acc = 119
add 127 # acc = 246
add 255 # acc = 245
sub 1 # acc = 244
sub 3 # acc = 241
sub 7 # acc = 234
sub 15 # acc = 219
sub 31 # acc = 188
sub 63 # acc = 125
sub 127 # acc = 254
sub 255 # acc = 255
and 255 # acc = 255
and 127 # acc = 127
and 63 # acc = 63
and 31 # acc = 31
and 15 # acc = 15
and 7 # acc = 7
and 3 # acc = 3
and 1 # acc = 1
move 1 # acc = 1
store A # M[87] = 1
move 3 # acc = 3
store B # M[88] = 3
move 7 # acc = 7
store C # M[89] = 7
move 15 # acc = 15
store D # M[90] = 15
move 31 # acc = 31
store E # M[91] = 31
move 63 # acc = 63
store F # M[92] = 63
move 127 # acc = 127
store G # M[93] = 127
move 255 # acc = 255
store H # M[94] = 255
load A # acc = M[87] = 1
load B # acc = M[88] = 3
load C # acc = M[89] = 7
load D # acc = M[90] = 15
load E # acc = M[91] = 31
load F # acc = M[92] = 63
load G # acc = M[93] = 127
load H # acc = M[94] = 255
addm A # acc = 0
addm B # acc = 3
addm C # acc = 10
addm D # acc = 25
addm E # acc = 56
addm F # acc = 119
addm G # acc = 246
addm H # acc = 245
subm A # acc = 244
subm B # acc = 241
subm C # acc = 234
subm D # acc = 219
subm E # acc = 188
subm F # acc = 125
subm G # acc = 254
subm H # acc = 255
and 0 # acc = 0
jumpz b1 # TAKEN
move 255 # set acc to 255 if error
b1:
add 1 # acc = 1
jumpnz b2 # TAKEN
move 255 # set acc to 255 if error
b2:
and 0 # acc = 0
jumpnz b3 # FALSE
jumpu b4 # unconditional jump
b3:
move 255 # set acc to 255 if error
b4:
add 1 # acc = 1
jumpz b5 # FALSE
jumpu b6 # unconditional jump
b5:
move 255 # set acc to 255 if error
b6:
jumpu start # jump back to start
A:
.data 0
B:
.data 0
C:
.data 0
D:
.data 0
E:
.data 0
F:
.data 0
G:
.data 0
H:
.data 0
The instruction-set simulator takes as its input the raw machine code generated by the assembler. The simulator could of been implemented in a similar way as the minimalCPU instruction-set simulator: (Link) i.e. simulates the raw assembly language. However, i decided to base this simulator around the simulation of machine-code files, rather than assembler code, as this would allow some more specialised coding examples e.g. self modifying code, and would hopefully be a common platform for the later development of the SimpleCPUv1d's instruction-set simulator. The primary aims of the instruction-set simulator are to:
single step : allow the user to execute one instruction at a time, displaying the updated values of registers, memory etc.
run : automated single step :), allow the user to observe the execution of a program e.g. executing one instruction every 0.25 seconds. Maybe, a break option i.e. if space bar pressed, revert back to single step mode.
breakpoints : allow the user to specify an instruction address at which the simulator will stop i.e. the user selects run, the program is executed until the break point is reached, where it then reverts back to single step mode
view registers : in single step mode the user can view all internal register e.g. acc, pc etc.
view memory : in single step mode the user can view the contents of memory e.g. the user will be prompted to enter an address, the contents of this memory address are displayed to the user in binary and decimal formats.
The first version of the SimpleCPUv1a instruction-set simulator is shown below. You can download a copy of this code here :(Link).
#!/usr/bin/python3
import getopt
import sys
import os
import re
import time
if sys.platform =='win32':
import msvcrt
else:
import tty
import termios
###################
# INSTRUCTION-SET #
###################
# a very simple instruction set simulator for the simpleCPUv1a,
# input file is a .dat file generated by the assembler i.e.
# AAAA DDDDDDDDDDDDDDD, a deciaml address (A) and a 16bit binary (D)
# machine code instruction.
# INSTR IR15 IR14 IR13 IR12 IR11 IR10 IR09 IR08 IR07 IR06 IR05 IR04 IR03 IR02 IR01 IR00
# MOVE 0 0 0 0 X X X X K K K K K K K K
# ADD 0 0 0 1 X X X X K K K K K K K K
# SUB 0 0 1 0 X X X X K K K K K K K K
# AND 0 0 1 1 X X X X K K K K K K K K
# LOAD 0 1 0 0 X X X X A A A A A A A A
# STORE 0 1 0 1 X X X X A A A A A A A A
# ADDM 0 1 1 0 X X X X A A A A A A A A
# SUBM 0 1 1 1 X X X X A A A A A A A A
# JUMPU 1 0 0 0 X X X X A A A A A A A A
# JUMPZ 1 0 0 1 X X X X A A A A A A A A
# JUMPNZ 1 0 1 0 X X X X A A A A A A A A
# JUMPC 1 0 1 1 X X X X A A A A A A A A
# .data IMM
#############
# FUNCTIONS #
#############
def get_key():
if sys.platform == 'win32':
return msvcrt.getch().decode('utf-8')
else:
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
def pad(number):
if number <10:
return "00" + str(number)
elif number >9 and number <100:
return "0" + str(number)
else:
return str(number)
################
# MAIN PROGRAM #
################
def simple_cpu_v1a_simulator(argv):
if len(sys.argv) <= 1:
print ("Usage: simple_cpu_v1d_simulator.py -i ")
print (" -b
To run this simulator you first have to assemble the code, this is done using the python based assembler available here: (Link). A screenshot of this in action is shown in figure 4, the -i parameter specifies the input file name (code.asm) and the -o parameter specifies the output file name (code.asm).
Figure 4 : Assembling code
Remember that the file to be assembled must end with the extension .asm, be in the same directory as the assembler and not on a network drive. If you get the "number of lines" message, no errors and the assembler does not crash :), you will find a .dat file in the same directory, as shown in figure 5.
Figure 5 : Files
Note, if you run the assembler with the debug mode enabled: -d 1, the assembler will display the address in memory of each label, as shown in figure 6. Knowing the address of each label can be helpful when running the instruction-set simulater later. If you enable "advanced" debug mode : -d 2, the assembler will display each instructions machine-code.
Figure 6 : Label addresses
The code.dat file is an "object code" file containing the address and machine code values that will be used to generate the other object code formats used by the Xilinx tools e.g. .asc and .mem, these formats are discussed here: (Link). A snippet of this file is shown below (middle section cut to save space):
Each line is a memory location, line format is "AAAA DDDDDDDDDDDDDDDD", the address (AAAA) in memory and 16bit binary data (DDDDDDDDDDDDDDDD), representing either data, or an instruction. A screenshot of this instruction-set simulator in action is shown in figure 7. Note, the -i parameter specifies the input file name i.e. code.dat shown in this example.
Figure 7 : running a simulation
Once the simulator has executed the first instruction the user can single step through the program i.e. one instruction at a time, by pressing the S key, or run to completion by pressing the R key, or finish by pressing the Q key. Note, if you press the R key and you code get stuck in an infinite loop, CRTL-C is your friend :). Whilst in simple step mode pressing the V key will display the state of the processor's registers i.e. the accumulator (acc) and program counter (pc). If the M key is pressed the user will prompted to enter an address, the data at this address is displayed as a binary and decimal value.
SimpleCPU v1d
The first version of the SimpleCPUv1d instruction-set simulator is shown below. Again, don't judge :), there are definitely things i need to restructure in the code, but it works and if nobody looks at the code tooooo closely its allllll fine :). You can download a copy of this code here :(Link). This version of the instruction-set simulator builds on the basic framework of the simpleCPUv1a. Note, not all instructions have been implemented i.e. only the instructions used in the labs have been implemented, the optional instructions used in the open assessment are listed in the code, but their functionality has not been implemented. However, i would hope given the cut-&-paste nature of this code it would be a relatively simple task for someone to add these. As always need to do a bit more testing in Windows, rather than on Linux.
#!/usr/bin/python3
import getopt
import sys
import os
import re
import time
if sys.platform =='win32':
import msvcrt
else:
import tty
import termios
###################
# INSTRUCTION-SET #
###################
# a very simple instruction set simulator for the simpleCPUv1d,
# program file plain text, single step through program.
###################
# INSTRUCTION-SET #
###################
# INSTR IR15 IR14 IR13 IR12 IR11 IR10 IR09 IR08 IR07 IR06 IR05 IR04 IR03 IR02 IR01 IR00
# MOVE 0 0 0 0 RD RD X X K K K K K K K K
# ADD 0 0 0 1 RD RD X X K K K K K K K K
# SUB 0 0 1 0 RD RD X X K K K K K K K K
# AND 0 0 1 1 RD RD X X K K K K K K K K
# LOAD 0 1 0 0 A A A A A A A A A A A A
# STORE 0 1 0 1 A A A A A A A A A A A A
# ADDM 0 1 1 0 A A A A A A A A A A A A
# SUBM 0 1 1 1 A A A A A A A A A A A A
# JUMPU 1 0 0 0 A A A A A A A A A A A A
# JUMPZ 1 0 0 1 A A A A A A A A A A A A
# JUMPNZ 1 0 1 0 A A A A A A A A A A A A
# JUMPC 1 0 1 1 A A A A A A A A A A A A
# CALL 1 1 0 0 A A A A A A A A A A A A
# OR 1 1 0 1 RD RD X X K K K K K K K K -- Version 1.2
# XOP1 1 1 1 0 U U U U U U U U U U U U -- NOT IMPLEMENTED
# RET 1 1 1 1 X X X X X X X X 0 0 0 0
# MOVE 1 1 1 1 RD RD RS RS X X X X 0 0 0 1
# LOAD 1 1 1 1 RD RD RS RS X X X X 0 0 1 0 -- REG INDIRECT
# STORE 1 1 1 1 RD RD RS RS X X X X 0 0 1 1 -- REG INDIRECT
# ROL 1 1 1 1 RSD RSD X X X X X X 0 1 0 0 -- Version 1.1
# ROR 1 1 1 1 RSD RSD X X X X X X 0 1 0 1 -- NOT IMPLEMENTED
# ADD 1 1 1 1 RD RD RS RS X X X X 0 1 1 0 -- NOT IMPLEMENTED
# SUB 1 1 1 1 RD RD RS RS X X X X 0 1 1 1 -- NOT IMPLEMENTED
# AND 1 1 1 1 RD RD RS RS X X X X 1 0 0 0 -- NOT IMPLEMENTED
# OR 1 1 1 1 RD RD RS RS X X X X 1 0 0 1 -- NOT IMPLEMENTED
# XOR 1 1 1 1 RD RD RS RS X X X X 1 0 1 0 -- Version 1.1
# ASL 1 1 1 1 RD RD RS RS X X X X 1 0 1 1 -- Version 1.2
# XOP2 1 1 1 1 RD RD RS RS X X X X 1 1 0 0 -- NOT IMPLEMENTED REG INDIRECT
# XOP3 1 1 1 1 RD RD RS RS X X X X 1 1 0 1 -- NOT IMPLEMENTED
# XOP4 1 1 1 1 RD RD RS RS X X X X 1 1 1 0 -- NOT IMPLEMENTED REG INDIRECT
# XOP5 1 1 1 1 RD RD RS RS X X X X 1 1 1 1 -- NOT IMPLEMENTED
# .data IMM
#############
# FUNCTIONS #
#############
def get_key():
if sys.platform == 'win32':
return msvcrt.getch().decode('utf-8')
else:
fd = sys.stdin.fileno()
old_settings = termios.tcgetattr(fd)
try:
tty.setraw(sys.stdin.fileno())
ch = sys.stdin.read(1)
finally:
termios.tcsetattr(fd, termios.TCSADRAIN, old_settings)
return ch
# 1000 - 4095
# 0100 - 0999
# 0010 - 0099
# 0000 - 0009
def pad(number):
if number<10:
return "000" + str(number)
elif number>9 and number<100:
return "00" + str(number)
elif number>99 and number<1000:
return "0" + str(number)
else:
return str(number)
def getReg( reg, ra, rb, rc, rd ):
if reg == "00":
return ra
elif reg == "01":
return rb
elif reg == "10":
return rc
elif reg == "11":
return rd
else:
print("Error: invalid reg: " + str(regx))
sys.exit(1)
################
# MAIN PROGRAM #
################
def simple_cpu_v1d_simulator(argv):
if len(sys.argv) <= 1:
print ("Usage: simple_cpu_v1d_simulator.py -i ")
print (" -b
Stuff still to do:
Did not get around to adding a pause key to the simulator i.e. to stop the simulator when the R (run) key is pressed. This feels like it would be a simple, separate thread and a shared variable, so should be simple to do at some point.
Need to add a memory view option i.e. print the contents of memory to the screen, some sort of grid, displaying the address and data values in hex to help reduce the size. Would also be nice to simulate some sort of EDSAC type display :(Link). This first generation computer could display each bit stored in memory on a CRC display (scope). Therefore, by loading into memory the correct values you could draw simple "images", as shown in figure 8. This allowed people to play one of the first computer games: noughts and crosses. It would be nice to do the same on the simpleCPUv1a in HW, have some sort of bit map display using LEDs, or implement the same scope like display, proper retro :). The simpleCPUv1d has a VGA controller so its sorted.
Would be nice to add some sort of GUI / visual interface, to help show / illustrate how the hardware works etc, as you run or single step through the code.
Maybe some more debugging support, some sort of GUI similar to that used by CPUSim
Like CPUSim maybe nice to add the ability to allow the user to add instruction via RTL description, however, this would be a full rewrite.
Finally, may be nice to go full emulator, allow a user to implement games e.g. emulate the simpleCPUv1d and its VGA controller running Pong.
Figure 8 : EDSAC's "display"
However, ignoring all these "could does" i think these instruction-set simulators are fine for their intended purpose, to allow students to test out code fragments, rather than having to go to Xilinx ISim and running a full cycle accurate simulation. As always i will add a buyer beware warning, this code was tested by me :)