Sunday, 6 July 2025

VIC20 Turbo Wedge

Shipping this week from TFW8b there is an updated version of the VIC20 Penultimate Cartridge, the Penultimate Plus 3 DCR.

Like the original Plus 3, this has a built in SD2IEC. This time it uses a full size SD card, and has the drive LEDs visible through the top of the case. There is also a detachable cable IEC cable. If you don't want to use the SD2IEC, just unplug the cable.

The menu is essentially the same, but you might notice the RAM now goes up to 280K.

That is 8 banks of 35K. Just waiting on someone to write a game that will make use of it.....

Turbo Wedge

A big change is this now boasts a turbo loader and DOS wedge that are automatically activated at boot.

This is going to be another one of those posts that scroll a lot, so here is a brief summary for those in a hurry.

  • COMMODORE + RUN/STOP - load and run first program on disk (LOAD"0:*",d,1)
  • SHIFT + RUN/STOP - load and run from tape (as normal)
  • # - display selected drive number
  • #d - change selected drive number (#8 #9 #10 #11)
  • /name - load a BASIC program (LOAD "name",d)
  • %name - load a machine code program (LOAD "name",d,1)
  • name - load and run a program (LOAD "name",d,1 RUN)
  • name - save a program (SAVE "name",d) and display status
  • $ - display directory
  • $C* - display files that start with C etc.
  • @ - get drive status
  • @command - send drive command and wait for response (@X? etc.)
  • >command - send drive command and return immediately
  • @C:newfile=existingfile - copy a file on the same diskette
  • @R:newname=oldname - rename a file
  • @S:name scratch (delete) a file (wildcards supported)
  • @UI - get drive ID string
  • @UI- - reduce send delay (VIC20 only)
  • @UI+ - increase send delay (for C64 compatibility)

Real drive commands

  • @I - re read disk (after disk change)
  • @N:diskname,id - format a disk with name and id
  • @N:diskname - quick format a previously formatted disk
  • @V - validate a disk - tidy up free blocks, unclosed files etc.

SD2IEC commands

  • @CD:name - change directory
  • @CD:name.d64 - mount a disk image
  • @MD:name - create a directory
  • @RD:name - delete a directory
  • @CD← - change back / unmount disk image
  • @XE+ - enable extension hiding
  • @XE- - disable extension hiding
  • @XW - remember changes to XE and UI after reset
  • @X? - display extended drive ID

In the last release of the Penultimate Cartridge, I added a fastloader, which was present in ROM, but needed to be activated with a SYS 40000 command.

That was using 1K of ROM in a space designed for IO devices on the cartridge port.

There are actually two 1K IO blocks available on the cartridge port, but only one was wired for ROM use on the old version of the hardware.

New PCBs were being produced to integrate an SD2IEC with a full size SD socket, making the Penultimate +3 DCR boards (a nod to the C128DCR).

Since the boards were being respun, I took the opportunity to give access to the second block.

With the additional 1K of space (luxury!), I have added a DOS wedge, similar to that used on the Mini PET.

I initially looked to the one in the C64 Epyx Fastload, but I didn't like the way that operated, so I have gone more towards the PET version (which was based on Nil's Eilder's version).

I have also saved you having to type SYS 40000, so now it is enabled when you exit to BASIC (unless you disable it from the Penultimate menu)

Status

With the wedge, you can issue disk commands directly from the READY prompt. With just the @, you can get drive status.

When a disk drive has been reset, the first call to status will return a drive ID. You can ask for that at any time using the @UI command.

Commands operate on the last used disk drive, normally ID 8. On the Penultimate +3, this will be the internal SD2IEC.

Drive IDs

You can display the currently selected drive using the # command.

