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


I am still looking into the TI 9900 and 99/4A. Some disturbing things are:

  • Incrementing and decrementing affects the carry flag unlike the behavior of most other processors. This wreaks havoc with multi-precision arithmetic code.
  • The TI assembler is brain-damaged with regard to the precedence of arithmetic operators. Multiplication and division are the same level as addition and subtraction. At least I can parenthesize 1+(2*3) but I should not have to.



Would this one work?


A stock 99/4A without some kind of memory expansion will not have enough RAM.


Work continues on the run-time libraries for the other processors.

The AddRef subroutine increments the reference count for an object allocated on the heap. It is interesting to see the relative strengths and weaknesses of the several architectures.

The code for the 6502:

 05C3			  00852	AddRef
 05C3 A5 16	      [3] 00853		lda	PtrA			; Invalid if outside of the heap
 05C5 38	      [2] 00854		sec
 05C6 E9 13	      [2] 00855		sbc	#Heap&$FF
 05C8 A5 17	      [3] 00856		lda	PtrA+1
 05CA E9 3C	      [2] 00857		sbc	#Heap>>8
 05CC 90 17 (05E5)  [2/3] 00858		bcc	AddRef_Done		; Do not attempt to count if invalid
 05CE A5 16	      [3] 00860		lda	PtrA			; Point to arena header of the block
 			  00861	;	sec
 05D0 E9 05	      [2] 00862		sbc	#5
 05D2 85 1A	      [3] 00863		sta	Ptr1
 05D4 A5 17	      [3] 00864		lda	PtrA+1
 05D6 E9 00	      [2] 00865		sbc	#0
 05D8 85 1B	      [3] 00866		sta	Ptr1+1
 05DA A0 04	      [2] 00868		ldy	#4			; Get reference count
 05DC B1 1A	    [5/6] 00869		lda	(Ptr1),Y
 05DE 18	      [2] 00870		clc				; Increment the count
 05DF 69 01	      [2] 00871		adc	#1
 05E1 F0 02 (05E5)  [2/3] 00872		beq	AddRef_Done		; If it was the max, leave it there
 05E3 91 1A	      [6] 00874		sta	(Ptr1),Y
 05E5			  00876	AddRef_Done
 05E5 60	      [6] 00877		rts

The code for the 8080:

 02C6			  00757	AddRef
 02C6 2A 003E	     [16] 00758		lhld	PtrA			; Load address of the block
 02C9 7D	      [5] 00760		mov	A,L			; Invalid if outside of the heap
 02CA D6 03	      [7] 00761		sui	Heap and 0FFh
 02CC 7C	      [5] 00762		mov	A,H
 02CD DE 04	      [7] 00763		sbi	Heap shr 8
 02CF D8	   [5/11] 00765		rc				; Do not attempt to count if invalid
 02D0 2B	      [5] 00767		dcx	H			; Point to the reference count
 02D1 7E	      [7] 00769		mov	A,M			; Increment reference count
 02D2 3C	      [5] 00770		inr	A
 02D3 C8	   [5/11] 00772		rz				; If it was the max, leave it there
 02D4 77	      [7] 00774		mov	M,A			; Update reference count
 02D5 C9	     [10] 00776		ret

The code for the 6800:

 0261			  00437	AddRef
 			  00438	 ; Note: 6800 cpx instruction faulty!
 0261 96 15	      [3] 00439	         ldaa   PtrA+1    ; Invalid if outside of the heap
 0263 80 F7	      [2] 00440	         suba   #Heap&$FF
 0265 96 14	      [3] 00441	         ldaa   PtrA
 0267 82 02	      [2] 00442	         sbca   #Heap>>8
 0269 25 0A (0275)    [4] 00444	         bcs    AddRef_Done ; Do not attempt to count if invalid
 026B DE 14	      [4] 00446	         ldx    PtrA      ; Point to the reference count
 026D 09	      [4] 00447	         dex
 026E A6 00	      [5] 00449	         ldaa   ,X        ; Increment reference count
 0270 4C	      [2] 00450	         inca
 0271 27 02 (0275)    [4] 00452	         beq    AddRef_Done ; If it was the max, leave it there
 0273 A7 00	      [6] 00454	         staa   ,X
 0275			  00456	AddRef_Done
 0275 39	      [5] 00457	         rts

