Sunday, 25 May 2025

ZX81 BASIC on the Minstrel 4th

I have written this post several times, one got to over 4000 words and I wasn't even half way through, but they have all been a bit too rambling and probably going into more detail than anyone needs.

So, this is the forth attempt (how appropriate). This time with more pictures and diagrams and (hopefully) fewer words.

Let's see how I get on....

The aims:

  • ZX80 4K ROM Integer BASIC running on the Minstrel 4th
  • ZX81 8K ROM Floating Point BASIC running on the Minstrel 4th

I already have the ZX80 version working - see posts from 2023:

ZX81 BASIC is going to be a bit more work, but I will get onto why later.

The ZX80, ZX81, Jupiter Ace and Minstrel 4th are all Z80 based machines with a 32x24 character based display, cassette storage and 40 key keyboards. All good. But there are differences.

Display differences

The Jupiter Ace and Minstrel 4th have a 32x24 character, memory mapped display. This uses 1K of dedicated video RAM at $2400 and is stored sequentially from the top left character to the bottom right. Each character on the screen is 1 byte of RAM. 768 bytes in total.

Looking at $2400, we can see where the display is stored.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
2400
76
6C
69
73
74
20
20
20
20
20
20
20
20
20
20
20
2410
20
20
20
20
20
20
20
20
20
20
20
20
20
20
20
20
2420
46
4F
52
54
48
20
55
46
4C
4F
41
54
20
49
4E
54
2430
20
46
4E
45
47
41
54
45
20
46
2F
20
46
2A
20
46
2440
2B
20
46
2D
20
4C
4F
41
44
20
42
56
45
52
49
46
2450
59
20
56
45
52
49
46
59
20
42
4C
4F
41
44
20
42

What do you mean you can't translate ASCII character codes in your head?

Oh dear, OK, let be do it for you.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
2400
v
l
i
s
t
2410
2420
F
O
R
T
H
U
F
L
O
A
T
I
N
T
2430
F
N
E
G
A
T
E
F
/
F
*
F
2440
+
F
-
L
O
A
D
B
V
E
R
I
F
2450
Y
V
E
R
I
F
Y
B
L
O
A
D
B

That is all quite logically laid out, starting at $2400, with each row of characters taking 32 characters and ending at $26FF.

The area of RAM $2400 to $27FF on the Ace is also mirrored at $2000 to $23FF. When the CPU accesses video RAM in the upper region, it has to wait if the video circuitry is currently drawing the screen. In the lower region, the CPU takes priority, and the display will be blocked. It cannot wait, so just shows empty regions on the display until it can get back. This creates the "snow" effect. The Minstrel 4th does not suffer from this, both areas are uncontended.

The display only used 768 bytes out of the 1024 available. That leaves 256 bytes at $27xx available as scratch RAM, although it's access is governed by the same rules as the video RAM.

ZX81 Collapsed Display File

The ZX80 and ZX81 also have a 32x24 character based display, but the way it is stored is very different. It's location is not fixed, and the format is different.

Let's have a look how that is stored in memory. I'll look at the ZX81 to be consistent throughout this post, but the ZX80 used the same display format with slightly different addresses.

The ZX81 only came with 1K of RAM, so a character mapped display like the Ace would use more than 3/4 of the available RAM. To get around that, those machines used the minimal amount of RAM for the display.

The RAM in the ZX81 starts at $4000, and the start of that contains various system variables. The important ones here are $400C and $400D, together they store the location of the display file.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4000
FF
80
FC
7F
00
80
00
FE
FF
00
00
00
98
40
93
43

In this case, it is $4098.

Looking at $4098, we can see where the "collapsed" display file is stored

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4090
-
-
-
-
-
-
-
76
76
00
00
1D
1C
00
35
37
40A0
2E
33
39
00
0B
2D
2A
31
31
34
0B
76
00
00
1E
1C
40B0
00
2C
34
39
34
00
1D
1C
76
76
76
76
76
76
76
76
40C0
76
76
76
76
76
76
76
76
76
76
76
76
76
76
1C
18
40D0
1C
76
-
-
-
-
-
-
-
-
-
-
-
-
-
-

