Sea16 is the working name for a 16-bit processor designed for high level languages. It's based on Koei's VM used for their NES strategy games, where it provided a better machine to run C on than the 6502 does. This version is better suited for a hardware implementation with a rearranged and modified instruction set, and adds additional helpful instructions.

Table of contents:

Registers

name

bits

purpose

A

16/32

"left" side of math operations

B

16/32

"right" side of math operations

SP

16

stack pointer

FP

16

frame pointer

PC

16

program counter

A and B may be implemented as 32-bit rather than 16-bit. In this case, the top 16 bits are included in most math operations.

Addressing modes

mode

description

<fast>

FP + offset from a lookup table

<near>

FP + 8-bit signed offset

<far>

FP + 16-bit signed offset

<abs>

16-bit address

This processor is heavily focused around accessing variables kept on the stack, so priority is given to using the frame pointer as a base.

The "fast" addressing mode uses 4 bits to select an item from the following table:

code

location

assembler name

0

FP - 24

local12

1

FP - 22

local11

2

FP - 20

local10

3

FP - 18

local9

4

FP - 16

local8

5

FP - 14

local7

6

FP - 12

local6

7

FP - 10

local5

8

FP - 8

local4

9

FP - 6

local3

a

FP - 4

local2

b

FP - 2

local1

FP

old_fp

FP + 2

old_pc

c

FP + 4

arg1

d

FP + 6

arg2

e

FP + 8

arg3

f

FP + 10

arg4

16-bit instructions

Fast addressing and 4-bit constants

opcode

mnemonic

description

0x

lda fast

A = <fast>

1x

ldb fast

B = <fast>

2x

push fast

push <fast> to the stack

3x

sta fast

<fast> = A

4x

lda #x

A = #x

5x

ldb #x

B = #x

6x

push #x

push #x to the stack

7x

add #x

A += #x

inc is an alias for add #1 and nop is an alias for add #0

Near/Far and 8/16-bit constants

opcode

mnemonic

description

80 xx

lda near

A = <near>

81 xx xx

lda far

A = <far>

82 xx

ldb near

B = <near>

83 xx xx

ldb far

B = <far>

84 xx

push near

push <near> to the stack

85 xx xx

push far

push <far> to the stack

86 xx

sta near

<near> = A

87 xx xx

sta far

<far> = A

88 xx

lda #xx

A = #xx (sign extend)

89 xx xx

lda #xxxx

A = #xxxx

8A xx

ldb #xx

B = #xx (sign extend)

8B xx xx

ldb #xxxx

B = #xxxx

8C xx

push #xx

push #xx to the stack

8D xx xx

push #xxxx

push #xxxx to the stack

8E xx

add #xx

A += #xx (sign extend)

8F xx xx

add #xxxx

A += #xxxx

Absolute instructions

opcode

mnemonic

description

90 xx xx

lda abs xxxx

A = <abs>

91 xx xx

ldb abs xxxx

B = <abs>

92 xx xx

push abs xxxx

push <abs> to the stack

93 xx xx

sta abs xxxx

<abs> = A

94 xx xx

blda abs xxxx

A = <byte abs>

95 xx xx

bldb abs xxxx

B = <byte abs>

96 xx xx

bpush abs xxxx

push <byte abs> to the stack (zero extended)

97 xx xx

bsta abs xxxx

<byte abs> = A

Additional "far" instructions

opcode

mnemonic

description

98 xx xx

blda far

A = <byte far>

99 xx xx

bldb far

B = <byte far>

9A xx xx

bpush far

push <byte far> to the stack

9B xx xx

bsta far

<byte far> = A

9C xx xx

leaa far

A = &<far>

9D xx xx

leab far

B = &<far>

9E xx xx

pea far

push &<far> to the stack

9F xx xx

?? far

reserved

Miscellaneous/subroutine calling

opcode

mnemonic

description

A0

deref

A = *A

A1

popstore

pop B, *B = A

A2

bderef

A = (byte)*A

A3

bpopstore

pop B, (byte)*B = A

A4

pha

push A to the stack

A5

plb

pop B from the stack

A6 xx

unstack #xx

sp += #xx (for cleaning up after a function)

A7 xx xx

unstack #xxxx

sp += #xxxx

A8 xx xx

call xxxx

call an address (push PC, PC = xxxx, push FP, FP = SP)

A9

callptr

call A

AA xxxxyy

call xxxx, #yy

call an address, decrease the stack pointer by yy

AB yy

callptr #yy

call A, decrease the stack pointer by yy

AC

operand size override prefix?

AD

address size override prefix?

AE

return

return (SP = FP, pop FP, pop PC)

AF

swap

A = B and B = A

Comparisons and math

opcode

mnemonic

description

B0

ucmplt

A = A < B (unsigned)

B1

ucmple

A = A <= B (unsigned)

B2

ucmpgt

