Sunday, 21 June 2026

Lambda 8300 ROM Reverse Engineering

The Lamdba 8300 is a 1980s sort of clone of the ZX81.

It was also called the BASIC 2000, Power 2000, PC 2000, PC 8300 and many other names.

I have looked at this previously, getting the ROM sort of working on the Minstrel 3.

I am now wondering if I can get the ROM fully working on the Minstrel 4th.

What Makes the Lambda 8300 Different?

Well, first off, it uses inverse video (like the Jupiter Ace). It has a blank cursor rather than the K. And that flashes.

When you type, 10P, you expect a ZX81 clone to fill in the rest, but this version is different, you have to type it in full, 10 PRINT.

If you type in the usual 1 0 P shift+P to start your favourite program, you don't get 10 PRINT ", you get 10P>.

To get what you want, you have to type in 10 P R I N T shift+5 H E L L O  W O R L D shift+5.

(yes, " is not SHIFT+P or even SHIFT+2, it is SHIFT+5)

It also squeaks at you as you press each key, with a different note for each one. There is a way to disable that if it drives you mad. The note is based on the position in the keymaps, so it seems a bit random, but the notes from high to low are ZXCV ASDFG QWERT 12345 09876 POIUY ENTER LKJH SPACE DOT MNB.

OK, 10 PRINT is maybe not the best example, there is a shortcut, you can press SHIFT + Z and it will type PRINT for you. You can also press SHIFT + X and it will type the next line number.

It is the last line number plus 10, which seems reasonable.

The maths functions are also on SHIFT + keys, see the keyboard overlay (stolen from the Eighty One emulator).

Marvel at that for a moment. See if you can find your favourite keys. Where is DELETE? Where is ", Where are the arrow keys ....

All the rest of the functions, you have to type yourself. F O R, N E X T etc.

It has different error codes, a bit more descriptive, two digits ones like OK IN 10, rather than the 5/10 on the ZX81.

No, it's not being judgmental, just telling me there is a Bad Subscript in Line 40.

It is compatible with ZX81 BASIC programs, but under the hood is implemented quite differently.

It comes with 2K of RAM as standard, and it appears it uses 1/3 of that immediately by having a fixed fully expanded display file.

Cue Curious Marc's elevator music: the ZX81 can support a collapse display file. Each line of the display file corresponds to a line on the screen. A line in a collapsed display file is 0-32 characters followed by a newline character. A fully collapsed display file (which is how the 1K ZX81 starts up) is just 24 newline characters (plus one extra one at the start). 25 bytes. The more you type, the larger the DFILE gets. The fully expanded display file (as the 3K+ ZX81 starts with) is 32 characters followed by a newline. That immediately uses 793 bytes of the 1024 bytes available on the ZX81.

The Lambda 8300 dedicates one third of it's 2K of RAM to the display file. Not having to deal with the collapsed display file simplifies editing and scrolling and CLS etc. so is a worthwhile optimisation, if you can justify the lost RAM.

It uses the same tape format, and can load ZX81 BASIC programs, but the programs it saves cannot be loaded on a ZX81. (I think due to changes in the system variables and keyword tokens)

I need to test it further, I imagine some machine code programs may have issues, but I tested things like Tut-Tut and 3D Monster Maze in the post linked above, and they seemed to work 

(Future Dave here, to clarify that, no machine code programs will work, only one that are purely BASIC and don't PEEK and POKE at the DFILE. The ones I tested previous were using an alternate ROM for the PC 8300 which is more ZX81 compatible. More on that in part 2. Of course there is a part 2. Don't you know me by now?)

(albeit with issues like the screen tearing and the font differences.)

As I found in the previous posts, the ULA has an internal 512 byte character ROM, which frees up the last 512 bytes of the ROM for more code.

This character set includes some modified characters, including a racing car and UFOs and diagonals, but none of the 50% chequerboard characters.

Timex(?) PC 8300

There is another ROM for the same hardware, when it is called the PC 8300 (sometimes attributed to Timex, but didn't they already have the TS1500 etc?)

This is more ZX81 compatible, with "press P for PRINT" type BASIC.

Like the Lambda 8300, it beeps when you type and has a flashing cursor, but this time the more familiar K and L.

I might look at this later, but for now I am concentrating on the Lambda 8300 ROM.

The Plan

There are three outputs from this process, each of which depends on it's predecessor.

1) Reverse Engineering

A complete reverse engineering of the ROM to generate a commented source file that produces a binary identical ROM. This will be very useful for further steps, it will help me understand what is going on with the changes (and is a nice thing to have).

2) Lambda 8300 for the Minstrel 4th

