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

Very well, then. I had been planning to pick up a copy of the dead-tree edition I prefer at a local store, but I just registered and paid to save myself the hassle and possible disappointment.

BTW, I have been studying Python using other books already. I often reinforce my learning by implementing, so I am writing a toy Python compiler and targeting the 6502…

5 Likes

please tell me its somewhere near alpha state?!?

https://m.popkey.co/338088/mpjp5.gif

2 Likes

No, it is very early on. Working on the lexer right now. I expect to be able to compile some very simple programs by the class next week.

1 Like

All you ever wanted to know about the Python lexical analyzer but was afraid to ask… 2. Lexical analysis — Python 3.12.5 documentation

1 Like

This is hard!

I am still working on the run-time code for a Python cross compiler. The more I do, the more I realize that implementing Pythonic behavior bears great cost on a small system. Hence, there is probably not much demand for a Python cross compiler targetting a small microcontroller like an AVR (Arduino) or TI MSP-430. The overhead is that bad.

The 6502 microprocessor won many design deals over the 8080/Z80 and the 6800 due to its lower per unit cost. But its lauded 16-bit capabilities with its unusual addressing modes was highly overrated.

For example, I just fixed what is hopefully the last bug in the heap management code. A bit of it looks like this:

 04C9 B1 12	    [5/6] 00640		lda	(Ptr1),Y		; If previous block is free, merge
 04CB D0 31 (04FE)  [2/3] 00641		bne	Free4
 			  00642
 04CD A0 00	      [2] 00643		ldy	#0			; Copy next link
 04CF B1 10	    [5/6] 00644		lda	(Ptr0),Y
 04D1 91 12	      [6] 00645		sta	(Ptr1),Y
 04D3 C8	      [2] 00646		iny
 04D4 B1 10	    [5/6] 00647		lda	(Ptr0),Y
 04D6 91 12	      [6] 00648		sta	(Ptr1),Y
 			  00649
 04D8 C8	      [2] 00650		iny				; Adjust length by size of freed block
 04D9 B1 10	    [5/6] 00651		lda	(Ptr0),Y
 04DB 18	      [2] 00652		clc
 04DC 71 12	    [5/6] 00653		adc	(Ptr1),Y
 04DE 91 12	      [6] 00654		sta	(Ptr1),Y
 04E0 C8	      [2] 00655		iny
 04E1 B1 10	    [5/6] 00656		lda	(Ptr0),Y
 04E3 71 12	    [5/6] 00657		adc	(Ptr1),Y
 04E5 91 10	      [6] 00658		sta	(Ptr1),Y		; FORGOT THIS INSTRUCTION!!!
 04E7 88	      [2] 00659		dey				; And its header
 04E8 B1 12	    [5/6] 00660		lda	(Ptr1),Y
 04EA 18	      [2] 00661		clc
 04EB 69 05	      [2] 00662		adc	#5
 04ED 91 12	      [6] 00663		sta	(Ptr1),Y
 04EF C8	      [2] 00664		iny
 04F0 B1 12	    [5/6] 00665		lda	(Ptr1),Y
 04F2 69 00	      [2] 00666		adc	#0
 04F4 91 12	      [6] 00667		sta	(Ptr1),Y

In this particular case, the upper byte of a calculation was not stored. The problem did not appear until the heap crossed a page (256 byte) boundary.

Still, I am well on my way toward my “moonshot,” making this little Python program run on a Commodore 128:

I = 1

while True:
    print('bytes, bits:', divmod(I.bit_length(),8), ', value:', I)
    I = I * 10
3 Likes

For the 6502 and Pythons fans out there, a couple of additional code morsels.

