[Project Log] Python on the 6502/C64, 8080, 6800, 6809 and AVR


#222

PL/M defines true as a byte of all 1’s and false as a byte of all 0’s.

Implementing relational operators between two single-byte values is fairly easy.

 					  00019	**B0 = B1 <> B2;
 					  00020
 					  00021	*   *  0 := v B0 -> 1
 					  00022	*   *  1 L r 2
 					  00023
 					  00024	*      *  2 L v B1 -> 3
 					  00025	*      *  3 NE v B2
 					  00026
 					  00027
 					  00028	*  2 L v B1 -> 3
 					  00029	*  3 NE v B2
 0019 96 0E	      [3] 00030	         ldaa   B1
 001B 5F	      [2] 00031	         clrb
 001C 90 0F	      [3] 00032	         suba   B2
 001E 27 01 (0021)[4] 00033	         beq    L00000
 0020 5A	      [2] 00034	         decb
 0021				  00035	L00000
 					  00036	*  1 L r 2
 					  00037	*  0 := v B0 -> 1
 0021 D7 0D	      [4] 00038	         stab   B0

Likewise for comparing two address variables.

 					  00019	**B0 = A1 = A2;
 					  00020
 					  00021	*   *  0 := v B0 -> 1
 					  00022	*   *  1 L r 2
 					  00023
 					  00024	*      *  2 L v A1 -> 3
 					  00025	*      *  3 EQ v A2
 					  00026
 					  00027
 					  00028	*  2 L v A1 -> 3
 					  00029	*  3 EQ v A2
 0019 5F	      [2] 00030	         clrb
 001A DE 07	      [4] 00031	         ldx    A1
 001C 9C 09	      [4] 00032	         cpx    A2
 001E 26 01 (0021)[4] 00033	         bne    L00000
 0020 5A	      [2] 00034	         decb
 0021				  00035	L00000
 					  00036	*  1 L r 2
 					  00037	*  0 := v B0 -> 1
 0021 D7 0D	      [4] 00038	         stab   B0

Anything else gets quite involved.

 					  00019	**B0 = A1 + A2 <> A3;
 					  00020
 					  00021	*   *  0 := v B0 -> 1
 					  00022	*   *  1 L r 4
 					  00023
 					  00024	*      *  4 L r 2 -> 5
 					  00025
 					  00026	*         *  2 L v A1 -> 3
 					  00027	*         *  3 + v A2
 					  00028
 					  00029	*      *  5 NE v A3
 					  00030
 					  00031
 					  00032	*  2 L v A1 -> 3
 					  00033	*  3 + v A2
 0019 D6 08	      [3] 00034	         ldab   A1+1
 001B 96 07	      [3] 00035	         ldaa   A1
 001D DB 0A	      [3] 00036	         addb   A2+1
 001F 99 09	      [3] 00037	         adca   A2
 					  00038	*  4 L r 2 -> 5
 					  00039	*  5 NE v A3
 0021 7F 0004     [6] 00040	         clr    Bool
 0024 D0 0C	      [3] 00041	         subb   A3+1
 0026 92 0B	      [3] 00042	         sbca   A3
 0028 26 03 (002D)[4] 00043	         bne    L00001
 002A 5D	      [2] 00044	         tstb
 002B 27 03 (0030)[4] 00045	         beq    L00000
 002D				  00046	L00001
 002D 7A 0004     [6] 00047	         dec    Bool
 0030				  00048	L00000
 0030 D6 04	      [3] 00049	         ldab   Bool
 					  00050	*  1 L r 4
 					  00051	*  0 := v B0 -> 1
 0032 D7 0D	      [4] 00052	         stab   B0

This because the CPX instruction is “broken” on the 6800; only the Zero flag is set correctly.


#223

Looking at some of the not-so-good code for relational comparisons made me wonder how well PL/M-80 did it.

I cannot get PL/M-80 for DOS to work. I may have to find an emulator of ISIS-II on an Intel MDS.

Study of the 8080 instruction set shows that is has limited 16-bit data handling capability. It can

  • load and store 16 bits at a time with lots of restrictions
  • exchange some of the 16-bit registers to work around some (but not all) of the above restrictions
  • push and pop a 16-bit register
  • increment or decrement a 16-bit register
  • add two 16-bit registers together
  • use a 16-bit register as a pointer to load or store the accumulator.

That is it. No easy way to compare two 16-bit quantities without going through the single accumulator a byte at a time.


#224

Well, the 16 bits, I would think would be mainly used in addressing rather than values.


#225

16-bit numerical quantities would be needed to implement the CP/M filesystem.

Also, if the assembler was written in PL/M, there are many needs for 16-bit numbers.


#226

A very interesting read about the early days of microprocessor development at Intel…

http://www.rogerarrick.com/osiris/burgett.txt


#227

