6502.org http://forum.6502.org/ |
|
My approach to interrupt routing http://forum.6502.org/viewtopic.php?f=10&t=4283 |
Page 1 of 1 |
Author: | Aslak3 [ Fri Oct 21, 2016 11:34 am ] |
Post subject: | My approach to interrupt routing |
The fact that I've implemented my method for interrupt routing in a HDL and programmable logic is probably just a detail - doubtless what I've done could be implemented in traditional 74xxx parts. So my micro has around 8 interrupt sources. I had a few goals in mind: Fast - obviously it needs to be more efficient then simply polling each peripheral IC. Enabling/disabling particular interrupts, so if the IC - for whatever reasons - generates an interrupt it can be ignored. Work within the restrictions of my current circuit: there is no way to directly present a ISR vector on the databus due to some missing CPU pins at the programable logic part So what I have is a register map like this: SETINTMASK - write only: each bit that is a 1 is set in the "IRQ filter" CLEARINTMASK - write only: each bit that is a 1 is cleared in the "IRQ filter" INTSTATUS - read only: a priority encoded number for the interrupt bit that is currently active At the same time as generating INTSTATUS the implementation also generates the CPUs IRQ line (by comparing the masked status against 0). The reason for the SET and CLEAR action was to make the interrupt setup as simple as possible - no need to read in the current mask, OR the new mask and write it out again. The priority encoding saves a whole bunch of code in the top-level ISR. It just has to read INTSTATUS, rotate it left once and extract the particular device's handler from a table. The value 0 means something has gone wrong (it should never happen because the IRQ line should be high in this case.) The priorities are hardcoded in the HDL, something I'm not especially pleased with (it could be nice to make them configurable from CPU code) but I can live with it. I'm not sure if this is how "real" interrupt routers deal with prioritising device interrupt lines? I guess for efficiency I could rotate the INTSTATUS in the HDL, but that seems a bit dirty somehow. One thing I've pondered is for the HDL itself to hold the table of the device ISRs. In theory that would reduce the CPU ISR code to reading the "INTSTATUS" 16 bit value and jumping through it, probably the next best thing to having the interrupt router present the device's vector directly. But this would consume a fair amount of resources in my limited FPGA. What approaches have others taken for dealing with computers with "many" interrupt sources? |
Author: | MichaelM [ Fri Oct 21, 2016 1:49 pm ] |
Post subject: | Re: My approach to interrupt routing |
Aslak3: I use an approach similar to that which you suggest near the bottom of your post. IOW, the interrupt handler presents a vector from which is then fetched the ISR address. You can take a similar approach by making your INTSTATUS register a 16-bit register as you noted, and using jmp (INTSTATUS) to fetch the vector to the ISR. This approach should be fairly easy to implement with your fixed priority scheme, i.e. not wasteful of your limited FPGA resources, particularly since the upper half of the INTSTATUS register is likely to be a constant like 0xFF. |
Author: | GARTHWILSON [ Fri Oct 21, 2016 9:20 pm ] |
Post subject: | Re: My approach to interrupt routing |
I like your eight interrupt-request inputs, since it reduces the need for polling. With the standard '02 on my workbench computer, I put VIA1 on NMI\ to reduce the polling overhead, since the only interrupt I use on VIA1 is T1, for the real-time clock, and nothing else is on NMI. I'm really just using it to get for the additional interrupt input. When I need to turn it off, I just do Code: LDA #%01000000 And to turn it back on:STA VIA1IER Code: LDA #%11000000 STA VIA1IER I have the other interrupt sources on IRQ\. Here are some things to keep in mind if you're going to use off-the-shelf outboard I/O ICs (rather than incorporating the I/O in the same FPGA with the processor). Each I/O IC that can generate interrupts has a simple way to disable the interrupts, even individual interrupts within the same IC, as in the example above which requires no ANDing or ORing, and takes six cycles (or five cycles in the remote chance that you have your I/O in ZP). A fast (single cycle!) way to keep generated interrupt signals from reaching the 65c02 would be to use OR gates with one input of a gate connected to an output from Jeff's circuits at http://wilsonminesco.com/6502primer/potpourri.html#Jeff, under "Some tricks from Jeff Laughton (Dr Jefyll on 6502.org's forum)." A high output bit from his circuit there would hold the output of the OR gate high, meaning no IRQ. All of this can of course be done in your programmable logic if desired. As for making polling fast, remember:
The ISR examples I see in books are usually way more complicated and inefficient than they need to be. |
Author: | Aslak3 [ Sun Oct 23, 2016 11:05 am ] |
Post subject: | Re: My approach to interrupt routing |
MichaelM wrote: ... This approach should be fairly easy to implement with your fixed priority scheme, i.e. not wasteful of your limited FPGA resources, particularly since the upper half of the INTSTATUS register is likely to be a constant like 0xFF. It's an interesting way to save resources if I fixed the entry point of handlers into a single page; I hadn't thought of that, thanks. I'm also going to see if I can use the RAM bits in the FPGA to hold the 16x8 pointers. If that's possible then it probably won't really matter. (I'm using a Altera Flex 10K (EPF10K10)) GARTHWILSON wrote: I like your eight interrupt-request inputs, since it reduces the need for polling. With the standard '02 on my workbench computer, I put VIA1 on NMI\ to reduce the polling overhead, since the only interrupt I use on VIA1 is T1, for the real-time clock, and nothing else is on NMI. I'm really just using it to get for the additional interrupt input. Yes, I have that option available too. Quote: Next, you probably won't have more than just a few of the many possible interrupts enabled at any given time. My workbench computer has more than three dozen possible sources of interrupt; but I can't think of any time I had more than three enabled at once. The ISR should poll the most likely or the most urgent one first, and never poll ones that aren't even enabled. I probably should have put some additional context: the software is a multitasking OS, which complicates things somewhat. I'm assuming you either have multiple ROM images for different tasks, or some kind of self-modifying code solution? I don't really have that choice. From the point the ISR runs, any of the sources could be in use. Hence looking at hardware solutions. Quote: The ISR examples I see in books are usually way more complicated and inefficient than they need to be. Indeed. Another reason, though, for making ISRs with "redundant" code in them is it can sometimes simplify debugging. I have a check against a unitialised handler pointer in my main ISR just because I know if I don't put it in (it should never trip, of course) I'll get bitten by some subtle and annoying bug later down the road. |
Author: | GARTHWILSON [ Tue Oct 25, 2016 12:12 am ] |
Post subject: | Re: My approach to interrupt routing |
Quote: The priority encoding saves a whole bunch of code in the top-level ISR. It just has to read INTSTATUS, rotate it left once and extract the particular device's handler from a table. The value 0 means something has gone wrong (it should never happen because the IRQ line should be high in this case.) Make sure you shift, not rotate, so the resulting low bit won't be a surprise. Anyway, the scheme will most efficiently be for 7 interrupts, not 8, since the 8th bit will be shifted or rotated out. The following is not particularly related to programmable logic, but I'll reply to these quotes in case it gives someone any ideas. Aslak3 wrote: I probably should have put some additional context: the software is a multitasking OS, which complicates things somewhat. I'm assuming you either have multiple ROM images for different tasks, or some kind of self-modifying code solution? I don't really have that choice. From the point the ISR runs, any of the sources could be in use. Hence looking at hardware solutions. The routines that install and delete ISRs are (or can be) in ROM. The ISRs I use all the time are also in ROM on the workbench computer, but other ones get compiled and put in RAM on an as-needed basis. If I were to need multiple ones of the very highest-priority ISRs, especially without your multiple IRQ\ inputs, then yes, some kind of self-modifying code solution might be in order. See further down though. I don't have any experience with preemptive multitasking OSs; but for several products, I've used a cyclic executive to do OS-less cooperative multitasking with extremely low overhead, in systems that lacked the resources to run a multitasking OS, or where hard realtime requirements would rule one out anyway. I wrote it up in my article on simple methods for multitasking without a multitasking OS. In the last major job, I used a PIC16 microcontroller switching tasks at approximately 10,000-16,000 times per second while running timer-generated interrupts at 39.0625kHz which is the 20MHz clock speed divided by 512, or one interrupt per 128 instruction cycles, with a jitter of only one instruction cycle in spite of the task switches which were neither related to, nor synchronous with, the interrupts. Since the tasks relinquish control only when they're at a good stopping point, the task-switch overhead was nothing more than a subroutine return and call, which can be interrupted. As mentioned in the article, if the task list needs to change from time to time, it would have to be in RAM, and the task called at the top of the list should probably be the one that adjusts the list, adding new tasks or deleting ones that are no longer needed. After deleting one, it can either move the following ones up to fill in the spot, or replace it with NOPs. Quote: Quote: The ISR examples I see in books are usually way more complicated and inefficient than they need to be. Indeed. Another reason, though, for making ISRs with "redundant" code in them is it can sometimes simplify debugging. I have a check against a unitialised handler pointer in my main ISR just because I know if I don't put it in (it should never trip, of course) I'll get bitten by some subtle and annoying bug later down the road. What I have in my '816 Forth is that the ISR installer maintains a pair of lists of handler pointers (both for IRQ, no such list for NMI): one for assembly-language ISRs, and one for Forth-language ISRs. The assembly-language list gets higher priority (since, if they didn't need the highest performance, they'd be in Forth); but within each list, the pointers are in order of priority, given by the priority which is one of the input parameters for the ISR installer. The lists are initialized with a length of zero upon boot-up, and each subsequently-installed ISR increases the length. The ISR deleter disables the interrupt associated with the referenced ISR, and removes the entry from the list, scooting the following ones up to fill in the gap. The ISRs themselves are very simple, leaving little to debug. On the '02, I only do the Forth-language ISRs this way. The '02 was too inefficient to do the assembly-language ones this way. It added a lot of overhead, whereas the '816 was able to do it while adding very little overhead. The way I install and delete assembly-language ISRs on the '02 is less automatic and requires care in making sure that each one takes the responsibility of correctly chaining to the next if it finds that its interrupt source was not the one that caused the interrupt. All of this might sound awfully complicated, especially since I said he ISR examples I see in books are usually way more complicated and inefficient than they need to be; but the complication is in the ISR installer and deleter (and you can also list the ISRs). The ISRs themselves are very simple, and I've never had to spend much time at all debugging them. I know this doesn't have much to do with having lots of IRQ inputs, but it might give someone ideas for their system. |
Author: | ttlworks [ Tue Oct 25, 2016 9:27 am ] |
Post subject: | Re: My approach to interrupt routing |
Sorry for unexpectedly bumping in... about having IRQs with different priorities: Suddenly, I feel reminded to the Synertek Hardware Manual, page 120 (labeled page 107 in the PDF). Attachment: "Quad 2 input data select" -> 74HC\HCT157 "priority encoder" -> 74HC\HCT148 The concept looks pretty simple... But when building it that way, I would be afraid that a IRQ with a higher priority might go active between fetching the low_Byte and the high_Byte of the interrupt vector. Edit: If all of the 8 IRQ vectors would be pointing into the same 256 Bytes page in memory, the high_Bytes of the vectors would be identical, and there would be nothing to worry about... |
Author: | BigDumbDinosaur [ Tue Oct 25, 2016 4:53 pm ] |
Post subject: | Re: My approach to interrupt routing |
ttlworks wrote: But when building it that way, I would be afraid that a IRQ with a higher priority might go active between fetching the low_Byte and the high_Byte of the interrupt vector. Proper programming would handle that situation. What the priority encoder is doing is producing an eight bit index. So your ISR would start by pushing the registers as required and then loading the interrupt index into, say, .X. After that, conditions will effectively be "static" and you can safely load the LSB and MSB of the relevant interrupt vector at your leisure (however, "leisure" doesn't mean "waste time"). Don't forget that upon responding to an IRQ the MPU will do the equivalent of an SEI instruction after it pushes PC and SR, thus preventing the pending interrupt from being interrupted by another IRQ source. Incidentally, priority encoders are relatively slow devices, as they have a lot of gates. Priority encoding is a good job for a PLD. |
Author: | ttlworks [ Wed Oct 26, 2016 6:33 am ] |
Post subject: | Re: My approach to interrupt routing |
BigDumbDinosaur wrote: What the priority encoder is doing is producing an eight bit index. So your ISR would start by pushing the registers as required and then loading the interrupt index into, say, .X. Sorry, the ISR isn't involved. From what I have figured so far, the basic idea of the schematic from the Synertek manual seems to be to decode when the CPU reads the memory addresses in the ROM which contain the IRQ vector. If the CPU reads those two Bytes in memory (while responding to an IRQ for instance), a 4 Bit 2:1 multiplexer is used for tweaking some of the address lines to make the CPU read from different memory locations. The priority encoder then selects from which memory locations the IRQ vector is fetched according to the IRQ level. Quote: Don't forget that upon responding to an IRQ the MPU will do the equivalent of an SEI instruction after it pushes PC and SR, thus preventing the pending interrupt from being interrupted by another IRQ source. True, but unfortunately this doesn't affect the priority encoder. For instance, if /IRQ6 at the 74148 priority encoder input is active, and the CPU starts fetching the IRQ6 vector, it reads the low_Byte of the IRQ6 vector. If /IRQ7 goes active shortly after the CPU has fetched the low_Byte of the IRQ6 vector, the encoder would generate the address for fetching the high_Byte of the IRQ7 vector, because IRQ7 has a higher priority than IRQ6. Would suggest to place a 74574 latch clocked by SYNC at the inputs of the priority encoder, this probably would fix this problem and the 74148 would have nearly one CPU clock cycle of time (minus SYNC setup time and the other setup and propagation delay times etc.) for decoding. // Maybe 74573 might work, too... somebody please post the schematic after building something like that. Quote: Incidentally, priority encoders are relatively slow devices, as they have a lot of gates. Priority encoding is a good job for a PLD. A simple GAL\PAL probably would do. 74F148 seems to be out of production, and there seems to be no 74AC\ACT148. BTW: maybe 20ns propagation delay at 5V for a 74HC148 from TI. But another problem is to decode when the CPU tries to read the memory addresses containing the IRQ vector. The 74688 comparator is quite slow, and it isn't available as AC\ACT. 7430 eight input NAND gate is available as ACT, namely the 74ACT11030, but there seems to be no 74AC version of the 7430. 74133 (13 input NAND) only seems to be available as ALS. |
Author: | magetoo [ Fri Oct 28, 2016 1:31 am ] |
Post subject: | Re: My approach to interrupt routing |
ttlworks wrote: BigDumbDinosaur wrote: Incidentally, priority encoders are relatively slow devices, as they have a lot of gates. Priority encoding is a good job for a PLD. 74F148 seems to be out of production, and there seems to be no 74AC\ACT148. BTW: maybe 20ns propagation delay at 5V for a 74HC148 from TI. The datasheet says that for a HC device, all signals are under 40ns at 25°C. What on Earth are you people doing that makes a 40ns delay for an interrupt an issue? Quote: But another problem is to decode when the CPU tries to read the memory addresses containing the IRQ vector. I presume that the vectors are where they are specifically to simplify decoding. Decoding the addresses FE and FF (for the IRQ vector) is just a matter of one 8-input (N)AND gate, and decoding page FF uses another one. But of course on WDC silicon, you have the VPB output, for which the datasheet explicitly mentions interrupt vectoring as the intended use. Again, IRQ is the highest vector, so that decoding it can be done with just one gate. The inputs to the priority encoder changing their state is still going to be a problem, since the signals are potentially asynchronous to the CPU's interrupt handling and we might be reading the outputs as they are changing, which might give invalid results. Ideally, you'd want to freeze them when fetching the vector, but I don't see an easy way to do that; by the time we know that vectors are being fetched it's too late. (Too late, unless we're latching the inputs on VPB and switching the high 8 bits of the address... But wasn't the "early interrupt warning" idea pretty simple, as in just one or two flip-flops?) |
Author: | ttlworks [ Fri Oct 28, 2016 6:06 am ] |
Post subject: | Re: My approach to interrupt routing |
magetoo wrote: The datasheet says that for a HC device, all signals are under 40ns at 25°C. What on Earth are you people doing that makes a 40ns delay for an interrupt an issue? Well, if your CPU would be running at 20MHz, one clock cycle would be 50ns, and a 40ns delay probably would be an issue then... Quote: I presume that the vectors are where they are specifically to simplify decoding. Decoding the addresses FE and FF (for the IRQ vector) is just a matter of one 8-input (N)AND gate, and decoding page FF uses another one. But of course on WDC silicon, you have the VPB output, for which the datasheet explicitly mentions interrupt vectoring as the intended use. Again, IRQ is the highest vector, so that decoding it can be done with just one gate. That's all "implementation specific"... and a "homework assignment" for somebody who might be trying to build something like this, in other words, it's not "my homework assignment". Of course, decoding the vector fetch will be a bit more more in the time critical signal path than decoding the IRQ level, because vector decoding plus address multiplexer switching delay adds _directly_ to the memory read delay. It's just that the schematic is from an old Synertek manual, and I think that 6502 CPUs manufactured by Synertek had no /VP output... so somebody please post a schematic after modifying the circuitry for a W65C02. Quote: The inputs to the priority encoder changing their state is still going to be a problem, since the signals are potentially asynchronous to the CPU's interrupt handling and we might be reading the outputs as they are changing, which might give invalid results. Ideally, you'd want to freeze them when fetching the vector Yep, that's why I had suggested something like placing a 74574 latch clocked by SYNC in front of the interrupt priority decoder... after mentioning that having 8 IRQ vectors with an identical high_Byte also might fix the problem... |
Author: | magetoo [ Fri Oct 28, 2016 10:02 am ] |
Post subject: | Re: My approach to interrupt routing |
ttlworks wrote: Well, if your CPU would be running at 20MHz, one clock cycle would be 50ns, and a 40ns delay probably would be an issue then... Again, why? What could you possibly be doing that would be affected by an interrupt coming in one cycle late? (A maximum of one cycle late, with worst-case timings, at a system speed of 25MHz.) Quote: That's all "implementation specific"... and a "homework assignment" for somebody who might be trying to build something like this, in other words, it's not "my homework assignment". I see, I misread you when you said it was "a problem". Quote: Of course, decoding the vector fetch will be a bit more more in the time critical signal path than decoding the IRQ level, because vector decoding plus address multiplexer switching delay adds _directly_ to the memory read delay. Fortunately the logic needed is trivial (two gates), and both muxes and simple gates are available in extremely fast modern logic families. Quote: somebody please post a schematic after modifying the circuitry for a W65C02. You just get rid of that 15-input AND gate, substitute /VP for its output and switch the A and B inputs on the mux (since /VP is active low rather than high). Quote: Quote: The inputs to the priority encoder changing their state is still going to be a problem Yep, that's why I had suggested something like placing a 74574 latch clocked by SYNC in front of the interrupt priority decoder... after mentioning that having 8 IRQ vectors with an identical high_Byte also might fix the problem... Right, we'll need that latch, without it we might get invalid outputs from the priority encoder; that is, outputs that are not related to any interrupt signal being asserted. Temporary and short-lived invalid outputs, before settling to a valid state, but if that path is asynchronous we might still land in the wrong ISR. That's not a catastrophic failure, but it means we would still have to check in our code that we are servicing an interrupt that has been actually triggered, and it means occasional delays whenever the logic glitches. That might be acceptable, or it might not. |
Author: | ttlworks [ Fri Oct 28, 2016 10:26 am ] |
Post subject: | Re: My approach to interrupt routing |
magetoo wrote: Again, why? What could you possibly be doing that would be affected by an interrupt coming in one cycle late? Priority decoder output better should be stable when the multiplexer switches, that's all. Else, some of the priority decoder delay would add to the memory read delay. Quote: I see, I misread you when you said it was "a problem". Should have phrased it more clearly and detailed, sorry. Quote: You just get rid of that 15-input AND gate, substitute /VP for its output and switch the A and B inputs on the mux (since /VP is active low rather than high). /VP also might go active for RES and NMI, so we need to check if the address lines A1 and A2 are '1', too... the multiplexer shouldn't try to map RES and NMI to 8 different vectors. Quote: Right, we'll need that latch, without it we might get invalid outputs from the priority encoder; We'll need that latch. Clocking it with SYNC probably won't increase IRQ response time, but to me this looked like the most simple solution. |
Author: | magetoo [ Fri Oct 28, 2016 6:29 pm ] |
Post subject: | Re: My approach to interrupt routing |
ttlworks wrote: Priority decoder output better should be stable when the multiplexer switches, that's all. Else, some of the priority decoder delay would add to the memory read delay. If the priority encoder is slow, that just means that the IRQ line is pulled down a few ns later (from the /GS output). The address outputs are guaranteed to be stable before that happens, so the worst that could happen is that the interrupt is serviced one cycle late. (Assuming, of course, that its inputs are stable when the vector is fetched, but we already covered that.) Quote: /VP also might go active for RES and NMI, so we need to check if the address lines A1 and A2 are '1', too... the multiplexer shouldn't try to map RES and NMI to 8 different vectors. Right, that's where the "two gates" come from in the paragraph above. The details are left as a homework assignment. Or maybe I should have expanded on that. But on a more serious note, the details will depend on how the rest of the system works too, what tradeoffs are acceptable, and that sort of thing. Strictly speaking we might not even need those extra gates if we aren't going to use NMI - leave the lowest priority input unconnected and put the reset vector on its address instead. Quote: Clocking it with SYNC probably won't increase IRQ response time, but to me this looked like the most simple solution. There might be corner cases where a higher priority interrupt is ignored if comes in between SYNC and when the vector is fetched - but again, if that's important depends on other things. |
Author: | ttlworks [ Wed Nov 02, 2016 8:06 am ] |
Post subject: | Re: My approach to interrupt routing |
Had some spare time on my hands, so I tried to draw two schematics. It's been a few years that I had tinkered with a 6502, and it wasn't a W65C02, so I don't know if this will work as intended... Since the interrupt vector address has to be stable during vector fetch, I'm now using a 74573 latch which freezes when /VP goes active. Because of the dinosaurs among the readers, I'm posting monochrome schematics: Attachment: irq1.png [ 6.37 KiB | Viewed 6221 times ] Attachment: irq2.png [ 6.85 KiB | Viewed 6221 times ] /IRQ7 has highest priority. /IRQ7 -> $FFE0,1 /IRQ6 -> $FFE2,3 /IRQ5 -> $FFE4,5 /IRQ4 -> $FFE6,7 /IRQ3 -> $FFE8,9 /IRQ2 -> $FFEA,B /IRQ1 -> $FFEC,D /IRQ0 -> $FFEE,F ;--- ...If anybody has a better idea or knows how to improve this, please post it here. |
Page 1 of 1 | All times are UTC |
Powered by phpBB® Forum Software © phpBB Group http://www.phpbb.com/ |