ZalaXa 13 — You Gotta Roll With It

In the last tutorial I looked at creating a randomized starfield for the main menu. Since then I’ve been playing around with techniques to scroll it vertically, like the starfield on the Galaga arcade machine.

I found a bug in the code I wrote. Instead of using a pair of random(ish) numbers for the X and Y coordinates of each star, I was actually using the Y coordinate of the previous star for the X coordinate of the next one. This was causing unnecessary vertically-repeating patterns.

After fiddling around with this for a while, I decided to use a third randomish number to represent the star position with the character. This leaves me with some spare bits I can use for attribute colours without introducing more repeating patterns.

I also added a seed randomizer in my test routine—instead of advancing the ROM seed sequence by one every time you press a key, I completely randomized it to any address between $0000 and $3FFF. I used Patrik Rak’s Complementary Multiply With Carry pseudo-random number generator again for this.

Same deal as last time, except this time it scrolls vertically:

Most of the code is pretty similar to last time. The section that draws the stars is duplicated. In the first iteration, the part that creates the set n, a opcode now creates a res n, a instruction instead, which undraws the previous star.

37
38
39
40
41
42
; stars.asm
 
                        ld a, (hl)                      ; Read the byte represnting the star pixel,
                        or %10000111                    ;      turn it into a "res n, a" instruction ($80 10nnn111) by setting
                        and %10111111                   ;      and clearing bits,
                        ld (ResetBit), a                ; SMC> then write this into the pixel-drawing code.

In the second iteration, we keep the set n, a instruction as it was in part 12. But before we draw the star pixel, we manipulate the Y coordinate by including the ScrollStar macro:

72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
; macros.asm
 
ScrollStar              macro()
                        ld a, d                         ; Retrieve the byte containing bits 0..2 of the Y coordinate.
                        and %00000111                   ; Calculate Y mod 8 (0..7).
                        cp %00000111                    ; If 7 then
                        jp z, CharScroll                ;   do a character scroll.
                        ld a, d                         ; Otherwise do
                        inc a                           ;   a pixel
                        ld d, a                         ;   scroll.
                        inc (hl)                        ; Save px-scrolled offset back to StarField.Table.
                        jp Continue
CharScroll:
                        ld a, e                         ; Retrieve the byte containing bits 3..4 of the Y coordinate.
                        add 32                          ; Increment those two bits by one
                        ld e, a                         ;   and save back the byte. This moves into the next char row.
                        dec l                           ; Now retrieve the byte
                        ld (hl), a                      ;   containing bits 0..2 of the Y coordinate,
                        inc l                           ;   and
                        xor a                           ;   reset it to zero, ensuring we start at the top of that char row.
                        ld d, a                         ; Save that back to the byte, and also
                        ld (hl), a                      ;   save the px-scrolled offset back to StarField.Table.
Continue:
mend

Doing this as a macro isn’t really necessary at all, but when reading the code, it does emphasise that the pixel-setting half is almost the same as the pixel-clearing half. It achieves this by abstracting the extra code into a different place—without any of the call/ret/jp overhead a separate routine would entail.

The stuff in the macro decides whether we’re scrolling within a character boundary, or scrolling between character boundaries. Then it sets the appropriate bits of the Y coordinate accordingly. The specific maths here is needed because of how the Y coordinate is distributed within the screen address:

Image from the L Break Into Program blog, with thanks to Dean Belfield.

In the Spectrum’s distinctive display file, the bits labelled Y0, Y1 and Y2 govern the position (0..7) within a character row. We set these bits in the first half of the macro.

The bits labelled Y3, Y4 and Y5 govern the position of the character row (0..7) within the current screen third. We set these bits in the second half of the macro.

The bits labelled Y6 and Y7 govern which screen third the character row appears in. Because we’re repeating the same stars in all three thirds, we set these bits in the SetupStars routine:

84
85
86
87
88
89
90
; stars.asm
 
                        ld b, 8                         ; Fast way of doing ld bc, $0800 (size of a screen third).
                        add hl, bc                      ; hl is now a pixel address in the middle screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        add hl, bc                      ; hl is now a pixel address in the bottom screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.

Actually, adding $0800 to the screen address using the add hl, bc instruction is an inefficient way of doing this. All we really need is to add 8 to h. Or even better, flip one or two bits of h. I might optimise this part another time.

I made a list of 32 of my favourite starfields using this new version of the randomizer test routine. I particularly like how it tends towards drifts of stars that look like constellations.

This is my favourite starfield (seed address $26B1), complete with the scroll effect:

Next time, I’ll explore setting attribute colours to make the stars twinkle. Cheers!

ZalaXa 12 — Towards a Scrolling Starfield

The technology in Namco’s arcade version of Galaga (1981) is from the same era as the ZX Spectrum, but is actually a little beefier. The main board has three Z80 processors, clocked at 3Mhz—each one just under the speed the Spectrum’s single processor runs! Sadly, I think that rules out being able to code an authentic arcade version of Galaga in this tutorial series. In particular, if you recall, NIRVANA+’s multicolour raster-chasing technique uses about 78% of the available processor T-states we normally have when writing Spectrum programs. That said, this should still be a fun exercise in pushing the machine to its limits 🙂