What do you mean you can't translate ZX81 character codes in your head either?

Oh dear, OK, let be do it for you. Again.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4090
-
-
-
-
-
-
-
NL
NL
 
 
1
0
 
P
R
40A0
I
N
T
 
"
H
E
L
L
O
"
NL
 
 
2
0
40B0
G
O
T
O
 
1
0
NL
NL
NL
NL
NL
NL
NL
NL
NL
40C0
NL
NL
NL
NL
NL
NL
NL
NL
NL
NL
NL
NL
NL
NL
0
/
40D0
0
NL
-
-
-
-
-
-
-
-
-
-
-
-
-
-

The size varies depending on what is on the display. In this case, it only takes up 59 bytes, after that the rest is available for use.

The blank lines are represented by a single NewLine character only (highlighted above). This is very efficient in terms of storage space, but it is quite slow whenever you want to add a character as you have to move the entire rest of the screen down.

As the program gets bigger, the available memory decreases, so the screen gets smaller to compensate when you are running very low.

This display is implemented with very little external hardware. The ZX80 was incredibly cleverly designed, and the ZX81 followed on from that (again, continuing to refer to the ZX81, but also covering the ZX80)

A very quick look at how the ZX81 display works

The RAM in the ZX81 is mirrored so that addresses in the $4xxx range can also be accessed in the $Cxxx range, so the display file at $4098 can also be read at $C098 etc.. In the ZX81, part of drawing the display is to get the Z80 to jump to that higher mirrored address and start trying to execute the display characters as if they were code.

The video hardware detects when the higher address mirror is being executed as bit 15 of the address is set. When that happens, it intercepts the RAM read. It uses the character that has actually been read to draw the screen and passes a NOP to the Z80.

That makes the Z80 do nothing and move onto the next address.

This continues along the line reading characters executing NOPs until it hits a character with bit 6 set ($40-$7F and $C0-$FF). Those it does not display, it passes those through to the Z80.

The only characters in the display file in that range are the newline characters, $76. That just happens to be the Z80 OP code for "HALT".

Character Read
2D
2A
31
31
34
76
..
2D
2A
31
31
34
76
..
Z80 Executes
NOP
NOP
NOP
NOP
NOP
HALT
(waits)
NOP
NOP
NOP
NOP
NOP
HALT
(waits)
Character Displayed
H
E
L
L
O
 
 
H
E
L
L
O
 
 

The Z80 duly halts and waits for an interrupt, which the hardware generates at the start of the next line.

The Z80 wakes up, and starts executing characters again, and again continues until it hits a newline character.

(I am glossing over a lot of detail here, for example each line is read 8 times to build up the bitmap line by line, but I hope you get the general idea)

ZX81 Expanded Display File

This smaller but slower "collapsed" display file was designed to come with minimal RAM.

When 4K or more RAM is installed, the ZX81 switches to using an "expanded" display file. This is faster, but uses a lot more RAM.

$400C/D again tells us the display file starts at $4098.

Looking at $4098, things are a lot more spaced out.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4090
-
-
-
-
-
-
-
76
76
00
00
1D
1C
00
35
37
40A0
2E
33
39
00
0B
2D
2A
31
31
34
0B
00
00
00
00
00
40B0
00
00
00
00
00
00
00
00
00
76
00
00
1E
1C
00
2C
40C0
34
39
34
00
1D
1C
00
00
00
00
00
00
00
00
00
00
40D0
00
00
00
00
00
00
00
00
00
00
76
00
00
00
00
00
40E0
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
40F0
00
00
00
00
00
00
00
00
00
00
00
76
00
00
00
00

And so it continues, up to $4395 where the last NL is.

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4370
00
00
00
00
76
1C
18
1C
00
00
00
00
00
00
00
00
4380
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
00
4390
00
00
00
00
00
76
-
-
-
-
-
-
-
-
-
-

Here you can see each line now takes up 32 bytes for the characters, plus 1 byte for the NewLine, and most of those are spaces ($00).

Address
0
1
2
3
4
5
6
7
8
9
A
B
C
D
E
F
4090
-
-
-
-
-
-
-
NL
NL
 
 
1
0
 
P
R
40A0
I
N
T
 
"
H
E
L
L
O
"
 
 
 
 
 
40B0
 
 
 
 
 
 
 
 
 
NL
 
 
2
0
 
G
40C0
O
T
O
 
1
0
 
 
 
 
 
 
 
 
 
 
40D0
 
 
 
 
 
 
 
 
 
 
NL
 
 
 
 
 
40E0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40F0
 
 
 
 
 
 
 
 
 
 
 
NL
 
 
 
 
....
4370
 
 
 
 
NL
0
/
0
 
 
 
 
 
 
 
 
4380
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4390
 
 
 
 
 
NL
-
-
-
-
-
-
-
-
-
-

That takes up more memory than the Ace / 4th version, 32+1 characters per line, 792 bytes, actually 793 as there is an extra NewLine before the first one vs 768 bytes total on the Ace.

The clever thing is the hardware does not need to know what type of display file you are using, it can use either, or even a combination, a "partly collapsed" display file.

Running ZX80 BASIC on The Minstrel 4th

My first thoughts were to try to convert the ZX80 BASIC code to write directly to the Minstrel 4th video RAM rather than using the display file.

That was quickly abandoned as it would require an awful lot of changes, and would not be compatible with many programs that would be expecting the standard display file format.

Then the moment of inspiration came. The ZX80 spends most of it's time drawing the display. All I need to do is change that code. Instead of generating the video signal, it needs to parse the display file and writes that into the Minstrel 4th display RAM.

This could run every frame and so would keep the Minstrel 4th display updated, and as long as it didn't take longer than the time it used to take for the ZX80 to draw the screen, I can pad it out to the end.

This leaves the ZX80 still spending most of it's time drawing the display, just in a different way.

Let me interrupt you

The ZX80 display relies on getting an interrupt generated by the hardware at the start of every line.

The Minstrel 4th is based on the Jupiter Ace. There the display hardware only generates an interrupt at the start of every frame.

Ah, OK, no, that's not actually a problem.

Actually, it's great.

Again, skipping over rather a lot here, but the ZX80 display routine has a function it calls twice. The first time it draws the top border and the actual display lines. The second time it draws the bottom border.

This display routine sets up the line to be drawn and then starts executing the display as described above. When it hits a $76 / NewLine / Halt character, the Z80 halts and waits until the start of the next line. The IRQ wakes up the Z80 where it sets up the address of next line and away it goes again.

I have replaced that function with one that on the first call, parses the ZX80 display file and writes it to the Minstrel 4th video RAM.

On the second call, it halts the Z80 and wait for the Minstrel 4th video interrupt which happens at the start of the next frame.

Perfect.

Character Set

The ZX80 and ZX81 have a character set in ROM. 64 characters, and they get another 64 by inverting those in hardware.

The Jupiter Ace has a different character set, closer to ASCII, as seen above. That is stored in ROM in a slightly compressed format, but it is not accessed from there. At power on, it is copied into 1K of write only memory that is also shared with the video hardware.

All I need to do then is to copy the ZX80 font into that write only font memory at power on and bingo.

You can tell the ZX80 font by things like wide Z character and unslashed zero.

There are space for 128 characters in the Minstrel 4th character RAM, so the second 64 characters are unused. User software could make use of those to create user defined characters, or indeed redefine the standard 64 character set.

So far, I have not found any programs that actually write to the character ROM, as $2800-$2FFFF is not normally RAM on a ZX81.