The code for the 6809:

 023F				  00406	AddRef
 023F 9E 14		      [5] 00407	         ldx    PtrA      ; Load address of the block
 0241 8C 02CF		      [4] 00409	         cmpx   #Heap     ; Invalid if outside of the heap
 0244 25 07 (024D)	      [3] 00410	         bcs    AddRef_Done ; Do not attempt to count if invalid
 0246 A6 1F		      [5] 00412	         ldaa   -1,X      ; Increment reference count
 0248 4C		      [2] 00413	         inca
 0249 27 02 (024D)	      [3] 00415	         beq    AddRef_Done ; If it was the max, leave it there
 024B A7 1F		      [5] 00417	         staa   -1,X
 024D				  00419	AddRef_Done
 024D 39		      [5] 00420	         rts

The code for the AVR:

 0000B4			  00158	AddRef:
 0000B4 91E0 0100     [2] 00159		lds	R30,PtrA		; Load address of the block
 0000B6 91F0 0101     [2] 00160		lds	R31,PtrA+1
 0000B8 E061	      [1] 00162		ldi	R22,high(Heap)		; Invalid if outside of the heap
 0000B9 30EC	      [1] 00163		cpi	R30,low(Heap)
 0000BA 07F6	      [1] 00164		cpc	R31,R22
 0000BB F428=0000C1 [1/2] 00166		brcc	AddRef_Done		; Do not attempt to count if invalid
 0000BC 9731	      [2] 00168		sbiw	R30,1			; Point to the reference count
 0000BD 8010	      [1] 00170		ld	R1,Z			; Increment reference count
 0000BE 9413	      [1] 00171		inc	R1
 0000BF F009=0000C1 [1/2] 00173		breq	AddRef_Done		; If it was the max, leave it there
 0000C0 8210	      [1] 00175		st	Z,R1			; Update reference count
 0000C1			  00177	AddRef_Done:
 0000C1 9508	      [2] 00178		ret


The following is a full assembler/99/4A dev tool set which has a lot of enhancements over the standard 99/4A assembler. The good thing about this that it is open source and written in…python.

If you wanted you could change the expression parser so it enforces the standard operator priority instead of the current left-to-right evaluation priority, thus eliminating the requirements for explicit parentheses. Of course, then the your 9900 assembly code would only be valid with your modified assembler.



Most development can be done with an emulator (see above post for emulator link) and cross-assembler. For doing testing on actual hardware the silver/black console is desired because it will recognize software cartridges with ROM only (no TI GROMs needed as with the beige console).

BTW, Atariage is the forum for discussion and questions related to all modern 99/4A development (hardware and software) - which is still taking place. There will be someone from that forum who can answer almost any technical detail related to the 99/4A or the 9900. You would like find several users on that forum who would jump at the chance to do some testing of any 9900 port. If you want to move forward with this, PM me and I can recommend some of the power users.

I will be receiving some additional 99/4A hardware this week, which I believe includes some full systems - console + PEB (Peripheral Expansion Box) w/memory expansion and floppy drives. There are also some newer peripherals which integrate the 32K memory expansion, floppy emulated on compact flash, as well as serial port. With this much smaller peripheral you don’t need the PEB, which is good as the PEB is quite large.

There is also a board available that allows a USB (including wireless) keyboard to be used instead of the built-in keyboard. With this, you can then shove the console+peripheral board to the side and use a KVM switch.


The 99/4A outputs composite video at 256x192 resolution, so you’ll need some kind of line doubler box to use it with a VGA monitor. Another option is to install a 3rd party replacement, the F18A. The original version outputs analog VGA, but there is a new version coming that outputs HDMI.


