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
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:
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