Arcade Galaga has a rather nice winking multicolour starfield scrolling down the screen during gameplay. This isn’t really necessary for the game, and won’t demonstrate any NIRVANA+ techniques, but I am quite attached to the effect, so I thought I’d take a short detour and try and get this working on the start menu.

I did some rough calculations, and decided we need about 100 stars, give or take. My first thought was to write a short BASIC program to prototype this, using RND and PLOT in a loop, but I decided to do it in machine code, which I find a bit easier.

My first attempt used Patrick Rak’s 8-bit Complementary-Multiply-With-Carry (CMWC) random number generator, to generate a table of random bytes. This proved the concept nicely, but then I remembered people often use the ZX Spectrum ROM as a source of weakly-random numbers. Surely this would be good enough for my purposes, as it’s only a few stars. The trick would be to pick a short sequence of numbers from somewhere in the ROM that looks natural enough. We have 16KB to play with—more on the later models, but let’s try to limit ourselves to something that will work and give the same results on all Spectrum models.

Thinking further, I realised the vertical scrolling will be the trickiest part of the problem. The Spectrum screen is laid out in vertical thirds, and having the pixels jump between third-boundaries will complicate things. For our purposes, though, we should be able to duplicate the same stars across all three. If each third wraps around, then individual stars will seem to scroll continuously into the next third at the same time as wrapping round. Einfach genial!!

Better still, we might be able to mitigate any obvious repetitive patterns, by using different attribute colours for each third. The entire starfield will appear to be shimmering because of the colour effects, and that should provide some distraction.

I came up with a routine that does this, noting the “seed” ROM address on the debug screen for reference. It waits till you press a key, then adds one byte to the seed address and does it all over again. Like this:

Excellent! There’s a bit of flicker in the middle third, but that’s just a side effect of the way I coded the keypress and screen re-clear loop.

It turns out that most of the seed values are not very random at all. But, persevering, I found a few seed values that gave fairly uniform results, without any ugly double pixels or tight bunchings. This was the most promising one, $03F3:

Not perfect, but good enough.

I’ll develop this a bit further in the next tutorial, but let’s finish up by looking at my code:

3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
; stars.asm
 
SetupStars              proc
                        ld a, DimWhiteBlackP            ; Custom colour for ClsAttr.
                        call ClsAttr.WithCustomColour   ; Alternate entry point without setting colour.
RandomLoop:
                        call ClsPixels                  ; Clear all the pixels.
Seed equ $+1:           ld hl, $0000                    ; Starting point of ROM "psuedo-random" table (2 bytes per star). Try $03F3!
                        zeusdatabreakpoint 1, "zeusprinthex(1, hl)", $ ; Log current value of ROM table to debug window.
                        ld a, NumberOfStars             ; Loop through this many times, once for each star.
DrawLoop:
                        ex af, af'                      ; Save number of stars (loop counter).
                        ld e, (hl)                      ; Read 2 bytes,
                        inc hl                          ;   for this star,
                        ld a, (hl)                      ;   into de.
                        and %00000111                   ; Constrain de between 0..2047
                        ld d, a                         ;   (size of top screen third).
                        ld a, e                         ; Mask out a bit number (0..7) from the X coordinate,
                        or %11000111                    ;      turn it into a "set n, a" instruction ($CB 11nnn111),
                        ld (SetBit), a                  ; SMC> then write this into the pixel-drawing code.
                        ex de, hl                       ; Sawp the reading addr into de, and the writing addr into hl.
                        ld b, high(PixelAddress)        ; c stays 0, so this is a fast way of doing ld bc, $4000.
                        add hl, bc                      ; Calculate a pixel address in the top screen third.
                        xor a                           ; Draw a single pixel star,
SetBit equ $+1:         set SMC, a                      ; <SMC  by setting that pseudo-random
                        ld (hl), a                      ;       bit (0..7) from earlier.
                        ld b, 8                         ; Fast way of doing ld bc, $0800 (size of a screen third).
                        add hl, bc                      ; hl is now a pixel address in the middle screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        add hl, bc                      ; hl is now a pixel address in the bottom screen third.
                        ld (hl), a                      ; Draw the same single pixel star here, too.
                        ex de, hl                       ; Swap the writing addr back into de, and reading addr into hl.
                        ex af, af'                      ; Retrieve the number of stars (loop counter),
                        dec a                           ;   decrease it,
                        jp nz, DrawLoop                 ;   and do all over again if there are any stars left.
 
                        call WaitForAnyKeyPress         ; Spin until any key is pressed
 
                        ld hl, (Seed)                   ; Increase starting point of ROM "psuedo-random" table
                        inc hl                          ;      by one byte,
                        ld (Seed), hl                   ; SMC> save it into the routine,
                        jp RandomLoop                   ;      and rerun the routine again.
 
NumberOfStars           equ 32                          ; Constant declared locally to keep it handy.
pend

Once again, apologies for the syntax highlighter I’m using. My pair of ex af, af' opcodes apparently turned a whole bunch of the midsection into one long string. Fortunately the Z80 is not fooled by such shenanigans…

Anyway, this rather dense chunk of code clears the screen at the beginning, then on line 10 sets the ROM address seed value ($0000 initially).

