; IBM AT keyboard interface for 6502 L.Davison, 2001
; port address is set by your hardware configuration
; page zero bytes used = 8
; code bytes (including tables) less than 690
; works with 102 key standard and 105 key Win95/98 keyboards
; The main, externally useable routines are ...
; ResetAT resets the keyboard, flags & LEDs
; KeyLEDs sets the keyboard LEDs
; GetAT scans the keyboard and waits for a key
; ScanAT scans the keyboard, returns after timeout if no key
; Send_AT sends a byte to the keyboard
; GetKey scans & decodes the keyboard, waits for a key but may still
; return null for some keys.
; ScanKey scans & decodes the keyboard, returns after a timeout if no key
; addresses used ...
KPort = $F120 ; keyboard port, o.c. outputs, tristate inputs
; bit 0 is data
; bit 1 is clock
; bits 2 to 7 are unused
KTable = $F1 ; (and $F2) keyboard table pointer
TxChar = $F3 ; transmit character buffer
RxChar = $F4 ; receive character buffer
RxParity = $F5 ; receive character parity buffer (Bit 0 only)
LEDBits = $F6 ; LED status bits
; bit 0 = scroll lock, 1 = LED on
; bit 1 = num lock, 1 = LED on
; bit 2 = caps lock, 1 = LED on
; bits 3 to 7 are unused but may get trashed
KeyBits = $F7 ; key status bits
; bit 0 = Lshift, 1 = key down
; bit 1 = Lcontrol, 1 = key down
; bit 2 = Lwin, 1 = key down
; bit 3 = Lalt, 1 = key down
; bit 4 = Rshift, 1 = key down
; bit 5 = Rcontrol, 1 = key down
; bit 6 = Rwin, 1 = key down
; bit 7 = Ralt, 1 = key down
RxTwo = $F8 ; key two code flag and temporary counter
*= $2000 ; set origin (put this where you like. ROM or RAM)
; keyboard decoding routine. the entry point is ScanKey for a non
; halting keyboard poll. if there is a key waiting it will be
; returned in RxChar, else, on return, RxChar will be null.
; key pressed was break [pause] key
WasBRK
LDA #$07 ; seven more scancodes to ignore
; ($14,$77,$E1,$F0,$14,$F0,$77)
STA RxTwo ; copy code count
Ploop
JSR GetAT ; get scancode
DEC RxTwo ; decrement count
BNE Ploop ; loop if not all pause bytes done
LDA #$03 ; make break = [CTRL-C]
STA RxChar ; save key
RTS
; process lifted key
KeyUp
JSR GetAT ; get lifted key scancode
ORA RxTwo ; OR in table half
TAY ; set index from scancode
LDA (KTable),Y ; get keycode
TAX ; save key for now
AND #$F0 ; mask top nibble
; CMP #$C0 ; function key ?
; BEQ FuncKeyUP ; was function key, use this branch for you own
; ; function key up handler (X holds char code)
CMP #$90 ; bit hold ?
BNE ScanKey ; not bit hold, go get next
; ignores other key releases
LDA #$00 ; clear A
; was bit hold key (for up OR down key)
BitHold
STA RxTwo ; save key up/down flag ($90 for down, $00 for up)
TXA ; get key back
CMP #$98 ; compare with win_menu key
BCS ScanKey ; ignore $98 - $9F
; if here key is either, [L_SHIFT], [L_CTRL],
; [L_WIN], [L_ALT], [R_SHIFT], [RCTRL], [R_WIN]
; or [R_ALT]
; rotates a 1 into a byte of 0s to
; toggle the pressed/lifted key's bit
AND #$07 ; mask lower 3 bits
TAX ; key # to X
LDA #$00 ; clear A
SEC ; set -1 bit
R_Loop
ROL A ; rotate zero bit through A
DEX ; decrement bit count
BPL R_Loop ; loop if not that key
LDX RxTwo ; get key up flag
BEQ ClearBit ; branch if was it key up
ORA KeyBits ; set the bit
BNE SavBits ; branch always (always at least one bit set)
ClearBit
EOR #$FF ; make only one bit = 0
AND KeyBits ; clear the bit
SavBits
STA KeyBits ; and save it back
BCC ScanKey ; go get next scancode (branch always)
; handle num, caps and scroll lock keys
SetUnset
TXA ; get key back
EOR LEDBits ; toggle the LED bits
STA LEDBits ; save new LED bits
JSR KeyLEDs ; set the keyboard LEDs
; get a key from the keyboard, exits with null after timeout
; This is the main entry point for this routine
ScanKey
JSR ScanAT ; scan keyboard
BNE ProcKey ; if key go do next
RTS
; get a key from the keyboard, waits for a valid key
; This is the secondary entry point for this routine
GetKey
JSR GetAT ; get key scancode
ProcKey
CMP #$E1 ; is it pause (break)
BEQ WasBRK ; branch if so
CMP #$AA ; is it selftest pass
BEQ ScanKey ; branch if was
CMP #$FF ; is it error or buffer overflow
BNE NotOF ; branch if not
JSR ClearOF ; clear overflow
BNE ScanKey ; get scancode again (branch always)
; when here we have handled startup, errors and [BREAK]
NotOF
CMP #$E0 ; is it $E0 (two byte sequence)
BNE NotE0 ; branch if not
JSR GetAT ; get second scancode
LDX #$80 ; set for double key code
NotE0
STX RxTwo ; set/clear two code flag
CMP #$F0 ; is it $F0 (key up)
BEQ KeyUp ; branch if up
; key has been pressed
ORA RxTwo ; OR in table half
TAY ; set index from scancode
LDA (KTable),Y ; get keycode
TAX ; save key for now
AND #$F0 ; mask top nibble
CMP #$80 ; bit set/unset ?
BEQ SetUnset ; was bit set/unset
CMP #$90 ; bit hold ?
BEQ BitHold ; was so go set hold bit
CMP #$C0 ; function key ?
; BEQ FuncKey ; was function key, use this branch for you own
; function key down handler (X holds char code)
BEQ ScanKey ; was function key, ignore for now
; key is not function, bit change or lock key
LDA LEDBits ; get the LED bits
AND #$04 ; check CAPS lock
BEQ NotCapsL ; skip CAPS if not set
TXA ; get key back
CMP #("a"+$80) ; compare with "a" (shiftable)
BCC NotCapsL ; branch if less
CMP #("z"+$80+1) ; compare with "z"+1 (shiftable)
BCS NotCapsL ; branch if >=
; caps lock on and was alpha, so shift it
TYA ; copy index
EOR #$80 ; do CAPS LOCK
TAY ; back to index
NotCapsL
TYA ; copy scancode
CMP #$CA ; compare with keypad "/"
BEQ Shift ; correct keypad "/"
CMP #$69 ; compare scancode with lowest numeric pad code
BCC NotNumpad ; branch if <
CMP #$7E ; compare scancode with highest numeric pad code+1
BCS NotNumpad ; branch if >= (not numeric)
; gets here if numeric pad code
LDA LEDBits ; get the LED bits
AND #$02 ; check NUM lock
BNE NoShift ; skip NUM if set (works backwards! like it should)
TXA ; get key back
BMI Shift ; branch if was numeric (shiftable)
; key wasn't numeric so now check shift keys status'
NotNumpad
LDA KeyBits ; get held bit flags
AND #$11 ; mask shift bits
BEQ NoShift ; there was no shift applied
; if here then either shift was held (or both)
TXA ; get key back
BPL NoShift ; branch if not shiftable
Shift
TYA ; copy index
EOR #$80 ; do the shift (or NUM LOCK)
TAY ; copy to index
NoShift
LDA (KTable),Y ; get keycode
CPY #$80 ; check scancode
BCS FixPound ; skip if from second half of table (¬ and £)
AND #$7F ; clear shiftable bit (lower half of table only)
FixPound
TAX ; save key for now
LDA KeyBits ; get held bit flags
AND #$22 ; mask control bits
BEQ NoCtrl ; was no control key applied
TXA ; get key back (again)
CMP #$40 ; compare with "@"
BCC NoCtrl ; branch if <, not in range
CMP #$80 ; compare with [DEL]+1
BCS NoCtrl ; branch if >=, not in range
; [CTRL] key held and in range, mask it
AND #$1F ; convert to control key
TAX ; copy key to X
NoCtrl
STX RxChar ; save key
RTS
; scan the keyboard to see if a key is waiting. returns scancode in A
; and RxChar, or $00 if no key waiting. this is the main entry point.
; if the keyboard response seems flakey when holding a key for auto
; repeat then increase the timeout, it helps. This was the shortest
; I could reliably get away with with all the test keyboards.
; possible bytes (apart from scancodes) are ...
; $AA Power On Self Test Passed (BAT Completed)
; $EE See Echo Command (Host Commands)
; $FA Acknowledge
; $FE Resend
; $FF Error or Buffer Overflow
; when exiting this routine the clock is pulled low to hold off transmission.
; the X register is assumed to be $00 on exit and that the Z flag reflects
; the byte in A.
ScanAT
; LDX #$0D ; set timeout count (150uS, 1MHz 6502)
LDX #$1A ; set timeout count (150uS, 2MHz 6502)
LDA #$03 ; clock high, data high
STA KPort ; out to clock port (allow to send)
LDA #$02 ; set for clock line bit test
WaitW0
BIT KPort ; test the clock line
BEQ WaitW1 ; go do rest if the clock falls
DEX ; else decrement timeout value
BNE WaitW0 ; go wait some more if time is not up yet
LDA #$01 ; else stop keyboard
STA KPort ; out to port (prevent sending)
TXA ; copy $00 to A
STA RxChar ; save in buffer (no character)
RTS ;
; Rx error, try again
RxWasNOK
LDA #$FE ; resend command
JSR SendAT ; send A to keyboard
; scan the keyboard and wait for a key. returns scancode in A and RxChar.
; this is the secondary entry point.
GetAT
LDA #$03 ; clock high, data high
STA KPort ; out to clock port (allow to send)
LDA #$02 ; set for clock line bit test
WaitW1
JSR GetBit ; get bit from keyboard (start bit)
LDX #$08 ; eight data bits to get
LDY #$01 ; 1's count to one (odd parity)
RecByte
JSR GetBit ; get bit from keyboard (data bit)
BCC NoRx1 ; brabch if bit was zero
INY ; else increment 1's count
NoRx1
ROR RxChar ; bit into receive byte
DEX ; decrement bit count
BNE RecByte ; loop if more data
JSR GetBit ; get bit from keyboard (parity bit)
ROL RxParity ; bit into parity byte b0
JSR GetBit ; get bit from keyboard (stop bit)
LDA #$01 ; drive clock low (hold off more data)
STA KPort ; out to port
TYA ; get computed parity
EOR RxParity ; compare with received parity bit
ROR A ; only interested in bit 0
BCS RxWasNOK ; go try again if not ok
; (rx parity <> computed parity)
LDA RxChar ; else copy scancode
BEQ ClearOF ; branch if was error or buffer overflow
RTS
; gets a bit from the keyboard, bit to send is in Cb
; take care to enter this routine with A = $02
GetBit
BIT KPort ; test the clock line
BNE GetBit ; wait for the clock to fall
LDA KPort ; get data
LSR A ; data bit to Cb
LDA #$02 ; set for clock line bit test
WaitR1
BIT KPort ; test the clock line
BEQ WaitR1 ; wait for the clock to rise
RTS ;
; sets the pointers to the decode table, resets the keyboard and sets the
; lock LEDs. also clears the status bits for the decoder.
ResetAT
LDA #ATtable ; point to ATtable (high addr)
STA KTable+1 ; save pointer high byte
LDA #$00 ; clear bits
STA KeyBits ; clear hold bits
STA LEDBits ; clear LED bits
LDA #$FF ; reset the keyboard
JSR SendAT ; send A to keyboard
LDA #$F6 ; restore default settings
JSR SendAT ; send A to keyboard
JSR KeyLEDs ; set keyboard LEDs
ClearOF
LDA #$F4 ; clear the buffer
BNE SendAT ; send A to keyboard & return (branch always)
; set the keyboard LEDs from LEDBits
KeyLEDs
LDA #$ED ; next byte is LED bits
JSR SendAT ; send A to keyboard
LDA LEDBits ; get LED bits
AND #$07 ; make sure bits 3 to 7 = 0
; SendAT sends the special codes to the keyboard, codes are ..
; $ED set the LEDs according to the next byte I send
; bit 0 = scroll lock
; bit 1 = num lock
; bit 2 = caps lock
; bits 3-7 must be 0, 1 = LED on
; $EE echo, keyboard will respond with $EE
; $F0 set scan code set, upon sending the keyboard will
; respond with ACK ($FA) and then wait for a second
; byte. sending $01 to $03 determines the code set
; used, sending $00 will return the code set currently
; in use.
; $F3 set typematic repeat rate, upon sending the keyboard
; will respond with ACK ($FA) and then wait for a second
; byte. this byte sets the rate.
; $F4 keyboard enable, clears the keyboard buffer and starts
; scanning.
; $F5 keyboard disable, clears the keyboard buffer and stops
; scanning.
; $F6 restore default values upon sending the keyboard will
; respond with ACK ($FA)
; $FE retransmit the last character please upon sending the
; keyboard will respond by resending the last character
; $FF reset, you stupid keyboard
SendAT
STA TxChar ; save A in transmit buffer
Send_AT
LDX #$08 ; eight bits to send
LDY #$01 ; 1's count to one (odd parity)
LDA #$02 ; clock high, data low
STA KPort ; out to clock port (tell keyboard to receive)
SendByte
ROR TxChar ; bit into carry
BCC NotOne ; skip increment if zero
INY ; else increment 1's count
NotOne
JSR SendBit ; send bit to keyboard
DEX ; decrement bit count
BNE SendByte ; loop if not all done
ROR TxChar ; last bit back into TxChar
TYA ; copy parity count
LSR A ; parity bit into carry
LDA #$02 ; set for clock line bit test
JSR SendBit ; send bit to keyboard
SEC ; set stop bit
JSR SendBit ; send bit to keyboard
JSR SendBit ; send bit to keyboard (skips extra clock after bit 10)
; LDX #$04 ; 20uS delay (1Mhz 6502)
LDX #$08 ; 20uS delay (2Mhz 6502)
Wait20
DEX ; decrement count
BNE Wait20 ; loop for a while
LDA #$01 ; drive clock low (handshake)
STA KPort ; out to port
; LDX #$09 ; 44uS delay (1Mhz 6502)
LDX #$12 ; 44uS delay (2Mhz 6502)
Wait44
DEX ; decrement count
BNE Wait44 ; loop for a while
JSR GetAT ; get the response
CMP #$FE ; compare with not ok response
BEQ Send_AT ; if wrong go do it again
RTS ;
; send bit to keyboard, Cb is the bit to send
; take care to enter this routine with A = $02
SendBit
BIT KPort ; test the clock line
BNE SendBit ; wait for the clock to fall
LDA #$01 ; unshifted bit for clock
ROL A ; shift Cb into A
STA KPort ; out to clock port
ROR A ; shift bit back to Cb
LDA #$02 ; set for clock line bit test
WaitT1
BIT KPort ; test the clock line
BEQ WaitT1 ; wait for the clock to rise
ATtable ; the first byte of the table is unused so
RTS ; this RTS can be the first byte
; AT keyboard decoding table
; [Fn] keys are coded $C1 to $CC but not acted on
; Lock keys are ..
; [SCROLL LOCK] $81
; [NUM LOCK] $82
; [CAPS LOCK] $84
; ... and they change flags internal to the decode routine,
; they also toggle the keyboard LEDs
; other non character keys are ..
; [L_SHIFT] $90
; [L_CTRL] $91
; [L_WIN] $92 (toggles bit but otherwise ignored)
; [L_ALT] $93 (toggles bit but otherwise ignored)
; [R_SHIFT] $94
; [R_CTRL] $95
; [R_WIN] $96 (toggles bit but otherwise ignored)
; [R_ALT] $97 (toggles bit but otherwise ignored)
; [WIN_MENU] $98 (ignored)
; AT keyboard decoding table first part.
; this mostly holds unshifted key values
; the second character in the comments field is usually the shifted
; character for that key
;ATtable
; .byte $00 ; first byte replaced by RTS above
.byte $C9 ; [F9]
.byte $00 ;
.byte $C5 ; [F5]
.byte $C3 ; [F3]
.byte $C1 ; [F1]
.byte $C2 ; [F2]
.byte $CC ; [F12]
.byte $00 ;
.byte $CA ; [F10]
.byte $C8 ; [F8]
.byte $C6 ; [F6]
.byte $C4 ; [F4]
.byte $09 ; [TAB]
.byte ("`"+$80) ; ` ¬
.byte $00 ;
.byte $00 ;
.byte $93 ; [L_ALT]
.byte $90 ; [L_SHIFT]
.byte $00 ;
.byte $91 ; [L_CTRL]
.byte ("q"+$80) ; q Q
.byte ("1"+$80) ; 1 !
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte ("z"+$80) ; z Z
.byte ("s"+$80) ; s S
.byte ("a"+$80) ; a A
.byte ("w"+$80) ; w W
.byte ("2"+$80) ; 2 "
.byte $00 ;
.byte $00 ;
.byte ("c"+$80) ; c C
.byte ("x"+$80) ; x X
.byte ("d"+$80) ; d D
.byte ("e"+$80) ; e E
.byte ("4"+$80) ; 4 $
.byte ("3"+$80) ; 3 £
.byte $00 ;
.byte $00 ;
.byte " " ; [SPACE]
.byte ("v"+$80) ; v V
.byte ("f"+$80) ; f F
.byte ("t"+$80) ; t T
.byte ("r"+$80) ; r R
.byte ("5"+$80) ; 5 %
.byte $00 ;
.byte $00 ;
.byte ("n"+$80) ; n N
.byte ("b"+$80) ; b B
.byte ("h"+$80) ; h H
.byte ("g"+$80) ; g G
.byte ("y"+$80) ; y Y
.byte ("6"+$80) ; 6 ^
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte ("m"+$80) ; m M
.byte ("j"+$80) ; j J
.byte ("u"+$80) ; u U
.byte ("7"+$80) ; 7 &
.byte ("8"+$80) ; 8 *
.byte $00 ;
.byte $00 ;
.byte (","+$80) ; , <
.byte ("k"+$80) ; k K
.byte ("i"+$80) ; i I
.byte ("o"+$80) ; o O
.byte ("0"+$80) ; 0 )
.byte ("9"+$80) ; 9 (
.byte $00 ;
.byte $00 ;
.byte ("."+$80) ; . >
.byte ($2F+$80) ; / ?
.byte ("l"+$80) ; l L
.byte (";"+$80) ; ; :
.byte ("p"+$80) ; p P
.byte ("-"+$80) ; - _
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte ($27+$80) ; ' @
.byte $00 ;
.byte ("["+$80) ; [ {
.byte ("="+$80) ; = +
.byte $00 ;
.byte $00 ;
.byte $84 ; [CAPS LOCK]
.byte $94 ; [R_SHIFT]
.byte $0D ; [RETURN]
.byte ("]"+$80) ; ] }
.byte $00 ;
.byte ("#"+$80) ; # ~
.byte $00 ;
.byte $00 ;
; keys with bit 7 set, but not the /, are only affected by num lock
; these are the num lock on values
.byte $00 ;
.byte ("\"+$80) ; \ |
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $08 ; [BACKSPACE]
.byte $00 ;
.byte $00 ;
.byte ("1"+$80) ; 1 keypad
.byte $00 ;
.byte ("4"+$80) ; 4 keypad
.byte ("7"+$80) ; 7 keypad
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte ("0"+$80) ; 0 keypad
.byte ("."+$80) ; . keypad
.byte ("2"+$80) ; 2 keypad
.byte ("5"+$80) ; 5 keypad
.byte ("6"+$80) ; 6 keypad
.byte ("8"+$80) ; 8 keypad
.byte $1B ; [ESC]
.byte $82 ; [NUM LOCK]
.byte $CB ; [F11]
.byte "+" ; + keypad
.byte ("3"+$80) ; 3 keypad
.byte "-" ; - keypad
.byte "*" ; * keypad
.byte ("9"+$80) ; 9 keypad
.byte $81 ; [SCROLL LOCK]
.byte $00 ;
; AT keyboard decoding table second part.
; this mostly holds shifted key values of the first half
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $C7 ; [F7]
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte "¬" ; [SHIFT] `
.byte $00 ;
.byte $00 ;
.byte $97 ; [R_ALT]
.byte $00 ;
.byte $00 ;
.byte $95 ; [R_CTRL]
.byte "Q" ; [SHIFT] q
.byte "!" ; [SHIFT] 1
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte "Z" ; [SHIFT] z
.byte "S" ; [SHIFT] s
.byte "A" ; [SHIFT] a
.byte "W" ; [SHIFT] w
.byte $22 ; [SHIFT] 2
.byte $92 ; [L_WIN]
.byte $00 ;
.byte "C" ; [SHIFT] c
.byte "X" ; [SHIFT] x
.byte "D" ; [SHIFT] d
.byte "E" ; [SHIFT] e
.byte "$" ; [SHIFT] 4
.byte "£" ; [SHIFT] 3
.byte $96 ; [R_WIN]
.byte $00 ;
.byte $00 ;
.byte "V" ; [SHIFT] v
.byte "F" ; [SHIFT] f
.byte "T" ; [SHIFT] t
.byte "R" ; [SHIFT] r
.byte "%" ; [SHIFT] 5
.byte $98 ; [WIN_MENU]
.byte $00 ;
.byte "N" ; [SHIFT] n
.byte "B" ; [SHIFT] b
.byte "H" ; [SHIFT] h
.byte "G" ; [SHIFT] g
.byte "Y" ; [SHIFT] y
.byte "^" ; [SHIFT] 6
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte "M" ; [SHIFT] m
.byte "J" ; [SHIFT] j
.byte "U" ; [SHIFT] u
.byte "&" ; [SHIFT] 7
.byte "*" ; [SHIFT] 8
.byte $00 ;
.byte $00 ;
.byte "<" ; [SHIFT] ,
.byte "K" ; [SHIFT] k
.byte "I" ; [SHIFT] i
.byte "O" ; [SHIFT] o
.byte ")" ; [SHIFT] 0
.byte "(" ; [SHIFT] 9
.byte $00 ;
.byte $00 ;
.byte ">" ; [SHIFT] .
.byte $3F ; / keypad
.byte "L" ; [SHIFT] l
.byte ":" ; [SHIFT] ;
.byte "P" ; [SHIFT] p
.byte "_" ; [SHIFT] -
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte "@" ; [SHIFT] '
.byte $00 ;
.byte "{" ; [SHIFT] [
.byte "+" ; [SHIFT] =
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $0D ; [ENTER] keypad
.byte "}" ; [SHIFT] ]
.byte $00 ;
.byte "~" ; [SHIFT] #
.byte $00 ;
.byte $00 ;
; keys marked "& keypad" are only affected by num lock
; these are the num lock off values
.byte $00 ;
.byte "|" ; [SHIFT] \
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ; [END] & keypad
.byte $00 ;
.byte $00 ; [CURSOR_LT] & keypad
.byte $00 ; [HOME] & keypad
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ; [INSERT]
.byte $7F ; [DELETE] & keypad
.byte $00 ; [CURSOR_DN] & keypad
.byte $00 ;
.byte $00 ; [CURSOR_RT] & keypad
.byte $00 ; [CURSOR_UP] & keypad
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ;
.byte $00 ; [PAGE_DN] & keypad
.byte $00 ;
.byte $00 ;
.byte $00 ; [PAGE_UP] & keypad
; .byte $00 ; these two bytes are never accessed so don't
; .byte $00 ; need to be defined in the table
END
|