Keeping things simple - Wozniak's "Sweet-16"

No instruction set is perfect, and early microprocessor design engineers were working against many constraints: die size, transistor count, process limitations, commercial deadlines and the expectations of their sales and marketing department.

The 6502 was no exception, and it was Chuck Peddle’s vision to make a microprocessor targeted at control applications for a tenth of the cost of the nearest competitor.

This strategy would hopefully lead to volume sales to large manufacturing corporations - including automotive, industrial and process control. In 1974 nobody in the IC design and fabrication industry was thinking about the forthcoming microcomputer revolution.

The upshot of this, was that the 6502 and other 8-bit processors of that era were not well equipped for performing 16-bit math - due to a limitation of 16-bit wide registers. The 6502 was further restricted by a stack pointer that was only 8-bits wide.

The exception perhaps was the 8080 and the forthcoming Z80, where some 16-bit support could be obtained by pairing registers together. Hence the BC, DE, HL register pairs and a 16-bit stack pointer SP, and a few, limited 16-bit instructions to support them, such as LXI (load 16-bit immediate), DAD - 16-bit ADD into the HL register pair and 16-bit increment INX and decrement DCX.

In 1977 Wozniak realised the limitations of the 6502 especially when it came to moving strings or other data around memory when writing BASIC for Apple II.

Chuck Peddle’s design strategy was to encourage the use of zero page memory, with it’s faster addressing time, to compensate for the lack of internal registers. Woz realised the importance of zero page RAM and exploited it to full advantage.

His solution was elegant and compact - create a virtual machine, with a wealth of 16-bit registers created in the first 32 bytes of zeropage RAM and a minimum instruction set to support operations on these 16-bit registers.

Thus was born “Sweet-16” a virtual machine with a 16-bit accumulator R0, and 15 general purpose registers R1 to R15. (Registers R12 to R15 were used as internal Stack Pointer, Program counter etc).

Sweet-16 used an 8-bit instruction format to be compatible (and code-dense) with the 8-bit memory.

Woz chose his instruction coding to be trivial, so that the codes were easy to remember, yet achieve maximum flexibility and to compensate for the limitations of the 6502.

There are 3 unassigned instruction slots, which could be utilised to extend the capabilities.

Instructions were roughly divided into those that worked on a specified register, n and the accumulator R0, and those that did not involve register addressing such as branching:

Register OPS-

 1n        SET       Rn     Constant  (Set)
 2n        LD        Rn     (Load)
 3n        ST        Rn     (Store)
 4n        LD        @Rn    (Load Indirect)
 5n        ST        @Rn    (Store Indirect)
 6n        LDD       @Rn    (Load Double Indirect)
 7n        STD       @Rn    (Store Double Indirect)
 8n        POP       @Rn    (Pop Indirect)
 9n        STP       @Rn    (Store POP Indirect)
 An        ADD       Rn     (Add)
 Bn        SUB       Rn     (Sub)
 Cn        POPD      @Rn    (Pop Double Indirect)
 Dn        CPR       Rn     (Compare)
 En        INR       Rn     (Increment)
 Fn        DCR       Rn     (Decrement)

Non-register OPS-

 00        RTN              (Return to 6502 mode)
 01        BR        ea     (Branch always)
 02        BNC       ea     (Branch if No Carry)
 03        BC        ea     (Branch if Carry)
 04        BP        ea     (Branch if Plus)
 05        BM        ea     (Branch if Minus)
 06        BZ        ea     (Branch if Zero)
 07        BNZ       ea     (Branch if NonZero)
 08        BM1       ea     (Branch if Minus 1)
 09        BNM1      ea     (Branch if Not Minus 1)
 0A        BK               (Break)
 0B        RS               (Return from Subroutine)
 0C        BS        ea     (Branch to Subroutine)
 0D                         (Unassigned)
 0E                         (Unassigned)
 0F                         (Unassigned)

Needless to say, Sweet-16 was compact and efficient. The complete interepreter is coded into about 230 lines of 6502 assemby, occupying about 300 bytes of ROM space.

Sweet-16 ran at approximately one tenth of the speed of native 6502 code, but it vastly simplified the process of programming within a 16-bit environment.

The few instructions were easy to remember and Sweet-16 instructions could be interspersed within 6502 code.

Parameters could be set up in the zero page registers, and then Sweet-16 execution mode begins with a subroutine call to SW16. All 6502 registers are saved at this time, to be restored when a Sweet-16 RTN instruction returns control to the 6502.

Sweet-16 is a historically significant example of the use of a minimal virtual machine, to create a layer of abstraction above the native assembly language.

It is a forerunner of bytecode, where a few 16-bit operations can be coded into 8-bit instructions in memory, and executed using a simple interpreter.

Full details and a listing of Sweet-16 can be found here:

http://www.6502.org/source/interpreters/sweet16.htm

6 Likes

I like Sweet16 and wrote things in it “back in the day” using the Ted II+ assembler/editor, I guess from that point of view it might be considered the 2nd assembly language I learned…

However, as far as I can tell, Woz never actually used it in his BASICs… (or anything that I’m aware of)

A couple of years back, I wrote my own implementation of it which removed some of the limitations in the original (there is a critical alignment issue which Woz relied on for the opcode jump mechanism) and used a few 65C02 instructions to make it faster (Woz was good at tight code, but it wasn’t always the fastest - ROM and RAM were expensive then!)

I re-implemented it because I needed a memory allocator for a project - and manipulating a lot of 16-bit data seemed a worthy task for Sweet16 - and it was and made that part of the project easier to implement.

Some code:

; Larson scanner in Sweet16
;*****************************************************************************************

.proc       larson16

    jsr     strout
    .byte   "Larson 16",13,10,0

    goSweet16

    set     r12,$0100       ; Sweet16 stack pointer
    set     r2,$00FF        ; Larson data end marker value
    set     r8,VIA_ORA      ; Update LEDs

loop0:
    set     r1,larsonData   ; Address of the data table
loop1:
    ld      r8              ; Copy r8 - address of LEDs into r7 because ...
    st      r7

    ld      @r1             ; Loads and increments r1
    cpr     r2
    bz      loop0           ; Reached the end
    st      @r7             ; ... store increments

    bs      delay
    br      loop1

delay:
    set     r3,$0800
sub1a:
    dcr     r3
    bnz     sub1a
    rs
    .setcpu "65816"
.endproc

Cheers,

-Gordon

1 Like