Still no luck finding a way to run the Intel PL/M 8080 compiler, but one of the manuals had some sample code in it.

Consider this PL/M procedure:

PRINT$STRING: PROCEDURE(NAME,LENGTH);
    DECLARE NAME ADDRESS,
        (LENGTH,I,CHAR BASED NAME) BYTE;
        DO I = 0 to LENGTH-1;
        CALL PRINT$CHAR(CHAR(I));
        END;
    END PRINT$STRING;

Before we get to the code generated by the compiler, a few words about the compiler conventions.

Parameters to a procedure are allocated statically in memory unless the procedure is declared to be reentrant. Local variables are allocated next, contiguously.

If there are two or fewer parameters, they are passed in registers.

The first in register pair BC if ADDRESS and in register C if BYTE.

The second in register pair DE if ADDRESS and in register E if BYTE.

 00C8 21 02E2	     [10] 00078			lxi		H,2E2h	; Point to NAME
 00CB 71		      [7] 00079			mov		M,C		; Store NAME
 00CC 23		      [5] 00080			inx		H
 00CD 70		      [7] 00081			mov		M,B
 00CE 2C		      [5] 00082			inr		L		; Store LENGTH
 00CF 73		      [7] 00083			mov		M,E
 00D0 2C		      [5] 00084			inr		L		; Clear I
 00D1 36 00		     [10] 00085			mvi		M,0
 00D3 21 02E4	     [10] 00086			lxi		H,2E4h	; Point to LENGTH
 00D6 4E		      [7] 00087			mov		C,M
 00D7 0D		      [5] 00088			dcr		C		; LENGTH-1
 00D8 79		      [5] 00089			mov		A,C
 00D9 2C		      [5] 00090			inr		L
 00DA 96		      [7] 00091			sub		M		; Compare with I
 00DB DA 00F1	     [10] 00092			jc		0F1h	; End loop
 00DE 4E		      [7] 00093			mov		C,M		; Make index into NAME
 00DF 06 00		      [7] 00094			mvi		B,0
 00E1 2A 02E2	     [16] 00095			lhld	2E2h	; Add base of NAME
 00E4 09		     [10] 00096			dad		B
 00E5 7E		      [7] 00097			mov		A,M		; Get next CHAR
 00E6 4F		      [5] 00098			mov		C,A
 00E7 CD 00C0	     [17] 00099			call	0C0h	; call PRINT$CHAR
 00EA 21 02E5	     [10] 00100			lxi		H,2E5h	; Point to I
 00ED 34		     [10] 00101			inr		M		; Increment I
 00EE C3 00D3	     [10] 00102			jmp		0D3h	; Back to top of the loop
 00F1 C9		     [10] 00103			ret

This is not great code, but it is not bad. The compiler definitely knows about the strengths and weaknesses of the 8080.

Loading a byte from or storing a byte to an arbitrary location in memory takes three bytes of machine code and 13 clock cycles. And it has to go through the accumulator.

Contrast that with indirect access using an address in the HL register pair. The “memory” register M is an equal to the other single-byte registers except for slightly slower access.

This is what an assembly language programmer might write:

 00C8 21 02E2	     [10] 00107			lxi		H,2E2h	; Point to NAME
 00CB 71		      [7] 00108			mov		M,C		; Store NAME
 00CC 23		      [5] 00109			inx		H
 00CD 70		      [7] 00110			mov		M,B
 00CE 23		      [5] 00111			inx		H		; Store LENGTH
 00CF 73		      [7] 00112			mov		M,E
 00D0 AF		      [4] 00113			xra		A		; Check for LENGTH = 0
 00D1 BB		      [4] 00114			cmp		E
 00D2 C8		   [5/11] 00115			rz
 00D3 2A 02E2	     [16] 00116	Loop:	lhld	2E2h	; Get next character
 00D6 4E		      [7] 00117			mov		C,M
 00D7 23		      [5] 00118			inx		H		; Point to next character
 00D8 22 02E2	     [16] 00119			shld	2E2h	; Save for next time
 00DB CD 00C0	     [17] 00120			call	0C0h	; call PRINT$CHAR
 00DE 21 02E4	     [10] 00121			lxi		H,2E4h	; Point to LENGTH
 00E1 35		     [10] 00122			dcr		M		; Decrement LENGTH
 00E2 C2 00D3	     [10] 00123			jnz		Loop
 00E5 C9		     [10] 00124			ret

Now I am curious what the compiler would generate if I had written PL/M code to do it this way…


#228

Success! The Intel PL/M compiler lives!

Though I cannot figure out how to set the program origin. Not necessary for this test since I am not actually trying to build a working program.

The rewritten PL/M program:

PRINT$STRING: PROCEDURE(NAME,LENGTH);
    DECLARE NAME ADDRESS,
        (LENGTH,CHAR BASED NAME) BYTE;
        DO WHILE LENGTH <> 0;
        CALL PRINT$CHAR(CHAR);
        NAME = NAME+1;
        LENGTH = LENGTH-1;
        END;
    END PRINT$STRING;

