Thursday 9 November 2023


Hellorld!

I follow Usagi Electric on YouTube; David set a challenge to create a text string - namely "Hellorld!". As a result of a programming error on his part on a restored Centurion computer, what should have been a "Helloworld!" test message had a few errors.

Following on from this, David has encouraged his followers to recreate this 'error message' on their own computers. Two particular criteria:

1. Must be in assembly or hexadecimal

2. And preferably on a unique machine.

My solution to this challenge involves using a 1-bit computer based on the Motorola MC14500 Industrial Control Unit to generate 7-bit ASCII characters, which connects to my 6502 based computer via a 6821 Peripheral Interface Adapter (PIA). The MC14500 computer also triggers an Interrupt Request on the 6821, to which the 6502 responds with an Interrupt Service Routine (ISR). The ISR reads the data from the 6821, and saves it to the 6551 Asynchronous Communications Interface Adapter, with the ASCII character values sent to a serial display terminal.

The images below show the MC14500 computer (breadboard based), connected to the 6502 computer.

Complete set-up MC14500 & 6502 based computers

The MC14500 1-bit computer

The 6502 based computer

MC14500 Output Register Display - 7-bit ASCII code

The screen shot shows the "Hellorld!" string, repeatedly printed.

'Hellorld!' Serial terminal - displaying ASCII characters from the 6502 computer.
The 6502 hex main code & interrupt service routine code shown above.

MC14500 - 6502 description:

An EEPROM contains the MC14500 instructions - to read an Input Register bit (value = 0), then in turn write out the value or its complement to the Output Register bits 0 to 6 (7 bits in total) to create the ASCII character codes. Bit-7 of the Output Register is written to, to create a single pulse.

Output Register bits 0 to 6 are patched to Port B on the 6821 PIA. The 6821 bit-7 is tied Low as it isn't used and ensures the ASCII code has a leading '0'. MC14500 Output Register bit-7 is patched to the 6821 Control Port (CB1).

The MC14500 cycles through the stored EEPROM instructions, and on reaching the last instruction resets the clock counter, and the routine starts over. The 'speed' of character generation and terminal output can be changed using the MC14500 clock (my circuit runs from 1Hz to 2kHz). It's even possible to use a manual clock - though this takes ages to step through the whole routine.

When the MC14500 writes a 'pulse' to the 6821 PIA CB1, an Interrupt Request from the 6821 triggers an ISR on the 6502.

The 6502 reads the data on the 6821 PIA, writes out the result to a 6551 Asynchronous Communications Interface Adapter (ACIA) to display the characters on a serial terminal at 9600bps (running on my laptop).

Here is a short video showing the MC14500 generation of the ASCII characters, displayed on the serial terminal.


Below I've listed the MC14500 code - fully documented, followed by the 6502 assembler listing. The MC14500 code can be shortened where only single bit changes are made between each change of character. I programmed as below to ensure I was creating the correct values for EEPROM programming. The 6502 code is loaded into RAM, and started from within my 6502 monitor program. The main 6502 code just loops, with the ISR doing the reading/writing of data.

MC14500 listing:

MC14500 coding notes:

8-bit values for the characters Hellorld!

H = 01001000
e = 01100101
l = 01101100
l = 01101100
o = 01101111
r = 01110010
l = 01101100
d = 01100100
! = 00100001
CR= 00001101
LF= 00001010

The binary or hex values are written to/stored in EEPROM

Instruction Code CS Address Binary Hex Note
NOPO 0000 1 000 00001000 08 Flag O 'H'
ORC 0110 1 000 01101000 68 RR goes High, also Data goes High
IEN 1010 1 000 10101000 A8 Input enable
OEN 1011 1 000 10111000 B8 Output enable

Character 'H' = 01001000

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STO 1000 1 010 10001010 8A Store  RR > OR bit-2 [0]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STO 1000 1 101 10001101 8D Store  RR > OR bit-5 [0]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store  RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

Character 'e' = 01100101

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STOC 1001 1 000 10011000 98 Store /RR > OR bit-0 [1]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STO 1000 1 011 10001011 8B Store  RR > OR bit-3 [0]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

Character 'l' = 01101100

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

Character 'l' = 01101100

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

o = 01101111

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STOC 1001 1 000 10011000 98 Store /RR > OR bit-0 [1]
STOC 1001 1 001 10011001 99 Store /RR > OR bit-1 [1]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

r = 01110010

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STOC 1001 1 001 10011001 99 Store /RR > OR bit-1 [1]
STO 1000 1 010 10001010 8A Store  RR > OR bit-2 [0]
STO 1000 1 011 10001011 8B Store  RR > OR bit-3 [0]
STOC 1001 1 100 10011100 9C Store /RR > OR bit-4 [1]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

l = 01101100

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

d = 01100100

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STO 1000 1 011 10001011 8B Store  RR > OR bit-3 [0]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STOC 1001 1 110 10011110 9E Store /RR > OR bit-6 [1]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

! = 00100001

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STOC 1001 1 000 10011000 98 Store /RR > OR bit-0 [1]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STO 1000 1 010 10001010 8A Store  RR > OR bit-2 [0]
STO 1000 1 011 10001011 8B Store  RR > OR bit-3 [0]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STOC 1001 1 101 10011101 9D Store /RR > OR bit-5 [1]
STO 1000 1 110 10001110 8E Store  RR > OR bit-6 [0]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

C/R = 00001101

LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STOC 1001 1 000 10011000 98 Store /RR > OR bit-0 [1]
STO 1000 1 001 10001001 89 Store  RR > OR bit-1 [0]
STOC 1001 1 010 10011010 9A Store /RR > OR bit-2 [1]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STO 1000 1 101 10001101 8D Store  RR > OR bit-5 [0]
STO 1000 1 110 10001110 8E Store  RR > OR bit-6 [0]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]

L/F = 00001010
LD 0001 1 001 00011001 19 Load value on IR bit-1 > RR [0]
STO 1000 1 000 10001000 88 Store  RR > OR bit-0 [0]
STOC 1001 1 001 10011001 99 Store /RR > OR bit-1 [1]
STO 1000 1 010 10001010 8A Store  RR > OR bit-2 [0]
STOC 1001 1 011 10011011 9B Store /RR > OR bit-3 [1]
STO 1000 1 100 10001100 8C Store  RR > OR bit-4 [0]
STO 1000 1 101 10001101 8D Store  RR > OR bit-5 [0]
STO 1000 1 110 10001110 8E Store  RR > OR bit-6 [0]
STOC 1001 1 111 10011111 9F Store /RR > OR bit-7 [0>1] {strobe pulse}
STO 1000 1 111 10001111 8F Store  RR > OR bit-7 [1>0]


JMP 1100 1 000 11001000 C8 JMP FLAG goes HIGH > trigger a restart

========================================================================

6502 Assembler Listing
; 6502 assembler to configure 6821 PIA, reading input data on an IRQ.
; Send to serial o/p. Run the routine code from within the machine monitor.
; When /IRQ occurs, 6502 reads from the 6821 and writes to the 6551 ACIA.
; The ACIA is already setup as part of the monitor code.
;
; IRQ Vector address $7FFD stored in EEPROM. $7FFD holds a JMP instruction
; to a configurable address (default $7600)
; The ISR start address $7600 is stored in $7FFE ($00) & $7FFF ($76).
; /IRQ Interrupt Service Routine, resides in SRAM - start address $7600.
; Top of SRAM = $7FFF

.target "6502"         ; running a 6502 processor
.code
; Hardware I/O Locations
; ACIA 0
ACIA0_Data = $D000 
ACIA0_Status = $D001 
; PIA-Port-B
PIAB_Data = $D102
PIAB_Control = $D103

; Main code start address
* = $0300

Start
; Configure Port B all Inputs, CB1 /IRQ active, Low to High triggers /IRQ.
; PIA-Port-B
; PIAB_Data = $D102
; PIAB_Control = $D103
;
InitPIAB
LDA #$00 ; Configure the Control Register
; Set bit 0 with '0' this keeps IRQ disabled during setup.
; Set bit 2 with '0' to access DDR on $D102
STA PIAB_Control ; Store in $D103 to access Data Direction Reg. B
;
LDA #$00 ; Configure Data Direction Register - Set bits 0-7 to determine which pins/bits are
                                        ; inputs or outputs
STA PIAB_Data ; Set all bits as inputs ['0' for Input, '1' for Output] store in $D102
;
LDA #$07 ; Configure the Control Register
; Set bit 0 with '1' - enables IRQB set by transition on CB1
; Set bit 1 with '1' - IRQB set by Low to High transition on CB1
; Set bit 2 with '1' - to select Data Register B on $D102 (#$07 = binary 00000111)
; Set bits 3 to 5 with '0' - bit 5 = 0 configures CB2 (not required here)
; (CB1 pin needs connecting to pull-down resistor to avoid false flag on bit-6)
STA PIAB_Control ; store in $d103
; When /IRQB is triggered, connected to /IRQ on 6502, the 6502 triggers 
                                        ; an Interrupt Service Routine
; To read Port B data inputs, PB bits 0 - 7, LDA $D102 // PIAB_Data.
; Reading this clears the IRQ set condition on 6821 IRQB
CLI ; Clear / enable interrupts

Main_Loop
NOP
JMP Main_Loop

; Interrupt Service Routine start address - as set by monitor code.
* = $7600
; The only interrupt request comes from 6821 PIA
; Read Port B - 6821
PORT_READ
LDA PIAB_Data                 ; $D102 - also clears Interrupt Status Flag in Control Register
JSR ACIA0_SendByte                 ; print it
RTI                 ; Return from Interrupt

ACIA0_SendByte                         ; Sends a byte out ACIA 0
                                                        ; Byte must be in A
                                                        ; Returns with registers intact
PHA ; Put A on the stack
A0SB_Loop LDA ACIA0_Status ; Get ACIA Status.
AND #$10 ; Is TX buffer full?
BEQ A0SB_Loop ; Yes. Go ask again.
PLA ; No. Restore A
STA ACIA0_Data ; And send the byte out.
RTS