Line 11 is a sweet Zeus feature that can break, or print values, based on expressions you write. Here, I’m writing an expression in slot 1, and setting it to be active on line $—the next opcode, which in this case is ld a, NumberOfStars. It doesn’t matter too much where you set it—in this case, the important thing is that it’s after hl has been set, but before it changes again.

The expression itself is zeusprinthex(1, hl). The first 1 means “always evaluate the expression1“. Everything else inside the brackets is a comma-separated list of things to print—in this case, just hl. Because I used zeusprinthex, the arguments are printed as hex values—as you might expect, zeusprint(1, hl) would print the decimal value of hl.

Referring back to the video, this is indeed what happens.

The next section (lines 12-19) sets up a loop counter, stores it away in the a' alternative register, then reads a couple of bytes from the ROM. These bytes, taken together, are effectively a random number between 0 and 65535. The size of our screen thirds is 2KB, or 2048 bytes. It turns out we can zeroise the leftmost five bytes, to turn it into a random number between 0 and 2047. Once again, powers of two are the bomb!

10
11
12
13
14
15
16
17
18
19
; stars.asm
 
                        ld a, NumberOfStars             ; Loop through this many times, once for each star.
DrawLoop:
                        ex af, af'                      ; Save number of stars (loop counter).
                        ld e, (hl)                      ; Read 2 bytes,
                        inc hl                          ;   for this star,
                        ld a, (hl)                      ;   into de.
                        and %00000111                   ; Constrain de between 0..2047
                        ld d, a                         ;   (size of top screen third).

The following section (lines 20-22) Grabs three of the bits from the part of the screen address that governs the X coordinate (meaning it won’t change as it vertically scrolls), and picks one of the 8 pixels within the pixel address to plonk a star into.

18
19
20
21
22
; stars.asm
 
                        ld a, e                         ; Mask out a bit number (0..7) from the X coordinate,
                        or %11000111                    ;      turn it into a "set n, a" instruction ($CB 11nnn111),
                        ld (SetBit), a                  ; SMC> then write this into the pixel-drawing code.

This is really fancy stuff. The Z80 processor is wired up internally in a very logical and consistent way, which means there are many relationships between similar opcodes. The opcodes for setting a bit all follow this rule, or formula: SET b, r is a two-byte instruction, the first of which is always $CB. The second is $C0+(8*b)+r, where b is a bit number between 0 and 7, and r is a register chosen from one of these values:

Register    Value
A           7
B           0
C           1
D	    2
E	    3
H	    4
L	    5

We’re using register a, which is %00000111, to set the star pixel. $C0 also happens to be $11000000. Adding these two together gives us a mask of %11000111. We take the three bits we chopped out of the X coordinate, which are in the exact position to fill the three zero bits in our mask, then or them together in line 22. The result is an opcode that’s always one of the following:

set 0, a
set 1, a
set 2, a
set 3, a
set 4, a
set 5, a
set 6, a
set 7, a

Having done that, line 22 writes it into the correct place in the program (line 27), ready to be run shortly. This is similar to how an assembler works, but we used the technique in our own program.

The code between lines 23 and 35 calculates a pixel byte based on our X and Y coordinates, executes our hand-assembled set instruction, and writes the star byte to the display file. It then does it two more times to the other two screen thirds, taking advantage of the fact that the screen thirds are all separated by exactly $0800 bytes (2048 in decimal).

The rest of it is just boring glue code that waits for a key to be pressed, moves to the next ROM seed value, clears the screen again, and reruns the whole process.

In the next tutorial I’ll explore ways to vertically scroll the stars. Cheers!

ZalaXa 5 — Wide Tiles

In this tutorial I’d like to take a little step sideways, and look at something that might come in useful later, and which will pave the way to some fun stuff in part 6.

The btiles we looked at in part 4 were 16×16 pixel size. NIRVANA+ also supports 24×16 wide tiles, in a .wtile file format. Take a look at \tutorials\part5\tiles\monster.wtile in the ZX-Paintbrush graphical editor.

These are the same two monster tiles we animated in part 4, in wtile format instead of btile format, repeated four times each. We will come back to the reason for the repetitions in part 6!

In order to use these wide tiles, I’ve made a few small changes to the part 4 code. Defining the ENABLE_WIDE_SPRITE and ENABLE_WIDE_DRAW constants enables wide tiles in NIRVANA+.

40
41
42
43
; constants.asm
 
ENABLE_WIDE_SPRITE      equ true
ENABLE_WIDE_DRAW        equ true

The database has a new WTile table importing the new tiles. (I suppose it doesn’t look much like a table, but I tend to think of any data in a regular repeating format as a table—the curse of having been a database programmer for many years. In this particular case, each btile is always 48 bytes long, and a wtile is 72 bytes long, so files containing tiles are totally made up of tabular data!)

14
15
16
17
18
19
20
21
22
23
; database.asm
 
WTile proc
::WIDE_IMAGES:
;                                                           BTile         Best
;          FileName                                       Indices    Viewed As     Notes
Monster equ ($-WTile)/Sprites.BTileLen
import_bin "..\tiles\monster.wtile"                     ; 000-007        4 x 2     Preshifted monster
 
pend

You may notice that they’re in a separate table, and the WTile indices start again at 0, even though we’ve left the BTiles in the program. This is because NIRVANA+ treats them separately—in fact you can have 255 of each type of tile, and can even mix and match them.

