Thursday, May 9, 2024

Running SWEET16, Steve Wozniak's "The Dream Machine" from WozMon


The story goes that Steve Wozniak was running out of space in Apple Integer BASIC, and out of necessity, determination, and will power, developed SWEET16 to do 16-bit operations in a more compact fashion.  There is already a great article on SWEET16, the interpretive processing 16-bit supplement to 6502 machine language programs, over at 6502.org.  The article tells the history, instructs how to port, and provides a tutorial on how to use it.  (And see article on Wikipedia, and Steve Wozniak's 1977 article in Byte magazine).

My article will follow the steps to produce a binary to run on my minimal 6502 with MC6850 UART, and run through a few examples to use it together with the Apple-1 Hex Monitor, ported from listings included in the Apple-1 Operation Manual (1976).  The Hex Monitor (or WozMon as people generally refer to it) is the original 256-byte ROM included with the Apple-1 computer.  It only needed to include display memory, edit memory, and run from memory operations to initiate control of that computer.

This article and efforts are inspired by videos produced by Ben Eater and his 6502 breadboard computer.  I wanted to do something similar, and followed instructions he provided to do something similar with my 6502 emulator, but simpler.   

I already had a 6502 emulator.  One version is in C++, is very cross-platform (Windows, Linux, Mac, Arduino, STM32, etc.).  But I had focused on Commodore emulation to match the computers I grew up with learning to program.  While that is working well, it is using Commodore ROMs and some I/O emulation. 

Also inspirational are similar videos produced by Michael Cartwright for his Hopper development environment and 6502 breadboard and PCB computer, inspired by Ben Eater's 6502 breadboard computer.  Michael (sillycowvalley on github) uses the MC6850 UART instead of the MC6821 PIA used in Apple-1, or MOS6522 VIA or MOS6551 UART used by Ben Eater.

I surely didn't want to bit bang serial, and wanted it as simple as possible.  Reviewing resources on the web, including datasheets, it was apparent the MC6850 has an advantage of being rather minimalistic.  It has one data register (read/write), one control register (write), and one status register (read).  While originally intended for use with 6800 series microprocessors, it can be used with others.  Only caveat is that the status is not designed for the 6502 BIT instruction.  But the advantage is that it doesn't have a status bug like the 6551 which intended to replace it.

So, I sat down and added a minimal emulation profile to my emulator including configurable RAM/ROM, and I/O address for the MC6850.  My initial implementation supports polling but not interrupts, which is fine for our purposes today.  It does not include a parallel I/O chip, so no hooking up virtual LEDs.  Wait, what?  Oh yeah, we don't need those (yet?), we exclusively have a terminal console.   But that also means we don't have a way to load or save programs.

Also imagine 60K RAM and 4K ROM here (oops!)

I ported WozMon to the 6502/MC6850 minimum profile so it will serve as our launching off point, similar to how it was utilized on the Apple-1.  Porting required changing the assembler syntax to match my 6502 assembler of choice (ACME), especially note that all the hex values in the file should include # for immediate mode (if extracting from Usenet article instead of Byte article), DFB must stand for define byte, and you do need to include some save/restore register routines.

An advantage of having a minimal system unlike Commodore, is that the 32 consecutive zero-page bytes necessary for SWEET16 are guaranteed to be available because it is a minimal system, addresses are not used by IRQ routines, keyboard handlers, timers, KERNAL I/O, etc.

Steps

1. Clone (or download Zip of) the emulator c-simple-6502-cbm.  Note it is not necessary to gather the Commodore ROMs this time around, because we don't need them.

2. Compile using Visual Studio, or make/gcc (Makefile included), or similar.

3. Look for wozmon.bin in the roms/minimal folder (its ported source is in child src folder).

4. Launch the minimal profile

    c-simple-6502-cbm.exe 1 roms/minimal/wozmon.bin

Now WozMon should be running.  You can review the Apple-1 Operation Manual (1976) for more detailed instructions on how to use WozMon.  But if you're quick, you can follow along with the required inputs here.

5. Paste SWEET16 binary into WozMon.   See copyright at start of article, included here for educational purposes, trusting this is fair use and for your own personal use.  Not for commercial use.

# SWEET16 compiled for EE80 start address
EE70: 20 EA EE 68 85 1E 68 85
EE78: 1F 20 7F EE 4C 79 EE E6
EE80: 1E D0 02 E6 1F A9 EF 48
EE88: A0 00 B1 1E 29 0F 0A AA
EE90: 4A 51 1E F0 0B 86 1D 4A
EE98: 4A 4A A8 B9 C8 EE 48 60
EEA0: E6 1E D0 02 E6 1F BD CB
EEA8: EE 48 A5 1D 4A 60 68 68
EEB0: 20 F6 EE 6C 1E 00 B1 1E
EEB8: 95 01 88 B1 1E 95 00 98
EEC0: 38 65 1E 85 1E 90 02 E6
EEC8: 1F 60 00 F7 02 9B 0B 9C
EED0: 23 AD 14 B0 45 B7 4F BE
EED8: 2D C7 59 D0 83 DB 6C 03
EEE0: 31 E6 6E 91 1C E5 63 E5
EEE8: E5 E5 85 20 86 21 84 22
EEF0: 08 68 85 23 D8 60 A5 23
EEF8: 48 A5 20 A6 21 A4 22 28
EF00: 60 10 B3 B5 00 85 00 B5
EF08: 01 85 01 60 A5 00 95 00
EF10: A5 01 95 01 60 A5 00 81
EF18: 00 A0 00 84 1D F6 00 D0
EF20: 02 F6 01 60 A1 00 85 00
EF28: A0 00 84 01 F0 ED A0 00
EF30: F0 06 20 64 EF A1 00 A8
EF38: 20 64 EF A1 00 85 00 84
EF40: 01 A0 00 84 1D 60 20 24
EF48: EF A1 00 85 01 4C 1D EF
EF50: 20 15 EF A5 01 81 00 4C
EF58: 1D EF 20 64 EF A5 00 81
EF60: 00 4C 41 EF B5 00 D0 02
EF68: D6 01 D6 00 60 A0 00 38
EF70: A5 00 F5 00 99 00 00 A5
EF78: 01 F5 01 99 01 00 98 69
EF80: 00 85 1D 60 A5 00 75 00
EF88: 85 00 A5 01 75 01 A0 00
EF90: F0 E9 A5 1E 20 17 EF A5
EF98: 1F 20 17 EF 18 B0 0E B1
EFA0: 1E 10 01 88 65 1E 85 1E
EFA8: 98 65 1F 85 1F 60 B0 EC
EFB0: 60 0A AA B5 01 10 E8 60
EFB8: 0A AA B5 01 30 E1 60 0A
EFC0: AA B5 00 15 01 F0 D8 60
EFC8: 0A AA B5 00 15 01 D0 CF
EFD0: 60 0A AA B5 00 35 01 49
EFD8: FF F0 C4 60 0A AA B5 00
EFE0: 35 01 49 FF D0 B9 60 A2
EFE8: 18 20 64 EF A1 00 85 1F
EFF0: 20 64 EF A1 00 85 1E 60
EFF8: 4C AE EE 00 00 00 00 00

Note: SWEET16 entry point for this port is $EE70, and the operations are in a single page $EF01-$EFFA.   A save registers routine is at $EEEA and restore registers is at $EEF6.   Zero page usage is $00-$1F for the SWEET16 registers, and $20-$23 for saving 8-bit registers.

6. Let's try it out.  Start by pasting the following to enter a program at 

1000: 20 00 FE A9 2A 20 70 EE 
1008: 11 12 34 12 56 78 21 A2
1010: 33 00 8D F8 FF 00

Which is equivalent to

20 00 FE JSR $FE00
A9 2A    LDA #$2A
20 70 EE JSR $EE70
11 12 34 SET R1,$3412
12 56 78 SET R2,$7856
21       LD  R1 
A2       ADD R2
33       ST  R3
00       RTN
8D F8 FF STA $FFF8
00       BRK

JSR $EE70 switches to SWEET16 interpretation, and these are SWEET16 instructions through RTN, before being back in 6502.  The vectors are set such that a BRK will return to WozMon. ($FE00 is my specific UART initialization routine for MC6850, and storing a byte to $FFF8 will send it across the UART).

All this program does is set the R1 and R2 16-bit registers to constant values, the LD instruction loads the 16-bit accumulator from R1, then ADD R2 adds R2 to the accumulator, and the result is stored in R3 by the ST instruction.   RTN returns to normal 6502 operation.

7. To run the program, enter the following into WozMon:

1000R

8. Display the results by entering

0.1FR

From the last two steps the output should look something like the following (blank lines added for separation)

1000R

1000: 20*\


0.1F

0000: 68 AC 12 34 56 78 68 AC
0008: 00 00 00 00 00 00 00 00
0010: 00 00 00 00 00 00 00 00
0018: 00 00 00 00 00 06 12 10

And the accumulator (R0) at addresses 0 and 1 now has the value $AC68 which appears to be the sum of the other two 16-bit values, with a copy also stored at R3 (addresses 6/7).  The asterisk is printed to demonstrate registers being saved/restored and 6502 operation returning to normal.

That's just a quick example.   Hopefully you've got a little taste of WozMon and SWEET16. 

Example run of SWEET16

But that's not all!  

I made a Commodore port of WozMon, so why not also port SWEET16 to Commodore and run it there too?

# Commodore port of SWEET16
1540: 85 22 86 23 84 24 08 68
1548: 85 25 d8 60 a5 25 48 a5
1550: 22 a6 23 a4 24 28 60 00
1558: 00 00 00 00 00 00 00 00
1560: 00 00 00 00 00 00 00 00
1568: 00 00 00 00 00 00 00 00
1570: 00 00 00 00 00 00 00 00
1578: 00 00 00 00 00 00 00 00
1580: 20 40 15 68 85 7e 68 85
1588: 7f 20 8f 15 4c 89 15 e6
1590: 7e d0 02 e6 7f a9 16 48
1598: a0 00 b1 7e 29 0f 0a aa
15a0: 4a 51 7e f0 0b 86 7d 4a
15a8: 4a 4a a8 b9 d8 15 48 60
15b0: e6 7e d0 02 e6 7f bd db
15b8: 15 48 a5 7d 4a 60 68 68
15c0: 20 4c 15 6c 7e 00 b1 7e
15c8: 95 61 88 b1 7e 95 60 98
15d0: 38 65 7e 85 7e 90 02 e6
15d8: 7f 60 00 f7 02 9b 0b 9c
15e0: 23 ad 14 b0 45 b7 4f be
15e8: 2d c7 59 d0 83 db 6c 03
15f0: 31 e6 6e 91 1c e5 63 e5
15f8: e5 e5 00 00 00 00 00 00
1600: 00 10 c3 b5 60 85 60 b5
1608: 61 85 61 60 a5 60 95 60
1610: a5 61 95 61 60 a5 60 81
1618: 60 a0 00 84 7d f6 60 d0
1620: 02 f6 61 60 a1 60 85 60
1628: a0 00 84 61 f0 ed a0 00
1630: f0 06 20 64 16 a1 60 a8
1638: 20 64 16 a1 60 85 60 84
1640: 61 a0 00 84 7d 60 20 24
1648: 16 a1 60 85 61 4c 1d 16
1650: 20 15 16 a5 61 81 60 4c
1658: 1d 16 20 64 16 a5 60 81
1660: 60 4c 41 16 b5 60 d0 02
1668: d6 61 d6 60 60 a0 00 38
1670: a5 60 f5 60 99 60 00 a5
1678: 61 f5 61 99 61 00 98 69
1680: 00 85 7d 60 a5 60 75 60
1688: 85 60 a5 61 75 61 a0 00
1690: f0 e9 a5 7e 20 17 16 a5
1698: 7f 20 17 16 18 b0 0e b1
16a0: 7e 10 01 88 65 7e 85 7e
16a8: 98 65 7f 85 7f 60 b0 ec
16b0: 60 0a aa b5 61 10 e8 60
16b8: 0a aa b5 61 30 e1 60 0a
16c0: aa b5 60 15 61 f0 d8 60
16c8: 0a aa b5 60 15 61 d0 cf
16d0: 60 0a aa b5 60 35 61 49
16d8: ff f0 c4 60 0a aa b5 60
16e0: 35 61 49 ff d0 b9 60 a2
16e8: 18 20 64 16 a1 60 85 7f
16f0: 20 64 16 a1 60 85 7e 60
16f8: 4c be 15

This SWEET16 entry point for Commodore changed to $1580 so the sample program has to change to call this entry point instead, to call $FFD2 for screen output instead of using the UART, and jump to WozMon entry at end instead of BRK.   Note that this Commodore port puts the SWEET16 registers at $60 (instead of $00) in zero page.   The Commodore port was trivial, disassembly will show there's nothing secret going on, mostly moving around stuff in zero page, and moving the save/load registers code.

1000: A9 2A 20 80 15 11 12 34
1008: 12 56 78 21 A2 33 00 20
1010: D2 FF 4C 00 14
 
Vic-20 running WozMon and SWEET16

C64 running WozMon and SWEET16

It appears there's a bug restoring the registers.  That is not an asterisk!!!   But an underscore.   Exercise left for another day...   Update: I had replaced the call to UART_INIT from the other system with starting SWEET16 followed by 6502 code.  Bad, so bad (see screenshot for bad code).  Have since updated the sample program to be correct.   Disassembling here to double check.   The asterisk is now showing up fine!  Would have been doing some crazy stuff interpreting 6502 opcodes as SWEET16.

A9 2A    LDA #$2A
20 80 15 JSR $1580
11 12 34 SET R1,$3412
12 56 78 SET R2,$7856
21       LD  R1 
A2       ADD R2
33       ST  R3
00       RTN
20 D2 FF JSR $FFD2
4C 00 14 JMP $1400


3 comments:

  1. I think it's great you're porting Apple code to the C64. So much gold to mine there. But what does Sweet16 do? It just mentions "do 16 bit operations".

    ReplyDelete
    Replies
    1. SWEET16 is an interpreter of made up instructions that Steve Wozniak found useful to do 16-bit math, pointer stuff, etc. without the overhead of doing multiple 8-bit instructions. The example I provide calls into SWEET16 mode, initializes two registers, loads into the accumulator (R0), performs addition, and stores in R3, returning to 6502 mode, all in 13 bytes. It would have taken a lot more 6502 to do the same instructions.

      Delete
    2. I assume the trade-off is doing this in Sweet16 uses more cycles?

      Delete