If you can support page RAM, there are several boards with (corresponding psuedo-standards) that support more than 32K of RAM. Two of the most popular are the Horizon ramdisk and SupeRAMs boards. Most of the emulators support these boards, most development can be done without the actual hardware.

One of the best sources of technical information on the TI 99/4A has been compiled by Thierry Nouspikel, and can be found on his TI-99-4A Tech Pages. There is detailed here info on all of the 99/4A chips and peripherals, including several that Thierry designed himself. He is truly a 99/4A savant, who started is 99/4A activities while he was a pre-med student. He is currently doing DNA-related research. You will be amazed at the depth and breadth of his 99/4A information.


If you are interested in doing a 99/4A port of your python project, I will donate the necessary 99/4A hardware needed for testing after you have everything working on the emulator. This would include: console, peripherals, keyboard adapter, line doubler/F18A. With this you could then use a KVM if that is more convenient.


I am seeking a new place for discussion about the Python compiler project.

The options are:

  • Resurrect a Wordpress blog I have not used for 10 years. Advantages are you do not have to register to post comments and I have full control over the formatting. Disadvantage is people will not easily find it.
  • Create a thread on one of the several forums catering to vintage computer enthusiasts. Advantage is more exposure to people who may be interested in it. Disadvantage is you have to register an account to comment.
  • Something else I am not seeing?



This is one of the things “going on at DMS” that I like to vicariously imagine myself to be a part of. I have no idea what you’re doing, but every once in a while, I page through your log and actually understand something. This is why I really like the Discourse arrangement; this is a project I get to feel connected to, even though it’s well outside my ability. I regret mightily that you are seeking another host for your log, but please post here whenever you choose a venue, as I would love to get to continue my voyeurism.


Would hate to see it move. I’m not a programmer, but I’ve enjoyed following the progression.

If you’re looking for a home for it, try here: http://www.vcfed.org/forum/forum.php



How about VCC’s hackaday or patreon?


Does hackaday offer potentially higher exposure than say, vcfed?


Implementation and testing is done on the heap manager for the 8080. Though the processor can use its register pairs as pointers to access memory, it has no indexing capability, meaning it cannot temporarily add an offset to the address in the register. That limitation made the heap code quite cumbersome.

Hence, I am adding the Z80 as another target since it has two index registers the 8080 lacks. Modifying an 8080 disassembler to generate Z80 syntax was not very difficult; making an 8080 assembler parse Z80 syntax is proving to be a challenge.

Work also continues on the heap manager for the 6800. I was initially drawn to Motorola microprocessors due to the fact that a usable computer from SWTPC can be had somewhat cheaper than an equivalent one based on an 8080 or Z80.

The “68” and “80” communities both thought their processor was superior. Writing very similar programs for both at the same time is a very enlightening way to appreciate both the strengths and weaknesses of each one. So far, the one index register of the 6800 is a joy to use compared with the three nonindexable ones of the 8080.

Also on deck is the AVR. As I have said before, this one is slow going because all of my experience with this platform in the last three years has been in C on an Arduino. I am having to relearn everything.

This effort so far has really validated the value of working on multiple platforms at the same time instead of serially. While heap management algorithms are still fresh, I will be implementing the same for the additional platforms of x86, 68K, 9900 and ARM on the Raspberry Pi. Still on the bubble are the 1802 and MSP430.


How else to spend a rainy day than study the source code of my FORTH interpreter and BASIC compiler for the AVR microcontroller? :slight_smile:

Most of you will not know this since Allen is the only other person I am aware of who will admit to programming an AVR in assembly language.

The AVR has what is known as a Harvard architecture. That is, it has separate code and data memory address spaces. It is a natural since the code is stored in flash memory while the data is in conventional RAM.

The memory maps of an AVR look like this:

0 : data address space
| Registers and I/O | free RAM |
                    |<-- 2K -->|