First up, converting a variable precision integer to a string:

 			  01778	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 			  01779	;
 			  01780	; int.str - Convert an integer to string.
 			  01781	;
 			  01782	; Input:
 			  01783	;	Ptr0 = address of the integer
 			  01784	;
 			  01785	; Output:
 			  01786	;	Ptr0 = address of the string
 			  01787	;
 			  01788	; Uses:
 			  01789	;	Int3 - the number of bytes in the integer
 			  01790	;	Int4 - the number of digits
 			  01791	;	Scr0 - the dividend
 			  01792	;	Scr1 - the value 10
 			  01793	;	Scr2 - the quotient
 			  01794	;	Scr3 - the string buffer
 			  01795	;
 09CF			  01796	int.str
 09CF A0 00	      [2] 01797		ldy	#0			; Get object type
 09D1 B1 10	    [5/6] 01798		lda	(Ptr0),Y
 09D3 29 C0	      [2] 01799		and	#$C0
 09D5 C9 40	      [2] 01800		cmp	#$40
 09D7 F0 01 (09DA)  [2/3] 01801		beq	int.str0
 			  01802
 09D9 FF		  01803		.fcb	$FF			; Hook for future big integer type
 			  01804
 09DA			  01805	int.str0
 09DA A5 10	      [3] 01806		lda	Ptr0			; Stash address of the integer
 09DC 18	      [2] 01807		clc
 09DD 69 01	      [2] 01808		adc	#1
 09DF 85 14	      [3] 01809		sta	Ptr2
 09E1 A5 11	      [3] 01810		lda	Ptr0+1
 09E3 69 00	      [2] 01811		adc	#0
 09E5 85 15	      [3] 01812		sta	Ptr2+1
 			  01813
 09E7 A0 00	      [2] 01814		ldy	#0			; Get length of integer
 09E9 18	      [2] 01815		clc				; Add 1 in case of MININT value
 09EA B1 10	    [5/6] 01816		lda	(Ptr0),Y
 09EC 29 3F	      [2] 01817		and	#$3F
 09EE 85 0C	      [3] 01818		sta	Int3
 09F0 69 01	      [2] 01819		adc	#1
 09F2 AA	      [2] 01820		tax
 09F3 85 0A	      [3] 01821		sta	Int2
 09F5 A9 00	      [2] 01822		lda	#0
 09F7 85 0B	      [3] 01823		sta	Int2+1
 09F9 85 0D	      [3] 01824		sta	Int3+1
 			  01825
 09FB			  01826	int.str1
 09FB A9 1C	      [2] 01827		lda	#Scr0&$FF		; Verify scratch space 0 length
 09FD 85 16	      [3] 01828		sta	Ptr3
 09FF A9 00	      [2] 01829		lda	#Scr0>>8
 0A01 85 17	      [3] 01830		sta	Ptr3+1
 0A03 20 096B	      [6] 01831		jsr	scratch.realloc
 			  01832
 0A06 A9 20	      [2] 01833		lda	#Scr1&$FF		; Verify scratch space 1 length
 0A08 85 16	      [3] 01834		sta	Ptr3
 0A0A A9 00	      [2] 01835		lda	#Scr1>>8
 0A0C 85 17	      [3] 01836		sta	Ptr3+1
 0A0E 20 096B	      [6] 01837		jsr	scratch.realloc
 			  01838
 0A11 A9 24	      [2] 01839		lda	#Scr2&$FF		; Verify scratch space 2 length
 0A13 85 16	      [3] 01840		sta	Ptr3
 0A15 A9 00	      [2] 01841		lda	#Scr2>>8
 0A17 85 17	      [3] 01842		sta	Ptr3+1
 0A19 20 096B	      [6] 01843		jsr	scratch.realloc
 			  01844
 0A1C A5 0C	      [3] 01845		lda	Int3			; Get length of integer * 3
 0A1E 0A	      [2] 01846		asl	A
 0A1F 85 0A	      [3] 01847		sta	Int2
 0A21 A5 0D	      [3] 01848		lda	Int3+1
 0A23 2A	      [2] 01849		rol	A
 0A24 85 0B	      [3] 01850		sta	Int2+1
 0A26 88	      [2] 01851		dey
 0A27 A5 0C	      [3] 01852		lda	Int3
 0A29 18	      [2] 01853		clc
 0A2A 65 0A	      [3] 01854		adc	Int2
 0A2C 85 0A	      [3] 01855		sta	Int2
 0A2E A5 0D	      [3] 01856		lda	Int3+1
 0A30 65 0B	      [3] 01857		adc	Int2+1
 0A32 85 0B	      [3] 01858		sta	Int2+1
 			  01859
 0A34 A9 28	      [2] 01860		lda	#Scr3&$FF		; Verify scratch space 3 length
 0A36 85 16	      [3] 01861		sta	Ptr3
 0A38 A9 00	      [2] 01862		lda	#Scr3>>8
 0A3A 85 17	      [3] 01863		sta	Ptr3+1
 0A3C 20 096B	      [6] 01864		jsr	scratch.realloc
 			  01865
 0A3F A5 14	      [3] 01866		lda	Ptr2			; Copy integer to dividend
 0A41 85 10	      [3] 01867		sta	Ptr0
 0A43 A5 15	      [3] 01868		lda	Ptr2+1
 0A45 85 11	      [3] 01869		sta	Ptr0+1
 0A47 A5 1C	      [3] 01870		lda	Scr0
 0A49 85 12	      [3] 01871		sta	Ptr1
 0A4B A5 1D	      [3] 01872		lda	Scr0+1
 0A4D 85 13	      [3] 01873		sta	Ptr1+1
 0A4F A5 0C	      [3] 01874		lda	Int3
 0A51 85 06	      [3] 01875		sta	Int0
 0A53 A5 0D	      [3] 01876		lda	Int3+1
 0A55 85 07	      [3] 01877		sta	Int0+1
 0A57 20 03B5	      [6] 01878		jsr	MovBlk
 			  01879
 0A5A A5 1C	      [3] 01880		lda	Scr0			; Determine address of high byte
 0A5C 18	      [2] 01881		clc
 0A5D 65 0C	      [3] 01882		adc	Int3
 0A5F 85 12	      [3] 01883		sta	Ptr1
 0A61 A5 1D	      [3] 01884		lda	Scr0+1
 0A63 65 0D	      [3] 01885		adc	Int3+1
 0A65 85 13	      [3] 01886		sta	Ptr1+1
 0A67 A5 12	      [3] 01887		lda	Ptr1
 0A69 38	      [2] 01888		sec
 0A6A E9 01	      [2] 01889		sbc	#1
 0A6C 85 12	      [3] 01890		sta	Ptr1
 0A6E A5 13	      [3] 01891		lda	Ptr1+1
 0A70 E9 00	      [2] 01892		sbc	#0
 0A72 85 13	      [3] 01893		sta	Ptr1+1
 			  01894
 0A74 A0 00	      [2] 01895		ldy	#0			; Is the number negative
 0A76 B1 12	    [5/6] 01896		lda	(Ptr1),Y
 0A78 85 03	      [3] 01897		sta	Byt0			; Remember it
 0A7A 10 2D (0AA9)  [2/3] 01898		bpl	int.str2
 			  01899
 0A7C A5 1C	      [3] 01900		lda	Scr0			; Negate the number
 0A7E 85 10	      [3] 01901		sta	Ptr0
 0A80 A5 1D	      [3] 01902		lda	Scr0+1
 0A82 85 11	      [3] 01903		sta	Ptr0+1
 0A84 A5 0C	      [3] 01904		lda	Int3
 0A86 85 06	      [3] 01905		sta	Int0
 0A88 A5 0D	      [3] 01906		lda	Int3+1
 0A8A 85 07	      [3] 01907		sta	Int0+1
 0A8C 20 0797	      [6] 01908		jsr	IntNegate
 			  01909
 0A8F A0 00	      [2] 01910		ldy	#0			; MININT if upper byte is the same
 0A91 B1 12	    [5/6] 01911		lda	(Ptr1),Y
 0A93 C5 03	      [3] 01912		cmp	Byt0
 0A95 D0 12 (0AA9)  [2/3] 01913		bne	int.str2
 			  01914
 0A97 C8	      [2] 01915		iny				; Need an extra byte to handle it
 0A98 A9 00	      [2] 01916		lda	#0
 0A9A 91 12	      [6] 01917		sta	(Ptr1),Y
 0A9C A5 0C	      [3] 01918		lda	Int3
 0A9E 18	      [2] 01919		clc
 0A9F 69 01	      [2] 01920		adc	#1
 0AA1 85 0C	      [3] 01921		sta	Int3
 0AA3 A5 0D	      [3] 01922		lda	Int3+1
 0AA5 69 00	      [2] 01923		adc	#0
 0AA7 85 0D	      [3] 01924		sta	Int3+1
 			  01925
 0AA9			  01926	int.str2
 0AA9 A5 24	      [3] 01927		lda	Scr2			; Initially, quotient is 0
 0AAB 85 10	      [3] 01928		sta	Ptr0
 0AAD A5 25	      [3] 01929		lda	Scr2+1
 0AAF 85 11	      [3] 01930		sta	Ptr0+1
 0AB1 A5 0C	      [3] 01931		lda	Int3
 0AB3 85 06	      [3] 01932		sta	Int0
 0AB5 A5 0D	      [3] 01933		lda	Int3+1
 0AB7 85 07	      [3] 01934		sta	Int0+1
 0AB9 20 0396	      [6] 01935		jsr	SetBlk
 			  01936
 0ABC A9 00	      [2] 01937		lda	#0			; Initially no digits
 0ABE 85 0E	      [3] 01938		sta	Int4
 0AC0 85 0F	      [3] 01939		sta	Int4+1
 			  01940
 0AC2 A5 28	      [3] 01941		lda	Scr3			; Build converted number backward
 0AC4 18	      [2] 01942		clc
 0AC5 65 2A	      [3] 01943		adc	ScrLen3
 0AC7 85 16	      [3] 01944		sta	Ptr3
 0AC9 A5 29	      [3] 01945		lda	Scr3+1
 0ACB 65 2B	      [3] 01946		adc	ScrLen3+1
 0ACD 85 17	      [3] 01947		sta	Ptr3+1
 			  01948
 0ACF			  01949	int.str3
 0ACF A5 20	      [3] 01950		lda	Scr1			; Divisor is 10
 0AD1 85 10	      [3] 01951		sta	Ptr0
 0AD3 A5 21	      [3] 01952		lda	Scr1+1
 0AD5 85 11	      [3] 01953		sta	Ptr0+1
 0AD7 A5 0C	      [3] 01954		lda	Int3
 0AD9 85 06	      [3] 01955		sta	Int0
 0ADB A5 0D	      [3] 01956		lda	Int3+1
 0ADD 85 07	      [3] 01957		sta	Int0+1
 0ADF 20 0396	      [6] 01958		jsr	SetBlk
 0AE2 A0 00	      [2] 01959		ldy	#0
 0AE4 A9 0A	      [2] 01960		lda	#10
 0AE6 91 20	      [6] 01961		sta	(Scr1),Y
 			  01962
 0AE8 A5 0C	      [3] 01963		lda	Int3			; Divide dividend by 10
 0AEA 85 06	      [3] 01964		sta	Int0
 0AEC A5 0D	      [3] 01965		lda	Int3+1
 0AEE 85 07	      [3] 01966		sta	Int0+1
 0AF0 20 0625	      [6] 01967		jsr	IDivMod
 			  01968
 0AF3 A5 16	      [3] 01969		lda	Ptr3			; Remainder is next digit
 0AF5 38	      [2] 01970		sec
 0AF6 E9 01	      [2] 01971		sbc	#1
 0AF8 85 16	      [3] 01972		sta	Ptr3
 0AFA A5 17	      [3] 01973		lda	Ptr3+1
 0AFC E9 00	      [2] 01974		sbc	#0
 0AFE 85 17	      [3] 01975		sta	Ptr3+1
 0B00 A0 00	      [2] 01976		ldy	#0
 0B02 B1 1C	    [5/6] 01977		lda	(Scr0),Y
 0B04 18	      [2] 01978		clc
 0B05 69 30	      [2] 01979		adc	#'0'
 0B07 91 16	      [6] 01980		sta	(Ptr3),Y
 			  01981
 0B09 A5 0E	      [3] 01982		lda	Int4			; Count the digit
 0B0B 18	      [2] 01983		clc
 0B0C 69 01	      [2] 01984		adc	#1
 0B0E 85 0E	      [3] 01985		sta	Int4
 0B10 A5 0F	      [3] 01986		lda	Int4+1
 0B12 69 00	      [2] 01987		adc	#0
 0B14 85 0F	      [3] 01988		sta	Int4+1
 			  01989
 0B16 A5 24	      [3] 01990		lda	Scr2			; Copy quotient to dividend
 0B18 85 10	      [3] 01991		sta	Ptr0
 0B1A A5 25	      [3] 01992		lda	Scr2+1
 0B1C 85 11	      [3] 01993		sta	Ptr0+1
 0B1E A5 1C	      [3] 01994		lda	Scr0
 0B20 85 12	      [3] 01995		sta	Ptr1
 0B22 A5 1D	      [3] 01996		lda	Scr0+1
 0B24 85 13	      [3] 01997		sta	Ptr1+1
 0B26 A5 0C	      [3] 01998		lda	Int3
 0B28 85 06	      [3] 01999		sta	Int0
 0B2A A5 0D	      [3] 02000		lda	Int3+1
 0B2C 85 07	      [3] 02001		sta	Int0+1
 0B2E 20 03B5	      [6] 02002		jsr	MovBlk
 			  02003
 0B31 A5 1C	      [3] 02004		lda	Scr0			; Repeat if the dividend is not zero
 0B33 85 10	      [3] 02005		sta	Ptr0
 0B35 A5 1D	      [3] 02006		lda	Scr0+1
 0B37 85 11	      [3] 02007		sta	Ptr0+1
 0B39 A5 0C	      [3] 02008		lda	Int3
 0B3B 85 06	      [3] 02009		sta	Int0
 0B3D A5 0D	      [3] 02010		lda	Int3+1
 0B3F 85 07	      [3] 02011		sta	Int0+1
 0B41 20 09AC	      [6] 02012		jsr	IsZero
 0B44 D0 89 (0ACF)  [2/4] 02013		bne	int.str3
 			  02014
 0B46 A5 03	      [3] 02015		lda	Byt0			; Is the number negative?
 0B48 10 20 (0B6A)  [2/3] 02016		bpl	int.str4
 			  02017
 0B4A A5 16	      [3] 02018		lda	Ptr3			; Add a minus sign
 0B4C 38	      [2] 02019		sec
 0B4D E9 01	      [2] 02020		sbc	#1
 0B4F 85 16	      [3] 02021		sta	Ptr3
 0B51 A5 17	      [3] 02022		lda	Ptr3+1
 0B53 E9 00	      [2] 02023		sbc	#0
 0B55 85 17	      [3] 02024		sta	Ptr3+1
 0B57 A0 00	      [2] 02025		ldy	#0
 0B59 A9 2D	      [2] 02026		lda	#'-'
 0B5B 91 16	      [6] 02027		sta	(Ptr3),Y
 			  02028
 0B5D A5 0E	      [3] 02029		lda	Int4			; Count the minus sign
 0B5F 18	      [2] 02030		clc
 0B60 69 01	      [2] 02031		adc	#1
 0B62 85 0E	      [3] 02032		sta	Int4
 0B64 A5 0F	      [3] 02033		lda	Int4+1
 0B66 69 00	      [2] 02034		adc	#0
 0B68 85 0F	      [3] 02035		sta	Int4+1
 			  02036
 0B6A			  02037	int.str4
 0B6A A5 0E	      [3] 02038		lda	Int4			; Allocate result string
 0B6C 18	      [2] 02039		clc
 0B6D 69 03	      [2] 02040		adc	#3
 0B6F 85 06	      [3] 02041		sta     Int0
 0B71 A5 0F	      [3] 02042		lda	Int4+1
 0B73 69 00	      [2] 02043		adc	#0
 0B75 85 07	      [3] 02044		sta	Int0+1
 0B77 20 03D6	      [6] 02045		jsr	Alloc
 			  02046
 0B7A A5 10	      [3] 02047		lda	Ptr0			; Stash memory address
 0B7C 85 12	      [3] 02048		sta	Ptr1
 0B7E 85 14	      [3] 02049		sta	Ptr2
 0B80 A5 11	      [3] 02050		lda	Ptr0+1
 0B82 85 13	      [3] 02051		sta	Ptr1+1
 0B84 85 15	      [3] 02052		sta	Ptr2+1
 			  02053
 0B86 05 10	      [3] 02054		ora	Ptr0			;*** Out of memory check here
 			  02055
 0B88 A5 16	      [3] 02056		lda	Ptr3			; Copy string into memory block
 0B8A 85 10	      [3] 02057		sta	Ptr0
 0B8C A5 17	      [3] 02058		lda	Ptr3+1
 0B8E 85 11	      [3] 02059		sta	Ptr0+1
 0B90 A5 12	      [3] 02060		lda	Ptr1
 0B92 18	      [2] 02061		clc
 0B93 69 03	      [2] 02062		adc	#3
 0B95 85 12	      [3] 02063		sta	Ptr1
 0B97 A5 13	      [3] 02064		lda	Ptr1+1
 0B99 69 00	      [2] 02065		adc	#0
 0B9B 85 13	      [3] 02066		sta	Ptr1+1
 0B9D A5 0E	      [3] 02067		lda	Int4
 0B9F 85 06	      [3] 02068		sta	Int0
 0BA1 A5 0F	      [3] 02069		lda	Int4+1
 0BA3 85 07	      [3] 02070		sta	Int0+1
 0BA5 20 03B5	      [6] 02071		jsr	MovBlk
 			  02072
 0BA8 A5 14	      [3] 02073		lda	Ptr2			; Recover memory address
 0BAA 85 10	      [3] 02074		sta	Ptr0
 0BAC A5 15	      [3] 02075		lda	Ptr2+1
 0BAE 85 11	      [3] 02076		sta	Ptr0+1
 			  02077
 0BB0 A0 00	      [2] 02078		ldy	#0			; Finish making it an official string
 0BB2 A9 81	      [2] 02079		lda	#$81
 0BB4 91 10	      [6] 02080		sta	(Ptr0),Y
 0BB6 C8	      [2] 02081		iny
 0BB7 A5 0E	      [3] 02082		lda	Int4
 0BB9 91 10	      [6] 02083		sta	(Ptr0),Y
 0BBB C8	      [2] 02084		iny
 0BBC A5 0F	      [3] 02085		lda	Int4+1
 0BBE 91 10	      [6] 02086		sta	(Ptr0),Y
 			  02087
 0BC0 60	      [6] 02088		rts				; Phew!

