Hardware programmable timers

Nagging hardware related question? Post here!
User avatar
Peter
Font of All Knowledge
Posts: 2542
Joined: Sat Jan 22, 2011 8:47 am

Re: Hardware programmable timers

Post by Peter »

Derek_Stewart wrote: Thu Sep 04, 2025 10:37 am Maybe connecting a Raspberry Pi or Pico to the Q68 expansion bus could achieve this, just then got write the SMSQ/E device driver and the Pi code to send data from the GPIO pins attached to the Q68 expansion port to the Pi USB connector.
A directly connected FDC controller as Dave suggested would certainly be easier.
Derek_Stewart wrote: Thu Sep 04, 2025 10:37 am But it is only 8 but transfers, might be slow?
Easily fast enough for floppy.


Derek_Stewart
Font of All Knowledge
Posts: 4854
Joined: Mon Dec 20, 2010 11:40 am
Location: Sunny Runcorn, Cheshire, UK

Re: Hardware programmable timers

Post by Derek_Stewart »

Hi

Floppy Disks were a great enhancement to the QL in 1990s with a Trump card and 4 Double Density drives, then the Gold Card and Super Gold card gave access to High Density disks made the QL an excellent computer.

But over 30 years on, a 1.44Mb or 3.2Mb disk has some limitations on size and access.

I mentioned a USB Floppy just an idea on what to connect the Q68 Expansion port.

Maybe a RPI 2W could have WIFI implemented on the Q68, but the Q68 has Ethernet, QL NETwok access to other QL, QXL and QL emulators via QLUB and the Spectrum Next QL Joy2Net adapter.

Much faster as easy to setup than floppy disks.


Regards, Derek
User avatar
Dave
SandySuperQDave
Posts: 2864
Joined: Sat Jan 22, 2011 6:52 am
Location: Austin, TX
Contact:

Re: Hardware programmable timers

Post by Dave »

Having settled on a counter-based system, I have some subtleties to address.

I think this question will get its most qualified answer from Peter or Nasta.

Can an interrupt be too frequent? Even in the fastest system? Is there any case where an interrupt over, say, 8096 Hz is useful?

I ask this very cautiously because the creation of registers is expensive in terms of logic if the logic is to remain in scope of a modest CPLD. In an FPGA this is not a concern, but at this stage an FPGA is an excess.

The issue is I would like a divider to be set, but also an interrupt number associated with that interrupt. The flip flop cost of a 16 bit counter, a 3 bit IRQ number and an up to 8-bit vector is expensive. Limiting the system to 16 expansion cards maximum does allow a limit of 16 vectors within the possible range to be used.

This creates a conflict because it is stated that Minerva starts at the top of the autovector table and doesn't leave room for the manual vector table that would reside right above it. QDOS certainly doesn't. This makes it likely I am limited to using alternate interrupts but restricting myself to autovector.

This leads into the second fork in the road:

This is disappointing because it limits the number of different cards that can uniquely interrupt to 4. Past that, alike cards would need to share an interrupt. I am sure if two identical cards in different slots share an interrupt, the person who develops them can write the handler to query the cards and have only the relevant one respond. This does mean only one card in the group can issue an interrupt at a time. The other card would have to be held up until the first card is acknowledged and negates its own interrupt request. You can see why this isn't ideal.

So, passing the buck to the programmers, what approach would close the fewest doors to accessibility in managing interrupt generating hardware?


User avatar
Pr0f
QL Wafer Drive
Posts: 1618
Joined: Thu Oct 12, 2017 9:54 am

Re: Hardware programmable timers

Post by Pr0f »

I've been looking at that bit of code in Minverva quite a lot - After the Autovectors - there is basically a block of code to do the redirection of the vectors that are supported in Qdos, then the code to display the logo. I think it might be possible with some careful thought and adjustments to free up the vectors area and move 'rd' to 400Hex.

Thre other option is to use the FC lines to decode the interrupt handling and place the vectors in parallel memory.


User avatar
Dave
SandySuperQDave
Posts: 2864
Joined: Sat Jan 22, 2011 6:52 am
Location: Austin, TX
Contact:

Re: Hardware programmable timers

Post by Dave »

Good thoughts.

