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