OK, well one program, but SYSInfo wrote there deliberately to test if it was RAM or RAM.

That has detected correctly that there is 48K of RAM available. The standard ZX81 only tests for a maximum of 16K, I have left that alone for best compatibility. If you want to make use of the extra RAM, you need to POKE RAMTOP to set the new value.

ZX81 BASIC for the Minstrel 4th

Getting the ZX80 4K BASIC ROM working is great, but my aim was always to get the ZX81 working.

(side note the 4K BASIC ROM is, unsurprisingly 4K, and the Ace ROM was 8K, so adding extra functionality was easy just use the spare 4K. ZX81 BASIC is 8K, so adding to that could be tricky. Luckily I thought of this in advance, and the Minstrel 4th has an extra 5K of ROM available, so all the new code can go in there)

One of the main changes on the ZX81 was adding "compute and display" mode, other wise knows as "slow" mode.

"Slow" mode is better than "Fast" mode

Confused?

The ZX80 only runs in "fast" mode. In "fast" mode, the ZX80 or ZX81 can either be drawing the screen OR running code.

In the new "slow" mode, the ZX81 can draw the screen AND run code. It only does this during the screen borders, so does not get much time (about 6ms out of each 20ms cycle), so is about 30% of the speed of "fast" mode.

This used another clever trick. During the borders, they enable the "NMI Generator". When this is enabled, at the start of each line, it generates a non-maskable interrupt as well as the maskable interrupt.

It took a long time to get my head around the this works in code, particularly the way the stack is used, I have written a very long and detailed post on that, I may post that separately if anyone is interested (so far it seems no one is, but I might still post it to Patreon)

Very briefly, the function that is called twice now does only the top and bottom borders, and there is now a second function that does the actual display bit.

The display bit is the same in both "fast" and "slow" mode, it draws the lines like the ZX80 did by executing the display and halting on the newlines and IRQs waking up a the start of each line.

The border in "fast" mode are also drawn the same as they were on the ZX80.

In "slow" mode, at the start of each border line, it jumps back to user code until the end of the line when the NMI is generated and returns to the display code.

So user code is being run in the borders of the display and it gets onto drawing the display in between.

In my version, I do nothing in the borders, just return. In the middle section, I parse the display as before.

My version of the ZX81 NMIs trick is I then jump to user code until it is interrupted by the frame IRQ and comes back to the display code.

(credit to my backup logic analyser, in a week when everything was breaking, including my normal logic analyser, this one actually performed flawlessly for once)

The implementation is different, rather than running user code in lots of small bursts on the lines in the borders, it gets one long run between the end updating the video RAM and the start of the next frame. User code should know nothing about this, it will appear the same.

Time to give it a go.

Hurray! it works, and I was able to type in a simple program.

On the ZX80 when you run something like that, it goes away and fills the screen with Xs, then onto updates the display when it's finished so you see them appear all at once.

On the ZX81, you see it draw each one in turn.

That may not be the effect you are looking for, so you the ZX81 has a FAST command so you can turn off the display, do all the screen updates quickly, then use SLOW to go back to show the results.

The first proper test

Of course I wanted to try 3D Monster Maze, and yes, that is working nicely, the Ringmaster does his thing, and then it goes to "fast" mode to generate the maze.

The screen appears to freeze at this point, so I have added code to the ZX81 FAST command routine to fill the Minstrel 4th screen with the 50% gray character to create the "mists of time".

As soon as it returns to "slow" mode, the display routine starts to be called again every frame and the first one rewrites the display with the newly generated the maze.

Goal achieved.

More than just 3D Monster Maze

There were various other issues do to with the difference in hardware, as covered in the previous posts. The keyboard is mapped slightly differently and the tape input is on a different bit of the input port. All those can be fixed in the ROM.

It means any games which have their own load routines will not work. Likewise any that have their own keyboard scanners will misread the middle 8 characters of the bottom row as they are rotated by one character. Shift and space are in the same place, and that is all most things would be testing.

