SWEET16 - A C64 / Kick Assembler port of Stephen Wozniak's ("Woz") 16-bit metaprocessor processor
Switch branches/tags
Nothing to show
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Failed to load latest commit information.
build
code
screenshots
.gitignore
Makefile
README.md
index.asm

README.md

Sweet16

A C64 / Kick Assembler port of Steve Wozniak's ("Woz") 16-bit metaprocessor processor

This project provides an implementation of Steve Wozniak's "SWEET16" ported to the C64 6502 / 6510 using the Kick Assembler. Using Kick's scripting language features SWEET16 can more natively be coded using pseudocommands that better reflect Woz's original description. Using an example from Jeff Tranter's Atari port of SWEET16:

TEST:
   JSR SWEET16
  .BYTE $11,$00,$70 ; SET R1,$7000
  .BYTE $12,$02,$70 ; SET R2,$7002
  .BYTE $13,$01,$00 ; SET R3,1
;LOOP:
  .BYTE $41         ; LD @R1
  .BYTE $52         ; ST @R2
  .BYTE $F3         ; DCR R3
  .BYTE $07,$FB     ; BNZ LOOP
  .BYTE $00         ; RTN

Looking at the comments you can see the SWEET16 mnemonics but to actually have them execute correctly each needs to be converted into the a byte sequence of their opcodes and operands by hand. So without the comments the code becomes at first glace straight assembler:

TEST:
  JSR SWEET16
  .BYTE $11,$00,$70,$12,$02,$70,$13,$01,$00
;LOOP:
  .BYTE $41,$52,$F3,$07,$FB,$00

Clearly this is not easy to maintain and coding in this manner is error prone. However, using Kick's pseudocommands the following mnemonics produces the same SWEET16 opcodes / operands:

TEST:
  SWEET16
  SET 1 : $7000
  SET 2 : $7002
  SET 3 : $0001
LOOP:
  LDI 1
  STI 2
  DCR 3
  BNZ LOOP
  RTN

Overview

In 1977, Steve Wozniak wrote an article for BYTE magazine about a 16-bit "metaprocessor" that he had invented to deal with manipulating 16-bit values on an 8-bit CPU (6502) for the AppleBASIC he was writing at the time. What he came up with was "SWEET16" which he referred to "as a 6502 enhancement package, not a stand alone processor". It defined sixteen 16-bit registers (R0 to R15) which under the bonnet were implemented as 32 memory locations located in zero page. Some of the registers were dual purpose (e.g., R0 doubled as the SWEET16 accumulator).

SWEET16 instructions fell into register and nonregister categories with the register operations specifying one of the 16 registers to be used as either a data element or a pointer to data in memory depending on the specific instruction. Except for the SET instruction, register operations only require one byte. The nonregister operations were primarily 6502 style branches with the second byte specifying a +/-127 byte displacement relative to the address of the following instruction. If a prior register operation result meets a specified branch condition, the displacement was added to SWEET16's program counter, effecting a branch.

Any implementation of SWEET16 required a few key things to achieve this. The first is that the implementation of all the instructions are located on the same 256 byte memory page. In this implementation this was achieved by using Kick's !align $100 command and then inserting a nop at the first address (see later). This way a jumptable can be used which only needs to specify a single byte as they will all share a common high byte. Another requirement is that the registers themselves are located in zero page due to the addressing modes required. (More information can be found in Carsten Strotmann article) detailing porting SWEET16.

Extensions

In addition to the standard SWEET16 mnemonics there was room for an additional 3 instructions which I have created in this implementation. This is in addition to creating another pseudocommand that appears to be a SWEET16 call but is actually simply a macro calling two successive SWEET16 calls to provide the ability to perform an absolute jump. These SWEET16 extensions are:

  • XJSR - Provides a means to calling 6502 code while still executing code as if within the SWEET16 metaprocessor. All state is kept intact within the SWEET16 virtual environment and after a RTS is executed in the regular 6502 code SWEET16 continues execution. This was found to be invaluable in the test suite for outputting intermediate results.
  • SETM - SWEET16 uses up half its mnemonics on setter routines which are only able to use direct absolute values. The SETM extension allows an indirect memory address to be used which will have their values loaded directly into the register instead
  • SETI - Very similar to SETM except that the byte ordering is High to Low which is how SWEET16 treats 16-bit values passed as constants to registers.

To elaborate:

SHOW_DIFFERECE:
	.const VAL_1 = $1234            // arbitrary number
	.const REGISTER = 5             // arbitrary register (location $0021)
	jmp !eg+
VAL_1_MEMORY:
	!byte $12, $34                  // same as VAL_1
!eg:
	SWEET16
	SET REGISTER : VAL_1            // assigns VAL_1 to register 5 - in memory:  34 12 ...
	SETM REGISTER : VAL_1_MEMORY    // assigns VAL_1_MEMORY memory to register 5: 12 34 ...
	SETI REGISTER : VAL_1_MEMORY    // assigns VAL_1_MEMORY high / low to register 5: 34 21 ...
	RTN
	rts
  • AJMP - This is simply a convenience call which sets the SWEET16 PC to the values specified which causes a jump to the desired address. To store this value in the PC it overwrites the value in the ACC register

Convenience

There are a number of convenience routine created to make using and verifying SWEET16 more readily accessable:

  • ibk - (see below) Installs an ISR handler for working with VICE and calling BK
  • ldyx - Loads the values from the passed in register to the X and Y registers. This is handy for debugging when you want to quickly inspect what is happening in SWEET16

Test Suite

