Setting Up a Modern ZX Spectrum Toolchain [part 1 of 2]

I recently started a retro-computing project, to reimagine and design a new ROM for the Sinclair ZX Spectrum, the best home computer from the 1980s. The philosophy of that project is to bring modern software approaches to bear on an ancient, 40-year-old machine. So what should a modern development pipeline for an antique computer technology look like?

(That project is nowhere near finished, but I hope to occasionally post about it when it’s further along.)

This post describes my build pipeline for modern–old-fashioned ZX Spectrum ROM development. The next post (part 2) will delve into how to perform automated unit testing for Z80 assembly language, using modern tools.

Read on…

Requirements

Fundamentally, I needed: 1) a text editor (or IDE), ideally with syntax highlighting support for Z80 code; 2) an assembler; 3) some kind of Spectrum emulator; 4) a build tool; and of course some way to do the unit testing.

I didn’t want to lock myself into a particular IDE, or an obscure technology set; I wanted as ‘open’ a solution as possible. I prefer high-level languages (i.e., not C).

I’m running on macOS, but I didn’t want to tie myself to anything platform-specific.

Options

There are a few ways this could go, and a large number of possible technology sets. The current, popular, cross-platform and supported ZX Spectrum IDEs appear to be:

DeZogVisual Studio Code Debugger for the Z80 and ZX Spectrum/ZX81.I may end up using this, but I didn’t want to completely depend upon it.
KliveA cross-platform Spectrum & ZX81 IDE based on VS Code. Supporting assembly (which I want) and BASIC (which I don’t need!)Seemingly actively maintained too.

They both offer an IDE (built on top of Visual Studio Code), emulation, debugging, reverse engineering, and some additional tools, like sprite editors.

Neither of them seems to include a mechanism for writing automated tests for Z80 code.

The solution (that I ended up with)

The solution I arrived at, and which I am pretty happy with, is less integrated than either of the two ‘full IDE’ solutions listed above, but I’ll try to persuade you that all the pieces work together very nicely.

It’s similar to the toolchain documented by Dean Belfield at his blog L Break Into Program.

IDE: Visual Studio Code

I was already familiar with VS Code; it’s very cross-platform; it has a wide range of plugins and support for pretty much every language and syntax you can imagine… including Z80 assembly code. For interest, I’m using these VSCode extensions:

  1. Z80 Macro-Assembler by Martin Bórik: Syntax highlighting, and support for modern conveniences like Jump to Definition, Rename Identifier, and Autocomplete.
  2. Z80 Assembly Meter by Néstor Sancho: This little beauty tells you (in the status bar) how many t-states each assembly instruction takes to execute. Invaluable for performance-tuning code.
  3. Deno for Visual Studio Code: Official VS Code extension from the Deno project. Beautifully integrated, and works absurdly well.

Z80 Assembler: SjASMPlus

There were fewer options here, and I went with: SjASMPlus

It’s cross-platform (including binaries available for macOS); it’s well-established; it has explicit support for the ZX Spectrum; very comprehensive macro support; and very actively maintained. Importantly for my purposes, I can run it on the command-line and it can generate a ROM image.

Its ability to generate a symbol table as output has turned out to be very useful for unit testing too.

Emulator: JSSpeccy 3

Okay, there are dozens of ZX Spectrum emulators to choose from, including some that have built-in debugger support. (I’ll need to investigate them at some point.)

However, there are a few nice things about depending on JSSpeccy3, which is built on JavaScript and WebAssembly:

  1. It’s necessarily cross-platform
  2. I’m familiar with these technologies, so I’m comfortable hacking into it and modifying it for my purposes. (Though I hardly needed to modify it.)
  3. Being based on web technologies, the components of it are very amenable to being reused directly, without a great deal of work to ‘extract’ them, (as one might need to do with a compiled, C-based project).
  4. A web-based emulator is embeddable in web pages. It’s appealing to use the same emulator for development as the one I might potentially use in future to show off my project on the web.
  5. This particular project has a good pedigree, is simply designed, and is actively developed.

Build & test: Deno & TypeScript

I’m going to gloss over the details of how the unit testing actually works here—I’ll explain in detail in the next blog post. Suffice to say I was keen to use a standard JavaScript unit test approach, and JSSpeccy let me do that.

I originally started with Node & NPM as my build & development framework, using the Jest framework for testing. However, as the project got bigger, and I wanted to write more of the JS parts in TypeScript—and as the build started to accumulate additional components— I moved to Deno as the runtime & build framework, with Deno Test as the unit-test framework.

Deno’s support for dependencies between build tasks makes it easy to structure the build dependency graph, without bringing in a more complex solution, (or a separate build tool).

Its solid, native support for TypeScript makes me more productive.

The integrated nature of the whole thing just makes everything more convenient. For example: I previously spent a full day trying to get Jest & TypeScript working together nicely in Node. That wasn’t fun. This is supposed to be a fun project, and Deno takes away a lot of the un-fun parts.

The Deno VS Code plugin is very polished too, and offers autocomplete and syntax highlighting for all the project configuration files.

Bringing everything together

You can see my project setup on GitHub.

This is what the build rules look like (from Deno.json):

"tasks": {
	"test": {
		"command": "deno test --allow-write=testout/ --allow-read",
		"dependencies": ["assemble"]
	},
	"deploy": {
		"command": "cp dist/neo48.rom ../dist/jsspeccy/roms/48.rom",
		"dependencies": ["assemble"]
	},
	"genfont": "deno run build/makefont.ts >generated/font.z80",
	"symtojs": "cat generated/symbols.txt | deno run --allow-env build/symbolstojs.ts >generated/symbols.ts",
	"assemble": {
		"command": "sjasmplus --msg=war --nologo --outprefix=dist/ --sym=generated/symbols.txt -Wno-fwdref root.z80 && deno task symtojs",
		"dependencies": ["genfont"]
	}
}

I can build the code and run all my unit tests (all within a couple of seconds) by typing:

deno task test

How does it work? I’ll cover the actual unit testing bit in Part II.

Summary

Fundamentally, I have a solution that I’m happy with and works well for me. As measured against my original goals:

  1. I have a Z80 assembly development toolchain running with modern build tools: Deno and the web-dev ecosystem
  2. All my project’s supporting code—everything except for the actual ZX Spectrum target code—is written in a high-level language, TypeScript, which I find fast and convenient to write and read.
  3. The solution is quite ‘open’: it’s cross-platform and based on Open Source tooling. Everything is relatively up-to-date and well supported.

Future work

I’d love to reengineer my project so that it can be built with only the JavaScript runtime, package dependencies, and no other external dependencies:

  • Currently I’m using a copy of JSSpeccy copied into my working directory. This could and should be changed to import the NPM JSSpeccy 3 package.
  • The assembler, SjASMPlus, is written in C/C++. It ought to be possible to compile it to WASM, publish it as an NPM package, and bring it in as a package dependency…
  • Even better would be if the project could build on any JS runtime—Deno, Node or Bun. But I suspect that that will have to wait until the various JS runtimes standardise their approaches and configuration files.

Next

Let me know if you found any of this useful.

The next post looks at how on earth you unit test Z80 assembly language from TypeScript.

One thought on “Setting Up a Modern ZX Spectrum Toolchain [part 1 of 2]

  1. Pingback: Retrofabulation: Using TypeScript to test ye olde Z80 assembly code [part 2 of 2] | Andrew’s Mental Dribbling!

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.