MCU Interrupt Handler
Third Experiment - Interrupts for the DCJ11
The DCJ11 SBC only uses interrupt IRQ0, which corresponds to the interrupt level 4. The PDP-11 software running on a DCJ11 will not be able to detect the difference. PDP-11 interrupt vectors typically block all interrupts of the same level as are associated by the source. E.g. the interrupt level of a DLV-11 is typically 6 and hence the vector will set the processor interrupt priority to level 6, which blocks all interrupts of level 6, 5 and 4 to avoid nested interrupts of the same level. Using only IRQ0 will therefore also assure that further interrupts are blocked by the processor until the interrupt routine exits. Multiple interrupt levels where designed to the PDP-11 architecture in case you have some very high level priority interrupts that need to be processed immediately to guarantee interrupt latency. This is not required for our purpose and as SLUs are the only interrupt source so far it does not matter. The only other interrupt source will be the event signal which also uses level 6 as the SLU. The locking occurs only on the processor level and not the hardware level. Therefore this does not create a conflict.
Interrupt sources
In the previous chapter we have seen that the MCU firmware has already been prepared to initiate an interrupt. As the MCU has several interrupt sources the combination and prioritization of the interrupt takes place within the firmware and the pin INTR represents the interrupt summary from the MCU towards the DCJ11. I.e. INTR is set if any interrupt source in the MCU is active and cleared after the last interrupt has been acknowledged.
But first we need to consider the interrupts of the console. The DC319 provides three interrupt sources, a receive, a transmit and a break interrupt. The break interrupt is intended to be used to assert HALT when hitting the break key on the console. This will be discussed later. Typically the interrupt from the console have the highest priority in a system as the console is located directly adjacent to the CPU. And this is also the case of the DCJ11 SBC.
DCJ11 Interrupt Handling
The interrupt inputs of the DCJ11 use positive logic. Therefore if we have multiple interrupt sources we need to logically OR the individual interrupts and create the INT0 input in our case. If the processor status allows for the interrupt, the DCJ11 will then initiate an interrupt acknowledge cycle after the current cycle has finished. In an interrupt acknowledge cycle the DCJ11 expects that the highest priority interrupt source places the vector onto the databus when BUFCTL is asserted.
Console Interrupt
The DC319 has two interrupt outputs that deal with the receive complete and the transmit buffer empty interrupt. When either xxx is true and the corresponding interrupt enable bit, IE in RCSR or XCSR, is set, the corresponding output is asserted.
The CPLD therefore only needs to provide the logic for recording the interrupts and acknowledging the highest priority interrupt. As a reference the logic of a DC003 without the IE flip-flop was used.
/*------------------------------------------------------------------------------*/
/*
Interrupt
When IRQ0 is set, the DCJ11 will start an interrupt acknowledge cycle.
During this cycle we need to evaluate the highest priority interrupt
active. For this we sample the status at the beginning of every CPU
cycle to have stable interrupt statuses during the cycle.
Note: RCVIRQC and XMTIRQC are leading edge intterupts and INTR is
a level interrupt. The console interrupts have precedence over the
MCU interrupt and are handled internally by the CPLD.
*/
RXIRQC.d = 'b'0;
RXIRQC.ck = RCVIRQC;
RXIRQC.ap = RXACKC & LAIO:[AIOIACK] & STRB;
RXIRQC.ar = INIT;
RXACKC.d = !RXIRQC & RCVIRQC;
RXACKC.ck = ALE;
RXACKC.ar = INIT;
TXIRQC.d = 'b'0;
TXIRQC.ck = XMTIRQC;
TXIRQC.ap = TXACKC & !RXACKC & LAIO:[AIOIACK] & STRB;
TXIRQC.ar = INIT;
TXACKC.d = !TXIRQC & XMTIRQC;
TXACKC.ck = ALE;
TXACKC.ar = INIT;
APPEND
DAL.oe = BUFCTL & LAIO:[AIOIACK] & RXACKC
# BUFCTL & LAIO:[AIOIACK] & TXACKC;
$DEFINE RXVECT 'o'000060
$DEFINE TXVECT 'o'000064
APPEND
DAL = RXVECT & LAIO:[AIOIACK] & RXACKC
# TXVECT & LAIO:[AIOIACK] & !RXACKC & TXACKC;
IRQ0 = !RXIRQC & RCVIRQC
# !TXIRQC & XMTIRQC;
The R/TXIRQC flip-flop will be reset by INIT or a leading edge of the interrupt inputs form the DC319. For each CPU cycle the logical and of the reset state of the R/TXIRQC flip-flop and the interrupt from the DC319 are latched into the R/TXACKC flip-flip using ALE. When the DCJ11 now issues an interrupt acknowledge cycle only the flip-flop R/TXIRQC with the highest interrupt active will then be set and at the corresponding vector is placed onto the data bus.
Once the interrupt acknowledge cycle is finished the R/TXACKC with the higher priority will be cleared in the next CPU cycle using ALE as its corresponding R/TXIRQC flip-flop is now set. Only a new leading edge of the corresponding interrupt output of the DCJ319 will clear it.
The DCJ11 expects that INT0 is cleared during interrupt acknowledge cycle. Of course only if there is no further interrupt pending. If INT0 is asserted after an interrupt acknowledge cycle the DCJ11 will know that another interrupt is pending and will acknowledge the next interrupt as soon as the interrupt level in the processor status allows to service the requested interrupt level.
MCU Interrupt
Different to the edge triggered interrupts of the DCJ319, the MCU interrupt
INTR is a level triggered interrupt and stays high as long as an interrupt source
still is pending.
APPEND
IRQ0 = INTR;
and another flip-flop is added to the interrupt acknowledge chain
MCUIRQ.d = INTR;
MCUIRQ.ck = ALE;
MCUIRQ.ar = INIT;
And the MCU is informed if the interrupt acknowledge cycle must be processed by the MCU firmware
/*
We add IACK in case the MCU is the only interrupt source and let the MCU
handle the interrupt acknowledge.
*/
APPEND
MCUIO = LAIO3 & LAIO2 & !LAIO1 & LAIO0 & MCUIRQ & !RXACKC & !TXACKC;
MCU Interrupt Initiation
Each device emulated by the MCU can activate an interrupt by setting the pin INTR.
intmask
As the MCU emulates multiple devices that can initiate an interrupt we need to
track which device has activated the interrupt. For this the location intmask in
memory needs to be updated by each device that asserts the interrupt. Each device
is associated with a bit in the interrupt mask. Currently the interrupt mask is
a byte and we support eight interrupt sources. Within the devices emulated by the
MCU the bit location defines the priority of the interrupt with bit 7 representing
the highest priority interrupt and bit 0 the lowest priority interrupt. The two SLUs
emulated by the MCU are given the interrupt priorities 7, 6, 5 and 4. Whenever a
device sets pin INTR to assert the interrupt line it also sets the corresponding
bit in the interrupt mask.
The following shows the USART receive data ready interrupt service routine
;
; Receive Data Register
;
rxca_isr:
push r8
in r8, CPU_SREG
push yl
push yh
;
; For character lengths of 8 or less bits RXDATAH needs to be read first
; as RXDATAL shifts the buffer and would destroy RXDATAH
;
lds yh, USARTA+USART_RXDATAH_offset ;
lds yl, USARTA+USART_RXDATAL_offset ;
;
; Now the interrupt is cleared for this character
;
; Bit 6 = Overflow
; Bit 2 = Framing Error
; Bit 1 = Parity Error
;
sts rbufa+0, yl ; Safe character in RBUF
bst yh, USART_FERR_bp ; Copy over the FE and PE bits
bld yh, rbuffe_bp ; OV is already at the correct
bst yh, USART_PERR_bp ; position
bld yh, rbufpe_bp
lds yl, rcsra+0 ; get buffer status
sbrc yl, rcsrdr_bp ; skip of DR clear
ori yh, rbufov_bm ; Mark overflow in rbuf
ori yl, rcsrdr_bm ; Mark DR in rcsr
sts rcsra, yl ; Safe status in lower byte of RCSR
andi yh, 0x70 ; status bits at correct place
breq rxca_isr_010 ; no error bits set
ori yh, rbuferr_bm ; if any error bit is set then set bit 15
rxca_isr_010:
sts rbufa+1, yh ; Safe error bits in upper byte of RCSR
;
; RBUF is now set and status reflects a waiting character
;
sbrs yl, rcsrie_bp ; Interrupt Enabled
rjmp rxca_isr_020 ; no
lds yh, intmask ; get interrupt mask
ori yh, int_rxa_bm ; set rxa interrupt
sts intmask, yh ; save interrupt mask
sbi b_INTR ; set interrupt request
rxca_isr_020:
pop yh
pop yl
out CPU_SREG, r8
pop r8
reti
MCU Interrupt Acknowledge
With the last addition to the logic of MCUIO the MCU will now also be
triggered by the interrupt acknowledge cycle when the MCU has set the pin INTR
and no other higher priority interrupt from the console is pending.
The interrupt acknowledge service routine in the MCU will then search for the
highest interrupt of its emulated devices and place the vector to the data
bus. For this it searches for the highest bit set in the interrupt mask,
sets the associated vector value and clears the bit in the interrupt mask.
If no further bit is set in the interrupt mask it will clear pin INTR and
thus deassert the MCU interrupt line.
io_isr_iack:
lds zl, intmask
tst zl
breq PC ; Should not happen
ldi yl, 0334 ; Assume lowest priority interrupt
sbrc zl, 1
ldi yl, 0330 ; Found a higher priority
sbrc zl, 2
ldi yl, 0324 ; Found a higher priority
sbrc zl, 3
ldi yl, 0320 ; Found a higher priority
sbrc zl, 4
ldi yl, 0314 ; Found a higher priority
sbrc zl, 5
ldi yl, 0310 ; Found a higher priority
sbrc zl, 6
ldi yl, 0304 ; Found a higher priority
sbrc zl, 7
ldi yl, 0300 ; Found a higher priority
clr yh
;
; yh:yl has now the vector of the active interrupt with the highest priority
;
ldi zh, ~0x01 ; Assume lowest priority interrupt
sbrc zl, 1
ldi zh, ~0x02 ; Found a higher priority
sbrc zl, 2
ldi zh, ~0x04 ; Found a higher priority
sbrc zl, 3
ldi zh, ~0x08 ; Found a higher priority
sbrc zl, 4
ldi zh, ~0x10 ; Found a higher priority
sbrc zl, 5
ldi zh, ~0x20 ; Found a higher priority
sbrc zl, 6
ldi zh, ~0x40 ; Found a higher priority
sbrc zl, 7
ldi zh, ~0x80 ; Found a higher priority
and zl, zh ;
sts intmask, zl
brne io_isr_iack_010
cbi b_INTR
io_isr_iack_010:
rjmp io_isr_exit_read