A = A > B (unsigned)

B3

ucmpge

A = A >= B (unsigned)

B4

scmplt

A = A < B (signed)

B5

scmple

A = A <= B (signed)

B6

scmpgt

A = A > B (signed)

B7

scmpge

A = A >= B (signed)

B8

cmpeq

A = A == B

B9

cmpne

A = A != B

BA

not

A = !A (or == 0)

BB

neg

A = -A

BC

compl

A = ~A

BD

and

A = A & B

BE

or

A = A | B

BF

xor

A = A ^ B

C0

add

A += B

C1

dec

A--

C2

rsub

A = B-A

C3

sub

A -= B

C4

lshift

A <<= B (unsigned)

C5

double

A <<= 1

C6

rshift

A >>= B (unsigned)

C7

arshift

A >>= B (signed)

C8

divu

A /= B (unsigned)

C9

divs

A /= B (signed)

CA

modu

A %= B (unsigned)

CB

mods

A %= B (signed)

CC

mult

A *= B

CD xy

sloadbf x,y

signed bit extract. a = b>>x, only keeping y bits (sign extended)

CE xy

uloadbf x,y

unsigned bit extract. a = b>>x, only keeping y bits

CF xy

storebf x,y

bit insert. a = (b&mask)|(a<<x)

Bitfield instruction implementation in C:

sloadbf x,y
    A = (B >> x) & (0xffff >> 15-y);
    if(A & (1 << y))
        A |= 0xffff << (y+1);

uloadbf x,y
    A = (B >> x) & (0xffff >> 15-y);

storebf x,y
    A = B & ~((0xffff >> 15-y) << x) | (A & (0xffff >> 15-y)) << x;

Jumps

opcode

mnemonic

description

D0 xx

jumpt xx

PC += xx + 1 IF A==1

D1 xx

jumpt xx

PC -= xx + 1 IF A==1

D2 xx xx

jumpt xxxx

PC = <abs> IF A==1

D3

?

possibly a long jump

D4 xx

jumpf xx

PC += xx + 1 IF A==0

D5 xx

jumpf xx

PC -= xx + 1 IF A==0

D6 xx xx

jumpf xxxx

PC = <abs> IF A==0

D7

?

possibly a long jump

D8 xx

jump xx

PC += xx + 1

D9 xx

jump xx

PC -= xx + 1

DA xx xx

jump xxxx

PC = <abs>

DB

?

possibly a long jump

DC ??

switchrange

switch statement with a range (see example)

DD ??

switchlist

switch statement with a list of values (see example)

Switch opcode examples:

  switchrange
  .word 1, 5    ; range is 1 to 5, inclusive
  .word default ; jump here if outside the range
  .word was1
  .word was2
  .word was3
  .word was4
  .word was5

  switchlist
  .word 5 ; 5 cases
  .word 1, was1
  .word 2, was2
  .word 3, was3
  .word 4, was4
  .word 5, was5
  .word default

Miscellaneous 2

opcode

mnemonic

description

DE xx

in xx

input on port x (A = read value)

DF xx

out xx

output on port x (write A to port)

E0

extenda

sign extend A from 8 bits to 16

E1

extendb

sign extend B from 8 bits to 16

E0 xx xx

copy #xxxx

