CDP 1802 Assembler in Rich Extended Basic on the TI 99/4A Computer
The ELF computer, which first made its appearance in August 1976 in the Popular Electronics magazine, was a very basic experimenter's board based on the RCA CDP1802 CPU, a rather obscure processor primarily used in embedded systems by the likes of the DOD and NASA. It's main advantage was its simplicity of interfacing and ease of programming. Needless to say that the ELF developed a large following as many hobbyists built their own boards from scratch using the published details in the magazine, and several companies sprung up offering upgraded versions of it.
Here's the original article: PopularElecELF.pdf
A modern iteration of it is the so-called Membership Card designed by Lee Hart, which reduces the ELF to the size of an Altoids tin, adds 32K of memory (as compared to the original 256 bytes) and replaces the hexadecimal display with a row of LEDs. It also conveniently incorporates a DB25 connector with the computer signals routed to it for easy interfacing. In fact, it is still being developed and sold here.
The ELF is programmed by entering CDP1802 machine language in binary using the 8 data switches, and as you might imagine this could become extremely tedious for long programs and very error prone, not to mention the horrendous debugging process using just this set up. Incidentally, I did write a basic monitor for the ELF which could help alleviate the difficulty of program development called ELFMON, and it can be found on the attached disk, but it was still clunky at best.
Happily, there was a far better way to go about programming the ELF, using a full-fledged computer to write the program in 1802 assembler, assemble it, and transfer it directly to the ELF using the DB25 connector. And while this could be done using any computer, I decided to do it using the TI 99/4A computer. Essentially it became a retrocomputing project inside another retrocomputer! ?
The way to go about this is to place a byte on the data lines of the ELF's connector (pins 2-9), toggle the ELF's input switch using pin 1 to store the byte in the ELF's memory, then repeat the process for the rest of the instructions. Clearly, the TI's parallel port would be ideal for this, so I created an adapter cable to connect it to the ELF.
The pinout is as follows:
TI PIO ELF DB25
1 -------------------->1
2 -------------------->2
3 -------------------->3
4 -------------------->4
5 --------------------->5
6 -------------------->6
7 -------------------->7
8 -------------------->8
9 -------------------->9
12 ------------------->14
From there, it was just a matter of software. The ELF program can written directly on the TI using any one of the available text editors, with the following format:
<label><opcode><operand><comment>
Each part needs to be separated by a single space. The label is optional and can be up to 6 alpha-numeric characters, the opcode cannot exceed 4 characters, the operand cannot exceed 7 characters, and the comment is optional and of arbitrary length.
When referring to a label in the operand field, the label needs to be preceded by a *.
Register numbers should be entered as a single hex digit from 0 to F.
Numbers should be entered as either 2 or 4 digit hex digits from 0 to F preceded by a >. For example F is entered as >0F. B41 is entered as >0B41.
Finally, the last opcode of the program should be the reserved word END .
If the formatting is wrong, then the assembler output will be wrong as well!
Once the program is typed in, it should be saved in the standard TI DV/80 format.
I wrote a primitive CDP 1802 assembler in Rich Extended Basic (RXB) which is my favorite interfacing language because it has facilities to access hardware at the low-level. The assembler is sloooooooooow, but hey it beats flipping switches! The operation of the assembler is self-explanatory: just follow the prompts. Below is a listing of the program:
//1802 ASSEMBLER FOR ELF MEMBERSHIP CARD
//BY WALID MAALOULI
//JANUARY 2020
//VERSION 0.1
CALL CLEAR
OPTION BASE 0
DIM REFTABLE$(100),HEX$(16),REFADR(100)
RESTORE HexData
FOR I=0 TO 15
READ HEXVALUE$::
HEX$(I)=HEXVALUE$
NEXT I
CRU=2432 !RS232 CRU OF >1300 DIVIDED BY 2
PRINT " CDP 1802 ASSEMBLER"
PRINT " WALID MAALOULI - JAN 2020"
PRINT::PRINT::PRINT::PRINT
//GET SOURCE FILE
ON ERROR InputSource
InputSource:
INPUT "ENTER SOURCE FILE PATH: ":SOURCE$
OPEN #1:SOURCE$
ON ERROR STOP
PRINT::PRINT "1- SEND HEX FILE TO ELF"
PRINT "2- ASSEMBLE FILE"
PRINT::INPUT FCTN
IF FCTN=1 THEN
SendELF
PRINT::PRINT "ENTER DECIMAL START ADDRESS:"
INPUT OFFSET
PRINT::PRINT "SELECT OUTPUT OPTION:"
PRINT "1- LIST TO SCREEN"
PRINT "2- SEND TO PRINTER"
PRINT "3- SAVE TO FILE"
PRINT "4- SEND TO MEMBERSHIP CARD"
PRINT
GetOutputSelect:
INPUT OUTSEL
IF OUTSEL<>1 AND OUTSEL<>2 AND OUTSEL<>3 AND OUTSEL<>4 THEN
GetOutputSelect
IF OUTSEL=2 THEN
OPEN #2:"PIO",OUTPUT
IF OUTSEL=4 THEN
SendELF
IF OUTSEL<>3 THEN
StartAsm
ON ERROR InputSource1
InputSource1:
PRINT
INPUT "ENTER SAVE FILE PATH: ":DEST$
OPEN #2:DEST$,OUTPUT
ON ERROR STOP
GOTO StartAsm
SendELF:
PRINT
PRINT "PREPARE ELF TO RECEIVE DATA:"
PRINT
PRINT "1-CONNECT CABLE"
PRINT "2-LOAD AND CLR SWITCHES DOWN"
PRINT "3-READ SWITCH UP"
PRINT "4-ALL DATA SWITCHES UP"
PRINT
PRINT "PRESS ANY KEY WHEN READY"
CALL KEY("",0,K,S)
IF FCTN=2 THEN
StartAsm
//TRANSFER HEX FILE TO ELF
TransferHex:
IF EOF(1) THEN
CLOSE #1::
PRINT::
PRINT "TRANSFER COMPLETE!"::
STOP
LINPUT #1:LINE$
PRINT LINE$
HVAL$=SEG$(LINE$,6,2)
CALL HEXDEC(HVAL$,DECVAL)
GOSUB SendByte
IF SEG$(LINE$,9,1)="" OR SEG$(LINE$,9,1)=" " THEN
TransferHex
HVAL$=SEG$(LINE$,9,2)
CALL HEXDEC(HVAL$,DECVAL)
GOSUB SendByte
IF SEG$(LINE$,12,1)="" OR SEG$(LINE$,12,1)=" " THEN
TransferHex
HVAL$=SEG$(LINE$,12,2)
CALL HEXDEC(HVAL$,DECVAL)
GOSUB SendByte
GOTO TransferHex
//START OF ASSEMBLY
StartAsm:
LINE=OFFSET
RPOINT=0
PASS=1
PRINT::PRINT "FIRST PASS"::PRINT
//READ LINE FROM FILE
ReadLine:
TEMP$=""
TEMP1$=""
LINPUT #1:LINE$
IF PASS=1 THEN
PRINT SEG$(LINE$,1,19)
IF PASS=2 THEN
SkipLabel
LABEL$=SEG$(LINE$,1,6)
FOR I=1 TO LEN(LABEL$)
IF SEG$(LABEL$,I,1)<>" " THEN
TEMP1$=TEMP1$&SEG$(LABEL$,I,1)
NEXT I
LABEL$=TEMP1$
SkipLabel:
OPCODE$=SEG$(LINE$,8,4)
IF SEG$(OPCODE$,1,1)=">" AND PASS=1 THEN
OPRNUM=0::
GOTO FirstPass
IF SEG$(OPCODE$,1,1)=">" THEN
HEXVAL$=SEG$(OPCODE$,2,2)::
OPRNUM=0::
OPERAND$=""::
GOTO FoundLabel
FOR I=1 TO LEN(OPCODE$)
IF SEG$(OPCODE$,I,1)<>" " THEN
TEMP$=TEMP$&SEG$(OPCODE$,I,1)
NEXT I
OPCODE$=TEMP$
OPERAND$=SEG$(LINE$,13,7)
IF SEG$(OPERAND$,1,1)<>">" THEN
NotNumber
OPERAND$=SEG$(OPERAND$,2,LEN(OPERAND$)-1)
TEMP$=""
FOR I=1 TO LEN(OPERAND$)
IF SEG$(OPERAND$,I,1)<>" " THEN
TEMP$=TEMP$&SEG$(OPERAND$,I,1)
NEXT I
OPERAND$=TEMP$
NotNumber:
IF OPCODE$="END" AND PASS=2 THEN
PRINT::
PRINT "ASSEMBLY COMPLETE!"::
CLOSE#1::
IF OUTSEL=3 THEN
CLOSE #2::
STOP
ELSE
STOP
IF OPCODE$="END" THEN
RESTORE #1::
PASS=2::
LINE=OFFSET::
PRINT::
PRINT "SECOND PASS"::
PRINT::
GOTO ReadLine
//ASSEMBLE LINE
IF PASS=2 THEN
SearchData
IF RPOINT=49 THEN
PRINT "REFERENCE TABLE FULL!"::
STOP
IF LABEL$<>"" THEN
REFTABLE$(RPOINT)=LABEL$::
REFADR(RPOINT)=LINE::
RPOINT=RPOINT+1
SearchData:
IF SEG$(OPCODE$,1,1)="A" THEN
RESTORE AData
IF SEG$(OPCODE$,1,1)="B" THEN
RESTORE BData
IF SEG$(OPCODE$,1,1)="D" THEN
RESTORE DData
IF SEG$(OPCODE$,1,1)="G" THEN
RESTORE GData
IF SEG$(OPCODE$,1,1)="I" THEN
RESTORE IData
IF SEG$(OPCODE$,1,1)="L" THEN
RESTORE LData
IF SEG$(OPCODE$,1,1)="M" THEN
RESTORE MData
IF SEG$(OPCODE$,1,1)="N" THEN
RESTORE NData
IF SEG$(OPCODE$,1,1)="O" THEN
RESTORE OData
IF SEG$(OPCODE$,1,1)="P" THEN
RESTORE PData
IF SEG$(OPCODE$,1,1)="R" THEN
RESTORE RData
IF SEG$(OPCODE$,1,1)="S" THEN
RESTORE SData
IF SEG$(OPCODE$,1,1)="X" THEN
RESTORE XData
ReadData:
READ OPC$,HEXVAL$,OPRNUM
IF OPC$="XXX" THEN
PRINT::
PRINT "INCORRECT OPCODE IN LINE ";LINE::
STOP
IF OPC$<>OPCODE$ THEN
ReadData
IF (OPRNUM>0 OR OPRNUM=-1) AND OPERAND$=" " THEN
PRINT::
PRINT "MISSING OPERAND IN LINE ";LINE::
STOP
IF OPCODE$="INP" THEN
OPERAND$=HEX$(VAL(OPERAND$)+8)
IF OPRNUM=-1 THEN
HEXVAL$=SEG$(HEXVAL$,1,1)&SEG$(OPERAND$,1,1)::
OPERAND$=""
IF SEG$(OPERAND$,1,1)<>"*" OR PASS=1 THEN
FoundLabel
OPERAND$=SEG$(OPERAND$,2,LEN(OPERAND$)-1)
TEMP$=""
FOR I=1 TO LEN(OPERAND$)
IF SEG$(OPERAND$,I,1)<>" " THEN
TEMP$=TEMP$&SEG$(OPERAND$,I,1)
NEXT I
OPERAND$=TEMP$
FOR I=0 TO 49
IF REFTABLE$(I)<>OPERAND$ THEN
NextEntry
CALL HEX(REFADR(I),OPERAND$)
IF OPRNUM=1 THEN
OPERAND$=SEG$(OPERAND$,3,2)
GOTO FoundLabel
NextEntry:
NEXT I
PRINT "LABEL NOT FOUND IN LINE ";LINE::
STOP
FoundLabel:
IF PASS=1 THEN
FirstPass
CALL HEX(LINE,HEXLINE$)
ASMLINE$=HEXLINE$&" "&HEXVAL$&" "&OPERAND$
PRINT ASMLINE$
IF OUTSEL=4 THEN
ElfSend
IF OUTSEL=2 OR OUTSEL=3 THEN
PRINT #2:ASMLINE$
GOTO FirstPass
ElfSend:
CALL HEX(HEXVAL$,DECVAL)
GOSUB SendByte
IF OPRNUM<=0 THEN
FirstPass
IF LEN(OPERAND$)>2 THEN
OPR1$=SEG$(OPERAND$,1,2)::
CALL HEX(OPR1$,DECVAL)::
GOSUB SendByte::
OPERAND$=SEG$(OPERAND$,3,2)
CALL HEXDEC(OPERAND$,DECVAL)
GOSUB SendByte
FirstPass:
IF OPRNUM=-1 THEN
OPRNUM=0
LINE=LINE+OPRNUM+1
GOTO ReadLine
//OPCODE DATABASE
AData:
DATA ADC,74,0
DATA ADD,F4,0
DATA ADI,FC,1
DATA AND,F2,0
DATA ANI,FA,1
DATA XXX,XX,0
BData:
DATA B1,34,1
DATA B2,35,1
DATA B3,36,1
DATA B4,37,1
DATA BDF,33,1
DATA BN1,3C,1
DATA BN2,3D,1
DATA BN3,3E,1
DATA BN4,3F,1
DATA BNF,3B,1
DATA BNQ,39,1
DATA BNZ,3A,1
DATA BQ,31,1
DATA BR,30,1
DATA BZ,32,1
DATA XXX,XX,0
DData:
DATA DEC,20,-1
DATA DIS,71,0
DATA XXX,XX,0
GData:
DATA GHI,90,-1
DATA GLO,80,-1
DATA XXX,XX,0
IData:
DATA IDL,00,0
DATA INC,10,-1
DATA INP,60,-1
DATA IRX,60,0
DATA XXX,XX,0
LData:
DATA LBDF,C3,2
DATA LBNF,CB,2
DATA LBNQ,C9,2
DATA LBNZ,CA,2
DATA LBQ,C1,2
DATA LBR,C0,2
DATA LBZ,C2,2
DATA LDA,40,-1
DATA LDI,F8,1
DATA LDN,00,-1
DATA LDX,F0,0
DATA LDXA,72,0
DATA LSDF,CF,0
DATA LSIE,CC,0
DATA LSKP,C8,0
DATA LSNF,C7,0
DATA LSNQ,C5,0
DATA LSNZ,C6,0
DATA LSQ,CD,0
DATA LSZ,CE,0
DATA XXX,XX,0
MData:
DATA MARK,79,0
DATA XXX,XX,0
NData:
DATA NOP,C4,0
DATA XXX,XX,0
OData:
DATA OR,F1,0
DATA ORI,F9,1
DATA OUT,60,-1
DATA XXX,XX,0
PData:
DATA PHI,B0,-1
DATA PLO,A0,-1
DATA XXX,XX,0
RData:
DATA REQ,7A,0
DATA RET,70,0
DATA XXX,XX,0
SData:
DATA SAV,78,0
DATA SD,F5,0
DATA SDB,75,0
DATA SDBI,7D,1
DATA SDI,FD,1
DATA SEP,D0,-1
DATA SEQ,7B,0
DATA SEX,E0,-1
DATA SHL,FE,0
DATA SHLC,7E,0
DATA SHR,F6,0
DATA SHRC,76,0
DATA SKP,38,0
DATA SM,F7,0
DATA SMB,77,0
DATA SMBI,7F,1
DATA SMI,FF,1
DATA STR,50,-1
DATA STXD,73,0
DATA XXX,XX,0
XData:
DATA XOR,F3,0
DATA XRI,FB,1
DATA XXX,XX,0
//Hexadecimal numbers
HexData:
DATA 0,1,2,3,4,5,6,7,8,9,A,B,C,D,E,F
//SEND DATA TO ELF ROUTINE
SendByte:
CALL IO(3,1,CRU,1) !TURN ON RS232 CARD
CALL IO(3,1,CRU+7,1) !TURN ON RS232 LED
CALL IO(3,1,CRU+2,1) !SET HANDSHAKE OUT LINE TO HIGH
CALL IO(3,1,CRU+1,0) !SET PIO PORT TO OUTPUT
CALL LOAD(20480,DECVAL) !PLACE BYTE ON PIO PORT
CALL IO(3,1,CRU+2,0) !CYCLE THE HANDSHAKE OUT LINE
CALL IO(3,1,CRU+7,0)
CALL IO(3,1,CRU+2,1) !TURN RS232 LED OFF
CALL IO(3,1,CRU,0) !TURN OFF RS232
RETURN
The attached disk contains the assembler called ELFASM as well as 3 programs for the ELF. I use the extension _S to indicate that this is the text source file which contains the assembly language code as well as the program instructions, and the _HEX extension to indicate that this is the assembled hexadecimal version of the program suitable for downloading to the ELF. Feel free to use your own extensions as you see fit.
- ELFMON is the ELF monitor program I mentioned earlier
- CYLON is a small demo of the Cylon Eyes effect on the ELF's LED's (a.k.a Battlestar Galactica)
- HILO is a small game where you have to guess a random computer picked number with as few guesses as possible
ELFASM can assemble a source file and then output it to the screen, to a file in HEX format, to the parallel printer, or transfer it to the ELF directly using the adapter cable. You can also load a previously assembled HEX file and transfer it to the ELF without the need of assembling it.
And here's a video of the entire project. As is usual with my hobby projects, it is highly unlikely anyone else will find this useful outside of myself, but hey, it was a great learning experience
-
4
8 Comments
Recommended Comments