0 : code address space
| Interrupt vectors | program code | unused |
|<------------------ 32K ------------------>|

The problem is that the AVR 328p as used in an Arduino has only 2 KBytes of RAM.

FORTH, BASIC and Python all have a large amount of data which is not changed as the program runs.

The obvious solution is to put it in among the 32 KBytes of flash memory. The problem is that there are two different flavors of pointers, data and code.

The GCC tool set used in the Arduino IDE addresses :slight_smile: the problem with a somewhat hairy set of compiler directives. The gory details if you are bored and care to read about it:


FORTH, BASIC and Python have no provision for dealing with this problem.

Since I have complete control over memory layout, I have come up with a way to give the illusion of a common pointer. My modified memory map looks like this:

0 : data address space
| Registers and I/O | free RAM |
                    |<-- 2K -->|

0 : code address space
| Interrupt vectors | program code | optional padding | static data | unused |
|<----------------------------------- 32K ---------------------------------->|

As long as the static data is at a higher address than the end of the RAM, no additional padding is necessary; this is the case except for very simple programs.

Code to access data must distinguish between RAM and flash memory by comparing an address with the end of RAM, somewhat like this:

 000090 2F6E	      [1] 00107	PStr:	mov	R22,R30
 000091 2B6F	      [1] 00108		or	R22,R31
 000092 F099=0000A6 [1/2] 00109		breq	PStr2
 000093 E161	      [1] 00110		ldi	R22,high(SRAM_START+SRAM_SIZE)
 000094 30E0	      [1] 00111		cpi	R30,low(SRAM_START+SRAM_SIZE)
 000095 07F6	      [1] 00112		cpc	R31,R22
 000096 F480=0000A7 [1/2] 00113		brcc	PStr3
 000097 91C1	      [2] 00114		ld	R28,Z+
 000098 91D1	      [2] 00115		ld	R29,Z+
 000099 9181	      [2] 00116		ld	R24,Z+
 00009A 8190	      [1] 00117		ld	R25,Z
 00009B 2F68	      [1] 00118		mov	R22,R24
 00009C 2B69	      [1] 00119		or	R22,R25
 00009D F041=0000A6 [1/2] 00120		breq	PStr2
 00009E E161	      [1] 00121		ldi	R22,high(SRAM_START+SRAM_SIZE)
 00009F 30C0	      [1] 00122		cpi	R28,low(SRAM_START+SRAM_SIZE)
 0000A0 07D6	      [1] 00123		cpc	R29,R22
 0000A1 F460=0000AE [1/2] 00124		brcc	PStr4
 0000A2 9169	      [2] 00125	PStr1:	ld	R22,Y+
 0000A3 DFE5=000089   [3] 00126		rcall	Echo
 0000A4 9701	      [2] 00127		sbiw	R24,1
 0000A5 F7E1=0000A2 [1/2] 00128		brne	PStr1
 0000A6 9508	      [2] 00129	PStr2:	ret
 0000A7 91C5	      [3] 00130	PStr3:	lpm	R28,Z+
 0000A8 91D5	      [3] 00131		lpm	R29,Z+
 0000A9 9185	      [3] 00132		lpm	R24,Z+
 0000AA 9194	      [3] 00133		lpm	R25,Z
 0000AB 2F68	      [1] 00134		mov	R22,R24
 0000AC 2B69	      [1] 00135		or	R22,R25
 0000AD F3C1=0000A6 [1/2] 00136		breq	PStr2
 0000AE 01FE	      [1] 00137	PStr4:	movw	R30,R28
 0000AF 9165	      [3] 00138	PStr5:	lpm	R22,Z+
 0000B0 DFD8=000089   [3] 00139		rcall	Echo
 0000B1 9701	      [2] 00140		sbiw	R24,1
 0000B2 F7E1=0000AF [1/2] 00141		brne	PStr5
 0000B3 9508	      [2] 00142		ret