And converting a tuple to a string. it even handles nested tuples! This one violates two of Zach’s rules of microcontrollers: it uses dynamic memory allocation and it is recursive.

 			  02240	;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
 			  02241	;
 			  02242	; tuple.str - Convert a tuple to string.
 			  02243	;
 			  02244	; Input:
 			  02245	;	Ptr0 = address of the object
 			  02246	;
 			  02247	; Output:
 			  02248	;	Ptr0 = address of the string
 			  02249	;
 			  02250	; Uses:
 			  02251	;	Int3 - the number of tuple objects left
 			  02252	;	Ptr3 - the address of the next object
 			  02253	;	Ptr4 - the string in progress
 			  02254	;
 0C4C			  02255	tuple.str
 0C4C A5 10	      [3] 02256		lda	Ptr0			; Get address of next object
 0C4E 85 16	      [3] 02257		sta	Ptr3			; Note: increment before use
 0C50 A5 11	      [3] 02258		lda	Ptr0+1
 0C52 85 17	      [3] 02259		sta	Ptr3+1
 			  02260
 0C54 A0 01	      [2] 02261		ldy	#1			; Get number of objects
 0C56 B1 10	    [5/6] 02262		lda	(Ptr0),Y
 0C58 85 0C	      [3] 02263		sta	Int3
 0C5A A9 00	      [2] 02264		lda	#0
 0C5C 85 0D	      [3] 02265		sta	Int3+1
 			  02266
 0C5E			  02267	tuple.str0				; Hook for future big tuple type
 0C5E			  02268	tuple.str1
 0C5E A9 05	      [2] 02269		lda	#2+3			; Start the output string
 0C60 85 06	      [3] 02270		sta	Int0			; Room for empty tuple '()'
 0C62 A9 00	      [2] 02271		lda	#0			;   and string header
 0C64 85 07	      [3] 02272		sta	Int0+1
 0C66 20 03D6	      [6] 02273		jsr	Alloc
 0C69 A5 10	      [3] 02274		lda	Ptr0
 0C6B 85 18	      [3] 02275		sta	Ptr4
 0C6D A5 11	      [3] 02276		lda	Ptr0+1
 0C6F 85 19	      [3] 02277		sta	Ptr4+1
 			  02278
 0C71 05 10	      [3] 02279		ora	Ptr0			;*** Out of memory check here
 			  02280
 0C73 A9 01	      [2] 02281		lda	#1
 0C75 85 03	      [3] 02282		sta	Byt0			; First time
 			  02283
 0C77 A0 00	      [2] 02284		ldy	#0			; Initially '('
 0C79 A9 81	      [2] 02285		lda	#$81
 0C7B 91 18	      [6] 02286		sta	(Ptr4),Y
 0C7D C8	      [2] 02287		iny
 0C7E A9 01	      [2] 02288		lda	#1
 0C80 91 18	      [6] 02289		sta	(Ptr4),Y
 0C82 C8	      [2] 02290		iny
 0C83 A9 00	      [2] 02291		lda	#0
 0C85 91 18	      [6] 02292		sta	(Ptr4),Y
 0C87 C8	      [2] 02293		iny
 0C88 A9 28	      [2] 02294		lda	#'('
 0C8A 91 18	      [6] 02295		sta	(Ptr4),Y
 			  02296
 0C8C A5 0C	      [3] 02297		lda	Int3			; Any more objects?
 0C8E 05 0D	      [3] 02298		ora	Int3+1
 0C90 D0 03 (0C95)  [2/3] 02299		bne	tuple.str2
 			  02300
 0C92 4C 0D6B	      [3] 02301		jmp	tuple.str6
 			  02302
 0C95			  02303	tuple.str2
 0C95 A5 0C	      [3] 02304		lda	Int3			; Decrement object count
 0C97 38	      [2] 02305		sec
 0C98 E9 01	      [2] 02306		sbc	#1
 0C9A 85 0C	      [3] 02307		sta	Int3
 0C9C A5 0D	      [3] 02308		lda	Int3+1
 0C9E E9 00	      [2] 02309		sbc	#0
 0CA0 85 0D	      [3] 02310		sta	Int3+1
 			  02311
 0CA2 A5 16	      [3] 02312		lda	Ptr3			; Increment object pointer
 0CA4 18	      [2] 02313		clc
 0CA5 69 02	      [2] 02314		adc	#2
 0CA7 85 16	      [3] 02315		sta	Ptr3
 0CA9 A5 17	      [3] 02316		lda	Ptr3+1
 0CAB 69 00	      [2] 02317		adc	#0
 0CAD 85 17	      [3] 02318		sta	Ptr3+1
 			  02319
 0CAF A0 00	      [2] 02320		ldy	#0			; Get next object
 0CB1 B1 16	    [5/6] 02321		lda	(Ptr3),Y
 0CB3 85 10	      [3] 02322		sta	Ptr0
 0CB5 C8	      [2] 02323		iny
 0CB6 B1 16	    [5/6] 02324		lda	(Ptr3),Y
 0CB8 85 11	      [3] 02325		sta	Ptr0+1
 			  02326
 0CBA A5 0D	      [3] 02327		lda	Int3+1			; Save my variables
 0CBC 48	      [3] 02328		pha
 0CBD A5 0C	      [3] 02329		lda	Int3
 0CBF 48	      [3] 02330		pha
 0CC0 A5 17	      [3] 02331		lda	Ptr3+1
 0CC2 48	      [3] 02332		pha
 0CC3 A5 16	      [3] 02333		lda	Ptr3
 0CC5 48	      [3] 02334		pha
 0CC6 A5 19	      [3] 02335		lda	Ptr4+1
 0CC8 48	      [3] 02336		pha
 0CC9 A5 18	      [3] 02337		lda	Ptr4
 0CCB 48	      [3] 02338		pha
 0CCC A5 03	      [3] 02339		lda	Byt0
 0CCE 48	      [3] 02340		pha
 			  02341
 0CCF 20 0D79	      [6] 02342		jsr	object.str		; Convert object to string
 			  02343
 0CD2 68	      [4] 02344		pla				; Recover my variables
 0CD3 85 03	      [3] 02345		sta	Byt0
 0CD5 68	      [4] 02346		pla
 0CD6 85 18	      [3] 02347		sta	Ptr4
 0CD8 68	      [4] 02348		pla
 0CD9 85 19	      [3] 02349		sta	Ptr4+1
 0CDB 68	      [4] 02350		pla
 0CDC 85 16	      [3] 02351		sta	Ptr3
 0CDE 68	      [4] 02352		pla
 0CDF 85 17	      [3] 02353		sta	Ptr3+1
 0CE1 68	      [4] 02354		pla
 0CE2 85 0C	      [3] 02355		sta	Int3
 0CE4 68	      [4] 02356		pla
 0CE5 85 0D	      [3] 02357		sta	Int3+1
 			  02358
 0CE7 A5 10	      [3] 02359		lda	Ptr0			; Save address of new string fragment
 0CE9 85 14	      [3] 02360		sta	Ptr2
 0CEB A5 11	      [3] 02361		lda	Ptr0+1
 0CED 85 15	      [3] 02362		sta	Ptr2+1
 			  02363
 0CEF A0 01	      [2] 02364		ldy	#1			; Get length of new string fragment
 0CF1 B1 14	    [5/6] 02365		lda	(Ptr2),Y
 0CF3 18	      [2] 02366		clc
 0CF4 69 06	      [2] 02367		adc	#3+3			; Plus room for ', ', ')'
 0CF6 85 06	      [3] 02368		sta	Int0			;   and string header
 0CF8 C8	      [2] 02369		iny
 0CF9 B1 14	    [5/6] 02370		lda	(Ptr2),Y
 0CFB 69 00	      [2] 02371		adc	#0
 0CFD 85 07	      [3] 02372		sta	Int0+1
 			  02373
 0CFF 88	      [2] 02374		dey				; Add length of current string
 0D00 B1 18	    [5/6] 02375		lda	(Ptr4),Y
 0D02 65 06	      [3] 02376		adc	Int0
 0D04 85 06	      [3] 02377		sta	Int0
 0D06 C8	      [2] 02378		iny
 0D07 B1 18	    [5/6] 02379		lda	(Ptr4),Y
 0D09 65 07	      [3] 02380		adc	Int0+1
 0D0B 85 07	      [3] 02381		sta	Int0+1
 			  02382
 0D0D 20 03D6	      [6] 02383		jsr	Alloc			; Allocate a new string
 			  02384
 0D10 A5 10	      [3] 02385		lda	Ptr0			; Remember the new string address
 0D12 85 1A	      [3] 02386		sta	Ptr5
 0D14 A5 11	      [3] 02387		lda	Ptr0+1
 0D16 85 1B	      [3] 02388		sta	Ptr5+1
 			  02389
 0D18 05 10	      [3] 02390		ora	Ptr0			;*** Out of memory check here
 			  02391
 0D1A 20 0C28	      [6] 02392		jsr	CopyString		; Copy current string to new
 			  02393
 0D1D A5 18	      [3] 02394		lda	Ptr4			; Free the old string
 0D1F 85 10	      [3] 02395		sta	Ptr0
 0D21 A5 19	      [3] 02396		lda	Ptr4+1
 0D23 85 11	      [3] 02397		sta	Ptr0+1
 0D25 20 0480	      [6] 02398		jsr	Free
 			  02399
 0D28 A5 1A	      [3] 02400		lda	Ptr5			; Use the enlarged string
 0D2A 85 18	      [3] 02401		sta	Ptr4
 0D2C A5 1B	      [3] 02402		lda	Ptr5+1
 0D2E 85 19	      [3] 02403		sta	Ptr4+1
 			  02404
 0D30 A5 03	      [3] 02405		lda	Byt0			; Is this the first one?
 0D32 D0 0A (0D3E)  [2/3] 02406		bne	tuple.str3		; Branch if yes
 			  02407
 0D34 A9 2C	      [2] 02408		lda	#','			; Add ', '
 0D36 20 0BDF	      [6] 02409		jsr	ConcatChar
 0D39 A9 20	      [2] 02410		lda	#' '
 0D3B 20 0BDF	      [6] 02411		jsr	ConcatChar
 			  02412
 0D3E			  02413	tuple.str3
 0D3E 20 0BF9	      [6] 02414		jsr	ConcatString		; Add the new object string
 			  02415
 0D41 A5 14	      [3] 02416		lda	Ptr2			; Free the new string fragment
 0D43 85 10	      [3] 02417		sta	Ptr0
 0D45 A5 15	      [3] 02418		lda	Ptr2+1
 0D47 85 11	      [3] 02419		sta	Ptr0+1
 0D49 20 0480	      [6] 02420		jsr	Free
 			  02421
 0D4C A5 03	      [3] 02422		lda	Byt0			; Is this the first one?
 0D4E F0 12 (0D62)  [2/3] 02423		beq	tuple.str4		; Branch if no
 			  02424
 0D50 A9 00	      [2] 02425		lda	#0			; No longer the first one
 0D52 85 03	      [3] 02426		sta	Byt0
 			  02427
 0D54 A5 0C	      [3] 02428		lda	Int3			; Was this the only object?
 0D56 05 0D	      [3] 02429		ora	Int3+1
 0D58 D0 0E (0D68)  [2/3] 02430		bne	tuple.str5		; Branch if no
 			  02431
 0D5A A9 2C	      [2] 02432		lda	#','			; Add ','
 0D5C 20 0BDF	      [6] 02433		jsr	ConcatChar
 			  02434
 0D5F 4C 0D6B	      [3] 02435		jmp	tuple.str6
 			  02436
 0D62			  02437	tuple.str4
 0D62 A5 0C	      [3] 02438		lda	Int3			; Any more objects?
 0D64 05 0D	      [3] 02439		ora	Int3+1
 0D66 F0 03 (0D6B)  [2/3] 02440		beq	tuple.str6
 			  02441
 0D68			  02442	tuple.str5
 0D68 4C 0C95	      [3] 02443		jmp	tuple.str2
 			  02444
 0D6B			  02445	tuple.str6
 0D6B A9 29	      [2] 02446		lda	#')'			; Close the tuple
 0D6D 20 0BDF	      [6] 02447		jsr	ConcatChar
 			  02448
 0D70 A5 18	      [3] 02449		lda	Ptr4			; Return string in Ptr0
 0D72 85 10	      [3] 02450		sta	Ptr0
 0D74 A5 19	      [3] 02451		lda	Ptr4+1
 0D76 85 11	      [3] 02452		sta	Ptr0+1
 			  02453
 0D78 60	      [6] 02454		rts
