Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Languages / assembler

A Z80 Assembler

5.00/5 (15 votes)
3 Oct 2020CPOL3 min read 20K   443  
Back in 1984, I wanted to write a Z80 assembler. At the time, this would have meant coding it in Z80 machine code, but due to the conveniences of modern technology, I have now done it in C++.
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:

C++
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:

ASM
ORG 23296 
LD IX, 16384
LD DE, 6912
CALL 1366
RET

Then the default output looks like this:

ASM
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:

ASM
  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
; Takes HL as Screen Address
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:

Image 1

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 mnemonics 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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)