George's Home Page

The CMD"T" Surprise

Level II BASIC for the TRS-80 Model I anticipated the arrival of floppy disk drives. It had code to load a boot sector off diskette and hooks to extend BASIC with commands to access floppy disk files. Yet there was one need that was missed. Cassette operations like saving and loading BASIC programs from cassette with CSAVE and CLOAD would not work under Disk BASIC. The reason was simple. The expansion interface added a clock interrupt so the TRS-80 could keep track of time. This conflicted with the delicate timing of the cassette routines. The clock interruptions made their bit pulses uneven when writing and missed pulses when reading.

Theoretically this was easy to fix. Disable interrupts before doing a cassette operation and turn them back on afterwards. The real time clock would run slow as it wouldn't be updated while interrupts were disabled but at least cassette operations would work. And for the most part disk users didn't need the cassette.

Practically it wasn't possible. The cassette routines were all in ROM so they couldn't be directly patched and they provided no hook points to customize their operation. The best that could be done is warn the user of the problem in the manual and have them disable the interrupts before they went to use the cassette drive.

In this spirit CMD"T" was born. To ensure the cassette worked properly you typed CMD"T" before a cassette operation like CLOAD. There was a complementary CMD"R" to turn the interrupts back on so the clock started updating again. It made Disk BASIC just a little less user friendly but what else could be done?

Many years later users of the trs80gp emulator run into the same problem. They try to use the cassette under Disk BASIC and discover it doesn't work. They've either forgotten CMD"T" or are new to the system and reasonably expect CLOAD and CSAVE to work just like they do under Level II BASIC. I know this has happened a few times since I've helped several people get it working on trs80gp.

Recently it occurred to me that the emulator can recognize this situation and warn the user. The emulator knows if interrupts are enabled or disabled. And it knows the cassette is going to be used when the cassette motor relay is activated. So if the cassette relay is activated and interrupts are still enabled I pop up a dialog box that warns that CMD"T" should have been used. It even offers to disable the interrupts right then so the load or save can continue unharmed. I added the feature to trs80gp version 2.5.1 and basked in the glow of an old problem completely solved.

Ha, just kidding. My brother Peter pointed out that it would sometimes give the warning spuriously. The trick was to do the CMD"T" inside a program or just before a cassette command like so:

    CMD"T":CSAVE"P"
trs80gp would think the interrupts were not disabled and throw up the warning dialog box. Yet there was no actual problem; the cassette save worked just fine. This was a surprise, but not the surprise I refer to in the title. Doing the sequence as two separate commands like so worked just fine:
    CMD"T"
    CSAVE"P"

The emulator warning code was 100% correct. The cassette relay had been activated and interrupts were not disabled. With the Z-80 debugger I confirmed that it was in the CSAVE code when the prompt came up. And I could even see the interrupts remain enabled at that point. How could this be? CMD"T" was supposed to disable the interrupts yet it had not. And despite this failing there was no problem. Which I could understand to some degree because after the CSAVE was done the interrupts were disabled.

Allow me to divert briefly into talking about programming interviews. It's fairly common practice for companies to ask programmers to solve little logic puzzles as part of the interview. Often there is a trick to solving the problem. Equally often the situation is not part of the job under consideration or maybe not even a programming problem at all. Many dislike this technique and argue that it is completely irrelevant to the practice of programming. I'll agree that there are a lot of downsides to this approach and it should at best be used sparingly. But irrelevant? Look where we are here. There's a program that does one thing that it clearly is not doing and yet it must be run otherwise the thing it is supposed to fix is broken. While you won't literally run into one truth telling and one lying knight the same kind of crap happens all the time when programming. Brain teasers abound only you get fewer clues and less reliability of information.

Anyhow, back to the issue at hand. I'm sure there are many of you out there who have already figured it out. However, I did not. I was baffled. I decided I better go look and see what CMD"T" actually does. Maybe that would give me a clue. trs80gp has a handy feature where it'll log a trace of every Z-80 instruction executed. Just bring up the debugger with Debug → Z-80 Debugger... and change the Trace Off dropdown to All. It'll prompt for a file to save the logged Z-80 instructions and you change the dropdown back to Trace Off to stop it. Or just exit the emulator; that'll work too.

