Close

Z80 Assembler in Common Lisp is Taking Shape

A project log for XiAleste

XiAleste Next is an 8-bit home computer, which is compatible with software for the Amstrad CPC6128 released on 13 June 1985

h2wh2w 02/05/2026 at 12:540 Comments

The project is gradually approaching a more-or-less complete form. Not everything works yet. The assembler itself was originally taken from a CL library, but in the process, it had to be rewritten from scratch. The previous version was good but overly convoluted: several levels of macros generated a large set of methods, each for one specific case (operation signature).

Quick Start

You need to add a standard initialization at the beginning of the source file. This can be done once per build session.

;; Load the assembler file
(load "common/xiff/soot/asm.sot")

;; Switch the type system to Z80
(init-types 'z80)

 Now Z80 data types are available, for example, uint16 or pointer (2 bytes in size).

Next, declare a module that will combine all segments into one logical block. This could be the entire program or a part of it. A module can consist of one or several segments, and segments can be written to one or more files.

(defmodule test-module)


Writing a Simple Program

Now you can write a simple program.

(zasm 
      (org #x0000)
$zero      
      (org #x8000)
$start
      (jp 'label3)
      (db 22)
      (db (list 22 33 44))
      (dw 64)
      (ds 8)
      (dw 'start)
      (dw '(1 2 3))
      (ascii "Hello World\n")
      (align 16)
$label1
      (jmp 'label1)
      (jmp 'label2)
      (ld @a @b) 
$label2
      (jmp 'label1)
      (jmp 'label3)
      (ld @c (+ 1 3)) 
      (subroutine label3
            (jmp 'label3)
            (jmp 'label2)
            (jmp 'label1)
            (ld @a @c)
            (jmp 0)
            (jmp 'zero)
            (ld @a [+ ix 5])    ; Should be: DD 7E 05
            (ld [- iy 10] #xAA) ; Should be: FD 36 F6 AA
      )
)

The org directive here is only for testing; in a real project, it might not be needed since each segment can have its own base address.

Finally, add the build directive:

(build test-module)

 
Running the Build

After launching:

> xiff
Loaded asm-z80
──────────────────────────────────────────
 BUILD SUCCESSFUL: test-module     
──────────────────────────────────────────
 Segment: MAIN       | Base: #0000 | Size: 32847 bytes
──────────────────────────────────────────

 Generating a Listing

The following command saves a listing (with :print-listing #t, it also prints it to the screen):

(generate-module-listing test-module :print-listing #t)

The resulting listing:

--- Segment: MAIN | Base: #0000 ---
0000 | --------         | org 00h
0000 | zero:            | 
8000 | --------         | org 8000h
8000 | start:           | 
8000 | C3 3B 80         | jp label3
8003 | 16               | db 16h
8004 | 16 21 2C         | db (22 33 44)
8007 | 40 00            | dw 40h
8009 | 00 00 00 00 00 00 00 00  | ds 08h
8011 | 00 80            | dw start
8013 | 01 00 02 00 03 00  | dw (1 2 3)
8019 | 48 65 6C 6C 6F 20 57 6F 72 6C 64 0A  | ascii Hello World

8025 | --------         | align 10h
8030 | label1:          | 
8030 | 18 FE            | jr label1
8032 | 18 01            | jr label2
8034 | 78               | ld a, b
8035 | label2:          | 
8035 | 18 F9            | jr label1
8037 | 18 02            | jr label3
8039 | 0E 04            | ld c, 04h
803B | label3:          | 
803B | 18 FE            | jr label3
803D | 18 F6            | jr label2
803F | 18 EF            | jr label1
8041 | 79               | ld a, c
8042 | C3 00 00         | jp 00h
8045 | C3 00 00         | jp zero
8048 | DD 7E 05         | ld a, (ix+5)
804B | FD 36 F6 AA      | ld (iy-10), AAh

Exporting the Result

You can save all or selected segments to a file:

(save-module-hex test-module)

This creates two files.

1. Intel HEX format file:

:10800000C33B801616212C40000000000000000039
:1080100000008001000200030048656C6C6F20576F
:108020006F726C640A000000000000000000000095
:1080300018FE18017818F918020E0418FE18F61820
:0F804000EF79C30000C30000DD7E05FD36F6AA10
:00000001FF

 
2. Symbol table file:

("test-module"
  (:export label3 label2 label1 start zero)
  (:symbols
    (:public 32827 0 "label3" :label "MAIN" :meta ())
    (:public 32821 0 "label2" :label "MAIN" :meta ())
    (:public 32816 0 "label1" :label "MAIN" :meta ())
    (:public 32768 0 "start" :label "MAIN" :meta ())
    (:public 0 0 "zero" :label "MAIN" :meta ())
  )
)

What's Next?

The data structures can already be saved to a buffer, but they cannot yet be used directly in the assembler. I hope this functionality will be working within a week.

To be continued...

Discussions