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:
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:
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.
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.
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"
How can a Z-80 program disable the interrupts without using DI?
Why the heck does CMD"T" not just use DI?
George Phillips, October 7, 2023. george -at- 48k.ca