You can also use that to change the currently selected drive.(#8 though #11)

That will persist until you reset, or change to another drive (or load from tape when it will change back to 8 afterwards).

Yes, you can still LOAD from tape, and since the SD2IEC is internal to the cartridge port, it does not need additional power, so you have access to both the datasette port and the userport.

Directories

One of the most useful things about a DOS wedge like this is you can get a disk directory without overwriting your current program.

This uses the $ command (or you can use @$ if you prefer), and you can use wildcards to limit the list.

LOADing

There are four new ways to LOAD a program, (yes, four), all shorter than typing in LOAD "game",8.

/game

This is the normal LOAD, and is equivalent to typing LOAD "game",8 (or whatever the current drive number is).

There is also %game if you want to do LOAD "game",8,1.

What's the difference?

Well, when you do /game or LOAD "game",8 this will load the program to the default LOAD address for the system with the current RAM options. This is normal for plain BASIC programs as it allows you to LOAD a game SAVEd on a system with a lower amount of memory on one with a larger amount (it is unlikely to work the other way around unless the program is small enough)

When you do %game or LOAD "game",8,1 if will load the program to the LOAD address specified in the file. This is generally the preferred option as it will ensure anything out of the ordinary is loaded where it is intended to go, specially for machine code programs, and buts of multi-part games or graphics data etc.

To demonstrate, I have switched to the VIC having no expansion RAM (yes, the turbo wedge still runs with no expansion RAM).

The  command is used to save the file.

The VIC20 memory map moves around when the memory size is changed. In unexpanded mode, the load address of that program is $1001.

Switching back to full RAM allocation, the normal load address is $1201.

If I load that program using /UNEXPANDED or LOAD "UNEXPANDED",8, then it get loaded to $1201. BASIC copes with this relocation fine.

(yes, you can use wildcards in the load commands as well).

If I was to use %UNEXPANDED or LOAD "UNEXPANDED",8,1 that would try to load it to address $1001, which in full RAM mode is actually the location of the screen, so you can see it overwrites the first line of the display.

BASIC programs are therefore best loaded with the built in relocation so that you can use them in a system with more memory that originally present (i.e. a 0K game with a 32K expansion, but not a 32K game on an unexpanded VIC).

Machine code programs and game and graphic data however do not like to be relocated, unless they have been designed to do so. They usually include jumps to specific addresses and if the code is loaded to a different location, the code will not be there when they jump (game over).

Most of the time % is the one you would want.

You said four?

Oh yes, so far there is / and %. The third is .

game is equivalent to %game (or LOAD "game",8,1) and then RUN.

That is using LOAD "game",8,1 as most of the time that is the right option (if you specifically need the BASIC relocation, then just type /game and then RUN)

The fourth option is COMMODORE + RUN/STOP. This will load the first program from the current disk and then run it.

It types in the %0:* automatically. I know I said fourth, but it's not Forth (or PERL or any kind of regular expression.)

% is LOAD as before, so that is a shortcut for LOAD "0:*",8,1

* means the first program on the disk.

Well, most of the time.

It actually means "the first program on the disk OR if you have previously loaded anything, the program you last loaded".

If I was to load HELLO next, that will be the last program loaded.

Now if you do LOAD "*",8 again, it will load HELLO instead.

Adding the 0: in front forces the "first program on the disk", specifically, "first program on the disk in the first drive of this unit". It is a throwback to the the PET disk drives like the 4040 and the 8250. These were dual drive units, device ID 8, or unit 8 with two drives per device. Drive 0 and drive 1 were selected with LOAD "0:game",8,1 or LOAD "1:game",8,1

And we are back to the first program on the disk again.

N.B. this is the first program displayed when you do a full directory listing. If you are using an SD2IEC type device and you have let Windows near the SD card, it can sometimes mess up the file table and place a different file first to the one you expect.

SAVEing

The  is used for save, so game is the same as SAVE "game",8.

Unlike the normal BASIC SAVE function, when the file has finished saving, the drive status is shown, so you can see how it got on.

With the normal SAVE function, you do not get any feedback, other than the error light flashing on the drive.

If you tried to save again with the same filename using the  command, you would see the error of your ways.

Tape

SHIFT + RUN/STOP will LOAD from tape and RUN, as normal.

Disk commands

There are various useful commands you can now easily access with the wedge using @ (or > if you prefer).

@ - Status

@ on it's own shows status, that can be useful to find out what is wrong if you ever see the drive light flashing.

In the example above, if you tried to save a file with a name that already exists, you could use the @ command to check what was going on.

@ will tell you what category of disaster has befallen you.

N.B. there is a way to force SAVE to overwrite the file, but that is buggy on most of the Commodore disk drives, so I would not suggest using it

@S - Scratch

The @S command can be used to scratch the file (in modern parlance, to delete it).

@S:name

Careful, there is no Y/N confirmation, it will just go ahead and delete the specified file or files, and tell you how many were deleted.

All gone now.

You can now save your file again.

The file has saved correctly this time.

@R - Rename

An alternative would have been to rename the old file.

@R:newname=oldname

That will, as it says on the tin, rename the oldname file to newname. Note the order, new=old.

You can then save your file as planned as there is now no file named HELLO.

And now both files are there.

@C - Copy

The file copy command has a similar syntax.

@C:newname=existingname.

Again, note the order of the names, new=old or alternatively think of it like assignment, destination=source.

And now there are two copies of that file

@UI-

The original IEC disk drive, the 1540, was designed to work with a VIC20. When the Commodore 64 came along, they found there were sometimes problems missing files due to the screen redraw, and so added an extra delay to slow the drive down, and produced the 1541.

(ironically, the 1540 had already been slowed down due to a bug in the 6522. The C64 used the new 6526 so this was no longer a problem, but they couldn't speed it up for that as it wouldn't be backwardly compatible with the VIC20, so the 1541 has doubly slowed down, no wonder SD2IEC is not the fastest in the world. Good thing we have a Turbo loader in the Penultimate Cartridge)

If you are using a VIC20 and want to get back to the faster version of the disk protocol, you can use the @UI- command to reduce the delay. This lasts until the drive is reset or a power cycle. You can also use @UI+ to increase the delay back to C64 safe levels.

That also works on an SD2IEC, when you send the @UI- command, it reduces a delay in the send byte function. You can use the @XW command to make this change permanent. (use @UI+ then @XW to revert this change).

I believe all the SD2IEC drives integrated into the Penultimate +3 DCR cartridges will have this mode already enabled since it is unlikely to be used with anything other than a VIC20. (although I am sure some user will find some way of stacking a C64 on top of their VIC20 and running two power supplies and standing on one leg, drinking a glass of water whilst playing a Beatles LP backwards.....)

Real Disk Drive Commands

If you are using a real 1541 or other IEC disk drive, there are a some extra commands you can use.

@I - Initialise

This re-reads the disk, handy if it gets confused after a disk change (looking at you, 1581).

@V - Validate

The @V command can be used to tidy up a disk, reclaim allocated blocks from unclosed files etc.

@N - Format

This command can be used to format a disk. There is no Y/N confirmation, so be careful.

@N:name,ID will format a disk with the name and ID supplied. (the ID is two characters, usually a number, used to help the drive differentiate between two disks both called "Dave")

You can also omit the ID and just use @N:name for a quick(er) format if the disk has already been formatted.

There is no status display during these operations, just the ticking of the head stepper motor, so you just need to wait for them to return.

>command

I have seen some of the other wedges use > instead of @. I originally had both performing the same task, sending a command to the drive, wait for it to do the job and then display the status when it's finished.

A few of those command may take a while to complete, things like disk format for example, so I have changed the operation of > to send the command and then return immediately. You can then get on with other things, and if you can later check the result using the @ command.

Whilst the disk is formatting, you can get on with writing your next program.

You can then save all the work you did in the minute or so of extra time you didn't have to spend waiting for the disk to format.

SD2IEC Commands

There are some commands that are useful on SD2IEC type devices

@CD - Change Directory

@CD:directory

This changes to a subdirectory.

@CD:image.d64

This mounts a disk image.

@CD←

This unmounts the disk image or moves back one folder

@MD - Make Directory

@MD:directory can be used to create a directory

@RD - Remove Directory

@RD:directory can be used to delete a directory. It need to be empty first, so you would need to scratch the files inside and in any subdirectories first (e.g. @S:directory/* )

@EX - Extension Hiding

Normally an SD2IEC will show file extensions as part of the filename, e.g. HELLO.PRG.

That means if you try to load it as HELLO, it will fail - you need to specify the full name, HELLO.PRG

You can use @XE+ to enable extension hiding. This will now show that as just HELLO

You can use @XW to remember changes to XE and UI after reset, and @XE- to disable extension hiding again

More Stuff

I think that covers most of the things you can do with the wedge.

I will go into more details next time, there are various new games and utilities added, and of course the extra paged RAM.


Adverts

The Penultimate +3 DCR is available to order now, shipping this week.


Tindie

Your reward for reading all of that is notice that there is 15% of everything in my Tindie store throughout July.


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.

Sunday, 29 June 2025

ZX81 BASIC on the Minstrel 4th [Original Version]

This is one of several attempts at writing the"ZX81 BASIC on the Minstrel 4th" blog post. It went on too long and into too much detail. I decided to start again and wrote a new version, the one you hopefully saw a few weeks ago.

I decided to finish it off and add the missing pictures and even some flowcharts and post it to my Patreon. Now it's your turn, and as a bonous, I have added some extra colourful diagrams.

I will warn you now, this is going to be a long blog post.

I've written a lot of it now when it's fresh, and before I forget how it works.

A while ago, I looked into getting ZX80 BASIC working on the Minstrel 4D, and had some success, it was mostly working. Loading and saving were going to be a problem because of the SD card interface on the Minstrel 4D which only understood Jupiter Ace format. It also relied on typing commands into the Ace for loading, which would be the wrong thing to type on BASIC so I would have to find a way to make that work.

I was also using ZX80 BASIC as a step to the actual goal of getting ZX81 BASIC working on there.

Roll on to now and the 4th is back and has a standard tape interface, so that simplifies things.

Lets have another go at ZX81 BASIC.

How hard can it be?

Very, as it turns out.

How the ZX80 Generates Video

Quick refresher. If you want to generate a video signal without a dedicated video chip, you have to sit there bit bashing and counting cycles to make sure your timing is correct.

One way around that is to stop the processor using the HALT command, and sit there doing nothing* until something triggers a low level on the INT pin and wakes the processor up again.

* When halted, the Z80 does not run any instructions, but it does have a mechanism for refreshing dynamic RAM which means it generates reads of each byte of RAM in turn, controlled by a few special registers. This happens all the time, normally when the Z80 has read an instruction and is working on it, the refresh happens in the background.

I won't go into great detail here (he lied), but for each horizontal line on the screen, the ZX80 draws the pixels of any characters that appear on that line, and then executes a HALT instruction and goes to sleep until a counter reaches the end of the line and triggers an interrupt to wake up the Z80. It then gets ready for the next line and repeat until the display is drawn.

The clever thing about the ZX80 is that is uses the refresh mechanism in the Z80 to read the display information, and that is also the counter that actually wakes the Z80 back up at the end of the line. To do this, the A6 address line is hard wired to the interrupt pin!

At the top of the screen there is a vertical sync section to tell the monitor a new frame is about to start. This is where the ZX80 scans the keyboard etc. and it does that in a way that always takes the same number of cycles to get the required timing.

The ZX80 display is ingenious and convoluted. Getting the most out of minimal hardware and software and RAM.

The display is stored in the main RAM, and operates in two modes. To save RAM, it starts with a "collapsed" display file. This only contains characters when it needs to. There is a newline character after the right most character than should be displayed, and on blank lines, there is just the newline character.

That means the display file can be as small as 26 bytes, or when fully populated, 793 bytes.

The next two screenshots are borrowed from an old post:

It uses the grey background mode of the Minstrel 2 to show where characters are not being drawn. The Z80 is halted for most of the area that is greyed out.

The other mode is "expanded" display file. Here every line is 32 characters long plus a newline character. This extra character for each line ironically wastes more space than if they have simply memory mapped the whole display as 32x24 characters, which would have needed 768 bytes instead.

Here the Z80 is working to draw all the characters, including the blank ones, and is only halted at the ends of the lines.

The display file can also be anywhere within RAM.

How the Minstrel 4th Generates Video

The Minstrel 4th hardware is very different to the ZX80. A6 is not hard wired to IRQ for a start. It does have an interrupt, but this comes once every frame, rather than once every line.

Video is generated by a CRTC microcontroller (CRT (Cathode Ray Tube) Controller.

The display is also very differently formatted. The Minstrel 4th has a memory mapped display, with it's own dedicated memory, handily in a place the ZX80 doesn't normally access. This is a straightforward 32x24 block of memory.

This display RAM is on a dual port RAM chip, the Z80 accesses one side of it, and the CRTC chip accesses the other. The CRTC reads through every character in the display RAM and generates a composite video signal from that.

The Minstrel 4th CRTC chip knows nothing of the ZX80 format, or where to find the display file in RAM, so how is that going to work?

Well, the ZX80 is already dedicated to spend most of its time drawing a display, it just needs to generate it in a different way. Rather than directly generating composite video, it will be locating and parsing the display file and writing out the characters to the Minstrel 4th video RAM. (which will be busy generating a display on the other side of the dual port RAM)

This takes less time that it would take to draw the frame, so it can simply* replace the normal display routine and run every frame to keep the Minstrel 4th video RAM up to date (well, one frame behind).

(* "simply", he said, rather optimistically)

One "feature" of the ZX80 is that is has to stop drawing the screen when it goes away to do other things. This creates the characteristic flickering on the display when you type characters.

There is no flicker here, as video RAM stops being updated when in "fast" mode, but the Minstrel 4th video chip is still running unaware of this and continues to display the previous screen.

(later I added code to fill the screen with 50% grey characters when in "fast" mode so it does not look like it has locked up)

That was working well, there were various issues I fixed, such as the LOAD and SAVE routines, keyboard scanner and adding support for "flicker free" games etc. These were covered in the previous posts.

I managed to squeeze in one more update to the Minstrel 4th before the first orders went out the door.

ZX81 BASIC

The Minstrel 4th can now run ZX81 8K ROM floating point BASIC, and it can do it reasonably well.

My criteria (as it often seems to be) is "can it play 3D Monster Maze". And the answer is yes, it can.

This infamously christened "fast" mode as "the mists of time".

Slow mode is better than fast mode. It sounds odd, so I will do a quick recap for those sitting at the back, not paying attention.

The ZX80 only worked in "fast" mode. In that mode, the ZX80 was either drawing the screen OR it was running code. It couldn't maintain the screen whilst running code, and would flicker each time you pressed a key (it has to run code to handle a keypress), or your program would go away and think for a bit and the screen would go away until it was finished.

This flickering was not ideal, and the ZX81 introduced "slow" mode. Which was better. Yeah, maybe a bad naming choice?

Anyway, in "slow" mode, the ZX81 can draw the screen AND run code. However, it can only run the code for some of the time (the top and bottom borders of the screen to be precise), so it runs slower than if it was in fast mode, about a third of the speed.

This used a Non-Maskable Interrupt to interrupt user code to draw the screen and scan the keyboard and then return. Here you can see the sections where the NMI Generator is enabled (all the signals shown are active low), an NMI signal is generated every time there is a horizontal sync pulse. This means out of each 20ms frame, the Z80 is running user code for about 7ms, so about a third of the time. (it is less than that as it is interrupted each line to check if it is finished the border, so it works out about 6ms per frame)

The times when the NMI generator is not active, there is an standard interrupt every line to setup the magic for the next line, just like the ZX80.

A lot of this is implemented in hardware in the ZX81.

The Minstrel 4th does not have any of that, so how did I do it?

Well, what the Minstrel 4th does have is an interrupt, but rather than being every line as on the ZX81, it is every frame. And there is no NMI, and no NMI generator.

I had started with the ZX80, as that was easier to deal with, not having the "slow" mode.

I had changed the display code in the ZX80 ROM. It now gets an interrupt at the start of every frame, allowing it to scan the keyboard, run the new display routine and then halt until the next frame interrupt.

The ZX81 was a bit more of a challenge, and it took an awfully long time to get my head around how to make this work.

How the ZX81 Generates a Display

I will be glossing over lots of things here, but there is a rough description of what goes on in the code. If you want to follow along, check out Geoff Wearmouth's annotated disassembly (courtesy or archive.org)

$0229 - Display 1

This is called at the start of every loop

This checks if the PAUSE command has timed out and returns if it has

...on to...

$023E - Display 2

This calls $02BB to read the keyboard, the IO read triggers the vertical sync pulse

...back to..

$0241 - Display 2 Continued

This checks if a key was returned, and if we are in "fast" mode, returns to process it

...on to...

$0264 - No Key

That's right, no key was pressed

$026B - LoopB

This is a carefully timed delay and then the vertical sync pulse is ended

This then calls $0292 to display the top border

Then it calls $02B5 to generate the actual display

Then it calls $0292 again to display the bottom border

We then go back to $0229 to draw the next frame.

I have tried to draw that as a flowchart.

Drawing Routines

Looking in more detail at the two functions there, in the wrong order to make things clearer.

$02B5 - Draw Lines

This sets up the magic used by the ZX80/ZX81 of using the refresh counter to clock through RAM whilst the Z80 is executing NOP instructions.

To achieve that, it jumps into the display (yes, it executes the characters in the display). The hardware takes over at this point and feeds the Z80 NOP instructions until it hits a newline character which is conveniently (by clever design) the HALT instruction for the Z80. The hardware stands back at this point and actually lets the Z80 see the HALT instruction, so it halts and waits for the interrupt which comes at the end of the line. The interrupt handler then sets up the refresh counter for the next line and jumps back into the display.

When it gets to the end of the lines, it returns to the display routine.

$0292 - Draw Blank Lines

The is where the magic happens.

In "fast" it calls the above function, but points to the newline character at the start of the display file, so it will immediately stop and so a blank line will be drawwn. The interrupt handler then checks if enough blank lines have been drawn, and goes back around until the border is complete. It then returns back to the display routine

In "slow" mode, it enables the NMI generator so that an NMI will be generated at the end of the line, it then goes back to the user code and that runs until the end of the line. The NMI handler also checks to see if the border is complete, and keeps going back to the user code until the border is complete when it also returns to the display routine.

Stacks of trouble

There is an extra complication that took quite a while to get my head around, the way the stack is used. Some of these functions are jumped to, and some called as routines. In simple terms they are the same thing, but with one difference. JP just jumps to the code, but CALL pushes the return address onto the stack, and then jumps to the code.

The key thing comes in the way "slow" mode is handled. It is activated by a function at $0207 which tries to cause an NMI to see if it works (since the ZX81 ROM could also be run on the ZX80 which didn't have the NMI hardware and so could not activate "slow" mode)

When the user code calls $0207, it's return address is pushed onto the stack. If slow mode is enabled, the main register pairs are also pushed onto the stack, it does not return at this point, it jumps to the display code.

(when the display is running, the stack contains user code return address and registers)

Sometime later when we are drawing one of the borders, the function at $0292 pops the four main registers back off the stack and then returns to the user code.

When the NMI handler is called, the user code return address is pushed onto the stack, if it is still in the border, it returns to that address. If it is finished, it leaves the return address on the stack and then pushes on the four main register pairs again and back to the display file.

(the stack again contains user code return address and registers)

I have to admit it did take me a long time to get my head around they way the stack was used by these routines. It's also important to know the ZX81 ROM contains no RETI, RETN or DI instructions. It never officially returns from interrupts, or disables them. It relies on calls to the interrupt handler to disable interrupts, then jumps into other code.

How to replicate that without NMI

My turn to make changes now.

I am trying to make a few changes as possible to keep this compatible. On the ZX80 version, it was easier as that ROM was only 4K, and the Minstrel 4th ROM was 8K (+ 5K extra), so I could add new routines in the 4K space.

The first of those had to copy the ZX80 font from it's ROM into the read only character ROM on the Minstrel 4th. It remains writable, so us users could actually change the font if they wanted to. So far I have only found one program that tries to write to that area (which is normally a ROM mirror on the ZX81) and that is a system info program, so that fine.

I know from the ZX80 version that some games jump into various points of the display routine to do "flicker free" graphics and things like that, so I want to try to keep all the entry points I know about the same.

The system I am trying to replicate get an IRQ every line, and with the option to control the generation of  an NMI every line as well.

The hardware I have to implement that on generates an IRQ every frame.


How hard can it be?......

.... two weeks later ....


After many, many attempts to get this working, and several "scratch that, go back and start over", I have come up with this.

$0292 - Draw blank lines

This now does nothing. It just returns

$02B5 - Draw display

This is important as it is the entry point for various games who have used their own code during the borders and just want the display bit drawn.

That is what I do, parse the display, reading the display file which may or may not be collapsed and generating the memory mapped display in the Minstrel 4th video RAM area.

Once complete, it does one of two things:

  • In "fast" mode, it enable interrupts, and halts the Z80 until the interrupt at the start of the next frame.
  • In "slow" mode, it does what $0292 used to do, it POPs the registers off the stack and returns to the user code.

The new IRQ handler is fired at the start of the next frame and again has two choices:

  • In "fast" mode, it returns to the new display code, which returns to the old display code.
  • In "slow" mode, it PUSHes the main registers onto the stack (which already contains the address of the users code) and heads off to the display code to scan the keyboard.

The section where the Minstrel 4th video RAM is being accessed is when the screen is being copied by the new display routine. You can see it takes almost as long as it takes to draw the actual frame (when the CRTC is addressing the same video RAM from the other side).

The section when it is not accessing the video RAM it is either halted ("fast" mode) or running user code ("slow" mode).

That works out to be about 6.5ms out of 20ms. About the same (well, a little more) than a ZX81.

If I jump ahead to things working, here is the result of a ZX81 speed test program.

When run on a Minstrel 3, it takes exactly as long as the stock ZX81 figure (good to know).

When run on the Minstrel 4th, it shows it is indeed faster, about 115% the speed of a ZX81. Makes it a little more responsive, but shouldn't cause too many issues for games etc.

(I later optimised the code further to get it almost twice as fast, but in the end decided to add a delay to get it to 100% of the speed of a ZX81 - I will cover the optimisation in a separate post)

Some colourful diagrams

Let's try and show that with some more colourful diagrams of a video frame in "fast" mode. Here green at the top shows when the keyboard is being scanned during the vertical sync pulse. Black shows where the screen is being drawn, and blue where the Z80 is halted.

When code is being run (red), it stops generating video frames or scanning the keyboard until the user code lets it restart.

In slow mode, you can see the borders at the top and bottom are now used for user code (red), with the visible lines in the centre as before a mix of drawing characters (black) and waiting for the start of the next line (blue).

In the Minstrel 4th version of "fast" mode, the keyboard is scanned (green), the screen is copied (black) and the rest of the frame is spent halted (blue), waiting for the start of the next frame.

In Minstrel 4th "slow" mode, the keyboard scan and screen draw happen as before, there is then a cycle counted delay, and then the user code is run (red) until the interrupt at the top of the next frame.

The delay (in blue) has been adjusted so the time the code is running (the red area) is the same as the ZX81 in "slow" mode.

RAM

The Minstrel 4th has lots of RAM, in various places.

The ZX81 as standard, only knows about RAM from $4000-$7FFF, I have left it like that, so it will appear as a 16K ZX81 for best compatibility.

The system variable RAMTOP is stored at 16388 and 16389, reading that shows the address of the first byte that is not being used by BASIC.

32768 shows the RAM runs from $4000 to $7FFF as expected.

You can use the standard procedure of POKEing RAMTOP if you want to enable the full 48K of RAM.

There is an extra 1K from $3C00 to $3FFF that you could use, and the "scratch RAM" at the top of video RAM from $2701-$27FF is also available (but not $2700, that needs to be set to 0.

Other Changes

I also had to make changes to the load, save and keyboard scan routines to cope with the hardware changes.

As with the ZX80 version, the display remains active during load, so I have added a countdown.

This shows the number of bytes remaining (in hexadecimal). This is based on a value read from the start of the program. If that is wrong, the countdown will be wrong. If you ever see over $4000, then it is probably corrupted and you may as well rewind and start again.

Limitations

Most things are working. 3D Monster Maze was my test, that needs "slow" mode to work, so wouldn't work on a ZX80 with the 8K ROM (or a Minstrel 2). Pleased to say that works fine.

I also wanted to test Paul Farrow's Kong and PacMan, as those use flicker free code for ZX80 compatibility, those also work.

Anything which uses a pseudo-high-resolution graphics mode is not going to work. Because of the display setup, only pure character based displays will work. So no Rocket Man or 25th anniversary demo I'm afraid.

Anything which uses a fancy tape loader will fail as that will be reading the wrong pin.

Anything which uses it's own keyboard scanner will sort of work, with only the middle 8 keys on the bottom row reading incorrectly. If you press X, it will see that as Z. Anything which uses the standard INKEY$ routine or LAST_KEY etc. will be fine.

For example, Mazogs uses it's own routines, so instead of V for view, you have to press C.

The problem is due to the Symbol Shift key being physically located between Shift and Z, but logically being to the left of Space.

When I designed a membrane overlay for the Minstrel 4th for a standard ZX81 membrane, I had to put they keys where they logically were, leaving the characters shifted to the right.

The only thing I have found so far that does not work is Dave Stephenson's Tut-Tut. I am not sure why, the later Minoss Knossoss works OK.

It starts up, display part of the title and then fulls the screen with garbage and gives up (update - I have now fixed my display code so that Tut-Tut can run unmodified - http://blog.tynemouthsoftware.co.uk/2025/06/a-zx81-game-on-a-zx81-emulator-on-a-jupiter-ace-emulator.html)

Further investigation seems to point at a Z88DK, the compiler used. That has it's own display routine which for some reason transposes IX and IY compared to the original. I can't easily see a way of making that work, it may be possible to build it without that version but I couldn't get Z88DK to build from its source (or indeed get Tut-Tut to build from its source when I used a pre-complied binary of Z88DK, so at that point I gave up)

Fear not there is a port of Tut-Tut for the Ace by George Beckett, so you can still run that version (George has also written a version of 3D Monster Maze, so now you have a choice).

Future features

This is still considered experimental. It works well enough for 10 PRINT "HELLO" 20 GOTO 10 and all of the programs that I have tried (apart from the ones with high resolution graphics that I expected to fail and Tut-Tut which I didn't expect to fail).

I figured the chances of anyone having a ZX Printer and bothering to find a way to wire it up to the RC2014 bus is low, so I think the three ZX Printer commands are up for grabs.

In future I would like to replace the LLIST and LPRINT commands with version of IN and OUT. These would work as on the Jupiter Ace and the ZX Spectrum, OUT 1, 255 would be write the value 255 to port 1, and LET X = IN 2 would set X to the value read from port 2.

I did try to do that, and OUT was OK, but IN is going to take a bit more work as it is going to need lots of things shuffled around to add it into the operator precedence list.

I would also like to add a BEEP command to make use of the Minstrel 4th's speaker. That is also going to require a bit of thought, so I have left those out.


Adverts

Your reward for reading all of that is notice that there is 15% of everything in my Tindie store throughout July.

The 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.