and the generated code:

 000B 21 0004	     [10] 00003		lxi		H,0004
 000E 73		      [7] 00004		mov		M,E
 000F 2B		      [5] 00005		dcx		H
 0010 70		      [7] 00006		mov		M,B
 0011 2B		      [5] 00007		dcx		H
 0012 71		      [7] 00008		mov		M,C
 0013 3A 0004	     [13] 00009		lda		0004
 0016 FE 00		      [7] 00010		cpi		00
 0018 CA 001E	     [10] 00011		jz		0030
 001B 2A 0002	     [16] 00012		lhld	0002
 001E 4E		      [7] 00013		mov		C,M
 001F CD 0000	     [17] 00014		call	0000
 0022 2A 0002	     [16] 00015		lhld	0002
 0025 23		      [5] 00016		inx		H
 0026 22 0002	     [16] 00017		shld	0002
 0029 21 0004	     [10] 00018		lxi		H,0004
 002C 35		     [10] 00019		dcr		M
 002D C3 000D	     [10] 00020		jmp		0013
 0030 C9		     [10] 00021		ret

Not quite as good as hand-written code, but much improved.


#229

Just when you thought you have seen everything, this test program came with the compiler. Cheers!

/*
 * 99 bottles of beer in PL/M-80
 *
 * by John Durbetaki using AEDIT
 *
 */
Ninety$Nine: do;

$include(:f1:common.lit)
$include(:f1:isis.ext)

declare as              LITERALLY   'LITERALLY';
declare CRLF            as          '0Dh,0Ah';

declare Beers           BYTE;
declare Message1(*)     BYTE DATA(' of beer on the wall,',CRLF);
declare Message2(*)     BYTE DATA(' of beeeeer . . . . ,',CRLF);
declare Message3(*)     BYTE DATA('Take one down, pass it around,',CRLF);
declare Message4(*)     BYTE DATA(' of beer on the wall.',CRLF);
declare End$Message(*)  BYTE DATA(CRLF,'Time to buy more beer!',CRLF);
declare STATUS          ADDRESS;
declare How$Many(128)   BYTE;
declare How$Many$Count  BYTE;

Copy: PROCEDURE(Ap,Bp,Count);
    declare Ap              ADDRESS;
    declare A BASED Ap      BYTE;
    declare Bp              ADDRESS;
    declare B BASED Bp      BYTE;
    declare Count           BYTE;

    DO WHILE Count > 0;
        B=A;
        Ap=Ap+1;
        Bp=Bp+1;
        Count=Count-1;
        END;
    END;

Make$How$Many: PROCEDURE(Beers);
    declare Beers           BYTE;

    if Beers = 0 THEN DO;
	How$Many$Count=15;
        CALL Copy(.('No more bottles'),.How$Many(0),How$Many$Count);
        END;
    else if Beers = 1 THEN DO;
	How$Many$Count=15;
        CALL Copy(.('One more bottle'),.How$Many(0),How$Many$Count);
        END;
    else DO;
        if Beers >= 10 THEN DO;
            How$Many(0)='0'+(Beers/10);
            How$Many(1)='0'+(Beers MOD 10);
            CALL Copy(.(' bottles'),.How$Many(2),8);
            How$Many$Count=10;
            END;
        else DO;
            How$Many(0)='0'+Beers;
            CALL Copy(.(' bottles'),.How$Many(1),8);
            How$Many$Count=9;
            END;
        END;
    END;

Chug: PROCEDURE(Beers);
    declare Beers           BYTE;

    CALL Make$How$Many(Beers);
    CALL WRITE(0,.How$Many,How$Many$Count,.STATUS);
    CALL WRITE(0,.Message1,SIZE(Message1),.STATUS);
    CALL WRITE(0,.How$Many,How$Many$Count,.STATUS);
    CALL WRITE(0,.Message2,SIZE(Message2),.STATUS);
    CALL WRITE(0,.Message3,SIZE(Message3),.STATUS);
    CALL Make$How$Many(Beers-1);
    CALL WRITE(0,.How$Many,How$Many$Count,.STATUS);
    CALL WRITE(0,.Message4,SIZE(Message4),.STATUS);
    END;

    Beers = /*99*/ 9;
    DO WHILE Beers > 0;
        CALL Chug(Beers);
        Beers=Beers-1;
        END;
    CALL WRITE(0,.End$Message,SIZE(End$Message),.STATUS);
    call exit;
    END;

#230

Through the FLEX Users Group, I have discovered that Programma International sold SPL/M, a PL/M lookalike, running on and generating 6800 code for the FLEX operating system.


#231

I’ll have to look more into this FLEX Operating system but did find some games for it:

http://tanrunomad.com/swtpc-flex-games/