Lee Hart, cosmacelf Yahoo discussion, May 2015: > How do I make subroutine calls after an interrupt? SCRT > doesn't work because the PC is not set to R3, but R1. > I see a standard routine MARK... Calling subroutines inside an interrupt handler is getting deep into [1802 programming mysteries]. It gets particularly tricky if the interrupt handler and subroutines have to be re-entrant. But like all things, the 1802 can do it! Just not quite the same way you'd do in on some other CPU. :-) You can dedicate a register as the subroutine's program counter. Call the subroutine with a SEP Rn, and return with a SEP R1. This works if the subroutine is only used by the interrupt handler. If the subroutine you want to call is used by both the main program and the interrupt handler, the most straightforward way is to change the program counter from R1 to R3 in the interrupt handler. Then SCRT can be used as usual to call subroutines. Change the PC back to R3 upon exit. Or, you can change the "standard call and return" SCRT routine's program counters to a different entry point, which uses R1 instead of R3 as the assumed calling program's PC. Or, you can use SAV to save the values of P and X, and use RET or DIS to have the subroutine restore them at the end. Or, use FORTH-like threaded code, with interrupts only enabled between words when you're in the NEXT interpreter. No doubt there are more ways. ;-) - Lee Hart [NOte: SCRT is described in old COSMAC literature and is documented among other 1802 software. 1802 hardware docs describe the actions of an interrupt and other 1802 instructions. - Herb] cdp1877 Priority Interrupt Controller ---------------- The CDP1877 doesn't seem like a very imaginative chip. Maybe that's why it was rarely used. The 1802 provides an exceptionally fast interrupt response mechanism. An interrupt saves the current values of P and X in register T, them sets P=1 and X=2. The interrupt handler program uses R1 as its program counter, so you have to initialize R1 to point to it. The first instructions in the interrupt handler are typically DEC R2 (point R2 to a free byte on the stack), and SAV (to save T on the stack). The interrupt handler then does its appointed tasks. You would typically poll the possible sources of the interrupt to see which one caused it. This can be slow. If you're in a hurry, you can add a priority interrupt scheme. While the CDP1877 is one, a plain old 4532, or 74x147 or 74x148 priority encoder is simpler and works about as well. These chips output the binary address of the highest-active of 8 interrupt bits. The interrupt handler reads an input port to see which interrupt it is, and uses it as a vector to the appropriate interrupt handler. This only takes a couple of instructions. It's possible to make it even faster. You use the interrupt state code to replace certain bits in the address bus with those from the priority encoder, so the program immediately executes the appropriate interrupt handler. When an interrupt service routine finishes, it jumps to the location just BEFORE its entry point. This is usually a RET instruction. It pops the saved values of P and X off the stack, restores them to P and X, and re-enables interrupts. After execution, it leaves R1 pointed right back at the entry point of the interrupt handler, ready for the next interrupt. ---------------- Ian says: After a bit more thought on the subject I think I have a solution that will reduce the address space to the 16 bytes which Mark achieved and that is the minimum that is possible. It relies on the Ax=1, Ay=1 condition being ignored by the 1877. Connect the inputs of a 4 input AND gate to MA3-MA0 and connect the output to the active high CS on the 1877. Connect the inputs of a 4 input NAND gate to MA7-MA4 and connect the output to the active low CS on the 1877 and to one input each of two 2 input OR gates. Connect MA3 to the other input of one OR gate and the output of that gate to Ax on the 1877. Connect MA2 to the second input of the other OR gate and the output of that gate to Ay on the 1877. When TPA is high and the high byte of the address is FF the 1877 active low CS is a 0, the active high CS is a 1, Ax and Ay are both 1s, so the 1877 is selected. After TPA if the high nibble of the low address byte is F then the output of the NAND gate will be 0 and MA3 and MA2 will select Ax and Ay. If the high nibble is anything else Ax and Ay are 1s. So the 1877 will respond to addresses between FFF0 and FFFB (FFFC-FFFF will be ignored since Ax and Ay are 1s - I guess that technically the 1877 is only using 12 addresses!). The MASK and STATUS registers will be at FFF0-FFF3, the CONTROL and POLLING registers at FFF4-FFF7 and the PAGE register at FFF8-FFFB. The CPU R1 should be set to FFF8. Since the 1877 issues a 3 byte long branch instruction R1 will start at FFF8, increment to FFF9 then FFFA and use 3 of the 4 addresses available. That is why 16 (or 12) is the minimum address space. If Ax and Ay were connected to MA2 and MA1 respectively there would only be 2 addresses for each register and when R1 attempted to fetch the 3rd byte of the branch instruction Ax and Ay would be 1s and the 1877 would not respond. Cheers, Ian. mark.bielman says, May 11: Ian has described my solution well, as best I recall. (no notes in front of me at the moment... I remember that the PIC register for the LBR is at FFF8!) He is correct in that my initial design (using HC gates as described... 4000 series too slow) used 4 bytes before I realized that to perform the LBR xx yy requires 3 bytes minimum, so obviously rounded up to 4 bytes [total, one for each register]. I also agree with Lee that the chip design is awful! I looked long and hard at other solutions (OTS priority encoders with input ports, etc) but was reluctant to give up i/o (N) ports. Other solutions (see "Idea Book") looked a bit clumsy and used too many CPU registers. Since I had the 1877 decided to play with it. The obvious issue is the return from interrupt method. Since R1 must point to the PIC register but then jumps to an INT routine (which can be anywhere and even branch from there) R1 must be restored. My quick & dirty approach was the devote a register for this. There is no RAM in the PIC space so I cannot jump there. As mentioned in a previous post, I do a SEP (Rn) which reloads R1. (also restores D) That small bit of code uses the standard COSMAC 'return from interrupt' which branches back to the instruction immediately preceding the entry point, leaving Rn pointing to the correct address. I wanted to move on to other items on my list, so that's where it stands for now. ALL FUN STUFF! - Mark [The 1877 looks klugey....] That's how I felt when I first looked at it in the 1980's. It seemed like RCA Marketing said, "We gotta have an interrupt controller. Quick, throw something together..." This is unfortunate, because the 1802 has such a fast elegant way to respond to an interrupt. It has its own PC, to avoids the pushes/pops required on all the other micros at the time. Heck, if you have multiple interrupt sources, and use a flag line for each to signal "I did it", your interrupt handler only needs a short branch instruction to jump directly to the associated interrupt handler. If you're going to use a GAL or some other programmable logic [to remap memory], you may as well program it to be a "proper" interrupt controller, and forget the 1877. There's an interesting technique that I saw used on a Godbout S-100 card with the 8080 CPU. It adds a memory chip that is used exclusively for the interrupt handler. When an interrupt occurs, a flip-flop maps out normal memory and maps in the interrupt memory. The interrupt memory can be quite small -- only big enough to hold the interrupt handler (for example, a 256 x 8 PROM). But it is partially decoded, so it fills the entire 64k address space. That means the high byte of any address in the interrupt handler doesn't matter! So you don't have to initialize or restore the high byte of R1, and can use it for other data. You can also just let the final RET instruction wrap to the beginning of the interrupt handler, saving a branch instruction. Godbout used a further refinement (to get around limitations of the 8080). When the interrupt memory is mapped in, its address comes not from the CPU, but from a *counter*. This counter is reset by the interrupt, so no matter what address the CPU puts out, it gets sequential instructions from the interrupt memory. The interrupt handler is then a "straight line" program with no branches. The advantage of this trick is that the CPU itself does not need to see the interrupt; it thinks it's still executing its main program, with no pushes to the stack (and no pops needed to return). "Return" at the end of the interrupt handler just subtracts the length of the interrupt handler program from the PC, and then maps normal memory back in. The CPU simply resumes execution right where it left off. Thanks to the 1802's skip instructions, it can still do conditionals in an interrupt handler implemented like this. - Lee Hart ----------------- mark says: Thanks to the "Ideas Book" (RCA Europe) I got this working! I modified/expanded it to work with the 1877 PIC. The code is attached. It basically uses R7 to do a few varying tasks which is loaded depending on the current task and doing a SEP R7. The main point is that is reloads the SCRT registers (R3, R4, R5) and the INT routines run in R3, now allowing SCRT to work normally. Cheers! - Mark [see Int_routines.txt] ===================================== Aug 2016 comments on interrupts and endless loops: From Lee Hart, posted in cosmacelf This is [code] in the VIP routine, to write data to a cassette tape. The code right before the [endless loop] is ; enters with P=R3 (program counter) 80BC SEP RC ; call BEEP at 816F (write 1 bit to tape) 80BD INP 1 ; turn on TV display 80BE DEC R6 80BF SEP R4 ; call DIS2HEX at 81DD (display 2 hex digits) 80C0 BR C0 ; loop forever > Doesn't this loop just throw the CPU into an endless loop? This is the end of the routine to write data to a cassette tape. When finished, it turns the 1861 back on (so there will be interrupts and DMA), and displays two hex digits (the last memory byte recorded on the tape). At this point, the system is waiting for you to press RESET and select whatever function you want to do next. The main program is "hung" by that BR CO, but the 1802 is still executing the interrupt handler 60 times per second. If desired, the interrupt handler could contain a routine that (for example) checks for a key, and if found, jumps R3 to the selected routine. > Why not use an IDL instruction instead of a endless loop? BR SELF waits for an interrupt or DMA, and after servicing it, resumes executing the BR SELF instruction. The only way out of this loop is RESET or for the interrupt handler to change the program counter before it returns. IDL serves a slightly different purpose. IDL waits for an interrupt or DMA, and after servicing it, execution resumes with the next instruction *after* the IDL.