Using the disassembly, I would like to create a version of the Lambda 8300 ROM that will run on the Minstrel 4th. I can use the extra 5K ROM on the Minstrel 4th for the character ROM and new functions. I expect I will be able to use much of the same modifications as the ZX81 BASIC for Minstrel 4th ROM I previously produced.

3) A dedicated Minstrel 4th BASIC

I think this will be an excellent starting point to generate a dedicated Minstrel 4th BASIC, with the keyboard fully mapped to match the Jupiter Ace keyboard, and all the hardware features supported. I have lots of ideas for this, if things work out as planned.

I am not sure at this stage, but as it already has a hard coded location for the fully expanded display file, I wonder if I could move that to the Jupiter Ace screen memory and remove the NL characters at the end of each line. Is that a step too far? Maybe. But, I could get rid of FAST and SLOW modes, as it would always run like the Ace, FAST and display at the same time.

Getting Started

I couldn't find a disassembly, so I thought I would do my own. How hard can it be ....

... several days later ...

And it's done, mostly.

It turns out that it is mostly the ZX81 ROM code, relocated and in some cases reordered. In some places modified, maybe fixed or optimised, or just changed to be different.

I started with Z80dasm and generated an asm file.

The format this generated was not ideal, is uses 01234h for numbers (I would prefer $1234), but does a good job of marking up labels and subroutines (as l01234h and sub_1234h).

Whilst I don't like that style, it is different to the ZX81 source, and will help me keep track of the differences, as any changes I make, I will use $1234 etc.

There are a couple of syntax changes that my assembler of choice, TASM, requires.

Simple search and replace, ORG => .ORG, DEFB => .BYTE, and there needs to be a .END at the end.

It also does not things like RST 8, it has to be RST 08h.

With those minor changes, it builds binary identical.

I then spent many days modifying the source file, adding comments, converting code back into tables or data, every time I built it, I would compare with the original to make sure it was still binary identical.

I made heavy reference to the commented assembly of the ZX81 ROM, by Geoff Wearmouth

Lots of functions turned out to be identical, so I just added a comment with the function name from the ZX81 ROM. When there were differences, I added lots of comments once I had worked out what was going on.

"Interesting Points"

I made a lot of notes of "Interesting Points" as I was going along, to make a blog post. Lots of code snippets etc.

Looking back, I guess some of them might be, but I think I will just give you the very edited highlights here, and no code.

The disassembly can be found on my GitHub.