Finally, I’ve tweaked the AnimateDemo routine to alternate the tile indices between 0 and 4. Technically-speaking, the xor opcodes do a boolean exlusive-or operation on numbers. xor %00000100 (or xor 4 in decimal) flips bit 2 of the a register, leaving the other bits unchanged. In this case, it comes to the same thing as repeatedly adding or subtracting 4 from a. Because of the cunning way our wtiles are laid out (in sets of powers of two), the effect is to select the first monster in each animation frame.

3
4
5
6
7
8
9
10
11
12
13
14
15
; sprites.asm
 
AnimateDemo             proc
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        and %00000111                   ; Take the lowest 3 bits (effectively FRAMES modulus 8),
                        ret nz                          ;   and return 7 out of every 8 frames.
 
                        ld a, (Sprites.AIndex)          ; For every 8th frame, read Sprite A's tile index,
                        xor %00000100                   ;   alternate between (0 => 4 => 0 => 4 => etc),
                        ld (Sprites.AIndex), a          ;   then save it back.
                        ret
pend

The end result looks just the same as part 4, but we have a new technique up our sleeves now!

In the next part, I’m going to make our sprite move around the screen—animation in space, as well as the animation in time we already have.

ZalaXa 4 — First Glimpse of Multicolour

Well here we are! If you pull the latest commits from GitHub. you should see the code for part 4 of the tutorial.

If you Assemble And Run \tutorials\part4\src\main.asm in Zeus, you’ll see the familiar menu we wrote in part 3. This time, if you press space, hopefully you’ll see some multicolour animation!

Let’s try and have a look at what I added. First of all, you will notice there are now eight source files, in which I’ve reorganised the previous code, and added some new code:

Many assemblers allow this, and it does help to organise your code. In Zeus, it’s done with the include directive:

25
26
27
28
29
30
31
32
33
; main.asm
 
include                 "menu.asm"
include                 "sprites.asm"
include                 "utilities.asm"
include                 "database.asm"
include                 "constants.asm"
include                 "macros.asm"
include                 "nirvana+.asm"

The last file (which is last deliberately, and I’ll explain why later) is Einar’s source code for NIRVANA+, taken directly from his Dropbox drive. We won’t look at this in detail for now—suffice it to say it has an NIRVANA+ API, and we can call routines to do magic multicolour things.

The /tiles/nirvana+.btile file also comes from Einar’s Dropbox. This file contains 16×16 pixel multicolour tiles, in a special 8×2 attribute format tailored for NIRVANA.

ZX-Paintbrush is a graphical editing tool we will find indispensible for working with btiles. Download it from Klaus Jahn’s website here (by clicking on the image).

If you open the file in ZX-Paintbrush, you can see it contains the 17 sprites from part 1 (by Dave “R-Tape” Hughes):

A slightly closer examination reveals the important detail—each 8×2 pixel block can have exactly one background and foreground colour (and its own brightness and flash value), enabling the multicolour magic!

All I’m doing here is cycling between the first two btiles in this set, to do some simple but effective animation. First we clear the standard 8×8 attributes (for a fast clean CLS), then we set the index and coordinates of NIRVANA+ sprite A (there are eight of them) to the first btile in the top left hand corner, then we enable NIRVANA+:

17
18
19
20
21
22
23
24
25
26
27
; menu.asm
 
SetupGame               proc
                        call ClsAttr                    ; Clear the 8x2 attributes for a fast CLS before setup
                        ld a, BTile.NirvanaDemo         ; BTile.NirvanaDemo is 0 - the index of the first tile in the set
                        ld (Sprites.AIndex), a          ; Set NIRVANA+ sprite A to this sprite index
                        ld hl, $1000                    ; LSB is $00 (the column), MSB is $10 (the line, decimal 16)
                        ld (Sprites.AColumn), hl        ; Set NIRVANA+ sprite A coords to 0, 16
                        call NIRVANA_start              ; Enable NIRVANA+
                        ret
pend

NIRVANA+ horizontal coordinates range from 0 to 31—standard Spectrum character columns. Vertical coordinates range from 0 to 215. NIRVANA+ never draws anything multicolour on the first character row. It also allows you to draw a tile completely off the top or bottom of the screen, so the visible range is actually between 16 and 199. Don’t worry too much about this for now, just take note that pixel line 16 is the top of the visible NIRVANA+ screen.

Our main loop now has a call to AnimateDemo, 50 times a second:

17
18
19
20
21
22
23
; main.asm
 
Loop:
                        halt                            ; Wait until the next 50th second frame
                        call AnimateDemo                ; Animate our monster
                        jp Loop                         ; Go into an endless loop (for now...)
pend

The AnimateDemo routine reads the lowest byte of the Spectrum ROM’s frame counter, which is increment every 50th of a second by NIRVANA’s interrupt handler—when it goes above 255 it wraps back round to zero. It turns that into a number between 0 and 7 with a fancy bitwise modulus calculation using the and opcode. There are 32 lots of 8 in the 256 possible values of a byte, so this is a clever fast way of counting in eights (well, between 0 and 7).

So, for 7 out of every 8 frames, it returns without doing anything.

