So far the program that my CPU executes has been hardcoded into the VHDL source for the CPU itself. Whilst OK for the initial simulation and debugging, it clearly this isn't very flexible.
The alternative at this stage was to manually modify each memory cell by hand in the simulator before starting the simulation. Painful!
Clearly I need an assembler...
The alternative at this stage was to manually modify each memory cell by hand in the simulator before starting the simulation. Painful!
Clearly I need an assembler...
The simulator is a repackaged version of ModelSim, called ModelSim_Altera. It's free, feature rich, and warrants a post or three in itself. They'll be along shortly.
One key feature of ModelSim is that it uses internally, and exposes, a Tcl interpreter. Using Tcl, one can operate the simulator from a command line window and via script files. For instance, one can set the value of signals, including my CPU's internal RAM cells.
A quick browse of the Tcl command structure (I haven't touched Tcl since ~1996) and I humbly present my Mk I Assembler. Far from foolproof, but very quick to write (~zero knowledge to assembler in ~ 90 minutes), easily extensible and, most importantly, it works!
One key feature of ModelSim is that it uses internally, and exposes, a Tcl interpreter. Using Tcl, one can operate the simulator from a command line window and via script files. For instance, one can set the value of signals, including my CPU's internal RAM cells.
A quick browse of the Tcl command structure (I haven't touched Tcl since ~1996) and I humbly present my Mk I Assembler. Far from foolproof, but very quick to write (~zero knowledge to assembler in ~ 90 minutes), easily extensible and, most importantly, it works!
# CPU_1 assembler
# 2012-02-26 v0.1
#
# Basic assembler to convert text file into machine code
# and load that machine code into memory
# This is the list of opcodes
# It MUST be in the same order as in the CPU source code.
set OPERANDS {NOP STO GET ADD SUB LDA CLR_Z CLR_OV BRF_Z BRB_Z
BRF_OV BRB_OV BRF_NZ BRB_NZ BRF_NOV BRB_NOV OUT_LEDs}
# Start by opening the assmbly code file
# Need to find a way to not have this hardcoded
set chan [open {D:\Mark's Documents\Desktop\prog.asm}]
# Initialise pointer into the code RAM
set index 0
# Process the source file line by line
# Ideally each line will only contain one opcode and, optionally,
# one byte of associated data.
while {[gets $chan line] >= 0} {
# Tokenise the line, using space(s) as delimeters
# but convert all the text to upper case first.
set ops [split [string toupper $line] " "]
# Process each token
foreach token $ops {
# Lookup the token in the list of opcodes.
# If found, lsearch returns the position, which equates to the machine code.
# If not found, lsearch returns -1. This *should* indicate that it is a byte
# Not foolproof - could be a mistyped opcode. This is an area for improving!
set opcode_value [lsearch $OPERANDS $token]
# If it was an opcode, store its machinecode value, else store the data byte.
if {$opcode_value >= 0} {
force -freeze sim:/CPU_1/code($index) $opcode_value
} else {
force -freeze sim:/CPU_1/code($index) $token }
# Point to next memory cell.
incr index
} # end of the foreach token loop
} # end of the line by line while loop
# Well behaved code
close $chan
# 2012-02-26 v0.1
#
# Basic assembler to convert text file into machine code
# and load that machine code into memory
# This is the list of opcodes
# It MUST be in the same order as in the CPU source code.
set OPERANDS {NOP STO GET ADD SUB LDA CLR_Z CLR_OV BRF_Z BRB_Z
BRF_OV BRB_OV BRF_NZ BRB_NZ BRF_NOV BRB_NOV OUT_LEDs}
# Start by opening the assmbly code file
# Need to find a way to not have this hardcoded
set chan [open {D:\Mark's Documents\Desktop\prog.asm}]
# Initialise pointer into the code RAM
set index 0
# Process the source file line by line
# Ideally each line will only contain one opcode and, optionally,
# one byte of associated data.
while {[gets $chan line] >= 0} {
# Tokenise the line, using space(s) as delimeters
# but convert all the text to upper case first.
set ops [split [string toupper $line] " "]
# Process each token
foreach token $ops {
# Lookup the token in the list of opcodes.
# If found, lsearch returns the position, which equates to the machine code.
# If not found, lsearch returns -1. This *should* indicate that it is a byte
# Not foolproof - could be a mistyped opcode. This is an area for improving!
set opcode_value [lsearch $OPERANDS $token]
# If it was an opcode, store its machinecode value, else store the data byte.
if {$opcode_value >= 0} {
force -freeze sim:/CPU_1/code($index) $opcode_value
} else {
force -freeze sim:/CPU_1/code($index) $token }
# Point to next memory cell.
incr index
} # end of the foreach token loop
} # end of the line by line while loop
# Well behaved code
close $chan