I don't know if anyone would do the work on Minerva. If they did, I don't know that it would benefit others. It creates a need for a custom Minerva fork for one family of compatibles.

I'm also wary of anything that requires hardware that existing clones don't have and can't get. I have no experience with using the FC0..2 codes to have different parallel memory at common addresses. The idea of system and user data in aliased/overlaid addresses is just *weird* to me. If it's something that 68K systems to all the time and it's an assembly coder common skill, sure.

I'll force my mind to keep open and read up some. My unfamiliarity/discomfort isn't a reason things shouldn't happen. I'm not here to make a lowest common denominator system.


User avatar
tofro
Font of All Knowledge
Posts: 3202
Joined: Sun Feb 13, 2011 10:53 pm
Location: SW Germany

Re: Hardware programmable timers

Post by tofro »

Dave wrote: Sun Sep 07, 2025 8:00 pm
Can an interrupt be too frequent? Even in the fastest system? Is there any case where an interrupt over, say, 8096 Hz is useful?
Of course it can. Interrupt handling is expensive, on a 68k it's 44 cycles, which is roughly 2-3 instructions (not counting the handler and the rte, and this is for auto vector interrupts. When the device provides the vector, its even worse - remember, for a 6502 its 13 cycles...). All in all, I would assume you can't write even a do-nothing interrupt handler (other than the bare necessities) using less than 200-300 cycles. With a cyclic interrupt, you're taking away these cycles from the rest of the system, effectively slowing it down.
Dave wrote: Sun Sep 07, 2025 8:00 pm I ask this very cautiously because the creation of registers is expensive in terms of logic if the logic is to remain in scope of a modest CPLD. In an FPGA this is not a concern, but at this stage an FPGA is an excess.
The issue is I would like a divider to be set, but also an interrupt number associated with that interrupt. The flip flop cost of a 16 bit counter, a 3 bit IRQ number and an up to 8-bit vector is expensive. Limiting the system to 16 expansion cards maximum does allow a limit of 16 vectors within the possible range to be used.
Reminder: Jumpers are the cheapest registers.... And would even be "period-correct" considering what PCs did after the QL had died.
Dave wrote: Sun Sep 07, 2025 8:00 pm This is disappointing because it limits the number of different cards that can uniquely interrupt to 4. Past that, alike cards would need to share an interrupt. I am sure if two identical cards in different slots share an interrupt, the person who develops them can write the handler to query the cards and have only the relevant one respond.
That shouldn't be much of a problem. Note the driver needs to know where in the address space the interrupting card is anyways - and detecting which of several cards triggered it is not much more work. But remember, the QL system architecture has no concept of "multiple cards the same" to occupy one single ROM space. I guess the simplest (maybe dumbest) approach would be to load two identical drivers into non-shared address space and two separate interrupts. I feel that's a waste.
Dave wrote: Sun Sep 07, 2025 8:00 pm This does mean only one card in the group can issue an interrupt at a time. The other card would have to be held up until the first card is acknowledged and negates its own interrupt request. You can see why this isn't ideal.
Well, that is a problem if the device provides the vector, and that is why auto vectoring is so much simpler. I'm not sure a system with a QL background justifies the effort of supporting vectored interrupts.


ʎɐqǝ ɯoɹɟ ǝq oʇ ƃuᴉoƃ ʇou sᴉ pɹɐoqʎǝʞ ʇxǝu ʎɯ 'ɹɐǝp ɥO
User avatar
M68008
Gold Card
Posts: 302
Joined: Sat Jan 29, 2011 1:55 am
Contact:

Re: Hardware programmable timers

Post by M68008 »

Interrupt (level 2) on the QL is slow as it calls the QDOS handlers. I think it also slows down the CPU while it's pending since it asserts the VPA signal.
If you can avoid both, and on faster CPU, it could run much faster.


Nasta
Gold Card
Posts: 476
Joined: Sun Feb 12, 2012 2:02 am
Location: Zapresic, Croatia

Re: Hardware programmable timers

Post by Nasta »

