Delay calibration loops

By | May 11, 2022

When you try to make a computer PC compatible, you usually think about BIOS interrupts and hardware peripherals. Instead, you should be thinking about timer interrupts and delay calibration loops.

Surprisingly many PC DOS applications run a small loop in the beginning of execution, trying to measure the amount of time it takes to execute X instructions. They can use this information later to make fine-grained delays that are unobtainable using the low-resolution PC 18.2 Hz timer. This is how Turbo Pascal’s Delay() function work. This is also why these applications crash on fast computers1.

It does not mean that these programs actually make these delays later; most of them do not. But this code resides in their library runtime startup code, and it must run properly for the application to start.

The easy way to do it

The delay calibration code is usually a tight loop which executes some instructions, and then checks how long it took the CPU to execute them. The last part can be done by using INT 1Ah function 00h (read system timer), but quite often the application hooks up its own IRQ1 handler (18 Hz timer) which increments an internal counter of system clock ticks. If there is no timer IRQ, the number of ticks will be zero and the application will crash. In the alternative implementation when the application counts the number it can execute until the next timer tick, it will hang forever waiting for an interrupt that never arrives.

This means that you absolutely must have a timer interrupt for these applications to work. It does not need to have the same resolution as the one on the PC – it’s easy enough to emulate a timer interrupt of a different frequency. But it needs to be present.

Fortunately, the CBM-II has a 60 Hz timer interrupt that is hooked to the board’s interrupt controller. Even better, the onboard CIA’s PB7 pin is also hooked there. Therefore the CIA’s timer B can be programmed to output a square wave of the same frequency as the PC timer. Problem solved.

Little quirks

All this was implemented many years ago, because without it it would not be possible to run Turbo Pascal on the machine. And yet when I tried running WordStar 4.0, it turned out that it hung in a similar delay calibration loop. Worse still, it worked perfectly fine when the loop instructions were executed step by step in the debugger. But as soon as the loop was executed as a whole, it hung.

A session with my logic analyzer showed that the timer interrupts were simply not occurring. Not the 60 Hz interrupt and not the 18 Hz interrupt. Which means that most likely the loop was run with interrupts disabled. Since in works on an actual PC, it means that something in my library had disabled the interrupts when it shouldn’t have.

The problem turned out to be located in the INT 16h interrupt handler (keyboard services). Normally, these BIOS interrupt handlers are called with an INT instruction and returned form with an IRET instruction. Something that wasn’t obvious for me as an 6502 programmer is that INT disables interrupts; it is normally not a problem because IRET pops the processor flags back from the stack along with the interrupt flag (which enables them again if they were enabled before the INT instruction).

But INT 16h is peculiar in that its function 01h (check if a key was pressed) returns its result in the zero flag. Zero flag set means no key was pressed. Therefore you cannot use IRET to return from this function because it would read the old flags from the stack and overwrite the zero flag. You must use RETF 2 which does not overwrite the flags, but then it also does not re-enable the interrupts. They remain then permanently disabled. Therefore one needs to specifically re-enable them with a STI instruction. After that, WordStar works.

  1. If the time required to execute X instructions is smaller than one timer tick, the application will measure a time of zero and will then try to calculate the actual delay resolution by dividing by this zero.

Leave a Reply

Your email address will not be published. Required fields are marked *