memcpy(b, a, #xxxx)

E1 xx xx

fill #xxxx

memset(b, a, #xxxx)

E2 xx xx

mcmp #xxxx

memcmp(b, a, #xxxx)

EC

lda sp

A = sp

ED

sta sp

sp = A

EE xx

zalloc #xx

decrease stack pointer by a constant and fill newly allocated bytes with zeros

EF xx

?

directly increase/decrease a variable

Direct count instructions take a parameter:

LLmm ffff
|||| ++++- "Fast" stack reference, 12 local variables or 4 arguments
||++------ amount. index into 1, 2, -1, -2
++-------- 0 no special effect
           1 load A with the new count value
           2 use old count value as a pointer and load A with it. (increase by 1 means zero extended byte, 2 means word)
           3 use old count value as a pointer and load it with A. (increase by 1 means byte, 2 means word)

and have the following mnemonics:

syntax

description

count var, 1

directly count a variable up/down

ldcount var, 1

directly count a variable up/down and load the new value into A

plcount var, 1

directly count a variable up/down and use the old value as a pointer to load

pscount var, 1

directly count a variable up/down and use the old value as a pointer to store

32-bit instructions

These are optional and would be invoked with an operand-size override prefix. Original Koei VM uses a second instruction set with different opcodes for 32-bit instructions. Maybe that would make more sense?

opcode

mnemonic

description

AC 0x

lda32 fast

A = <fast>

AC 1x

ldb32 fast

B = <fast>

AC 2x

push32 fast

push <fast> to the stack

AC 3x

sta32 fast

<fast> = A

AC 80 xx

lda32 near

A = <near>

AC 81 xx xx

lda32 far

A = <far>

AC 82 xx

ldb32 near

B = <near>

AC 83 xx xx

ldb32 far

B = <far>

AC 84 xx

push32 near

push <near> to the stack

AC 85 xx xx

push32 far

push <far> to the stack

AC 86 xx

sta32 near

<near> = A

AC 87 xxxxxxxx

sta32 far

<far> = A

AC 89 xxxxxxxx

lda32 #xxxxxxxx

A = #xxxxxxxx

AC 8B xxxxxxxx

ldb32 #xxxxxxxx

B = #xxxxxxxx

AC 8D xxxxxxxx

push32 #xxxxxxxx

push #xxxxxxxx to the stack

AC 8F xxxxxxxx

add32 #xxxxxxxx

A += #xxxxxxxx

AC 90 xx xx

lda32 abs xxxx

A = <abs>

AC 91 xx xx

ldb32 abs xxxx

B = <abs>

AC 92 xx xx

push32 abs xxxx

push <abs> to the stack

AC 93 xx xx

sta32 abs xxxx

<abs> = A

AC A0

deref32

A = *A

AC A1

popstore32

pop B, *B = A

AC A4

pha32

push A to the stack

AC A5

plb32

pop B from the stack

AC B0

ucmplt32

A = A < B (unsigned)

AC B1

ucmple32

A = A <= B (unsigned)

AC B2

ucmpgt32

A = A > B (unsigned)

AC B3

ucmpge32

A = A >= B (unsigned)

AC B4

scmplt32

A = A < B (signed)

AC B5

scmple32

A = A <= B (signed)

AC B6

scmpgt32

A = A > B (signed)

AC B7

scmpge32

A = A >= B (signed)

AC B8

cmpeq32

A = A == B

AC B9

cmpne32

A = A != B

AC BA

not32

A = !A (or == 0)

AC C6

rshift32

A >>= B (unsigned)

AC C7

arshift32

A >>= B (signed)

AC C8

divu32

A /= B (unsigned)

AC C9

divs32

A /= B (signed)

AC CA

modu32

A %= B (unsigned)

AC CB

mods32

A %= B (signed)

AC E0

extenda32

sign extend A from 16 bits to 32

AC E1

extendb32

sign extend B from 16 bits to 32

AC EF xx

direct count

directly increase/decrease a variable (for plcount/pscount, always counts by 4)

Code examples

Array reading

  lda index
  add #array_address
  deref

Array writing

  lda index
  add #array_address
  pha
  lda value_to_write
  popstore

Square table

  lda #0
  sta index
loop:
  ; get address to write to
  lda index
  double
  add #array_address
  pha

  ; get index*index
  lda index
  ldb index
  mult
  popstore

  ; increase counter by 1 and then load it to then compare against the ending index
  ldcount index, 1
  ldb #10
  cmpne
  jumpt loop

Passing arguments to functions

  push #1
  push #2
  push #3
  push #4
  call sum
  unstack #4*2

.proc sum
  lda arg1
  ldb arg2
  add
  ldb arg3
  add
  ldb arg4
  add
  return
.endproc

Passing a reference

  pea variable
  call function
  unstack #1*2

.proc function: ; writes 1 to the passed argument
  push arg1
  lda #1
  popstore
  return
.endproc

C library examples

strlen

.proc strlen
  args text
  locals length

  zalloc #2        ; Clear out space for "length"
loop:
  plcount text, 1  ; Load a character, increase pointer
  jumpf exit       ; Exit if a null character is read
  count length, 1  ; Otherwise increase the length counter
  jump loop
exit:
  lda length       ; Return length counter in A
  return
.endproc

strcpy

.proc strcpy
  args destination, source

loop:
  plcount source, 1
  pscount destination, 1
  jumpt loop
  return
.endproc

strchr

.proc strchr
  args string, character

  ldb character
loop:
  lda string
  deref
  jumpf not_found ; null terminator
  cmpeq
  jumpt found_it  ; was it found?

  ; didn't find it, so increase the pointer
  count string, 1
  jump loop

; It was found! Return the position it was found at
found_it:
  lda string
  return

; Character not found so return NULL
not_found:
  lda #0
  return
.endproc

puts (assuming output device 0 is a character output)

.proc puts
  args input

loop:
  plcount input, 1 ; read one character, step pointer forward
  jumpf exit       ; exit if a null character is found
  out 0            ; output that character
  jump loop        ; continue
exit:
  lda #'\n'        ; output line return
  out 0
  return
.endproc

abs

.proc abs
  args num

  lda num   ; get the number
  ldb #0
  scmplt    ; less than zero?
  jumpt negative

positive: ; positive, so keep it positive
  lda num
  return
negative: ; negative, so negate it to positive
  lda num
  neg
  return
.endproc

None: Sea16 (last edited 2018-10-29 20:00:24 by NovaSquirrel)