SWEET16

The SWEET16 is a virtual CPU interpreter, written by Stephen Wozniak as part of the first Apple ][ Computer. It was removed on later machines but still is an interesting piece of software, originally implemented in just 393 bytes.

It was described in Byte Magazine Volume 02 Number 11 (1977) . Most of the opcode description was transscribed from there.

The source code published there has been adapted to better fit the Sorbus Computer.

Implementation differences

There are a few things that are done differently in the Sorbus port of SWEET16 as compared to the original.

  • SWEET16 is invoked using BRK #$10 instead of JSR $xxxx
  • BK does not execute a 6502 BRK, but triggers the TRAP to meta-mode (for details see BK description below)
  • when R12 is zero (stack for BS instruction), it will be initialized to $0100 upon startup of SWEET16

Special Registers

The SWEET16 has a focus on registers compared to the 6502 which is more focused on memory stack. Five of the sixteen 16-bit registers provided by SWEET16 have a special function bound to them.

Name Function
R0 accumulator
R12 subroutine stack pointer
R13 stores the result of all comparison operations for branch testing
R14 status register
R15 program counter

Register Ops

SET Rn

Name Opcode Argument Function
SET Rn, Constant $1n $lb $hb Set

The 2 byte constant is loaded into Rn (n = 0 to F, hexadecimal) and branch conditions set accordingly. The carry is cleared.

Example:

15 34 A0       SET   R5,$A034 ; R5 now contains $A034

LD Rn

Name Opcode Function
LD Rn $2n Load

The ACC (R0) is loaded from Rn and branch conditions set according to the data transferred. The carry is cleared and the contents of Rn are not disturbed.

Example:

15 34 A0       SET   R5,$A034
36             LD    R5       ; ACC now contains $A034

ST Rn

Name Opcode Function
ST Rn $3n Store

The ACC (R0) is stored into Rn and branch conditions set according to the data transferred. The carry is cleared and the ACC contents are not disturbed.

Example:

25             LD    R5       ; copy contents
36             ST    R6       ; of R5 to R6

LD @Rn

Name Opcode Function
LD @Rn $4n Load indirect

The low order ACC byte is loaded from the memory location whose address resides in Rn, and the high order byte is cleared. Branch conditions reflect the final ACC contents which will always be positive and never minus 1. The carry is cleared. After the transfer, Rn is incremented by 1.

Example:

15 34 A0       SET   R5,$A034
45             LD   @R5       ; ACC is loaded from memory location $A034
                              ; R5 is incremented to $A035

ST @Rn

Name Opcode Function
ST @Rn $5n Store indirect

The low order ACC byte is stored into the memory location whose address resides in Rn, and the high order byte is cleared. Branch conditions reflect the 2 byte ACC contents, which are not disturbed. The carry is cleared. After the transfer, Rn is incremented by 1.

Example:

15 34 A0       SET   R5,$A034 ; load pointers R5 and R6
16 22 90       SET   R6,$9022 ; with $A034 and $9022
45             LDD  @R5       ; move byte from location $A034
56             STD  @R6       ; to location $9022
                              ; both pointers are incremented

LDD @Rn

Name Opcode Function
LDD @Rn $6n Load double byte indirect

he low order ACC byte is loaded from the memory location whose address resides in Rn, and Rn is then incremented by 1. The high order ACC byte is loaded from the memory location whose address resides in the (incremented) Rn and Rn is again incremented by 1. The carry is cleared.

Example:

15 34 A0       SET   R5,$A034
65             LDD  @R5       ; the low order ACC byte is loaded from location $A034
                              ; the high order ACC byte from location $A035
                              ; R5 is incremented to $A036

STD @Rn

Name Opcode Function
STD @Rn $7n Store double byte indirect

The low order ACC byte is stored into the memory location whose address resides in Rn, and Rn is then incremented by 1. The high order ACC byte is stored into the memory location whose address resides in (the incremented) Rn and Rn is again incremented by 1. Branch conditions reflect the ACC contents which are not disturbed. The carry is cleared.

Example:

15 34 A0       SET   R5,$A034 ; load pointers R5 and R6
16 22 90       SET   R6,$9022 ; with $A034 and $9022
65             LDD  @R5       ; move double byte from locations $A034 and $A035
76             STD  @R6       ; to locations $9022 and $9023
                              ; both pointers are incremented by 2

POP @Rn

Name Opcode Function
POP @Rn $8n Pop indirect

The low order ACC byte is loaded from the memory location whose address resides in Rn after Rn is decremented by 1 and the high order ACC byte is cleared. Branch conditions reflect the final 2 byte ACC contents which will always be positive and never minus 1. The carry is cleared. Because Rn is decremented prior to loading the ACC, single byte stacks may be implemented with the ST @Rn and POP @Rn operations (Rn is the stack pointer).

Example:

15 34 A0       SET   R5,$A034 ; init stack pointer
10 04 00       SET   R0,$0004 ; load 4 into ACC
35             ST   @R5       ; push 4 onto stack
10 04 00       SET   R0,$0005 ; load 5 into ACC
35             ST   @R5       ; push 5 onto stack
10 04 00       SET   R0,$0006 ; load 6 into ACC
35             ST   @R5       ; push 6 onto stack
85             POP  @R5       ; pop 6 off stack into ACC
85             POP  @R5       ; pop 5 off stack into ACC
85             POP  @R5       ; pop 4 off stack into ACC

STP @Rn

Name Opcode Function
STP @Rn $8n Store pop indirect

The low order ACC byte is stored into the memory location whose address resides in Rn after Rn is decremented by 1. Then the high order ACC byte is stored into the memory location whose address resides in Rn after Rn is again decremented by 1. Branch conditions will reflect the 2 byte ACC contents which are not modified. STP @Rn and POP @Rn are used together to move data blocks beginning at the greatest address and working down. Additionally, single byte stacks may be implemented with the STP @Rn and LOA @Rn ops.

Example:

14 24 A0       SET   R4,$A034 ; init pointers
15 22 90       SET   R5,$9022
84             POP  @R4       ; move byte from $A033
95             STP  @R5       ;   to $9021
84             POP  @R4       ; move byte from $A032
95             STP  @R5       ;   to $9020

ADD Rn

Name Opcode Function
ADD Rn $An Add

The contents of Rn are added to the contents of the ACC (RO) and the low order 16 bits of th e sum restored in ACC. The 17th sum bit becomes the carry and other branch conditions reflect the final ACC contents.

Example:

10 34 76       SET   R0,$7643 ; init R0 (ACC)
11 27 42       SET   R1,$4227 ; and R1
A1             SUB   R1       ; add R1 (sum = $B85B, carry clear)
A0             SUB   R0       ; double the ACC (R0) to $70B6 with carry set

SUB Rn

Name Opcode Function
SUB Rn $Bn Subtract

The contents of Rn are subtracted from the ACC contents by performing a two's complement addition:

ACC = ACC + (Rn ^ $FFFF) + 1

The low order 16 bits of the subtraction are restored in the ACC. The 17th sum bit becomes the carry and other branch conditions reflect the final ACC contents. If the 16 bit unsigned ACC contents are greater than or equal to the 16 bit unsigned Rn contents then the carry is set, otherwise it is cleared. Rn is not disturbed.

Example:

10 34 76       SET   R0,$7643 ; init R0 (ACC)
11 27 42       SET   R1,$4227 ; and R1
A1             SUB   R1       ; subtract R1 (difference = $340D with carry set)
A0             SUB   R0       ; clears the ACC (R0)

POPD @Rn

Name Opcode Function
POPD @Rn $Cn Pop double indirect byte

Rn is dec remented by 1 and the high order ACC byte is loaded from the memory location whose address now resides in Rn. Then Rn is again decremented by 1 and the low order ACC byte is loaded from the corresponding memory location. Branch conditions reflect the final ACC contents. The carry is cleared. Because Rn is decremented prior to loading each of the ACC halves, double byte stacks may be implemented with the STD @Rn and POPD @Rn operations. (Rn is the stack pointer).

Example:

15 34 A0       SET   R5,$A034 ; init stack pointer
10 12 AA       SET   R0,$AA12 ; load $AA12 into ACC
75             STD  @R5       ; push $AA12 onto stack
10 34 BB       SET   R0,$BB34 ; load BB34 into ACC
75             STD  @R5       ; push BB34 onto stack
10 56 CC       SET   R0,$CC56 ; load CC56 into ACC
75             STD  @R5       ; push CC56 onto stack
C5             POPD @R5       ; pop CC56 off stack
C5             POPD @R5       ; pop BB34 off stack
C5             POPD @R5       ; pop AA12 off stack

CPR Rn

Name Opcode Function
CPR Rn $Dn Compare

The ACC (R0) conten ts are compared to Rn by performing the 16 bit binary subtraction ACC - Rn and storing the low order 16 difference bits in R13 for subsequent branch tests. If the 16 bit unsigned ACC contents are greater than or equal to the 16 bit unsigned Rn contents then the carry is set, otherwise it is cleared. No other registers, including ACC and Rn, are disturbed.

Example:

15 34 AO       SET   R5,$A034 ; pointer to memory
16 BF AO       SET   R6,$A0BF ; limit address
10 00 00 LOOP  SET   RO,$0000 ; limit address
75             STD  @R5       ; zero data
25             LD    R5       ; clear 2 locations, increment R5 by 2
D6             CPR   R6       ; compare pointer R5 to limit R6
02 F8          BNC   LOOP     ; loop if carry clear

INR Rn

Name Opcode Function
INR Rn $En Increment

The contents of Rn are incremented by 1. The carry is cleared and other branch conditions reflect the incremented value.

Example:

15 34 AO       SET   R5,$A034 ; init R5 (pointer)
10 00 00       SET   RO,$0000 ; zero to RO
55             ST   @R5       ; clears loc A034 and increments R5 to $A035
E5             INR   R5       ; increment R5 to $A036
55             ST   @R5       ; clears location $A036 (not $A035)

DCR Rn

Name Opcode Function
DCR Rn $Fn Decrement

The contents of Rn are decremented by 1. The carry is cleared and other branch conditions reflect the decremented value.

Example: (clear nine bytes beginning at loc $A034)

15 34 AO       SET   R5,$A034    ; init pointer
14 09 00       SET   R4,$0009    ; init count.
10 00 00       SET   RO,$0000    ; zero ACC.
55       LOOP  ST   @R5          ; clear a mem byte
F4             DCR   R4          ; decrement count
07 FC          BNZ   LOOP        ; loop until zero

Nonregister Ops

RTN

Name Opcode Function
RTN $00 return to 6502 mode

Control is returned to the 6502 and program execution continues immediately following the RTN instruction. The 6502 registers and status conditions are restored to their original contencts (prior entering SWEET16 mode).

BR

Name Opcode Argument Function
BR ea $01 $rl branch always

An effective address (ea) is calculated by adding the signed displacement byte (rl) to the program counter. The program counter contains the address of the instruction immediately following the BR, or the address of the BR operation plus 2. The displacement byte is a signed two's complement value from -128 to +127. Branch contitions are not changed. Note that the effective address calculation is identical to that for 6502 relative branches.

Example

0300: 01 50          BR    $0352

BNC

Name Opcode Argument Function
BNC ea $02 $rl branch if no carry

A branch to the effective address is taken only if the carry is clear, otherwise execution resumes as normal with the next instruction. Branch conditions are not changed.

BC

Name Opcode Argument Function
BC ea $03 $rl branch if carry set

A branch is effected only if the carry is set. Branch conditions are not changed.

BP

Name Opcode Argument Function
BP ea $04 $rl branch if plus

A branch is effected only if the prior "result" (or most recently transferred data) was positive. Branch conditions are not changed.

Example:

15 34 A0       SET   R5,$A034 ; init pointer
14 3F A0       SET   R4,$A03F ; init limit
10 00 00 LOOP  SET   R0,$0000
55             ST   @R5       ; clear memory byte increment R5
24             LD    R4       ; compare limit to
D5             CPR   R5       ; pointer
04 F8          BP    LOOP     ; loop until done

BM

Name Opcode Argument Function
BM ea $05 $rl branch if minus

A branch is effected only if the prior "result" was minus (negative, MSB=1). Branch conditions are not changed.

BZ

Name Opcode Argument Function
BZ ea $06 $rl branch if zero

A branch is effected only if the prior "result" was zero. Branch conditions are not changed.

BNZ

Name Opcode Argument Function
BNZ ea $07 $rl branch if nonzero

A branch is effected only if the prior "result" was nonzero. Branch conditions are not changed.

BM1

Name Opcode Argument Function
BM1 ea $08 $rl branch if minus 1

A branch is effected only if the prior "result" was minus 1 ($FFFF hexadecimal). Branch conditions are not changed.

BNM1

Name Opcode Argument Function
BNM1 ea $09 $rl branch if not minus 1

A branch is effected only if the prior "result" was not minus 1 ($FFFF hexadecimal). Branch conditions are not changed.

BK

Name Opcode Argument Function
BK ea $0A $xx break

This differs from the original implementation: instead of issuing a 6502 BRK (break) instruction, the CPU is halted and the machine switches to meta-mode. This is done by writing the argument value ($xx) to the trap register $DF01. This way the BK causing the stop can be easier identified.

Note: since the Sorbus cannot run a BRK with a BRK and SWEET16 is invoked using a BRK, the original behavior cannot be implemented.

RS

Name Opcode Function
RS $0B return from SWEET16 subroutine

RS terminates execution of a SWEET16 subroutine and returns to the SWEET16 calling program which resumes execution (in SWEET16 mode). R12, which is the SWEET16 subroutine return stack pointer, is decremented twice. Branch conditions are not changed.

BS

Name Opcode Argument Function
BS ea $0C $rl branch to SWEET16 subroutine

A branch to the effective address (PC+2+d) is taken and execution is resumed in SWEET16 mode. The current PC is pushed onto a "SWEET16 subroutine return address" stack whose pointer is R12, and R12 is incremented by 2. The carry is cleared and branch conditions are set to indicate the current ACC contents.

Example:

0300: 15 34 A0       SET   R5,$A034 ; init pointer 1
0303: 14 3B A0       SET   R4,$A03B ; init limit 1
0306: 16 00 30       SET   R6,$3000 ; init pointer 2
0309: 0C 15          BS    MOVE
[...]
0320: 45       MOVE  LD   @R5       ; move one
0321: 56             ST   @R6       ;    byte
0322: 24             LD    R4
0323: 05             CPR   R5       ; test if done
0324: 04 FA          BP    MOVE
0326 :0B             RS             ; return

NP

Name Opcode Argument Function
NP ea $0D $xx no operation, reading dummy argument
NP ea $0E $xx no operation, reading dummy argument
NP ea $0F $xx no operation, reading dummy argument

These opcodes do nothing like the 6502 NOP instruction. Unlike that instruction, it also reads the following byte, so they behave more like a branch never taken.

However, these could be used for further expansions.

Operation Code Summary

Name Opcode Argument Function
[RTN] $00 - return to 6502 mode
BR ea $01 $rl branch always
BNC ea $02 $rl branch if no carry
BC ea $03 $rl branch if carry set
BP ea $04 $rl branch if plus
BM ea $05 $rl branch if minus
BZ ea $06 $rl branch if zero
BNZ ea $07 $rl branch if nonzero
BM1 ea $08 $rl branch if minus 1
BNM1 ea $09 $rl branch if not minus 1
BK ea $0A $xx break
RS $0B - return from SWEET16 subroutine
BS ea $0C $rl branch to SWEET16 subroutine
NP ea $0D $xx no operation, reading dummy argument
NP ea $0E $xx no operation, reading dummy argument
NP ea $0F $xx no operation, reading dummy argument
SET Rn, Constant $1n $lb $hb Set
LD Rn $2n - Load
ST Rn $3n - Store
LD @Rn $4n - Load indirect
ST @Rn $5n - Store indirect
LDD @Rn $6n - Load double byte indirect
STD @Rn $7n - Store double byte indirect
POP @Rn $8n - Pop indirect
STP @Rn $8n - Store pop indirect
ADD Rn $An - Add
SUB Rn $Bn - Subtract
POPD @Rn $Cn - Pop double indirect byte
CPR Rn $Dn - Compare
INR Rn $En - Increment
DCR Rn $Fn - Decrement