Each of the SWEET16 has some unit style tests around them. These are usually quite trivial and not exhaustive but they have proven to be suitable for catching game-changing breakages when my experiments have gone too far. Some do rely on my extension XJSR due to the nature of needing to call a lot of 6502 code to output the intermediate results to the screen memory. The other point looking at the branch tests is the need to place the jumps close within the calls themselves as the branches can only every be +/- 127 which causes issues when calling convenience routine to output to the display which can be quite a lot of code.

SWEET16 code changes for C64

There were a few things I needed to do to bring it into the C64 world. First was to find a place in Zero Page which wouldn't cause too much damage. I start at $0017 and take the next 34 bytes for registers and a convience zero page location I use as part of the SETI / SETM implementations. This clobbers some BASIC important values but that doesn't impact this work.

Its important to have the opcode lookup table all within the one page so that only a single address byte is required in the opcode itself. It doesn't matter where else the subroutine calls after that as long as all 32 branches are on the same page. As such some calls have had to be moved outside of this page to allow for the 3 new mnemonics. All POP, SET and RTN mnemonics have the bulk of their implementation moved out of page. This costs a single jump but it is a difference from other SWEET16 implementations and if the extensions are not required these out of page jumps can be moved into page.

Another difference is the introduction of a nop at the start of the page containing all the mnemonics. The reasons for this is that the Kick Assembler allows code to be page aligned which is done via the .align $100 command. This means it is now on a 256 byte page alignment. However, SWEET16 uses JSR's as JMP by putting the address minus one onto the stack and then executing an RTS. In every case except being page aligned this works but the first call (SET) being page aligned at 00 in its low byte becomes ff after the minus one. So to ensure this will always work a nop has been placed at the start of the table. This is not a deficiency in SWEET16, rather an implementation detail that affected this particular port.

Debugging

One aspect of using SWEET16 which at first might appear to be problematic is debugging. While working in assembler a lot of time is spent inspecting memory location and registers and SWEET16 is not forgiving in this regard. The registers you are inspecting are arbitrary memory locations outside of the normal 6502 ones and breakpoints don't work as well as would initially be expecting due to this (you don't simply put a BRK in the SWEET16 code and load up the debugger). When SWEET16 encounteres a BK it executes the the ISR for break. This is usually not setup unless debugging so I have added two ways to set this up to assist in debugging SWEET16 programs "natively". They both make the assumption the developer has access to VICE and is not developing on native hardware for this part of the development as it installs an ISR that produces a breakpoint in a VICE format. So when run in debug mode once the SWEET16 call BR is encountered the monitor will appear and the developer can inspect the state of the metaprocessor. There are two ways to achieve this.

  • Start SWEET16 with the optional flag to install the interrupt routine: sweet16 : 1
  • While within SWEET16 execute the extension IBK which will ensure the ISR is installed (only needs to be done once - use BK from then onwards).

In either case once the command is encountered (and assuming using VICE) the monitor will show up at that point. From this point it is important to realise that the user is in 6502 world (not SWEET16) so it is fine to inspect the mapped zero-page registers etc. which are all mapped in the debug output file breakpoints.txt However, once you continue execution the call will jump to SW16D which effectively continues where you left off back in the SWEET16 metaprocessor.

A more powerful alternative to this is using the extension of XJSR which will allow any 6502 routine to be called within SWEET16 execution to continue once it encounters a RTS.

Test Suite

I've added a rudimentary test suite based on Woz's original descriptions for each mnemonic. Very few look 1:1 with the description but they are similar in vibe. Often (to keep a single source of truth) I'll use a Kick .const instead of the original value so that I can pass the same value to an assert routine. The end code is the same but code maintainability and the flexibility is more-so in 2018 than it was in 1977. In total there are over 50 "unit" tests validating the original code, the extensions and my understanding of the metaprocessor. I'm sure there is room for many more but I do think there are enough to give a vague guide to anyone putting their toes into SWEET16 for the first time some confidence about how it is meant to work.

External Use

There are only four main files required to use this implementation of SWEET16:

  • sweet16.asm: the core implememtation with some extensions
  • sweet16_pseudocommands.asm: Kick Assembler pseudo commands to map mnemonics to SWEET16
  • sweet16_macros.asm: macro's used by the pseudo commands and core extensions
  • sweet16_functions.asm: functions used by the pseudo commands

Screenshots

Screen One Screen Two
alt text alt text

Binaries

Online Emulator

Development

Links

Collection of links related to the project development:

Opcode Reference

SWEET16 OP CODE SUMMARY
   00RTNReturn to 6502 mode
1nSET n : valConstant set value01BR eaBranch always
2nLD nLoad02BNC eaBranch if No Carry
3nST nStore03BC eaBranch if Carry
4nLDI nLoad indirect04BP eaBranch if Plus
5nSTI nStore indirect05BM eaBranch if Minus
6nLDDI nLoad double indirect06BZ eaBranch if Zero
7nSTDI nStore double indirect07BNZ eaBranch if NonZero
8nPOPI nPop indirect08BM1 eaBranch if Minus 1
9nSTPI nStore Pop indirect09BNM1 eaBranch if Not Minus 1
AnADD nAdd0ABKBreak
BnSUB nSubtract0BRSReturn from Subroutine
CnPOPDI nPop double indirect0CBS eaBranch to Subroutine
DnCPR nCompare0DXJSR addrExtension - Jump to External 6502 Subroutine
EnINR nIncrement0ESETMExtension - Sets register with value from memory
FnDCR nDecrement0FSETIExtension - Set reguster with value from address (High / Low) as if the value at the address was a const to SET
SWEET16 Operation Code Summary: Table 1 summarizes the list of SWEET16 operation codes. They are executed after a call to the entry point SWEET16. Return to the calling program and normal noninterpretive operation is accomplished with the RTN mnemonic of SWEET16. These codes differ from Woz's original only in the removal of the redundant R for register numbers and the replacement of I instead of @ to refer to indirect address mnemonics