I did a trace of CMD"T" and looked for the DI instruction which tells the Z-80 to disable interrupts. That was my next surprise. It simply did not execute a DI instruction. Yet when I stopped the trace I could see the interrupts were disabled as the IFF1 box in the debugger was not ticked. I ran the trace a few more times just to be sure I wasn't making some kind of mistake like starting it too late or stopping it too early or something that made a damn bit of sense.

Now the problem had devolved into a perfect interview puzzle question for a Z-80 programmer:

How can a Z-80 program disable the interrupts without using DI?

It's a tricky question. I'll tell you right now there isn't another instruction that does it. Unlike some processors the interrupt control bit isn't in the flags register so no matter what you put on the stack POP AF won't do it.

I encourage any Z-80 programmers out there to try and solve it before you read on. Just for the sport of it.

I do have one hint that came from the CMD"T" trace. It wasn't a hint that clued me in but it should have. I ended up looking more closely at exactly what CMD"T" did and also at CMD"R" and pretty much had the answer forced into my brain. But I did observe at some point that while CMD"T" did not execute DI it did execute EI to enable interrupts. In my confused state I glossed over that but in retrospect I should have asked how EI might be helpful in disabling interrupts.

What finally made me understand was noticing that CMD"R" would install (via a vector) the proper interrupt handler while CMD"T" would have the handler return immediately. On the Z-80 returning immediately from an interrupt is a classic blunder. At the very least you need to do an EI before the RET (return) to re-enable the interrupts. When an interrupt happens the Z-80 disables the interrupts and the handler routine must re-enable them.

Oh.

When an interrupt happens the Z-80 disables the interrupts. That's the answer to our little Z-80 puzzle we never asked for. Finally the real surprise: CMD"T" manages to disable the interrupts using EI. But it doesn't do so immediately. By making the interrupt handler return without an EI the interrupts will be disabled when the next clock interrupt happens (in at most 25 milliseconds or your pizza is free).

Which explains why we get the warning with CMD"T":CSAVE"P". The interrupts aren't disabled when the CMD"T" is done. CSAVE starts going, turns on the cassette and up comes the warning.

It might seem that it shouldn't work because cassette operations will be started with the interrupts enabled. The reason it isn't a problem is because cassette data starts with a header of over 200 bits that is nothing but zeros. The header is part of getting the cassette reading code into bit sync and it gives the cassette drive time to get up to speed. At 2 milliseconds per bit one last interrupt triggering in 25 milliseconds might screw up the 10th or so bit into the header but it doesn't matter. There will still be plenty of unaffected zero bits in a row to get things going. The slight damage to data written won't matter nor will a slight disruption at the start of reading the data.

I know what's going on. I can figure out how to fix the emulator. One question remains. One that we don't need to know but would sure like to know.

Why the heck does CMD"T" not just use DI?

My best guess is that it's a more robust technique in the face of other code enabling interrupts. If a CMD"T" is issued and some other code enables interrupts (perhaps because it disabled and enabled them) before the cassette operation the interrupts will automatically be re-disabled.

Less likely it may have evolved from an initial workaround that could be easily done in BASIC. A CMD"T" is just a POKE 16402,201 and CMD"R" is just POKE 16402,195. The CMD forms are a bit safer since they are only available in Disk BASIC. A BASIC program with those pokes that runs under ROM BASIC could crash if an expansion interface or other interrupting device is attached.

There's still a chance the original author could be found and asked about the decision. But don't hold out much hope; even if you can determine who wrote it and where they are I've found there is not generally much recall of obscure coding choices from 40+ years ago. Heck, I often wonder about code I wrote last week.

It never ceases to amaze me how even programs with the simplest functions can be different than you expect on the inside. A week ago I would have been certain that CMD"T" uses DI. Now I know better and eagerly await the next surprising program.


George Phillips, October 7, 2023. george -at- 48k.ca