A self contained Z80 assembler that can optionally write to a .sna file.
Implementation
I have, once again, used my own lexing and parsing libraries to implement the assembler. A typical rule looks something like this:
g_map[grules.push("opcode", "LD '(' HL ')' ',' integer")] = [](data& data)
{
data._memory.push_back(0x36);
data.push_byte();
};
There are two modes to run the assembler in. If only a source file is supplied, then the source will be dumped back to std::cout
complete with the opcodes, but if a template .sna and target pathname are supplied, then a new .sna is created which includes the assembled code.
For example, if your source looks like this:
ORG 23296
LD IX, 16384
LD DE, 6912
CALL 1366
RET
Then the default output looks like this:
23296 221 33 0 64 LD IX, 16384
23300 17 0 27 LD DE, 6912
23303 205 86 5 CALL 1366
23306 201 RET
Sample Assembly Source File
Here is a simple fill routine for the Sinclair ZX Spectrum:
ORG 23298
LD BC, (23296)
CALL GetScreenPos
Line:
CALL FillLine
CALL GetNextLine
LD A, (HL)
OR A
JR Z, Line
RET
FillLine:
PUSH HL
CALL FillLeft
POP HL
PUSH HL
INC HL
CALL FillRight
POP HL
RET
FillLeft:
LD A, (HL)
OR A
JR NZ, MaskLeft
LD (HL), 255
DEC HL
JR FillLeft
MaskLeft:
LD B, 0
PUSH AF
LeftAgain:
BIT 0, A
JR NZ, EndFillLeft
SLA B
SET 0, B
SRA A
JR LeftAgain
EndFillLeft:
POP AF
OR B
LD (HL), A
RET
FillRight:
LD A, (HL)
OR A
JR NZ, MaskRight
LD (HL), 255
INC HL
JR FillRight
MaskRight:
LD B, 0
PUSH AF
RightAgain:
BIT 7, A
JR NZ, EndFillRight
SRA B
SET 7, B
SLA A
JR RightAgain
EndFillRight:
POP AF
OR B
LD (HL), A
RET
GetScreenPos:
LD A, C
AND %00111000
RLCA
RLCA
OR B
LD L, A
LD A, C
AND %00000111
LD H, A
LD A, C
AND %11000000
RRCA
RRCA
RRCA
OR H
OR &40
LD H, A
RET
GetNextLine:
INC H
LD A, H
AND 7
RET NZ
LD A, L
ADD A, 32
LD L, A
RET C
LD A, H
SUB 8
LD H, A
RET
and here is the driver in Sinclair Basic:
10 CIRCLE 125,100,50
14 POKE 23296,25
16 POKE 23297,15
20 RANDOMIZE USR 23298
Which produces:
Using the Code
USAGE: z80_assembler <pathname> [<source .sna> <dest .sna>]
This is a work in progress, so be sure to report any issues.
History
- 3rd October, 2020: Released
- 3rd October, 2020: Added Z80 source example
- 3rd October, 2020: Now dumping
mnemonic
s with opcodes by default - 4th October, 2020: Now supporting integers as well as labels for CALL and JP
- 4th October, 2020: Fixed rule integer: Hex;
- 4th October, 2020: Fixed
wlabel()
- 5th October, 2020: Unrecognised disassembled opcodes now show as db nnn
- 5th October, 2020: Correctly calculate the address if a label is on the same line as an opcode
- 15th October, 2020: Now checking for running off the end of memory and simplified code generally.
- 16th October, 2020: Fixed RST instruction.
- 20th October, 2020: Added missing instructions.
- 21st October, 2020: Added basic expression support and fixed a load of invalid indexes.
- 21st October, 2020: Added '*', '/', '|' and '&' support as well as fixing more indexes.
- 21st October, 2020: Added gcc Makefile.
- 22nd October, 2020: Added undocumented instructions.
- 24th October, 2020: Now allowing parenthesis in EQU, DB and DW expressions.
- 10th January, 2020: Fixed DS/DEFS to work correctly. Took the disassembly of JSW and successfully assembled and ran it.
- 16th January, 2020: Added tests for all instructions and fixed the disassembly of many instructions.
- 17th January, 2020: Fixed DS directive, added missing whitespace in disassembler, added another test file.
- 19th January, 2020: Added mnemonic verification to test code.
- 2nd February, 2020: Assembler now records what type byte ranges are (code, db, dw, ds) to aid disassembly.
- 3rd February, 2021: Always set offset in dump().
- 3rd July, 2021: Updated
parsertl
. - 30th August, 2021: Introduced program struct.
- 1st September, 2021: Evaluation order fix in disassem.cpp and clang warning fixes.
- 2nd September, 2021: Fixed warnings when building with clang and -Wall.
- 15th March, 2022: Added switches for hex, dec and relative jumps (offset, absolute).
- 16th March, 2022: Now passing base and relative to parse() and dump() in main.cpp.
- 14th April, 2022: Fixed command line handling and added 'h' to realtive jumps in hex mode.
- 17th April, 2022: Disassembler now outputs up to 4 bytes per line for db, and 2 words per line for dw.
- 6th May, 2022: Index registers take a signed displacement (in the disassembler).
- 11th August, 2022: +/- fix in
sbto_string()
- 21st January, 2023: Updated to use the latest version of
parsertl
. - 31st January, 2023: Updated to use the latest version of
parsertl
. - 11th September, 2023: Updated to latest
lexertl
and parsertl
. Added lexertl
path to Makefile and .vcxproj. - 15th February, 2024: Updated to use
lexertl17
and parsertl17
. - 20th April, 2024: Added
$
(program counter) support. - 24th May, 2024: Added
.skool
file support.