2 Likes

That’s okay, the 6502 isn’t a microcontroller! :slight_smile:

1 Like

That’s all its progeny are today…

Because it’s not there…

I took on learning Python because it appeared to be a language with demand. Then came realization that it is so popular because it is today’s BASIC. For good and bad. Implementing some of the Pythonics was an irresistible challenge.

5 Likes

Because its better than running Node.js or even Lua on IoT/embedded devices. Seriously, have you tried nodemcu? A little bloated imho. Sure lua is great but python is an everyday language (just as Basic and TCL/Tk was) while javascript is a different beast all together.

6 posts were split to a new topic: Embedded Language Wars

The 6502 instruction set is kind of spare when you compare it with, for example, the 6809 (or even the lesser 6800, Z80, and 8080). However, even when you have to use several instructions in place of a single 6809 instruction, the number of cycles ends up being comparable. The code density is what suffers, and that’s because the 6502 is intentionally RISC. For many small embedded applications, the code density is comparable and the 6502 uses many fewer cycles.

A good macro assembler can make the 6502 much less tedious and can improve readability.

We did get an Amiga 1200 in and that is hooked up right now but the c128 will still be around for your use and we’ll have to find a way to secure it on the wall while not in use.

2 Likes

Actually, the 6800 and 6809 are very similar to the 6502 in that they take one machine cycle to do a memory read or write or ALU operation, unlike the 8080 or Z80 which takes around 3 T cycles. If you can get it into the X register, the 680x can do a 16-bit increment or decrement in 4 machine cycles. The 680x has two capable accumulators. The advantage the 6502 has is that it pipelines the fetch of the next instruction.