3
4
5
6
7
8
9
10
11
12
13
14
15
; sprites.asm
 
AnimateDemo             proc
 
                        ld a, (FRAMES)                  ; Read the LSB of the ROM frame counter (0.255)
                        and %00000111                   ; Take the lowest 3 bits (effectively FRAMES modulus 8),
                        ret nz                          ;   and return 7 out of every 8 frames.
 
                        ld a, (Sprites.AIndex)          ; For every 8th frame, read Sprite A's tile index,
                        xor %00000001                   ;   alternate between (0 => 1 => 0 => 1=> etc),
                        ld (Sprites.AIndex), a          ;   then save it back.
                        ret
pend

But for one frame in every eight, it flips the btile index of sprite A between 0 and 1 using a bitwise xor operation. In other words we do a two-frame animation, alternating every 0.16 of a second!

We won’t keep any of this code in the final game, but it neatly demonstrates two things:

  • we don’t need to do anything too fancy to set up NIRVANA+, and
  • a little animation really helps bring graphics to life.

More next time!

ZalaXa 3 — A Simple Start Menu

I bet everyone is eager to dive right in and start with some multicolour goodness. I definitely am. We’re not quite there yet, though. Lets’s set up a simple start menu first. There’s a good reason for this, which will be apparent later 🙂

Let’s have a look at the code for part 3 in GitHub. The first section, which deals with configuring Zeus and setting org, is the same as part 2.

The next section expands on the starting code that gets run as soon as we jump into our program:

12
13
14
15
16
Start                   proc                            ; A named PROCedure (also our start point)
                        ld sp, Start                    ; Put our stack right below the program
                        Border(Black)                   ; Set the border to black using a helper macro
                        call ClsAttr                    ; Call another named procedure to do a fast CLS (like GOSUB)
                        Print(MenuText, MenuText.Length); Print text on the screen using ROM routines

Immediately, on line 13 we set our stack to just below the program. This is somewhat of a personal preference, but stacks are generally either kept above or below our programs, and there’s a possibility this might end up being a 128K-only game. If that’s the case, the top 16K of RAM becomes quite valuable as it can be switched in and out with other 16K banks. Having the stack in this area tends to put a crimp on this.

Line 14 calls a macro to set the border to black. Hopefully this is self-explanatory. I like to treat macros as opportunities to improve code readability—although the opposite can be true too! The macro, further down at line 45, is the standard way of doing this in Z80.

45
46
47
48
Border                  macro(Colour)                   ; Macro (makes the code more readable) to set border
                        ld a, Colour                    ; Set a to the colour desired
                        out (ULAPort), a                ;   and output it to the ULA Port (defined in constants)
mend                                                    ; No RET is needed - this code is inserted inline

ULAPort is a constant I use instead of $FE (decimal 254), purely to make the code self-descriptive. The other readability win here is that the macro is parameterized—whatever you pass in as the value of Colour gets substituted. As I noted in the comments, there is no difference between writing Border(0), and writing:

                        ld a, 0                         ; Set a to black
                        out ($FE), a                    ;   and output it to the ULA Port

Incidentally, Zeus’s macro expansion is pretty clever. I could use my macro, unchanged, with Border(b) (without any quotes around the b). As b is a valid Z80 register, and ld a, b is a valid opcode, Zeus will assemble exactly that! It’s even smart enough to know that Border(hl) would result in ld a, hl—and grumble mightily that this is an invalid opcode.

But where were we? Oh yes. call ClsAttr on line 15 is a function. The code in this function is big enough that we don’t want to repeat it unnecessarily by inlining it every time we clear the screen. Nor does it take any parameters, so let’s assemble it once, invoke it with call, and let it return to the next line with ret, exactly like GOSUB/RETURN in BASIC. The function looks like this:

33
34
35
36
37
38
39
40
41
ClsAttr                 proc                            ; Do an attribute CLS using LDIR block copy
                        xor a                           ; Set a to 0 (blank ink, black paper)
                        ld hl, AttributeAddress         ; Address to start copying from (start of attributes)
                        ld de, AttributeAddress+1       ; Address to start copying to (next byte)
                        ld bc, AttributeLength-1        ; Number of bytes to copy (767, all the attirbutes)
                        ld (hl), a                      ; Set first byte to attribute value
                        ldir                            ; Block copy bytes
                        ret                             ; Return from the procedure (like RETURN)
pend

This is a pretty easy method to copy the same value into a range of bytes. Again, AttributeAddress and AttributeLength are defined as constants for readability.

On line 16, Print(MenuText, MenuText.Length) is another macro. This one is highly parameterized, and as such really improves readability.

52
53
54
55
56
57
58
59
Print                   macro(TextAddress, TextLength)  ; Macro to print text on the screen using ROM routines
                        ld a, ChannelUpper              ; Channel 2 (defined in constants) is the upper screen
                        call CHAN_OPEN                  ; Open this channel (ROM routine)
PrintLoop:              ld de, TextAddress              ; Address of string to print
                        ld bc, TextLength               ; Length of string to print
                        call PR_STRING                  ; Print string (ROM routine)
 
mend

This makes use of two ZX Spectrum ROM routines, to print a string of text on the upper screen. I won’t say too much about them, as they’re often used in other programs.