In general, it is the ZX81 ROM, relocated and reordered. There are some optimisations, some fixes, and possibly some new bugs.

  • The major changes are the fixed location and size display file and screen manipulation functions. Timing changes have caused the tear in the middle of the screen as seen in the previous posts.
  • There is now sound, three new commands, SOUND, TEMPO and MUSIC, to play notes on the built in speaker.
  • There is a keyboard beep, and new commands BEEP and NOBEEP to turn that on and off. 
  • The LPRINT routine is modified as it relied upon the character data that used to be at the end of the ROM, but is now internal to the ULA. It appears to be able to read that using some extra IO ports.
  • The NMI routines are missing the test function, on the ZX81, this was used to detect if it was running on ZX80 hardware (without an NMI generator). The Lambda 8300 ROM would never run on ZX80 hardware, so it is not required.
  • For some reason, the system variables, $400A (was E.PPC) and $4029 (was NXTLIN) have been swapped around.
  • The system variable $400C used to point to the display file, but it is now a fixed value pointing to the start of the program directly after the display file.
  • There were quite a few tables in the code, which was a little challenging, since the disassembler didn't know about those and tries to turn those into code. I kept finding what looked like nonsense code. Ah, I thought, another table.
  • Some of those are even more convoluted as they have an index table at the start which contains a list of offset into the main table, and of course, they are not in the same order.
  • One table I didn't have to deal with is the FUNCTION table, as they don't use that. You get to type most of them yourself.
  • There is some new code in the LOAD function, I think this is doing some conversion of ZX81 programs, as it can LOAD those, but cannot SAVE in a compatible format.
  • There seems to be an option to start an ROM at $2000 at boot. I am not sure if this was ever implemented, were there any Lambda 8300 cartridge ROMs (maybe something like the Timex TS1510 was planned?)

I will update that the version on github if I explore any functions further and add more comments.

Next

Next is to modify that to create the Lambda 8300 for Minstrel 4th ROM. Having the full disassembly should make that a lot easier (he said extremely optimistically)


Adverts

There are all sort of kits, test gear and upgrades and recreations of the ZX80, ZX81, Jupiter ACE and Commodore PET in my Tindie store.

Including the Minstrel 2, Minstrel 3 and the newly updated Minstrel 4th

EU shipping charges will have to increase from July 1st due to new rules to add a €3 charge + handling fee for every type of item in a parcel sent to the EU from here in the UK. It's unclear how that is going to work out, so if you want to avoid the extra charges, best order in the next few days (or spend over €150 where is apparently will not apply)


Patreon

If you enjoy posts like these, you can support me via Patreon, and get access to advance previews of blog posts (the next parts of this series are already on there) plus exclusive posts.

You also get progress updates on new projects and other behind the scenes updates, as well as access to my Patreon only Discord server for even more regular updates, and to discuss your own projects.

Sunday, 14 June 2026

Improving Tape Loading on Minstrels Part 4 - A New Hope

The new Minstrel 4th V4.52 is here - looks rather splendid, doesn't it?

Those are available to order now, and will be shipping next week once I have finalised the ROM(s).

I will go through all the changes in another post.

For now, I will roll back time to a Patreon post from a few weeks ago, when I was still trying to find a better solution to loading from tape.


In the last post, I had a brilliant idea, or at least I thought so. It seemed really neat, and it worked very well in certain cases, but not all, some needed various values tweaked to make it work, others just wouldn't work due to the signal level.

I accepted that was not, actually, the answer, even it the breadboard I tested it on did look very pretty.

So what is?

I went right back to the start with the input circuit from the Jupiter Ace.

At this stage, after all the different versions I have been through , I decided to go back to the original values, even the 12KΩ, which together with the 47nF, makes a high-pass filter at 282Hz.

There are lots of 47nF and 1KΩ parts in the Ace design, so you can see why they chose those values. This is the only 12KΩ, so I presume the value was chosen for a reason.

The seems to be tailored for an input threshold of about 1V, based on the original 74LS367 input gates.

The top trace (yellow) shows the input signal, the middle trace (green) shows the filtered signal, and the bottom trace (blue) shows the processed digital output. This will be the same for all the scope shots in this post.

You can see the filtered signal from the longer pulse (representing a 1) has decayed to about 1.5V by the end of the pulse. The shorter pulse (representing 0) only drops to about 2.5V.

This is from an ideal input source, but this could be used with various different media players, various different tape players playing tapes of various different ages and origins.

I tried various logic gates last time with different input thresholds, but most of those require too high an input. I think the best option is going to be a comparator as that can have a variable threshold which can be set to suit the input source.

