Sweet 16 was created by Steve Wozniak to reduce code size and make it easier to handle 16-bit pointers and arithmetic for his Apple Integer BASIC. He wrote it around 1977 and referred to it in an article as The 6502 Dream Machine. I have ported this to the Commodore VIC-20 so that we can play with it on the Vic.
Outline of Sweet 16
Sweet 16 is described as a pseudo-machine interpreter, which is probably a better description than a virtual machine because it shares native memory for storage and is designed to switch back and forth between itself and native 6502 code. The pseudo-machine has sixteen 16-bit registers with five of them having a defined function.
- R0 Accumulator
- R12 Subroutine return stack pointer
- R13 Comparison instruction results
- R14 Status register
- R15 Program counter
To execute Sweet 16 code you JSR
to an address within it which will save the registers and status then execute the following bytes in memory as Sweet 16 code until it reaches the Sweet 16 RTN
instruction which will restore the registers and status and continue executing the native 6502 that follows it.
The code is pretty compact and only takes around 300 bytes, it could have been made a little more compact and faster but it had to reside in ROM and therefore couldn't use self-modifying code.
Instruction Set
Register Operations | Nonregister Operations | ||||
---|---|---|---|---|---|
00 | RTN | Return to 6502 mode | |||
1n | SET Rn | Constant set | 01 | BR ea | Branch always |
2n | LD Rn | Load | 02 | BNC ea | Branch if No Carry |
3n | ST Rn | Store | 03 | BC ea | Branch if Carry |
4n | LD @Rn | Load indirect | 04 | BP ea | Branch if Plus |
5n | ST @Rn | Store indirect | 05 | BM ea | Branch if Minus |
6n | LDD @Rn | Load double indirect | 06 | BZ ea | Branch if Zero |
7n | STD @Rn | Store double indirect | 07 | BNZ ea | Branch if NonZero |
8n | POP @Rn | Pop indirect | 08 | BM1 ea | Branch if Minus 1 |
9n | STP @Rn | Store Pop indirect | 09 | BNM1 ea | Branch if Not Minus 1 |
An | ADD Rn | Add | 0A | BK | Break |
Bn | SUB Rn | Subtract | 0B | RS | Return from Subroutine |
Cn | POPD @Rn | Pop double indirect | 0C | BS ea | Branch to Subroutine |
Dn | CPR Rn | Compare | 0D | Unassigned | |
En | INR Rn | Increment | 0E | Unassigned | |
Fn | DCR Rn | Decrement | 0F | Unassigned |
There is an implicit Accumulator argument for most of the Register Operations and the Branch instructions all use relative addressing. The instruction set is well thought out with the ability to use 8-bit bytes or 16-bit words and helpfully instructions like the indirect instructions increment/decrement the register being used before/after use as appropriate. For a full description of the instructions take a look at the Byte Magazine Volume 02 Number 11 - November 1977 article.
Porting Sweet 16 to the Commodore VIC-20
The original Sweet 16 source code has been published a number of times including in Byte Magazine Volume 02 Number 11 - November 1977. In this article Steve Wozniak encourages users to modify Sweet 16 and even to port it to other processors, so hopefully he won't mind its being ported to the Vic. However, despite this and the fact that the code is widely available, it is still probably under copyright and therefore the port relies on patching the original source code rather than supplying a complete version.
I can't claim too much credit for porting it as beyond deciding on a suitable location for the registers in the zero page and aligning the code so that part of it fits into a single page, the majority of the work was converting the code to assemble using the XA assembler. Sweet16 needs 32 bytes in zero page for the registers, so I chose locations $2B-$4A. This will stop Basic programs from working, unless you save and restore those locations when needed, but does allow the Basic SYS
command to still execute machine code.
I have uploaded the patch and test program to github: sweet16_vic20. Full instructions for patching, assembling and running the code are provided in the README.
Example Code
Below are a few examples to show what Sweet 16 looks like in use.
Memory Region Copy
Copying a string to a new location with Sweet 16.
DST = $02BC ; Destination for memory copy
LEN = $0005 ; Length of memory to copy
copyHello jsr SW16 ; Execute following Sweet 16 code
.byt $11, <hello, >hello ; SET R1 Source Address
.byt $12, <DST, >DST ; SET R2 Destination Address
.byt $13, <LEN, >LEN ; SET R3 Length
.byt $41 ; LD @R1
.byt $52 ; ST @R2
.byt $F3 ; DCR R3
.byt $07, $FB ; BNZ -4
.byt $00 ; RTN
rts
hello .asc "HELLO" ; String to copy
16-bit Multiplication
Using Sweet 16 to calculate 255*100.
X = 255 ; The values to multiply
Y = 100
mulNums jsr SW16 ; Execute following Sweet 16 code
.byt $10 : .word 0000 ; SET R0 sum
.byt $11 : .word X ; SET R1 x
.byt $12 : .word Y ; SET R2 y
.byt $13 : .word result ; SET R3 result
.byt $F2 ; DCR R2
.byt $05, $03 ; BM 3
.byt $A1 ; ADD R1
.byt $01, $FA ; BR -5
.byt $73 ; STD @R3
.byt $00 ; RTN
rts
result .word $0000 ; The result of the multiplication
Two's Complement
Getting the two's complement of a number by using Sweet 16 to load the value, then returning to 6502 code to call a routine to one's complement the value, then finally going back to Sweet 16 to add one to the value.
A = $0010 ; The value to two's complement
negate jsr SW16 ; Execute following Sweet 16 code
.byt $11 : .word A ; SET R1 a
.byt $13 : .word result ; SET R3 result
.byt $21 ; LD R1
.byt $73 ; STD @R3
.byt $00 ; RTN
jsr notRes ; One's complement RESULT
jsr SW16 ; Continue executing Sweet 16 code
.byt $C3 ; POPD @R3
.byt $E0 ; INR R0
.byt $73 ; STD @R3
.byt $00 ; RTN
; One's complement RESULT
notRes lda result
eor #$FF
sta result
lda result+1
eor #$FF
sta result+1
rts
result .word $0000 ; The result of the operation
Video Demonstrating Sweet 16
You can see Sweet 16 running on the Vic in the following video: