Click here to Skip to main content
15,867,568 members
Articles / Programming Languages / Assembler

A Z80 Assembler

Rate me:
Please Sign up or sign in to vote.
5.00/5 (14 votes)
3 Oct 2020CPOL3 min read 17.4K   419   12   14
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.

License

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


Written By
Software Developer (Senior)
United Kingdom United Kingdom
I started programming in 1983 using Sinclair BASIC, then moved on to Z80 machine code and assembler. In 1988 I programmed 68000 assembler on the ATARI ST and it was 1990 when I started my degree in Computing Systems where I learnt Pascal, C and C++ as well as various academic programming languages (ML, LISP etc.)

I have been developing commercial software for Windows using C++ since 1994.

Comments and Discussions

 
QuestionSome good memories there... Pin
ZimFromIRK6-Oct-20 6:28
ZimFromIRK6-Oct-20 6:28 
AnswerRe: Some good memories there... Pin
Ben Hanson6-Oct-20 10:08
Ben Hanson6-Oct-20 10:08 
AnswerRe: Some good memories there... Pin
colins27-Oct-20 3:44
colins27-Oct-20 3:44 
QuestionCurious Pin
David Athersych5-Oct-20 8:11
David Athersych5-Oct-20 8:11 
AnswerRe: Curious Pin
Ben Hanson5-Oct-20 11:01
Ben Hanson5-Oct-20 11:01 
GeneralRe: Curious Pin
David Athersych6-Oct-20 7:07
David Athersych6-Oct-20 7:07 
Interesting. I didn't pick up on what you were using it for in my first read - my bad.
But, does that device still use a genuine Z80 or is it an FPGA emulator?
GeneralRe: Curious Pin
Ben Hanson6-Oct-20 10:01
Ben Hanson6-Oct-20 10:01 
QuestionKudos, but I hated Z80 Assembler, the Motorola 6800 (and PDP Macro Assembly) were beautiful Pin
Kirk 103898215-Oct-20 6:03
Kirk 103898215-Oct-20 6:03 
AnswerRe: Kudos, but I hated Z80 Assembler, the Motorola 6800 (and PDP Macro Assembly) were beautiful Pin
unit150518-Nov-20 9:37
unit150518-Nov-20 9:37 
PraiseHappy memories Pin
colins25-Oct-20 1:36
colins25-Oct-20 1:36 
QuestionPete Shelley Said it Best Pin
Ben Hanson5-Oct-20 0:17
Ben Hanson5-Oct-20 0:17 
QuestionSuperb ! Pin
FenderBaba4-Oct-20 23:45
FenderBaba4-Oct-20 23:45 
Generalexcellent! Pin
Southmountain3-Oct-20 9:29
Southmountain3-Oct-20 9:29 
GeneralRe: excellent! Pin
Ben Hanson3-Oct-20 9:51
Ben Hanson3-Oct-20 9:51 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.