This is the fourth post in my series on emulating a BBC Micro in Javascript. I’d recommend reading the previous part on 6502 internal timings before reading this post. It’s also handy to have read read the first part which covers general stuff, and the second part which focuses on the video hardware.
Again thanks to my good chum Rich Talbot-Watkins who helped demystify what goes on in the Beeb.
Like most CPUs, the 6502 has a physical pin (the IRQ line) which causes an interrupt when brought low: the current instruction is completed and then instead of proceeding to the next instruction, the CPU enters interrupt mode (which disables further interrupts) and jumps to an operating system handler. This handler then deals with the interrupt — a timer firing, a keyboard event, disc activity and so on. The handler is responsible for:
Of course in reality nothing is that simple. On the 6502 the simple pipelining has an interesting side effect: the IRQ pin is only checked on the penultimate cycle of each instruction. This is the point at which the next instruction’s address is determined and fetched, ready for execution on the next cycle.
The penultimate cycle in question is the last “logical” cycle of the 6502’s state machine, not necessarily the penultimate physical cycle — in the case of cycle stretching on the final cycle of the instruction, those stretched cycles don’t cause the 6502 to recheck the IRQ line.
If the IRQ line is brought back high mid-instruction when the processor had already noticed it being low, the interrupt is still taken — even though there’s no evidence left for the interrupt handler to determine why it was called.
Not emulating these strange quirks leads to subtle differences in when interrupts are taken, which causes the legendary Kevin Edwards’ protection systems to fail to decode.
Given all this complexity, it’s interesting to think how experiment to try and work out how Kevin sussed all this out. Rich had a guess and in this fascinating thread Kevin confirms how it was done.
While we’re talking cycle correctness we should mention the hardware timers. There are two timers in each of two 6522 Versatile Interface Adapters (VIA). Each timer can generate an interrupt when it overruns, and automatically reloads from a programmable start value.
From our cycle correctness point of view the interesting thing about the
timers is the exact time that the timer fires. They tick at 1MHz, and generate
their interrupt a half-cycle after they underflow below $0000
(back to
$ffff
). At this point they either stop (a “one-shot” timer), or reload their
initial value and start again (a “free-running” timer).
When used in the latter mode the reload happens on the half-cycle after the interrupt:
When a value N
is written to the timer’s latched start value, the
first interrupt will happen N + 1.5
1MHz cycles later (i.e. 2N + 3
CPU cycles); and subsequent interrupts happen at N + 2
1MHz cycle
(2N + 4
CPU cycle) intervals. (Recall that the CPU clock runs at 2MHz.)
If we configured a free-running timer with a counter of 2, and assuming we could acknowledge the IRQ almost straight away1, it would look something like:
Some games rely on the exact timing of the timers to change video settings
mid-frame. For example, the driving game Revs sets the timer to
fire every 312.5 * 64 - 2
cycles — which is to say exactly the number of
1MHz ticks there are in an interlaced PAL TV frame, minus two to account for the
later time the timer fires. Most games use the vertical blanking interrupt
generated by the video circuitry at the top of a frame to set a one-shot
timer to fire after the appropriate time, but Revs just leaves a free-running
timer going. If the emulation wasn’t spot on, then over time the place where
the video settings change would slowly creep up the screen.
Reading the low 8 bits of a timer has an extra meaning: it acknowledges any interrupts from that timer. This is what an interrupt handler would do: once it had ascertained that it was a timer that caused the IRQ (by checking the VIA status register) it would read from that timer to stop it holding the CPU IRQ pin low.
By now you’ll realise there’s going to be a catch: if the CPU tries to acknowledge the timer firing on the same cycle that it fired, then the acknowledgement gets lost2. An interrupt will still be generated on the next cycle. Again, Kevin Edwards’ protection systems take advantage of this unusual behaviour.
Of course, on a real system all the components run in parallel. In an emulator they’re emulated serially. Empirically, we determined that to get things to work right, at each tick we do things in this order:
Also there are a few extra + 1
s in the code when counters are being loaded
from or written to account for the actual point within a cycle the CPU would
actually read or write the timer.
Most of the relevant code for the interrupts in in the via.js source file.
The interrupt generation and counter handling is in the polltime
function and
it looks a little like:
// "cycles" is the number of 2MHz cycles that
// have occurred since the last time we checked.
count -= cycles;
justHit = false;
// Have we fired? -3 here accounts for the
// initial late fire time.
if (count <= -3) {
assertIRQ();
// "justHit" is our note that we
// should ignore any acknowledgment on
// this cycle.
if (count === -3) justHit = true;
}
// Reload the timer, accounting for the
// extra 4 2MHz ticks of a reload.
while (count < -3)
count += reload + 4;
Getting the emulation cycle-perfect has been a very satisfying achievement. Thanks are due to Ed Spittles for gathering real-world measurements, and Rich Talbot-Watkins for poring over technical documents and making spreadsheets of possible internal timing behaviour. It’s all been worth it as we finally cracked Kevin’s protection system.
As an amusing side-note, when Rich and I first tried cracking the Alien 8 protection system (one of Kevin’s earlier accomplishments) some 20 years ago we started down this road. We tried building a 6502 emulator — in BBC Basic — to run the decryption code. We never got it working, and now I have a much fuller appreciation of why!
Matt Godbolt is a C++ developer working in Chicago for Aquatic. Follow him on Mastodon.