And anything which replaces the display routine for pseudo-high-resolution graphics will most likely not work. 

I did make many of the flicker-free games work in the same way as before

I also needed to write new LOAD and SAVE routines, but since the Minstrel 4th video display is always active, I was able to add a countdown to loading and saving.

I tested lots of other things, and almost all that I expected to work did work.

I

Have

Done

A

Lot

Of

Testing

What works

  • Most BASIC programs
  • Most machine code programs
  • Keyboard read which uses the standard INKYE$
  • Keyboard read which uses $02BB keyboard routines and LAST_KEY
  • Character based displays using the display file as normal PRINT, PLOT etc.
  • Character based displays directly manipulating the display file
  • Flicker Free games which jump back to $02B5 to display the screen
  • The standard LOAD and SAVE commands
  • Loading and saving to standard ZX81 tapes (ensure your tape signals are strong enough)

What does not work

  • Anything which includes it's own display routines 
  • Anything which includes it's own keyboard scan routines
  • Anything with a custom loader
  • Anything which disables interrupts or relies on line interrupts

The only one I expected to work but unfortunately did not was Tut-Tut. I am not sure why, as Minoss Knossoss, it's sequel, works fine.

It is a display or timer issue, it starts to write to the screen, but only gets so far and then fills the screen with a pattern and locks up. I suspect either Z88DK is jumping into a place I am not expecting, but I haven't got to the bottom of that yet. Hopefully I will be able to provide a patched version once I find out what's wrong.

Time Critical

The display copy routine is time critical, and I have been working on speeding that up. I will write a separate post about it as it's is (hopefully) quite interesting.

I was able to speed it up about 3 times by skipping the check for newlines and just assuming that everything would be using an expanded display file. That is probably going to be OK, most of the time, but I can't leave it like that. That would run code for 14ms out of 20, so 70% of the speed of "fast" mode.

Putting the test back in place to support collapsed and expanded (and partly expanded) display files, I have managed to reduce the number of cycles it takes by almost a half thanks to some crafty coding and various suggestions on blusky.

Anything which uses PAUSE or the frame counter for timing will be fine, but things that just run as fast as they can, would run twice as fast. You might want that, in the case of Rex, you do not want him to run twice as fast!

For the moment, I have added in a delay loop to slow it back down. I have tweaked the timing to make in run the same speed as a ZX81, at least according to the one test program I have been using.

Currently Shipping

The Minstrel 4th kits are currently shipping with a ROM containing the following:

I think I need to add a bigger EPROM and an extra ROM jumper(s) on the next board revision, there are lots of ROMs to add)

Lots of things to think about for future:

  • I would like to add a simple monitor (like the one in the PET) to the existing Forth and BASIC ROMs, to that end I have added an NMI button in preparation for this.
  • I would like to add IN and OUT commands to the ZX81 BASIC. These are missing, but are on the Spectrum. I spent a while on this and had completed the OUT command, but IN was proving complicated as it meant moving a lot of things around to fit it into the operator precedence table, I will have to revisit that.
  • It would be nice to add a BEEP command, I expect something that will take bits from both the ZX Spectrum and Jupiter Ace BEEP keywords. That is going to need a lot more work.

I will be putting the various ROMs on my github, but for the moment if anyone wants to try those, I have posted the ROM on my Patreon.


Adverts

This Minstrel 4th (with the updated ROM including ZX81 BASIC) is available as a kit or built and tested, from my Tindie store.

It is now also available from Z80kits.com, home of the RC2014. 


There you can get a bundle with the Minstrel 4th, RC2014 backplane and whatever modules you wan to go with it.


Patreon

You can support me via Patreon, and get access to advance previews of development logs on new projects and behind the scenes updates. New releases like this will be notified to Patreon first, if you want to be sure to get the latest things. This also includes access to my Patreon only Discord server for even more regular updates.