My assembler does macros. But there are many flavors of operations: zeropage or absolute addressing versus indirect using register Y, simple increment or decrement versus adding or subtracting a constant or another variable. Then there are standalone operations versus ones to be done in a sequence where a little of gain can be had by keeping a byte in the X register. If I create macros to cover every case, it will begin looking like the ARM with its conditional execution and optional updating of condition codes.

This is sort of apples and oranges, but this is a block copy subroutine for the 6502:

 0344 A0 00	      [2] 00498	MovBlk	ldy	#0			; Start of first page
 			  00499
 0346 A6 07	      [3] 00500		ldx	Int0+1			; At least one full page remaining?
 0348 F0 07 (0351)  [2/3] 00501		beq	MovBlk0			; Branch if no
 			  00502
 034A A2 00	      [2] 00503		ldx	#0			; Set up to move an entire page
 034C C6 07	      [5] 00504		dec	Int0+1			; One fewer whole page remaining
 			  00505
 034E 4C 0357	      [3] 00506		jmp	MovBlk1
 			  00507
 0351 A6 06	      [3] 00508	MovBlk0	ldx	Int0			; Any remaining on a partial page?
 0353 F0 0F (0364)  [2/3] 00509		beq	MovBlk2			; No
 			  00510
 0355 84 06	      [3] 00511		sty	Int0			; This will finish the partial page
 			  00512
 0357 B1 10	    [5/6] 00513	MovBlk1	lda	(Ptr0),Y		; Move a byte
 0359 91 12	      [6] 00514		sta	(Ptr1),Y
 035B C8	      [2] 00515		iny
 035C CA	      [2] 00516		dex				; More to move on this page?
 035D D0 F8 (0357)  [2/3] 00517		bne	MovBlk1			; Yes
 			  00518
 035F E6 11	      [5] 00519		inc	Ptr0+1			; Address next page
 			  00520
 0361 4C 0344	      [3] 00521		jmp	MovBlk			; Check for another page
 			  00522
 0364 60	      [6] 00523	MovBlk2	rts

