The assembler is implemented as a Python 3.8 script that assembles programs into machine code binaries
using its command line interface. Run the script without parameters to see the help message. It's primarily
meant to be run by the Jetbrains plugin or the assemble.sh
script.
- Only string and character literals are case-sensitive.
- Operands must be comma separated and trailing commas aren't allowed.
The ORG
assembler directive tells the assembler where to place generated machine code in the generated
ROM binary. There are no checks to ensure that later origin changes in the program don't overwrite
previously generate machine code, so verify the resulting binary in the emulator using the ROM viewer and
the disassembler.
The DATA
and RODATA
directives switch between the RAM section for variables and the read-only section
for code and other constant data.
Labels are defined simply by a chain of word characters (numbers, letters, and underscores) followed by a colon. Label definitions must be unique, but can be defined after usages in instruction operands. Labels can be either by themselves or on the same line as an instruction.
JR
instructions take either a label or a signed immediate for the number to add to the program
counter relative to the address of the instruction following said JR
. Thus, JR #-2
is an
infinite loop. If the distance to the label address from the jump exceeds the size of a signed byte,
the program will fail to assemble.
Hexadecimal, decimal, and binary values can be used for immediate parameters using the prefixes $
, #
,
and %
, respectively. Character literals can also be used for immediate operands. Using =
before a
label with an address of $FF
or less will provide the address of said label, primarily for printing
strings, but doesn't work for programs running memory. Decimal immediates can also be negative and the
value will be stored correctly in 2's compliment form.
The four program registers can be specified using their respective letters: A
, B
, H
, and L
.
Address parameters take either a label or a 16-bit hexadecimal address. Load and Store instructions require square brackets around the address parameter.
The LDR
, STR
, and JMP
instructions accept HL
in place of an address to use the HL
register as the
address.
For JR
instructions, there is an optional conditional parameter that determines whether the jump will
occur. These correspond to the four CPU flags: Z
, C
, N
, and V
. To invert the condition, prefix the
flag with an n
.
String literals act like in most other languages where you have a quoted string of characters. Character literals use single quotes. Both types support Python style escaped characters.
Comments are signified by the presence of a semicolon in a line. Anything after said semicolon will be ignored by the assembler.
Constants can be defined in programs using the DEF
assembler directive in addition to the default
constants for ports, time units, and peripherals. Constants can be redefined, and the last value assigned
will be the actual value when evaluated as an instruction parameter. Constants are accessed by surrounding
the constant name with curley brackets.
Variables get assigned memory addresses sequentially starting at 0x8000 in the order of the VAR
declarations in the program data section. The DATA
assembler directive tells the assembler that any
instructions after it are variables. Variables can also be arrays of any size. Labels pointing to the VAR
assembler directives will point to the variable memory addresses.
Additional assembly programs can be included into other programs to reuse functionality. Includes are
recursively processed, so the machine code for the included file will be inserted at the INCLUDE
instruction's position. Variables in an included file get assigned addresses before the program that
included it. Label names must be unique between all included files. Circular dependencies are not allowed.
The BIN
assembly directive reads all bytes from a specified file and inserts them at the current assembler
origin. This reads the target file in binary mode, so the raw bytes from the file are what is read.
All I/O Ports are available as constants in addition to following ones.
Constant | Value | Function |
---|---|---|
controller_up | 35 | Controller peripheral up (GPIO 0) |
controller_down | 36 | Controller peripheral down (GPIO 1) |
controller_left | 37 | Controller peripheral left (GPIO 2) |
controller_right | 38 | Controller peripheral right (GPIO 3) |
microseconds | 0 | Timer microseconds unit (10 ^ -6 seconds) |
centimilliseconds | 1 | Timer centimilliseconds unit (10^-5 seconds) |
decimilliseconds | 2 | Timer decimilliseconds unit (10^-4 seconds) |
milliseconds | 3 | Timer milliseconds unit (10^-3 seconds) |
centiseconds | 4 | Timer centiseconds unit (10^-2 seconds) |
deciseconds | 5 | Timer deciseconds unit (10^-1 seconds) |
seconds | 6 | Timer seconds unit (10^0 seconds) |
decaseconds | 7 | Timer decaseconds unit (10^1 seconds) |
To assemble a program manually from the command line, enter the directory with the program and run:
# If using the repo project structure, just use ".." in place of <ASSEMBLER_DIR>
# Otherwise, just use the actual path to the assembler for the first parameter
# Replace <PROGRAM>.asm with the actual program name
python3 <ASSEMBLER_DIR>/assembler.py <PROGRAM>.asm
The ROM will be placed in the created build
folder in the working directory. To run the rom, just
run the Emulator and open the ROM binary file.
The assemble.sh
script can be run to assemble all assembly files in the programs directory. The Jetbrains
plugin can automatically generate run configurations that assemble and run programs in the emulator.
- --run (-r): Run the assembled program with the compiled emulator.
- --fpga (-f) <none|patch|flash>: Patch the FPGA ROM code with the assembled binary and or flash the FPGA.
- --emulator (-e): Specify the path to the compiled emulator if not using the default project structure
- --simulator (-s): Specify the path to the
simulation
directory if not using the default project structure - --memory (-m): Assemble the program for being run by the bootloader from an SD card in memory.