ld loads from data space whereas lpm loads from program space.


Heap managers are done for the 6800 and 6809.

Still working on the one for the AVR.

The Z80 assembler is finally working. It still needs work to detect and flag invalid input. The Z80 simulator needs work to disassemble the “new” Z80 instructions.

A serious issue has come up with the AVR which makes it impossible to use the official assembler and requires some ugly hacks in mine.

A string is currently defined to look like this in memory:

|		|		|		|
|      $81	|        length of string	|        characters of the string
|		|		|		|

The problem is when I want to put constant strings into flash memory. The official AVR assembler insists on putting things on an even address. That makes sense for machine instructions, but is quite limiting for compact data structures.


There turns out to be a solution to the problem - put everything on a single line.

Instead of trying to define the object type and the string contents separately:

 			  00017	.macro	msg
 			  00019	.db	strlen(@0)&$FF	; the length
 			  00020	.db	strlen(@0)>>8
 			  00021	.db	@0
 			  00023	.endm

 000486			  00295	S_True:
 000486 8100		  00296		.db	TYPE_STRING
 			  00297		msg	"True"
+000487 0400			.db	strlen("True")&$FF	; the length
+000488 0000			.db	strlen("True")>>8
+000489 54727565		.db	"True"

Create a macro to lay out a string object:

 			  00011	.macro	string
 			  00013	.db	TYPE_STRING,strlen(@0)&$FF,strlen(@0)>>8,@0
 			  00015	.endm


 000482			  00292	S_None:
 			  00293		string	"None"
+000482 8104004E6F6E		.db	TYPE_STRING,strlen("None")&$FF,strlen("None")>>8,"None"
+000485 6500


Currently giving the 6502 version some love in preparation for the next public demo release for the retrocomputing meeting on November 10.

Rewrote the parsing of if/else to handle if/elif/else.

Quite a bit has been added since the last release in July. The current feature list is:

  • Dynamic typing
  • Types: integer, string, True, False, function (partial)
  • Automatic memory management (using reference counting)
  • Variable precision integers
  • Integer operators +, -, *, //, %, &, ^, |
  • Variable length strings
  • String operators +, *
  • Relational operators ==, !=, <, <=, >, >=
  • if else elif
  • while else break continue
  • print function, including sep and end keyword arguments
  • input function
  • hardware access functions: peek, poke
  • randint function
  • int function
  • hex, oct and bin functions

Now working on right shift. Due to variable precision integers, this is not trivial.


A couple of other things worth a mention:

I have been reading this book:


I have remembered previous discussions about WebAssembly and think it deserves another look:


I regret that I have to raise the white flag for making another release by the retrocomputing meeting this weekend.

The work on right shift is taking longer than expected. There is no way I can get left shift working by Saturday. There is even not enough time for me to do the regression testing for another release.


Not much on this project has been easy.

Consider the following code:

""" locals.py
examples of local variables"""

A = 'This is the text message from main'

def test1():
    print('In test1.')
    print('Leaving test1.')

def test2():
    print('In test2.')
    A = "This is test2's text message."
    print('Leaving test2.')

def test3():
    print('In test3.')
    A = "This is test3's text message."
    print('Leaving test3.')

print('Back in main')
print('Back in main')

Running it yields:

In test1.
This is the text message from main
Leaving test1.
Back in main
This is the text message from main
In test2.
This is test2's text message.
Leaving test2.
Back in main
This is the text message from main
In test3.
Traceback (most recent call last):
  File "C:/Users/Bill/AppData/Local/Programs/Python/Python35-32/locals.py", line 29, in <module>
  File "C:/Users/Bill/AppData/Local/Programs/Python/Python35-32/locals.py", line 19, in test3
UnboundLocalError: local variable 'A' referenced before assignment

The error indicates that assigning to a variable in test3 blocks access to a global variable of the same name anywhere in the function, even before the assignment. What this means is that functions cannot be compiled in a single pass.


Notice anything?