M68008 wrote: Sun Sep 07, 2025 11:05 pm Interrupt (level 2) on the QL is slow as it calls the QDOS handlers.
Can't be avoided as the vecors are in ROM. On the other hand, there is not a lot of code between the interrupt and user code, if you take some care.
I think it also slows down the CPU while it's pending since it asserts the VPA signal.
Not much slow down compared to the rest of the operations the 68008 does as part of interrupt response. This is perhaps the most problematic part of having an 8-bit data bus, as unlike an older style 8-bit CPUs, there is quite a lot more complication that happens on any exception, thus also on interrupt. Putting stuff on the stack is some work, just considering the program counter is 32-bit. Given the rest of the interrupt stack frame, moving that to (slow QL) RAM and then restoring it all back to the CPU, does not make it fast. The supervisor stack on a regular QL remains in motherboard mounted 128k of RAM which is about half the speed that the 68008 can manage, so that does not help...


Nasta
Gold Card
Posts: 476
Joined: Sun Feb 12, 2012 2:02 am
Location: Zapresic, Croatia

Re: Hardware programmable timers

Post by Nasta »

Dave wrote: Sun Sep 07, 2025 9:00 pm Can an interrupt be too frequent? Even in the fastest system?
Sure - when the overhead of just responding to an interrupt gets to be on the order of normal processing, you got a problem.
Of course there is one more way this can happen and that's when interrupt processing (i.e. actual code that handles interrupts plus CPU interrupt overhead) gets on the order of non-interrupt processing you may need to re-think your system design...

One thing that helps when deciding on this is, assess the CPU incurred overhead on interrupt processing, which is the number of cycles needed for the CPU to recognize the interrupt and all the way to the first instruction of the handling code, and add to that the number of cycles needed to exit from interrupt back to previously executed code, i.e. from the last instruction of the interrupt handler (usually RTE) to non-interrupt code. This is the time penalty for every interrupt. Keep in mind there is a worst and best case scenario - for instance, it will take much more for an interrupt to be recognized if the CPU is executing a division than a simple move, since it needs to finish the current instruction - they take (sometimes radically) different times to execute.

Then there is the case of multiple level interrupts occurring just as one has entered processing, the other occurs - some of the processing is not interruptible even if a higher level interrupt occurs, so you get extra delays. This can actually make overhead less predictable and repeatable. Another thing to keep in mind when calculating worst case response.

Finally, if the CPU has a cache, worst and best case times can differ radically.

In any case, this is a calculation that should be made before one actually considers how fast the actual interrupt stuff (reading status registers, data registers, loading/storing data, et.) takes. More on this WRT interrupt sharing.
Is there any case where an interrupt over, say, 8096 Hz is useful?
Possibly. This is the other part of the basic interrupt system design.
What kind of an interrupt rate can we expect? This depends on a lot of aspects of the hardware design. For instance, if data is to be moved under interrupt, is there some sort of buffering, is there some sort of handshake involved, and can the data flow be completely stopped if needed?
The last question is important as the system can get bogged down with trying to do several things at once and some can be deferred, while others can't - and while deferring data transfers will slow down transfer, no loss of data will occur. Hence, the ones that can't be deterred are more important.

Here is an example - suppose we are reading from an ED floppy drive, which is reading at 1Mbit per second. Since the disk spins, there is no waiting or there will be data over-run, hence data lost. Since we are reading bytes, the byte rate is 125k/s, and the reading is done one byte at a time, i.e. there is only one byte of 'buffer' between reading the data from the floppy controller and the CPU. In this case, reading this data under interrupt is a kind of mission impossible as in order to move one byte, there are approaching 100 bytes moved in interrupt pre and post processing plus the actual moving the byte code. Normally this would be a loop along the lines of 'read status register to see if a new byte is ready, if so, move byte from a data register into RAM, increment address where it's going to go on the next loop, is this the last move, if not, loop to beginning.
If this was done under interrupt, along with CPU interrupt handling, now we need to restore the address of the status and data registers, the address where the data is supposed to go, then do what happens in the above loop iteration, and update the address for the next transfer, then return from interrupt, for EVERY byte.

On the other hand, what if there is a FIFO data buffer in the hardware of the floppy disk controller? Well, as soon as the buffer is not empty (OR even better the FIFO is full to a defined threshold), an interrupt is generated.
The CPU has as much time to react to the interrupt and get to moving the data from the FIFO to RAM, as there is space in the FIFO left. Once the moving starts, there might actually be some time savings because it is sometimes possible to know that there is a minimum of bytes that can be moved, without needing to check if there is a byte available. So, once the moving starts, it may actually be faster that doing direct programmed IO, as was described above.

So, how often would the interrupt occur if we manage to use the whole FIFO capacity? Well, about 125kbytes divide by the size of the FIFO, so that would be about 7813 times a second.
At this point it is worth mentioning that for tis particular case it would mean that just as you finish emptying the FIFO, and return from the interrupt, the FIFO will start filling again and you get an interrupt again - so in other words, this is the interrupt break-even with just ONLY doing the data transfer, obviously not an improvement.
However, if the CPU is fast, the actual data transfer could take a very short time, so the proper strategy would be not to generate an interrupt until a certain amount of data is already present in the FIFO, this being the FIFO threshold (number of bytes present) when the interrupt is generated. This takes into account hat the CPu can react fairly quickly to the interrupt, and that when it does occur, the FIFO will actually be close to full, the CPU will quickly unload it to RAM, and go back to usual business. For our example of a 16 byte FIFO, if we set the threshold to 12, what will happen is that the CPU will do 'other things' for 12/16 of time, while the data transfer will take 4/16 of time.

But what if your hardware does have a FIFO but no threshold setting?
Well, we calculated that the FIFO would need to be emptied at least 7813 times a second. If we cannot count on the actual hardware having a configurable interrupt timing, one thing we could use is a periodic interrupt which will check the FIFO state and if needed transfer data. Obviously one could make the periodic interrupt happen very often, but then you would not really be using the FIFO effectively, so some optimisation is necessary. Would 8096 times a second be often enough? (Hint: risky, but we are on the right track)
This creates a conflict because it is stated that Minerva starts at the top of the autovector table and doesn't leave room for the manual vector table that would reside right above it. QDOS certainly doesn't. This makes it likely I am limited to using alternate interrupts but restricting myself to autovector.
Wellllll..... note that there is no protection on interrupt vector numbers, so you could use other unused vector numbers below vector $31, which actually includes levels not implemented on 68008, unused and uninitialized vectors, etc.
This is disappointing because it limits the number of different cards that can uniquely interrupt to 4.
3, as level 0 is 'no interrupt'
Past that, alike cards would need to share an interrupt. I am sure if two identical cards in different slots share an interrupt, the person who develops them can write the handler to query the cards and have only the relevant one respond. This does mean only one card in the group can issue an interrupt at a time. The other card would have to be held up until the first card is acknowledged and negates its own interrupt request. You can see why this isn't ideal.
Actually no.
Cards would generate interrupts using a wired-or mechanism. The way interrupt handlers are linked into the OS makes it very simple, even when disparate cards use the same interrupt, although it would be prudent to share interrupts with similar characteristics regarding how they are processed (particularly regarding the time interrupt processing takes).
In any case, all hardware capable of causing interrupts must have a mechanism to clear the interrupt (stop generating the interrupt) once it's servicing has started.
Keep in mind the QL itself has multiple sources of interrupt all sharing one level. The handler(s) first check each linked interrupt handler checks some sort of status register to figure out if 'it's' hardware caused the interrupt, and then as a part of handling the interrupt, the bit that said 'I caused it' gets cleared, which also stops the interrupt signal. However, if there are other pieces of hardware are generating an interrupt, the next linked handler will check for that, etc. until all are checked, handled and cleared. This is an extremely common mechanism. Yes, it does have a risk of 'something' generating the interrupt but none of the handlers find it and the interrupt keeps hanging on. But so does any system that is not configured properly, for instance the wrong interrupt vector being generated.

It should be noted that interrupt sharing in this manner has actually less overhead than classic one-per-level/one-per-vector approach, because once an interrupt happens, the actual interrupt processing by the CPU happens only once for any number of shared interrupts having to be processed, so there is overlap, that does not require all the CPU context stacking and restoring, etc. When this is combined with an appropriate set of periodic interrupt(s) this can actually be very efficient. I have some nice documentation where Tony Tebby argues this as an important point regarding predictability of real time operating systems.


Post Reply