While this is the one for the 6800:

 0171 B6 016F	      [4] 00213	Copy_    ldaa   CopyC_
 0174 BA 0170	      [4] 00214	         oraa   CopyC_+1
 0177 27 21 (019A)    [4] 00215	         beq    Copy2_
 0179 FE 016B	      [5] 00216	Copy1_   ldx    CopyS_
 017C A6 00	      [5] 00217	         ldaa   ,X
 017E 08	      [4] 00218	         inx
 017F FF 016B	      [6] 00219	         stx    CopyS_
 0182 FE 016D	      [5] 00220	         ldx    CopyD_
 0185 A7 00	      [6] 00221	         staa   ,X
 0187 08	      [4] 00222	         inx
 0188 FF 016D	      [6] 00223	         stx    CopyD_
 018B 7A 0170	      [6] 00224	         dec    CopyC_+1
 018E 26 E9 (0179)    [4] 00225	         bne    Copy1_
 0190 7D 016F	      [6] 00226	         tst    CopyC_
 0193 27 05 (019A)    [4] 00227	         beq    Copy2_
 0195 7A 016F	      [6] 00228	         dec    CopyC_
 0198 26 DF (0179)    [4] 00229	         bne    Copy1_
 019A 39	      [5] 00230	Copy2_   rts