You’ll notice I’m using dot notation to refer to MenuText.Length in the macro invocation. MenuText is another procedure which I’ve used to encapsulate some data that will be assembled into bytes in RAM, and also some constants. All the constant definitions inside a proc are local to that proc, so there’s an opportunity to namespace your labels for greater semantic clarity. I’m also making use of colour and attribute constants, so that the code resembles a BASIC PRINT statement as much as possible.

63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
MenuText                proc                            ; Named procedure to keep our print data tidy
                        db At, 7, 13                    ; These codes are the same as you would use
                        db Paper, Black, Bright, 1      ;   with Sinclair BASIC's PRINT command
                        db Ink, Red, "Z"
                        db Ink, Yellow, "A"
                        db Ink, Cyan, "L"
                        db Ink, Magenta, "A"
                        db Ink, White, "X"
                        db Ink, Green, "A"
                        db At, 21, 6
                        db Ink, Yellow, "PRESS "
                        db Ink, White, "SPACE"
                        db Ink, Yellow, " TO START"
Length                  equ $-MenuText                 ; Let Zeus do the work of calculating the length
pend                                                   ; ($ means the current address Zeus is assembling to)

After printing the menu, the next section goes into an endless loop until the space key is pressed.

17
18
19
20
21
22
23
WaitForSpace:                                           ; All labels inside procedures are local to that procedure
                        halt                            ; Wait for the next 1/50th second interrupt (like PAUSE 1)
                        ld bc, zeuskeyaddr(" ")         ; Get the IO address to input
                        in a, (c)                       ; Read those 5 keys
                        and zeuskeymask(" ")            ; AND with the bit for SPACE
                        jr z SetupGame                  ; If it's zero the key is pressed
                        jp WaitForSpace                 ; Otherwise check keys again

Again, this is standard Spectrum Z80 stuff for reading keys. Zeus has nice zeuskeyaddr and zeuskeymask functions to make it slightly easier to code and read.

The final section does a clear screen and goes into another endless loop after the space key is pressed. This will probably get replaced in the next tutorial, but I wanted it to be clear that pressing space actually does something.

24
25
26
27
28
29
SetupGame:
                        call ClsAttr                    ; Clear the screen to prove we pressed space
EndlessLoop:
                        halt
                        jp EndlessLoop                  ; Go into an endless loop (for now...)
pend

The code at the end is worth mentioning briefly—Zeus can create TAP, TZX, SNA and Z80 files directly from code. What I’m doing here is similar to what Pasmo does with it’s --tapbas mode. Where Zeus comes into its own, though, is generating tape files for 128K Spectrums. We will see later, perhaps 🙂

101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
BinPath                 equ "..\bin"                    ; Relative to main.asm
TapFile                 equ BinPath+"\ZalaXa.tap"       ; Filename of tap file
 
 
 
; Make tape file
End                     equ $                           ; Calculate the last byte of our program
Size                    equ End-Start                   ; Count the bytes to save to tape
output_tap              TapFile, "ZalaXa", "seven-fff.com/zalaxa", Start, Size, 2, Start
                                                        ; Make a .TAP file. Parameters:
                                                        ;   1) the file name
                                                        ;   2) the name of the BASIC loader program
                                                        ;   3) a comment that goes in the TAP header
                                                        ;   4) Start of machine code program
                                                        ;   5) Length of machine code program
                                                        ;   6) Zeus mode 2 files use the standard ROM loader
                                                        ;   7) Tell the BASIC loader what to run with RANDOMIZE
                                                        ;       USR (like Zeus_PC tells the Zeus emulator)

Well, that was a very long, very explain-y post. Next time I will talk a little more about NIRVANA+ and introduce some multicolour code!

ZalaXa 2 — Setting up the dev environment

I’m going to dive right in and talk about setting up my dev environment. All the code and techniques we use with Z80 and ZX Spectrum development can be made to work with any toolchains, and every established developer has their favourites.

I’m no exception, and I’m an enthustiastic user of Simon Brattel‘s Zeus. Zeus has a lineage going back to 1977 on Simon’s homebrew computers, and on the Spectrum from the beginning. Many now-legendary games developers used Zeus back in the day.

Simon was also an early pioneer of cross-development. Many of his classic games, like Halls of the Things and Dark Star, were written on his Z80 homebrew computer, Basil, with a Parasys debugger link between Basil and the Spectrum.

The Windows cross-development IDE has had a continuous pedigree since, as the main IDE for Simon’s electronics and processor design business. And, for the last decade, as a continuously-developed Spectrum-oriented assembler, IDE and emulator with, incidentally, inbuilt support for NIRVANA+.

My personal affiliation to Zeus is based on how easy it makes my development process. I suspect most of this is an affinity between the way Simon and I think—some of this being that our thinking is similar, and some being the way I’m challenged to think differently.

Whatever the philosophical underpinnings, the end result is that I’ll be doing this tutorial series on Zeus. Feel free to follow along with Zeus if you’re a relative beginner, or adapt my examples for your own favourite IDE, assembler and emulator.

Installing Zeus is easy. Download the latest version of zeus.exe here, stick it in a directory and make a shortcut to it. If you’re on Windows 7/8/10, you’ll have to do the usual unblocking the first time you run the program. Zeus works well on Linux and MacOS under Wine, although I only use Windows myself.

