;I believe this is disassembly by Dave Roberts, provided to me by Dwight Elvey - Herb Johnson Nov 2024 TITLE "Tom's assembler for the Intel 4004 CPU running on a SIM4-01 card." ; Register equates. ; ================= 0000 R0 EQU 0 0001 R1 EQU 1 0002 R2 EQU 2 0003 R3 EQU 3 0004 R4 EQU 4 0005 R5 EQU 5 0006 R6 EQU 6 0007 R7 EQU 7 0008 R8 EQU 8 0009 R9 EQU 9 000a R10 EQU 10 000b R11 EQU 11 000c R12 EQU 12 000d R13 EQU 13 000e R14 EQU 14 000f R15 EQU 15 0000 P0 EQU 0 0002 P1 EQU 2 0004 P2 EQU 4 0006 P3 EQU 6 0008 P4 EQU 8 000a P5 EQU 10 000c P6 EQU 12 000e P7 EQU 14 000a CN EQU $A ; Jump if Carry flag = 0. 0002 CZ EQU $2 ; Jump if Carry flag = 1. 000c AN EQU $C ; Jump if A <> 0. 0080 TOPBIT EQU $80 00a0 ASPACE EQU TOPBIT + ' ' ; ASCII . 00b0 AZERO EQU TOPBIT + '0' ; ASCII ZERO. 0087 ABELL EQU TOPBIT + 7 ; ASCII BELL. 008d ACR EQU TOPBIT + $D ; ASCII . 008a ALF EQU TOPBIT + $A ; ASCII . 0080 ANUL EQU TOPBIT + 0 ; ASCII . 0000 ORG $000 ; Start of A0740 ROM. 0000 50 5f START: JMS PAG0 ; Set RAM/ROM page and address and a few other parameters. 0002 d1 LDM 1 ; A=1. 0003 e1 WMP ; RAM PORT[0] = A = 1. TTY OUTPUT LINE = 1 (idle). 0004 41 00 JUN L260 ; Get this show on the road... 0006 Z47: ; Clear registers R4, R5, R6 and R7 to binary 0 in preparation for subroutines BCD and D10 (and others?). ; ; Inputs: None. ; Outputs: Registers R4, R5, R6 and R7 are all set to 0. A is set to 4 on exit. ; Uses: Nothing else. ; 0006 24 00 FIM P2,0 ; R4=0, R5=0. 0008 26 00 FIM P3,0 ; R6=0, R7=0. 000a c4 BBL 4 ; Return to caller and set A=4. 000b BCD: ; This subroutine converts the 12-bit binary number in registers R1, R2 and R3 into decimal, and ; prints the four digits to the console with leading zero suppression. ; ; Inputs: R1, R2 and R3 hold the 12-bit binary number to output. ; R1 being the most significant four bits and R3 being the least significant four bits. ; Outputs: None. ; Uses: R4, R5, R6 and R7. ; 000b 50 06 JMS Z47 ; Initialise registers R4..R7 to zero (0) in preparation. 000d ba XCH R10 ; R10 = A = return from Z47 subroutine called above = 4. R10 is used as a loop counter. 4 to 15 (12 iterations - number of bits). ; 000e BCD1: ; ; Form a 12-bit shift register from the 4-bit R1, R2 and R3 input registers. ; Shift garbage (?) from the existing CY flag into the lsb (R3). ; Shift the msb of R1 into the CY flag. ; ; CY <- R1 <- R2 <- R3 <- CY(?) ; 000e a3 LD R3 ; A = R3. 000f f5 RAL ; CY <- A <- CY(?). 0010 b3 XCH R3 ; R3 = A. 0011 a2 LD R2 ; A = R2. 0012 f5 RAL ; CY <- A <- CY (from R3 msb). 0013 b2 XCH R2 ; R2 = A. 0014 a1 LD R1 ; A = R1. 0015 f5 RAL ; CY <- A <- CY (from R2 msb). 0016 b1 XCH R1 ; R1 = A. ; ; Transfer the Carry Flag from above into register quad R4..R7 to convert the staring 12-bit binary number ; (from R1..R3) into R4..R7. 12 bit binary would result in an unsigned value encoded in the range 0000 to 4095. ; 0017 a7 LD R7 ; A = R7. R7 is a temporary variable initialised to 0 by subroutine Z47. 0018 f5 RAL ; CY <- A <- CY (from R1 msb). (CY would, therefore, be 0 when the RAL has completed). 0019 fb DAA ; Decimal Adjust Accumulator. Ensure A is in the range 0-9. If not, add 6 and set the CY flag. 001a b7 XCH R7 ; R7 = A (result of DAA back into R7). ; 001b a6 LD R6 ; Same as above for R6... 001c f5 RAL 001d fb DAA 001e b6 XCH R6 ; 001f a5 LD R5 ; Same as above for R5... 0020 f5 RAL 0021 fb DAA 0022 b5 XCH R5 ; 0023 a4 LD R4 ; A = R4. R4 is a temporary variable initialised to 0 by subroutine Z47. 0024 f5 RAL ; CY <- A <- CY (from R5 msb). (CY would, therefore, be 0 when the RAL has completed). 0025 b4 XCH R4 ; R4 = A (result of RAL back into R4). Note, no DAA instruction here - so the assumption is that the value doesn't ; ; exceed 9. A 12-bit binary number maxes out at 4095; so this logic appears to be correct. ; 0026 7a 0e ISZ R10,BCD1 ; Iterate R10 times (a total of 12 bits). ; ***************************************** ; *** *** ; *** Fall through to subroutine PDN. *** ; *** *** ; ***************************************** 0028 PDN: ; This subroutine will print (with leading zero suppression) the four-digit decimal number ; contained within registers R4 through R7. @@@ Is this a four-digit decimal or BCD number I ask myself? It looks like BCD to me... ; ; Inputs: R4, R5, R6 and R7. ; Outputs: None. ; Uses: R10. @@@ ; 0028 df LDM 15 ; A = 15. Flag value for leading zero suppression. 0029 ba XCH R10 ; R10 = A. ; 002a b4 XCH R4 ; A = R4. Most significant digit of the number. 002b 50 35 JMS DIGIT ; Output this digit. ; 002d b5 XCH R5 ; A = R5. Next digit. 002e 50 35 JMS DIGIT ; Output this digit. ; 0030 b6 XCH R6 ; A = R6. Next digit. 0031 50 35 JMS DIGIT ; Output this digit. ; 0033 6a INC R10 ; Force the least significant digit to be printed if the previous 3 have been suppressed. ; 0034 b7 XCH R7 ; A = R7. Least significant digit of the number. ; ******************************************* ; *** *** ; *** Fall through to subroutine DIGIT. *** ; *** *** ; ******************************************* 0035 DIGIT: ; This subroutine @@@ ; ; Inputs: A, R10. ; Outputs: None. ; Uses: R14, R15. @@@ ; 0035 2e bf FIM P7,AZERO+15 ; Initially, R14 and R15 contain @@@ ; 0037 f8 DAC ; Decrement accumulator - updating CY. If the value in A is 0, then it will be decremented to 15. 0038 f2 IAC ; Increment accumulator - updating CY. Set the CY flag if the value in A increments from 15 to 0. ; 0039 bf XCH R15 ; R15 = A. Form the ASCII character for '0' to '9' within R14 and R15. ; ; A = the numeric value 15. ; 003a 7a 50 ISZ R10,PUN ; Increment R10. If the initial value was 15 (potential zero suppression), ; ; the value is incremented to 0 and the flow of instructions continues with the next instruction. ; ; Otherwise, a jump is made to output the character just formed in R14 and R15. ; ; Get here if there is a potential for zero suppression. ; 003c 1a 50 JCN CN,PUN ; Jump if CY flag not set to output the character just formed in R14 and R15. Note that R10 will have been incremented ; ; from 15 to 0 and no further zero blanking will take place for subsequent calls of this subroutine (for more digits). ; 003e ba XCH R10 ; R10 = A. Reset R10 back to 15 (override the result of the ISZ instruction above) as we are going to perform ; ; a leading zero suppression. ; ******************************************* ; *** *** ; *** Fall through to subroutine SPACE. *** ; *** *** ; ******************************************* 003f SPACE: ; This subroutine outputs one ASCII character to the console. ; ; Inputs: None. ; Outputs: None. ; Uses: R14 and R15 are set to an ASCII character. @@@ ; 003f 2e a0 FIM P7,ASPACE ; R14 and R15 hold the 8-bit ASCII character to output ('1' + 7 bits). 0041 40 50 JUN PUN ; Output the ASCII character in R14 and R15 to the console. ffff MSG EQU $-1 ; Subroutine MSG outputs the character in R14 and R15 to the console followed by an ASCII BELL. ; ; !!! This construct assembles but doesn't work as intended !!! ; 0043 46 DB $46 ; !!!BODGE!!! PUN($50) followed by $46 has the effect of 'JMS 46(16)' = 'JMS 70(10)' (i.e. the same as JMS IPR). ; ; The 'JMS IPR' above would return back to here for subsequent processing. ; 0044 2e 87 FIM P7,ABELL ; R14/R15 = ASCII BELL. ; ***************************************** ; *** *** ; *** Fall through to subroutine IPR. *** ; *** *** ; ***************************************** 0046 IPR: ; This subroutine does the same as PUN (below) with the that if register R11 is EVEN (echo mode on input), a 15 ms delay occurs to allow the ; teletype printer to settle. ; 0046 ab LD R11 ; A = R11. Input mode. 0047 f6 RAR ; CY- > A -> CY. Look at LSB of R11. 0048 12 50 JCN CZ,PUN ; Jump if the Carry Flag is SET (no additional delay required). ; ; Carry flag must be reset then. ; 004a 2c 39 FIM P6,57 ; R12=3 R13=9. Setup delay parameters. 004c 50 a1 JMS DELAY ; Perform the delay... 004e 50 a1 JMS DELAY ; Perform another delay. Note that R12/R13 would have been set to zero by the first call. ; ***************************************** ; *** *** ; *** Fall through to subroutine PUN. *** ; *** *** ; ***************************************** 0050 PUN: ; This subroutine prints or punches the character in registers R14/R15 on the teletype. Registers R11 through R15 are cleared to 0 on return. ; 0050 d5 LDM 5 ; A = 5. 0051 bb XCH R11 ; R11 = A = 5. Loop counter. 11 iterations of the loop (from 5 to 15). 0052 50 5f JMS PAG0 ; Set RAM/ROM address 0 etc. A=0, CY=0, R12/R13=0. A=0 on return. ; ; The first time around the loop the carry flag will be 0. This is the serial start bit (1 to 0 transition). 0054 PUN1: 0054 f7 TCC ; A = CY. CY = 0. Get carry flag value into the LSB of A. 0055 e1 WMP ; Write to RAM PORT 0. Bit 0 of A is the TTY OUT signal. perform a serial output. 0056 50 a1 JMS DELAY ; R12/R13 are 0 (from the call of PAG0) so this should result in the maximum bit delay period. 0058 fa STC ; Set the Carry Flag. Shift '1' bits into the 8 bit shift register comprising R14 and R15. These are the effective STOP bits. 0059 51 00 JMS SHIFT ; CY -> R14 -> R15 -> CY. Get the first/next bit from the serial output shift register comprising R14/R15. 005b 7b 54 ISZ R11,PUN1 ; Transmit loop for the 1 start bit + 8 data bits plus 2 stop bits = 1 + 8 + 2 = 11 (the same as the value loaded into R11 above. Tada! 005d 2e 80 FIM P7,ANUL ; R14=8 R15=0. Preload R14 and R15 with an ASCII NUL (for the RETN subroutine if required) Forward thinking! ; ****************************************** ; *** *** ; *** Fall through to subroutine PAG0. *** ; *** *** ; ****************************************** 005f PAG0: ; This subroutine initialises the RAM address (stored in R12 and R13) to 0. ; 005f 2c 00 FIM P6,0 ; R12=0. R13=0. Initialise the RAM address (for use with the SRC instruction) to 0. ; ****************************************** ; *** *** ; *** Fall through to subroutine PAG1. *** ; *** *** ; ****************************************** 0061 PAG1: ; This subroutine initialises the RAM address to whatever is stored in R12/R13 upon entry. ; 0061 f0 TXX3: CLB ; A = 0. CY = 0. 0062 fd DCL ; Select page 0 (defined by value stored in the least significant 3 bits of the accumulator). 0063 2d SRC P6 ; Set address defined by registers R12 and R13. 0064 c0 TXX4: BBL 0 ; Return to caller and set A=0. 0065 f1 f5 f1 f5 DB $F1, $F5, $F1, $F5, $1C, $5F ; !!!BODGE!!! 0069 1c 5f 006b RETN: ; This subroutine outputs a carriage return, a NULL and a linefeed. It may ONLY be called from the main routine. ; 006b 2e 8d FIM P7,ACR ; ASCII Carriage Return. 006d 50 46 JMS IPR ; Output it. R14/R15 (P7) are returned pre-loaded with an ASCII NUL. 006f 50 50 JMS PUN ; Output the ASCII NUL that has been automagically returned... 0071 2e 8a FIM P7,ALF ; ASCII Linefeed. 0073 40 50 JUN PUN ; Output it and complete the subroutine call. 0075 TTI: ; This subroutine inputs one 7-bit character from the teletype paper reader or keyboard (the reader control is enabled), and is ; otherwise exactly the same as subroutine KEY that follows. ; 0075 2c 40 FIM P6,64 ; R12=4 R13=0. RAM address for reader control port/bit. 0077 10 DB $10 ; Jump never opcode! ; ***************************************** ; *** *** ; *** Fall through to subroutine KEY. *** ; *** *** ; ***************************************** 0078 KEY: ; This subroutine inputs one 7-bit character from the teletype keyboard, and return it, left-justified, in registers R14 and R15. ; Registers R12 and R13 are cleared to zero (0). The least significant bit of register R11 determines whether the character is echoed back or not (0 = yes, 1 = no). ; The carry flag is set if the character typed is printable. ; ; What this all means is as follows: ; ; R14/R15 holds the 7-bit ASCII character shifted in the R14/R15 register pair left by 1 bit. So an ASCII 'A' ($41 = %0100_0001) becomes $82 = %1000_0010. ; The least significant bit of R15 is '0'. ; The Carry Flag is SET ('1') if the character is printable. This is defined to be between an ASCII ($20) and an ASCII ($5F). ; For all other characters (including the lower case letters) the Carry Flag is CLEAR ('0'). ; 0078 2c 00 FIM P6,0 ; R12=0 R13=0. RAM address for TTY OUT port. ; ; The above construct behaves as follows: ; ; If entered at label TTI: R12 and R13 are setup and then a 'JMP never,44' is processed - eating up the $10 and the first byte of the 'FIM P6,0'. ; The second byte of the 'FIM P6,0' instruction ($00) is then taken as a single 'NOP' instruction. ; ; If entered at label KEY: then the code that is executed is as written. ; ; Code common to both TTI: and KEY: follows... ; 007a 50 61 JMS PAG1 ; Set RAM address based upon the contents of R12 and R13 ($40 or $00 for TTI: or KEY: respectively). Returns with A=0. 007c bc XCH R12 ; R12 = A = 0. Set the R12/R13 address to 0 for the next SRC instruction that follows the WMP. 007d d1 LDM 1 ; A = 1. 007e e1 WMP ; RAM PORT[ address ] = A = 1. If through TTI this sets the READER CONTROL PORT to ACTIVE ('1'), otherwise it sets TTY OUT to '1'. 007f 2d SRC P6 ; Set RAM address to 0. 0080 2e 80 FIM P7,$80 ; R14=8, R15=0. Initialisation of the serial input shift register (1000_0000). 0082 ea KEY1: RDR ; A = ROM PORT[ address = 0 ]. Sample TTY IN on bit 0. 0083 f6 RAR ; CY -> A -> CY. Shift TTY in line into the Carry Flag. 0084 1a 82 JCN CN,KEY1 ; Jump if Carry Flag = 0. Wait for the TTY IN line to be HIGH (logical '1'). ; ; The Carry Flag must now be '1' (indicating the TTY IN line is also a '1'). ; 0086 2c 7f FIM P6,$7F ; R12=7 R13=15. Bit time delay parameter. 0088 f0 CLB ; A=0. CY=0. 0089 2d SRC P6 ; Set address to R12/R13 ($7F). 008a e1 WMP ; RAM PORT[ address ] = A = 0. This sets the READER CONTROL PORT to INACTIVE ('0') irrespective of the entry point. 008b 50 a1 KEY2: JMS DELAY ; Delay half a bit time (as defined by register pair R12/R13). Initially half a bit ($7F) on the first entry and then a full bit ($00) on subsequent entries. 008d 2d SRC P6 ; Set the address to 0 (R12/R13 set to 0 as a by-product of the DELAY subroutine). 008e ab LD R11 ; A = R11. Bit 0 is used as an indicator for a local echo from TTY IN to TTY OUT. '0' = local echo. '1' = no local echo. 008f f6 RAR ; CY -> A -> CY. Look at the lsb of A. 0090 ea RDR ; A = ROM PORT[ address = 0 ]. Sample TTY IN on bit 0. 0091 f4 CMA ; Invert the TTY IN line. A = NOT( A ). 0092 12 95 JCN CZ,KEY3 ; Jump if Carry Flag = 1. 0094 e1 WMP ; RAM PORT[ address = 0 ] = A. Send the inverted TTY IN to TTY OUT (local echo as the bits come in). 0095 f6 KEY3: RAR ; CY -> A -> CY (TTY OUT). Sample TTY OUT bit from A to the Carry Flag. 0096 51 00 JMS SHIFT ; CY (TTY OUT) -> R14 -> R15 -> CY shift register. 0098 1a 8b JCN CN,KEY2 ; Jump if CY = '0'. Not seen the '1' bit yet that has been pre-entered into the shift register. 009a d3 LDM 3 ; A = 3. 009b 8e ADD R14 ; A = A(3) + R14 + CY(1). @@@ Not too sure of this @@@ The code says 'sets the carry flag on a printable character' but what is the definition of a 'printable character' and 'left justified' I wonder? 009c f5 RAL ; CY <- A <- CY. 009d 50 a1 JMS DELAY ; Delay one bit time (as defined by R12/R13 = 0). 009f d1 LDM 1 ; A = 1. 00a0 e1 WMP ; RAM PORT[ address = 0 ] = A(1). Sets TTY OUT to '1' (stop bits and in readiness for the next character). ; ******************************************* ; *** *** ; *** Fall through to subroutine DELAY. *** ; *** *** ; ******************************************* 00a1 DELAY: ; This subroutine forms a delay based upon the contents of registers R12 and R13. At the conclusion of the subroutine, R12 and R13 are both left at 0. ; This delay is used for the TTY serial interface to delay for half a bit and full bit times. ; 00a1 00 NOP ; No operation... 00a2 7d a1 ISZ R13,DELAY ; Use R13 as the inner loop. This terminates the loop when R13 == 0. 00a4 00 NOP ; No operation... 00a5 00 NOP ; No operation... 00a6 7c a1 ISZ R12,DELAY ; Use R12 as the outer loop. This terminates the loop when R12 == 0. ; ; Both R12 and R13 must, therefore, be zero (0) now. ; 00a8 cc TXX5: BBL 12 ; Return to caller and set A=12 on exit. ; SOME STUFF HERE TODO: @@@ ; MUL10: ; D10: 00a9 T6R: ; This subroutine combines TTI and TXX such that a normal return occurs only on characters "0" through "<-". @@@ 00a9 T6L: ; This subroutine @@@ 00ea ORG $EA 00ea TXX: ; This subroutine examines the carry bit set by KEY or TTI as well as the accumulator value and the ; character in registers R14 and R15 to determine if one of the following conditions pertains: ; ; Group 1 : The character os some printable graphic between "0" and "<-". if so, the character is biassed to a six-bit ; value, centred in the byte. The carry is turned on if it is a letter/ A normal return is taken with A = 0. ; ; Group 2 : @@@ ; ; Group 3 : @@@ ; ; Group 4 : @@@ ; 00ea f3 CMC ; Complement carry flag. CY = 0 = printable. CY = 1 = non-printable. 00eb 1a f5 JCN CN,TXX1 ; Jump if Carry Flag = 0 (i.e. the character is printable - between and ($20 to $5F). 00ed 6f INC R15 ; R15 = R15 + 1. Increment the lower nibble of the returned character (set the lsb that is). 00ee 8e ADD R14 ; A/CY = A(12) + R14 (high nibble of character) + CY(1). A = R14 + 13 ($0d). 00ef 1a fe JCN CN,TXX2 ; Jump if Carry Flag = 0 (i.e. @@@ ). 00f1 1c a8 JCN AN,TXX5 ; Jump if A <> 0. This is a "BBL 12" instruction so returns with A = 12 (a Group 4 character). 00f3 41 00 JUN TXX6 ; This is a Group 3 character. ; 00f5 TXX1: 00f5 8e ADD R14 ; A/CY = A(12) + R4 + CY(0). 00f6 be XCH R14 ; R14 <> A. 00f7 f5 RAL ; CY <- A <- CY. 00f8 12 64 JCN CZ,TXX4 ; Jump if Carry Flag = 1 (i.e. @@@). This is a "BBL 0" instruction so returns with A = 0 (a Group 1 character). 00fa ae LD R14 ; A = R14. 00fb f6 RAR ; CY -> A -> CY. 00fc 1c 61 JCN AN,TXX3 ; Jump if A <> 0 (a Group 1 character). 00fe af TXX2: LD R15 ; A = R15. 00ff 3b JIN P5 ; A Group 2 character. 0100 TXX6: 0100 SHIFT: ; TODO: @@@ 213 0100 L260: ; TODO: @@@ 0000 END START Tom's assembler for the Intel 4004 CPU running on a SIM4-01 card. 0087 ABELL 008d ACR 008a ALF 000c AN 0080 ANUL 00a0 ASPACE 00b0 AZERO 000b BCD 000e BCD1 000a CN 0002 CZ 00a1 DELAY 0035 DIGIT 0046 IPR 0078 KEY 0082 KEY1 008b KEY2 0095 KEY3 0100 L260 ffff MSG 0000 P0 0002 P1 0004 P2 0006 P3 0008 P4 000a P5 000c P6 000e P7 005f PAG0 0061 PAG1 0028 PDN 0050 PUN 0054 PUN1 0000 R0 0001 R1 000a R10 000b R11 000c R12 000d R13 000e R14 000f R15 0002 R2 0003 R3 0004 R4 0005 R5 0006 R6 0007 R7 0008 R8 0009 R9 006b RETN 0100 SHIFT 003f SPACE 0000 START 00a9 T6L 00a9 T6R 0080 TOPBIT 0075 TTI 00ea TXX 00f5 TXX1 00fe TXX2 0061 TXX3 0064 TXX4 00a8 TXX5 0100 TXX6 0006 Z47