The 6800 code does not use variables in the direct page; if it did, it would take one fewer cycle for each instruction which read or wrote a variable (Copy?_)

Edit: …and for the 8080:

 0000			  00001	BlkMov:
 0000 2A 0017	     [16] 00002		lhld	Src
 0003 EB	      [4] 00003		xchg
 0004 2A 0019	     [16] 00004		lhld	Count
 0007 44	      [5] 00005		mov	B,H
 0008 4D	      [5] 00006		mov	C,L
 0009 2A 0015	     [16] 00007		lhld	Dest
 			  00008
 000C			  00009	Loop:
 000C 1A	      [7] 00010		ldax	D
 000D 77	      [7] 00011		mov	M,A
 000E 23	      [5] 00012		inx	H
 000F 13	      [5] 00013		inx	D
 0010 0B	      [5] 00014		dcx	B
 0011 C2 000C	     [10] 00015		jnz	Loop
 			  00016
 0014 C9	     [10] 00017		ret

Further edit; and for the AVR, the controller on the arduino:

 000000 9610	      [2] 00005	Copy:	adiw	R26,0
 000001 F041=00000A [1/2] 00006		breq	Copy2
 000002 E161	      [1] 00007		ldi	R22,high(SRAM_START+SRAM_SIZE)
 000003 30E0	      [1] 00008		cpi	R30,low(SRAM_START+SRAM_SIZE)
 000004 07F6	      [1] 00009		cpc	R31,R22
 000005 F428=00000B [1/2] 00010		brcc	Copy3
 000006 9161	      [2] 00011	Copy1:	ld	R22,Z+
 000007 9369	      [2] 00012		st	Y+,R22
 000008 9711	      [2] 00013		sbiw	R26,1
 000009 F7E1=000006 [1/2] 00014		brne	Copy1
 00000A 9508	      [2] 00015	Copy2:	ret
 00000B 9165	      [3] 00016	Copy3:	lpm	R22,Z+
 00000C 9369	      [2] 00017		st	Y+,R22
 00000D 9711	      [2] 00018		sbiw	R26,1
 00000E F7E1=00000B [1/2] 00019		brne	Copy3
 00000F 9508	      [2] 00020		ret