I’ve made a GitHub repository for this series of tutorials. Familiarize yourself with the git version control system, and the process of cloning a repository, if you don’t already know this. I use TortoiseGit on Windows, and find it very simple and intuitive.

I will establish a convention that the source for the latest version of ZalaXa will be inside the /src directory, and the source for a particular tutorial post will be inside the /tutorials directory—this posts code is found at /tutorials/part2, for example.

Once you have cloned the repository and updated it to the latest version, choose File >> Open in the Zeus menu, and open \tutorials\part2\main.asm. You will see this code—the bare minimum template to assemble a Z80 program and run it in Zeus’s emulator:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
; main.asm
 
zeusemulate             "48K"                           ; Tell the Zeus emulator to be a 48K Spectrum
zoLogicOperatorsHighPri = false                         ; Zeus assembler options
zoSupportStringEscapes  = false                         ;   (see Config tab
zoAllowFloatingLabels   = false                         ;   for details)
Zeus_PC                 = Start                         ; Tell the Zeus emulator where to start running code at
org                     $8000                           ; Tell the Zeus assembler where to place the code
 
 
 
Start                   proc                            ; A named PROCedure (also our start point)
                        jp Start                        ; Go into an endless loop!
pend

Click the Assemble Then Emulate button:

You should be rewarded with an assembler status message saying this:

nErrors = 0 nRedef = 0 nUndef = 0

and a completely blank white spectrum screen in the Zeus emulator:

And on that bombshell, we will continue in the next post!

Zeus Data Breakpoints — Part 1

One of the nice things about the Zeus Z80 cross-assembler is the debugger built into its integrated emulator.

OMOIDE, one of the ZX Spectrum games I’m working on has a huge conceit—the entire game is presented as if it’s an obscure Japanese game that never got translated into English. The text is written in English, in katakana script, as the Japanese do for foreign loanwords, and often also for videogames.

I softened slightly on the menu items, as it’s all too easy for players to accidentally select a joystick option that renders them a) unable to play the game, if not just b) confused. I added a little marquee at the top that discreetly cycles through the menu options in English.

The text for option 0 is supposed to say 0: PLAY in a tiny 3×5 pixel font (actually one of the Robotron 2084 fonts), but sadly it doesn’t. It’s something more akin to N. Qi Nw, which is no good to anybody, apart from possibly a klingon.

I checked all the obvious things, and couldn’t for the life of me figure out why this item (and only this item) gets corrupted. It happens consistently, even if you assign that text to a different option—the problem moves with the text, not with the menu slot. It’s semi-legible, like the lines got shuffled around, which makes it worse than random garbage, as there’s obviously some logic to it, albeit wrong logic.

Let’s look at the data that’s copied onto the screen. This is a multicolour NIRVANA+ menu, which means there’s only about 15,000 T-states available per frame to do everything, instead of the usual 70,000-odd. For speed of reading, writing and address calculation, I’m storing the data in the same format the screen does. Which, if you’ve ever watched a loading screen appear line by line, you’ll know is not laid out in a linear coordinate-based fashion.

I write the text in a standard .SCR file, and load it into memory at a convenient place. It turns out I don’t need the while file, only about 3/5ths of the first third of the pixels, up until the green dots. And none of the attributes – they’re just they’re to make it easier to work with in my graphics app. By checking in a hex editor, I can see I only need the first 1139 bytes—still a bit wasteful, but I have the space and I need every T-state.

This data is referenced in a table, where zxpixeladdr() is a Zeus helper function that converts pixels into addresses—zxpixeladdr(0, 0) would emit $4000, etc. Only the low byte of each address needs to be stored, because the high byte is the same for all the entries—halving the size of the table.

align 256
MenuExplanation proc Table:
 
  ;                                   Low   Index   Function
  db MenuText.Offset+zxpixeladdr(  0,  0)   ;   0   0: Play
  db MenuText.Offset+zxpixeladdr( 72,  0)   ;   1   1: Keyboard
  db MenuText.Offset+zxpixeladdr(144,  0)   ;   2   2: Kempston
  db MenuText.Offset+zxpixeladdr(  0,  8)   ;   3   2: Sinclair
  db MenuText.Offset+zxpixeladdr( 72,  8)   ;   4   2: Cursor
  db MenuText.Offset+zxpixeladdr(144,  8)   ;   5   2: Fuller
  db MenuText.Offset+zxpixeladdr(  0, 16)   ;   6   2: Kempston Mouse
  db MenuText.Offset+zxpixeladdr( 72, 16)   ;   7   2: AMX Mouse
  db MenuText.Offset+zxpixeladdr(144, 16)   ;   8   3: Help
  db MenuText.Offset+zxpixeladdr(  0, 24)   ;   9   4: High Scores
  db MenuText.Offset+zxpixeladdr( 72, 24)   ;  10   5: Credits
 
  struct
    Low         ds 1
  Size send
 
  Len           equ $-Table
  Count         equ Len/Size
  High          equ high( MenuText.Offset+zxpixeladdr(0, 0))
  Joystick      equ 2
  Items         equ 6
 
pend

