MCU Serial Line Units Emulation
Fifth Experiment - MCU Based SLU Emulation
The AVR DB has a two-level transmit buffer, in contrast to older ATMEGA microcontrollers from Microchip. Previously I always used the fact that the DLV11 and the ATMEGA both have a sinlge-byte transmit buffer and therefore I fully relied on the ATMEGA USART to emulate the DLV11. I.e. when the ATMEGA transmit buffer was full I just forwarded this to the PDP-11 and also used the transmit buffer empty interrupt to forward the status of the transmit buffer empty status bit which is the used to generate interrupt for the PDP-11.
I first used the same logic with the AVR DB, but the two-level transmit buffer caused severe issues with the timing when PDP-11 code generated a RS-232 BREAK signal. Either the break signal was too short or caused a protocol violation with the TU-58 protocol. As this will be used as the boot device for RT-11 this is unacceptable. Therefore the DLV11 emulation using an AVR DA/DB microcontroller switches from transmit buffer empty interrupt, named DRE interrupt by the AVR processor, to the transmission complete interrupt.
Another issue with generating the RS-232 BREAK signal occurred because not only did I emulate the XBUF and RBUF register with the microcontroller but as well XCSR and RCSR. Depending on the TU-58 driver routines that generate the BREAK signal this caused as well protocol violations when using higher baud rates or lower DCJ11 clock rates (e.g. 8MHz). Therefore I emulate the XCSR register in the CPLD. The reason is, the order of setting the BRK signal and sending a character could be too slow, which instead of a clean BREAK signal created some spikes on the transmit data line. PDP-11 code relies on the fact that these things happen within the time span of a single bit. With only 38400 baud this is all nice and easy, but with 115200 baud, although not standard for real TU-58 devices, this may be no longer the case.
MCU USARTs
For the emulation of the SLU we will be using the USARTs from the MCU. The AVR128DB64 has a fractional baud rate generator which means we can literally create all possible baud rates regardless of the CPU clock. The internal oscillator is sufficiently stable and accurate, so we do not even need a crystal.
The MCU IO board has actually three break-outs for USARTs. The USART1 on Port C is even isolated and is typically used as a command line interface during debugging and configuring the parameters.
All USARTs are designed to connect to USB to Serial TTL adapters.
SLU Emulation
The SLU emulated will be DLV11 serial line units. As the USART registers and the DLV11 registers are not identical we need to translate between the USART and the PDP-11.
To avoid any issues with synchronisation between the firmware and the DCJ11 all interface functions are running in interrupt mode to make each action atomic.
The register IO is already running as an interrupt service routine. For the USART we are adding two more routines, a receive character completion interrupt service routine and a transmission complete interrupt service routine.
So as long only interrupt service routines access the emulated DLV11 registers we do not need locks or mutex.
Receive Completion Interrupt Service Routine
When initialising the USART the RXC interrupt will be activated. The interrupt routine will read the data register, translate the status bits from AVR USART to DLV11 RCSR and then updates RCSR and RBUF. In case any of the status bits is set the error bit in RBUF is set as well. When the DR in RCSR is set, which means the PDP-11 did not fetch the last character, then we will set the overflow and error bit in RBUF.
If DCJ11 interrupts are enabled the interrupt mask is updated and the interrupt request pin is asserted. For details about interrupt mask and interrupt request pin see next chapter.
;
; 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
Transmission Complete Interrupt Service Routine
As the transmit buffer of the USART of the microcontroller has more stages then a DLV11 we also need to emulate the transmit buffer register we will be using the transmission complete interrupt now. Again the interrupt is automatically enabled when the USART is initialised.
This means we need to keep track when a transmission is on-going. Therefore whenever we put a byte into the TXDATAL register of the UART we set an internal flag xcsrtxbusy. Note that the memory locations named xcsra and xcsrb are not actually the XCSR seen by the PDP-11 as the emulation of the XCSR register is done in the CPLD. The flag xcsrtxbusy will be checked by the write XBUF routine. If the flag is set the character is just copied to the locatoin xbufa or xbufb and the TBREA or TBREB pin will be cleared respectively. If the flag is not set then we shortly toggle TBREA or TBREB low and copy the character directly to the TXDATAL register of the respective USART and set the busy flag.
The transmission complete interrupt routine will then check TBREB or TBREA and if the pin is cleared, it means the transmit buffer register, here xbufa or xbufb, contains the next character and immediately copies the character to the TXDATAL register and sets the busy bit. In all cases the transmission complete interrupt routine sets TBREA or TBREB as the transmit buffer is free.
;
;
;
txca_isr:
push r8
in r8, CPU_SREG
push yl
push yh
ldi yh, USART_TXCIF_bm
sts USARTA+USART_STATUS_offset, yh ; Acknowledge TXC Interrupt
lds yh, xcsra+0
cbr yh, xcsrtxbusy_bm ; Transmitter is no longer busy
sbic b_TBREA ; Check the transmit buffer status
;
; When b_TBREA is set the transmit buffer is empty and there is no other
; character waiting to be sent and we are done
;
rjmp txca_isr_010 ;
;
; There is another character in TXBUFFER=XBUF, therefore the transmitter
; will be busy again and we can set b_TBREA as the transmit buffer is
; ready to accept a character. See CPLD design file as how this will
; generate an interrupt when interrupts are enabled.
;
sbr yh, xcsrtxbusy_bm ;
sbi b_TBREA ;
;
; Now just copy the character in the transmit buffer to the TXDATAL
; register of the USART
;
txca_isr_005:
lds yl, xbufa+0 ; Get the buffer
sts USARTA+USART_TXDATAL_offset, yl ; send the character
;
; Save the internal xcsr flags and exit interrupt
;
txca_isr_010:
sts xcsra+0, yh ; Save modifed XCSR
pop yh ;
pop yl ;
out CPU_SREG, r8
pop r8
reti
SLU Routines for the DLV11 Registers
The MCU emulates the DLV11 registers as part of the DCJ11 IO interrupt service routine. Based on the IO addresses 176xxx the IO ISR jumps to the SLU interrupt routines. There are two entries, one for read and one for write access. First we will use the lower address bits to create another jump table index
;
; Read Device Registers
;
io_isr_slur:
mov zl, yl ; Address bits
andi zl, 0x07 ; Isolate A3, A2, A1
clr zh ; Jump Table Offset
subi zl, low(-io_isr_slur_jmptbl) ; Add Jump Table Address
sbci zh, high(-io_isr_slur_jmptbl)
extinc extslurcnt ; Increment SLU call count
ijmp
io_isr_slur_jmptbl:
rjmp io_isr_sluarcsrr ; Note each SLU has four registers
rjmp io_isr_sluarbufr ; which are addressed using A2 and A1
rjmp io_isr_sluaxcsrr ; A3 is used to distinguish SLUA from
rjmp io_isr_sluaxbufr ; SLUB, we assume the addresses of
rjmp io_isr_slubrcsrr ; the two SLUs emulated are adjacent.
rjmp io_isr_slubrbufr
rjmp io_isr_slubxcsrr
rjmp io_isr_slubxbufr
Here we will assume that the two SLUs are adjacent in the IO page. The SLU which has the address bit A3 set to LOW will be mapped to SLUA and the SLU with address bit A3 set to HIGH is mapped to SLUB.
The jump table for write access is similar. Each SLU has 4 registers. Hence each table has eight entries
;
; Write Device Register
;
; Actually only the lower 8-bits contain writable bits therefore
; write a word or write the lower byte is the same and write the
; upper byte is actually a no operation
;
io_isr_sluw:
io_isr_slul:
mov zl, yl
;- extinc extsluwcnt ; Increment SLU write count
andi zl, 0x07 ; Jump Table Offset
clr zh
subi zl, low(-io_isr_sluw_jmptbl)
sbci zh, high(-io_isr_sluw_jmptbl)
ijmp
io_isr_sluw_jmptbl:
rjmp io_isr_sluarcsrw
rjmp io_isr_sluarbufw
rjmp io_isr_sluaxcsrw
rjmp io_isr_sluaxbufw
rjmp io_isr_slubrcsrw
rjmp io_isr_slubrbufw
rjmp io_isr_slubxcsrw
RCSR - Read
Reading the RCSR just returns the current content of the internal register.
;
; Read RCSR
;
io_isr_sluarcsrr:
lds yl, rcsra+0 ; Just Return the RCSR
lds yh, rcsra+1
rjmp io_isr_exit_read
RCSR - Write
The only bit you can write to the RCSR is the IE (Interrupt Enable) bit.
;
; Write RCSR
;
io_isr_sluarcsrw:
in yl, dallowin ; Only bit 6 (IE) and bit 0 (RDR) can be written
bst yl, rcsrie_bp ;
lds yl, rcsra+0 ; Get current RCSR
bld yl, rcsrie_bp ; copy over IE
sts rcsra+0, yl ; Save new RCSR
brtc io_isr_sluarcsrw_020 ;
sbrs yl, rcsrdr_bp
rjmp io_isr_sluarcsrw_010 ; If IE is set and DR was set then this will
lds yl, intmask ; trigger an interrupt
sbr yl, int_rxa_bm
sts intmask, yl
sbi b_INTR
io_isr_sluarcsrw_010:
rjmp io_isr_exit_write
io_isr_sluarcsrw_020: ; If IE is cleared we need to remove INTR
lds yl, intmask ; if this is the last interrupt
cbr yl, int_rxa_bm
sts intmask, yl
brne io_isr_sluarcsrw_030
cbi b_INTR
io_isr_sluarcsrw_030:
rjmp io_isr_exit_write
RBUF - Read
Reading RBUF returns the value written by the RXC interrupt service routine and
resets the DR bit in the internal rbuf.
;
; Read RBUF
;
io_isr_sluarbufr:
lds yl, rcsra+0 ; Get RCSR
cbr yl, rcsrrxdone_bm ; Reset DR
sts rcsra+0, yl ; Update
lds yl, rbufa+0 ; Return RBUF
lds yh, rbufa+1
rjmp io_isr_exit_read
RBUF - Write
This is a no operation.
;
; Write RBUF
;
io_isr_sluarbufw:
rjmp io_isr_exit_write ; Literally a no-op
XCSR - Read
Implemented in CPLD
;
; Read XCSR
;
io_isr_sluaxcsrr:
rjmp io_isr_exit_read
XCSR - Write
Implemented in CPLD
;
; Write XCSR
;
io_isr_sluaxcsrw:
rjmp io_isr_exit_write
XBUF - Read
Reading XBUF will just return the previously written value. On older DLV11 implementation this is a no operation and normally just returns zero. However on DLV11 implementations using the DC319 UART this returns the character previously written to XBUF.
;
; Read XBUF, mimic behaviour of DC319
;
io_isr_sluaxbufr:
lds yl, xbufa+0 ; Normally the last character sent
lds yh, xbufa+1 ; cannot be read back
rjmp io_isr_exit_read
XBUF - Write
Writing the XBUF will mimic the behaviour of the CDP6402 UART or an equivalent UART used for DLV-11 interfaces.
;
; Write XBUF
;
io_isr_sluaxbufw:
cbi b_TBREA
in yl, dallowin ; get character to yl
sts xbufa+0, yl ; save in xbuf and to USART
lds yh, xcsra+0 ;
sbrc yh, xcsrtxbusy_bp ;
;
; Transmitter is busy, nothing we can do now
;
rjmp io_isr_exit_write_alt ; and normal exit
;
; Transmitter is not busy so we move the character to TXDATAL
; but to start transmission as late as possible because of the
; issue with wrong flags, when the BRK bit is set after sending a
; character. Therefore we have our own isr exit to be able to
; transfer the character the latest possible.
;
sbr yh, xcsrtxbusy_bm ; Transmitter is now busy
sts xcsra+0, yh ;
sbi b_ACKN
sbic i_RQST
rjmp PC-1
cbi b_ACKN ; Inform CPLD to finish CPU cycle
sbi f_RQST ; Acknowledge any pending interrupt
pop zh
pop zl
pop yh
;
; The character is in yl
;
sts USARTA+USART_TXDATAL_offset, yl
sbi b_TBREA
sbi b_PA5
pop yl
out cpu_sreg, r8 ; Restore state
pop r8
reti
Forth with TU-58
Obviously with a second SLU at the standard address Forth with TU-58 support will boot and run with the MCU IO card as it was running with the Multi IO card.
@173000g
$DD
FIG-FORTH V 1.3.3
1 36 index
1 ( LOAD SCREEN)
2 ( MISCELLANEOUS FIG-FORTH DEFINITIONS )
3
4 ( ERROR, WARNING, AND OTHER MESSAGES - SCREENS 4 AND 5 )
5 ( ERROR MESSAGES, CONTINUED )
6 ( EDITOR - SET-UP)
7 ( EDITOR - OPERATIONS)
8 ( EDITOR, SCREEN 3)
9 ( EDITOR, SCREEN 4)
10 ( ASSEMBLER) OCTAL
11 ( ASSEMBLER, CONT.) OCTAL
12 ( ASSEMBLER - INSTRUCTION TABLE) OCTAL
13 ( ASSEMBLER - CONT.) OCTAL
14 ( ASSEMBLER - REGISTERS, MODES, AND CONDITIONS) OCTAL
15 ( ASSEMBLER - STRUCTURED CONDITIONALS) OCTAL
16
17 ( ASSEMBLER - EXAMPLES)
18 ( STRING EXTENSIONS FOR PDP-11 ) DECIMAL
19 ( STRING ROUTINES) DECIMAL
20 ( STRINGS - CONTINUED)
21 ( STRINGS - CONTINUED)
22 ( STRINGS - CONTINUED )
23 ( DUMP DEFINITIONS; NEEDS SCREEN 18 )
24 ( TRIG LOOKUP ROUTINES - WITH SINE *10000 TABLE)
25
26 ( FORTRAN LINKAGE, RSX)
27
28 ( RT-11 SYSTEM-CALL EXAMPLE - DATE)
29 ( RSX-11M SYSTEM-CALL EXAMPLE - DATE)
30 ( RSX-11M SYSTEM-CALL EXAMPLE - TERMINAL I/O)
31
32 ( EXAMPLES - RANDOM #S, VIRTUAL ARRAY, RECURSIVE CALL)
33
34 ( CREATE BOOTABLE IMAGE ON SCREENS 40-47. FOR STAND-ALONE.)
35 ( CREATE A BINARY IMAGE ON SCREENS 40 - 47 )
36 ( CREATE BOOT LOADER. NOTE - DOES NOT WRITE BOOT BLOCK) OK