Edit once again; and the the champion of the 8-bitters, the 6809;

    1   0000 FE   0015     Copy    ldu    Src		; 6 cycles
    2   0003 10BE 0017             ldy    Dest		; 7 cycles
    3   0007 BE   0019             ldx    Count		; 6 cycles
    4   000A 27   08               beq    Done		; 2 cycles
    5                      
    6   000C A6   C0       Loop    lda    ,U+		; 4+2 cycles
    7   000E A7   A0               sta    ,Y+		; 5+2 cycles
    8   0010 30   1F               leax   -1,X		; 4+1 cycles
    9   0012 26   F8               bne    Loop		; 2 cycles
   10                      
   11   0014 39            Done    rts			; 5 cycles

I do not have one for the 8080/Z80 handy, but it can use BC, DE and HL to indirectly access memory, so it would use a lot less memory access switching pointers.

Looking at the 6800 code now, I could replace

 018B 7A 0170	      [6] 00224	         dec    CopyC_+1
 018E 26 E9 (0179)    [4] 00225	         bne    Copy1_
 0190 7D 016F	      [6] 00226	         tst    CopyC_
 0193 27 05 (019A)    [4] 00227	         beq    Copy2_
 0195 7A 016F	      [6] 00228	         dec    CopyC_
 0198 26 DF (0179)    [4] 00229	         bne    Copy1_

with something like (not correct code, just similar instructions to what would be needed):

 0179 FE 016B	      [5] 00216	Copy1_   ldx    CopyS_
 017E 08	      [4] 00218	         inx
 017F FF 016B	      [6] 00219	         stx    CopyS_
 0198 26 DF (0179)    [4] 00229	         bne    Copy1_

Edit: looking back at it, I would not do the replacement. In 255 out of 256 cases, the branch at the second instruction would be taken back to the top of the loop after only 10 cycles.

Further edit: CopyC_+1 could be kept in the B register for a savings of 4 cycles each time around the inner loop.

Looking back at the block copy routines, the 6800 is the worst processor in this particular case. The lack of a second index register severely hurt it. I do not have a version of the code for the 6809, but it should do much better since it has two additional index registers.

The 6502 does very well as the zero page locations can be effectively used for indexing. The 8080 did OK because its register pairs can be used for indexing.

In terms of raw speed, the AVR shines. It has many registers and several of them have fancy autoincrement modes. But it has a very limited amount of static RAM, so a general purpose computer it is not.

Edit: It appears that the 6502, with its funky indirect indexed addressing mode, managed to beat the 6809 by 2 cycles in the inner loop of a block copy.

An interesting exercise for another day is to compare how each one does implementing the FORTH inner interpreter.

Many of these chips have as much or more then the 70’s and 80’s general purpose computers. Some as much as 256 kbytes of RAM.