To that end, I am going to fit a variable resistor labelled "Threshold". This will allow the user to set the threshold level of the input to suit their particular input source.

(I don't want to dampen the sense of "will it work?" jeopardy, but this photo of the pot in the final product is maybe a slight give away. There is much tension in the universe these days, so I hope this little glimpse of the future will allow you to continue this, no doubt suspenseful, roller-coaster of a development blog, safe in the knowledge that everything is going to be fine.)

When set horizontally to the left (about 9 o'clock), should be about right for many cases, and 8 to 10 o'clock is probably the range for most things.

I have added a capacitor there to stabilise the input from the variable resistor.

The value of 47nF is arbitrary, I could have used 100nF like the rest of the decoupling, but I am conscious of managing the parts list.

There is already one 47nF in the new load circuit, and another in the save circuit, so by adding a third, it keeps all the load and save circuit capacitors the same, and also means there are now 3x47nF caps, which should make it easier for the kit builder to differentiate from the 1x1µF and 2x22pF.

I have shown an LM392 comparator, as these are the ones I used during testing. This is a single 8 pin package with a comparator and an op-amp, although I am not using the op-amp anymore.

You sometimes see spare op-amps in dual or quad packages with the pins left floating, or with both input tied to ground. According to the datasheet, it is best to set it up as a unity gain amplifier connected to some fixed signal rather than tied to either rail. The Vref reference voltage from the pot seems a good choice. I did consider using the buffered output to feed the comparator input, but as I saw last time, the output range of the op-amp is a bit over 0V to about 3.5V, so not the 0V-5V range the reference voltage would cover.

I may change this in future to an LM393, which is similar, but both halves are comparators. I don't need the second one, but it is cheaper than the LM392. There is also the LM311, which is a single comparator, but that is more expensive. For this first batch anyway, I will stick with the LM392.

The comparator doesn't like inputs below 0V, so I should add some input protection to the negative half of the input waves after the 47nF capacitor removed any DC offset.

Again, the 12KΩ is arbitrary, it is there to protect the input source when the signal is negative and the diode is shorting it out. A value of around 10KΩ would normally be used, so 12KΩ is near enough, and keeps with the other two 12KΩ resistors in these circuits.

That resistor will also double as an input resistor.

Next I want to add a bit of hysteresis to clean up the input signal.

The 220KΩ resistor adds some positive feedback. We all need positive feedback (don't forget to hit the like and comment on the social media post that led you here please folks, there is no algorithm on here, only my fragile ego, and that needs all the help it can get)

Before I explain how that works, there is one final thing to add. The output of the comparator is open collector, so it needs a pull-up resistor to drive a logic gate input.

The 74HC245 input buffer is nearly sandwiched between two 10KΩ resistor arrays, and there is a spare pin in just the right place, so I will use that rather than adding another discrete resistor.

(10KΩ is about right, 1KΩ would add 5mA to the power consumption all the time the comparator output was low - i.e. most of the time. The current board only draws 50mA in total, so that is a little unnecessary. A 10KΩ pull-up would use 0.5mA. 100KΩ would only use 0.05mA, but at the expense of slower rise time of the output, so 10KΩ is about right).

How Does the Hysteresis Work?

I am glad you asked. Let me explain.

The output of the comparator will be either 0V (when the output transistor drives it low), or pulled up to 5V (when the transistor is off).

This 0V or 5V is fed back to the input via the 220KΩ resistor. (why 220KΩ? well, there is already a 220KΩ resistor on the board, in the reset circuit, and is in the right range, around 20 times the value of the resistor feeding the input gives a maximum of about 5% hysteresis)

Let us look at those two conditions separately. Firstly, when the output is low.

One end of the 220KΩ is driven to ground by the comparator, the other to the input to the comparator.

I have rearranged the circuit, and removed some components that are not relevant here.

The 12KΩ and 220KΩ resistors form a potential divider across Vear, the input signal. The signal which drives the input of the comparator is Vcomp.

Vcomp = Vear * (220KΩ /(220KΩ + 12KΩ) )
Vcomp = Vear * 0.95

When the output of the comparator is low, the input to the comparator is a slightly attenuated version of the input, letting about 95% of the signal through.

When the output is high, the right hand end of the 220KΩ resistor is now pulled up to 5V via a 10KΩ resistor, so the circuit can be rearranged again.

There are different ways to calculate this, usually involving working out current flow at each node etc.

The way I looked at it is to imagine the case where Vear (the input on the left) is 0V. There will then be a potential divider with 12KΩ on one side and the 220KΩ and 10KΩ on the other, with the voltage across it being 5V-0V.

If Vear was 1V, then the end of the potential divider would be at 1V, so the actual potential across the potential divider would be 5V-1V.

With the potential as 5V-Vear, Vcomp can now be calculated.

Vcomp = Vear + ( (5V - Vear) * 12KΩ / (12KΩ + 220KΩ + 10KΩ) )
Vcomp = Vear + (5V * 12/242) - (Vear * 12/242)
Vcomp = Vear - (Vear * 12/242) + (5V * 12/242)
Vcomp = Vear (1 - 12/242) + (5V * 12/242)
Vcomp = Vear * 0.95 + 0.25V

The 95% attenuation is back, but now the input at the comparator is 250mV higher.

This means the input signal gets a 250mV boost whenever the output is high.

Note, this means the 250mV boost is fixed, and is relative to the 5V supply voltage, not the input voltage. This will have more of an effect on smaller signals, which are more likely to suffer from noise, so will benefit from the hysteresis more, and relatively less of an effect on larger signals (which should not need it as much). Perfect.

At the maximum expected input of 5V, this is 5% of that, so with the 5V input is attenuated to 95%, 4.75V, add the 0.25V boost and you get back to 5V.

At 1V, that is attenuated to 0.95V. The boost takes that to 1.20V.

To test that, I am using a triangle wave from the signal generator on the scope.

With no feedback, the filtered signal is just the triangle wave with negative half cut off by the diode.

With the feedback resistor added, you can see a slight step in the filtered signal.

If I zoom in, you can see the step lines up with the output.

That is a 5V triangle wave, so the 0.25V is not that visible, if I drop that to 1V then it is much more obvious.

Yes, I Know What it Says, But What Does it Mean?

This should ensure clean square wave output.

Suppose the threshold voltage is set at 1V.

Let's start with the input voltage as 0.75V, this is below 1V, so the output is off.

If there is some noise on the input, it might be varying all the time between 0.70V and 0.80V for example.

As it approaches the 1V threshold, the first time it crosses the line, the output goes high. But since the input is noisy, the input might be varying between 0.95V and 1.05V, so, it might immediately fall just a little below the line, so the output goes low. Then back up again, and repeat until the range of the input is all past the 1.0V threshold.

With hysteresis, the first time it passes 1V, the feedback adds 0.25V to the input, so the voltage the comparator sees is 1.25V. Even if the input is noisy, this would translate to 1.20V to 1.30V, well away from the threshold.

The same happens on the way down, The threshold is 1V, but the comparator is seeing the input voltage boosted by 0.25V, so it will not turn the output off until the actual input drops to 1V - 0.25V, 0.75V.

When this happens, the 0.25V boost is removed, so the comparator now sees the 0.75V, which is well under the threshold of 1V, so it will it will be a clean downward pulse as well.

The input voltage is now at 0.75V, with no boost, back to where we started, the output will not turn on until it hits 1V again.

Testing Ace Loading

Time to go through some tests, Jupiter Ace first, since this is the Minstrel 4th.

To start with I am looking at the best case. Driving the input direct from a 5V square wave from an Arduino based player, TZXDuino or one of the many variations.

Ace tapes start with a leader tone, this should appear as evenly spaced black and white lines.

If you don't see the spacing at the start like that, then you need to adjust the threshold.

If you have the threshold too low, you might see no lines.

If you have the threshold very high, you might see no lines.

If you have the threshold a little too hot for Goldilocks, then you might catch the top of the pulses, and the outputs will be too short and you will see thin lines.

The load data is always text colour, so it still works in inverse mode, the data is white lines here, rather than black.

Here the threshold is too high, close to the top of the green spikes, so the output pulse is correspondingly short.

Turning the threshold down, the lines will get thicker, until this get the the right size. Stop when the lines are about 50:50, they won't get any wider, they will just disappear if you go too far.

One the same scale, data is represented by a slightly shorter pulse for a one.

And an even shorter pulse for a zero.

Zooming out you see the sequence of ones and zeroes.

The data should look like thick and thin lines.

The threshold should already be set right, but it it was wrong you would again see nothing or only short pulses / thin lines.

This shows up as thin lines, and no thick lines.

You can see the point in the downward slope the output turns off and the 250mV boost is removed.

Once you get the level right, you probably won't need to adjust it, about 9 o'clock is usually about right.

I am not sure if it is the TZXDuino code, or the actual TZX file, but at the end of loading, it goes through a series of low frequency clicks.

It seems they are about 15Hz.

These show up as thick horizontal bars.

The bars you see are short compared to the actual pulses as they are filtered out, and drop below the threshold relatively quickly.

Computer, zoom and enhance.

That all seems to be working nicely.

It is A 3D Maze, but not that 3D Maze. Presumably set sometime after Rex went extinct.

RIP Rex.

Hang on.

Who built that other maze then? Surely Rex doesn't have opposable thumbs, or a propensity to use tools?

Did they use the time-scoop?

Anyway, sorry, this is a different maze game. One which draws a maze.

You can watch it building the maze as it goes.

Then you just need to navigate from the starting point (black circle, bottom right) and see if you can find the exit (hole it the wall at the top)

Which I did (although I may have selected a slightly smaller maze....).

Testing ZX81 Loading

Whilst I have it setup ready to go, it would be rude to not load the other 3D Maze, the one with Rex in, and the original ZX81 version.

Using the same scale as before, the ZX81 data pulses are shorter again.

Although it takes nine of these pulses to represent a logic one and four for a logic zero.

You can see the groups of pulses as it is loading.

The countdown is a useful diagnostic tool for loading.

When you have the setting wrong, you only see occasional lines, and the loading box shows "....", which indicates it is yet to received a valid byte.

When it does, it starts loading data into memory starting at $4009. A few bytes in, it writes to $4014 and $4015. Those sets the address of the end of the file.

The countdown then updates to show how many bytes are left to load.

Most games are less than 16K, so the countdown would be a few thousand up to almost $4000.

If you see anything above $4000, it is probably wrong, and is going to fail.

However, it is still a useful test as you need to adjust the volume of your player, and/or the threshold level until you see the countdown continually ticking down.

Once you have that set, you can rewind the tape, reset the computer and start loading again.

If you see any stuttering, this means bytes have been missed, and when you get to the tape, the count down will not have reached $0000.

Ooof. So close, but 15 bytes did not read correctly, give it a go again, maybe with a slight tweak, or try the other side of the tape.

In this case, it was a reliable source, so it loaded fine.

I am still not convinced Rex built this maze. Look at how even the walls are...

Oh no!, he heard me...

Oh well, that's what I get for daring to question his undoubted talents as a maze builder.

I will not annoy Rex any further for the moment, instead I will turn my attention to a new ZX81 game I have been testing. Blake's Zevious Seven, by David Stephenson.


This works very well on the Minstrel 4th port of ZX81 BASIC.


(with one exception, the keyboard remapping of the bottom row, so all you need to know is press N for fire, not M, or press K to redefine the keys and pick a different key)

David has even added support for the Boldfield joystick RC2014 card.