The code that reads and prints the date looks like this. PageBank() is a macro I use to switch the 128K upper RAM bank. As you can see, it uses ldir to do a zigzag block copy of the data—the first, third and fifth rows from left to right, and the second and fourth rows from right to left.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
PrintMenuExplanation    proc                            ; MenuExplanation.Index is passed in L
                        PageBank(0, true)
                        ld h, high(MenuExplanation)
                        ld l, (hl)
                        ld h, MenuExplanation.High
                        ld de, zxpixeladdr(80, 8)
                        ld bc, 9
                        ldir
                        inc h
                        inc d
                        dec l
                        dec e
                        ld bc, 9
                        lddr
                        inc h
                        inc d
                        inc l
                        inc e
                        ld bc, 9
                        ldir
                        inc h
                        inc d
                        dec l
                        dec e
                        ld bc, 9
                        lddr
                        inc h
                        inc d
                        inc l
                        inc e
                        ld bc, 9
                        ldir
                        ret
pend

As I always do when I hit a brick wall, I reached for the Zeus debugger and its zeusdatabreakpoint feature, inserting them into the code like this:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
PrintMenuExplanation    proc                            ; MenuExplanation.Index is passed in L
                        PageBank(0, true)
 
                        ld a, l                         ; Save L to print in the breakpoints
                                                        ; (not needed in the final code)
                        ld h, high(MenuExplanation)
                        ld l, (hl)
                        ld h, MenuExplanation.High
                        ld de, zxpixeladdr(80, 8)
                        ld bc, 9
                        zeusdatabreakpoint 2, "zeusprinthex(1, a, hl, de, bc)", $
                        ldir
                        inc h
                        inc d
                        dec l
                        dec e
                        ld bc, 9
                        zeusdatabreakpoint 2, $
                        lddr
                        inc h
                        inc d
                        inc l
                        inc e
                        ld bc, 9
                        zeusdatabreakpoint 2, $
                        ldir
                        inc h
                        inc d
                        dec l
                        dec e
                        ld bc, 9
                        zeusdatabreakpoint 2, $
                        lddr
                        inc h
                        inc d
                        inc l
                        inc e
                        ld bc, 9
                        zeusdatabreakpoint 2, $
                        ldir
                        ret
pend

There are nine general purpose slots that you can write data-driven breakpoint expressions in—plus slots to break on expressions involving data reads, writes, IO port reads and writes, and RAM page changes. Expressions can be set in the UI or in code—the latter allowing them to persist across multiple debugging sessions.

The expressions can be extremely complicated, and can read and change memory if you need to. Here I’m keeping it simple. zeusprinthex(1, a, hl, de, bc) means always (1) print these expressions (the values of a, hl, de and be) to the debug output window, whenever the emulator’s PC is pointing at these addresses (the five values of $, which equates to the five lines following each expression). I’m using the same slot (2) for all of them, as the expression is the same.

Running through the first three menu items, it looks like this:

I put a general non-data breakpoint in at the start of the routine too, purely because it separates out the debug output nicely.

Immediately you can see the pattern is wrong. For the second and third menu items, hl (the source address) increases by $100 for each of the five lines. But for the first item it doesn’t! It’s an edge-case—the lines that go wrong start on a 256-byte boundary (i.e. has an $NN00 address).

0000 D100 402A 0009
0000 D208 4132 0009
0000 D200 422A 0009
0000 D308 4332 0009
0000 D300 442A 0009
 
0001 D109 402A 0009
0001 D211 4132 0009
0001 D309 422A 0009
0001 D411 4332 0009
0001 D509 442A 0009
 
0002 D112 402A 0009
0002 D21A 4132 0009
0002 D312 422A 0009
0002 D41A 4332 0009
0002 D512 442A 0009

I could fix this in the code with special handling, but it’s much easier to shift everything along one byte—which equates to 8 pixels to the right—in the .SCR file! After all, I have the space 🙂

  ;                                   Low   Index   Function
  db MenuText.Offset+zxpixeladdr(  8,  0)   ;   0   0: Play
  db MenuText.Offset+zxpixeladdr( 80,  0)   ;   1   1: Keyboard
  db MenuText.Offset+zxpixeladdr(152,  0)   ;   2   2: Kempston
  db MenuText.Offset+zxpixeladdr(  8,  8)   ;   3   2: Sinclair
  db MenuText.Offset+zxpixeladdr( 80,  8)   ;   4   2: Cursor
  db MenuText.Offset+zxpixeladdr(152,  8)   ;   5   2: Fuller
  db MenuText.Offset+zxpixeladdr(  8, 16)   ;   6   2: Kempston Mouse
  db MenuText.Offset+zxpixeladdr( 80, 16)   ;   7   2: AMX Mouse
  db MenuText.Offset+zxpixeladdr(152, 16)   ;   8   3: Help
  db MenuText.Offset+zxpixeladdr(  8, 24)   ;   9   4: High Scores
  db MenuText.Offset+zxpixeladdr( 80, 24)   ;  10   5: Credits

Bingo, bug fixed! This took about a minute and half from starting to write the databreakpoint expressions to testing the fix—waaay shorter than it took to write up for the blog! Sure, I could have stepped through the code in any emulator, and figured out the same thing, but this approach scales up very well when the problem is more complicated, particularly when it is spread out over multiple routines across a longer timeframe.