Koei bytecode

Discuss technical or other issues relating to programming the Nintendo Entertainment System, Famicom, or compatible systems. See the NESdev wiki for more information.

Moderator: Moderators

AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Koei bytecode

Post by AWJ »

INSTRUCTION SET

Code: Select all

code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
00-0F | LOADL quick    |   0   | left = <quick>
10-1F | LOADR quick    |   0   | right = <quick>
20-2F | STORE quick    |   0   | <quick> = left
30-3F | PUSH quick     |   0   | push <quick>
40-4F | LOADL qimm     |   0   | left = <qimm>
50-5F | LOADR qimm     |   0   | right = <qimm>
60-6F | PUSH qimm      |   0   | push <qimm>
70-7F | ADD qimm       |   0   | left = left + <qimm>
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 80   | (illegal)      |  ---  | (illegal)
 81   | LOADL near     |   1   | left = <near>
 82   | LOADL far      |   2   | left = <far>
 83   | LOADR near     |   1   | right = <near>
 84   | LOADR far      |   2   | right = <far>
 85   | STORE near     |   1   | <near> = left
 86   | STORE far      |   2   | <far> = left
 87   | PUSH near      |   1   | push <near>
 88   | PUSH far       |   2   | push <far>
 89   | BYTE LOADL imm1|   1   | left = (int8)<imm1>
 8A   | LOADL imm2     |   2   | left = <imm2>
 8B   | BYTE LOADR imm1|   1   | right = (int8)<imm1>
 8C   | LOADR imm2     |   2   | right = <imm2>
 8D   | BYTE PUSH imm1 |   1   | push (int8)<imm1>
 8E   | PUSH imm2      |   2   | push <imm2>
 8F   | BYTE ADD imm1  |   1   | left = left + (int8)<imm1>
 90   | ADD imm2       |   2   | left = left + <imm2>
91-9F | (illegal)      |  ---  | (illegal)
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 A0   | BYTE LOADL far |   2   | left = (uint8)<far>
 A1   | BYTE LOADR far |   2   | right = (uint8)<far>
 A2   | BYTE STORE far |   2   | (uint8)<far> = left
 A3   | BYTE PUSH far  |   2   | push (uint8)<far>
 A4   | LOADL abs      |   2   | left = <abs>
 A5   | BYTE LOADL abs |   2   | left = (uint8)<abs>
 A6   | LOADR abs      |   2   | right = <abs>
 A7   | BYTE LOADR abs |   2   | right = (uint8)<abs>
 A8   | STORE abs      |   2   | <abs> = left
 A9   | BYTE STORE abs |   2   | (uint8)<abs> = left
 AA   | PUSH abs       |   2   | push <abs>
 AB   | BYTE PUSH abs  |   2   | push (uint8)<abs>
 AC   | CALL abs       |   2   | call <abs>
 AD   | COPY imm2      |   2   | copy <imm2> bytes from *left to *right
 AE   | UNSTACK imm1   |   1   | stackptr = stackptr + (uint8)<imm1>
 AF   | UNSTACK imm2   |   2   | stackptr = stackptr + <imm2>
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 B0   | DEREF          |   0   | left = *left
 B1   | POPSTORE       |   0   | pop right; *right = left
 B2   | NOP            |   0   | no operation
 B3   | PUSHL          |   0   | push left
 B4   | POPR           |   0   | pop right
 B5   | MULT           |   0   | left = left * right
 B6   | SDIV           |   0   | left = (int16)left / (int16)right
 B7   | LONG (prefix)  |  var. | (prefix for 32-bit instructions)
 B8   | UDIV           |   0   | left = (uint16)left / (uint16)right
 B9   | SMOD           |   0   | left = (int16)left % (int16)right
 BA   | UMOD           |   0   | left = (uint16)left % (uint16)right
 BB   | ADD            |   0   | left = left + right
 BC   | SUB            |   0   | left = left - right
 BD   | LSHIFT         |   0   | left = left << right
 BE   | URSHIFT        |   0   | left = (uint16)left >> right
 BF   | SRSHIFT        |   0   | left = (int16)left >> right
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 C0   | CMPEQ          |   0   | left = (left == right)
 C1   | CMPNE          |   0   | left = (left != right)
 C2   | SCMPLT         |   0   | left = ((int16)left < (int16)right)
 C3   | SCMPLE         |   0   | left = ((int16)left <= (int16)right)
 C4   | SCMPGT         |   0   | left = ((int16)left > (int16)right)
 C5   | SCMPGE         |   0   | left = ((int16)left >= (int16)right)
 C6   | UCMPLT         |   0   | left = ((uint16)left < (uint16)right)
 C7   | UCMPLE         |   0   | left = ((uint16)left <= (uint16)right)
 C8   | UCMPGT         |   0   | left = ((uint16)left > (uint16)right)
 C9   | UCMPGE         |   0   | left = ((uint16)left >= (uint16)right)
 CA   | NOT            |   0   | left = !left
 CB   | MINUS          |   0   | left = -left
 CC   | COMPL          |   0   | left = ~left
 CD   | SWAP           |   0   | swap left <-> right
 CE   | (illegal)      |  ---  | (illegal)
 CF   | RETURN         |   0   | return from function
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 D0   | INC            |   0   | ++left
 D1   | DEC            |   0   | --left
 D2   | LSHIFT1        |   0   | left = left << 1
 D3   | BYTE DEREF     |   0   | left = *(uint8 *)left
 D4   | BYTE POPSTORE  |   0   | pop right; *(uint8 *)right = left
 D5   | SWITCH offs,num|2+2+tbl| contiguous switch (see below)
 D6   | JUMP abs       |   2   | instrptr = <abs>
 D7   | JUMPT abs      |   2   | if (left != 0) instrptr = <abs>
 D8   | JUMPF abs      |   2   | if (left == 0) instrptr = <abs>
 D9   | SWITCH num     | 2+tbl | noncontiguous switch (see below)
 DA   | AND            |   0   | left = left & right
 DB   | OR             |   0   | left = left | right
 DC   | XOR            |   0   | left = left ^ right
 DD   | CALLPTR        |   0   | call *left
 DE   | LEAL far       |   2   | left = &<far>
 DF   | LEAR far       |   2   | right = &<far>
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 E0   | SLOADBF sz,pos |  1+1  | signed bitfield extract (see below)
 E1   | ULOADBF sz,pos |  1+1  | unsigned bitfield extract (see below)
 E2   | STOREBF sz,pos |  1+1  | bitfield insert (see below)
 E3   | JUMP back      |   1   | instrptr += <back>-256
 E4   | JUMPT back     |   1   | if (left != 0) instrptr += <back>-256
 E5   | JUMPF back     |   1   | if (left == 0) instrptr += <back>-256
 E6   | JUMP ahead     |   1   | instrptr += <ahead>
 E7   | JUMPT ahead    |   1   | if (left != 0) instrptr += <ahead>
 E8   | JUMPF ahead    |   1   | if (left == 0) instrptr += <ahead>
 E9   | CALL abs,imm1  |  2+1  | call <abs>; stackptr += (uint8)<imm1>
 EA   | CALLPTR imm1   |   1   | call *left; stackptr += (uint8)<imm1>
EB-FF | (illegal)      |  ---  | (illegal)
Switch instructions
There are two switch instructions, one ($D5) for contiguous cases and the other ($D9) for noncontiguous cases. They work exactly like a C switch statement with the left register as the variable to test.

For the contiguous switch, the first 16-bit word after the instruction is the twos-complement negative of the smallest case value. The next 16-bit word is the number of cases. Then comes the default jump target, followed by the target for each in-range value of left. Example:

Code: Select all

.byte $D5 ; contiguous switch instruction
.word -4, 5 ; smallest case 4, 5 cases
.word DefaultTarget ; jumps to this address if left was < 4 or > 8
.word Target4 ; jumps to this address if left was 4
.word Target5 ; jumps to this address if left was 5
.word Target6 ; jumps to this address if left was 6
.word Target7 ; jumps to this address if left was 7
.word Target8 ; jumps to this address if left was 8
For the non-contiguous switch, the first 16-bit word after the instruction is the number of cases. Then comes a table of comparison values followed by jump targets. After all the value-target pairs, the default target comes last. Example:

Code: Select all

.byte $D9 ; noncontiguous switch instruction
.word 5 ; 5 cases
.word 1, Target1
.word 4, Target4
.word 9, Target9
.word 16, Target16
.word 25, Target25
.word TargetDefault
Load effective address
Instructions $DE and $DF load a register with the value of the argument + the frame pointer, i.e. with the effective (absolute) address of a local variable. They can be used to pass a pointer to a local variable as a function argument, or in conjunction with the bitfield instructions.

Bitfields
There are three bitfield instructions: one to extract (load from) a signed bitfield, one to extract an unsigned bitfield, and one to insert (store to) a bitfield. For all three bitfield instructions, the first byte after the instruction is the size of the bitfield in bits, and the following byte is the bit position counting from the LSB. The entire bitfield must fit into a 16-bit word (i.e. size + pos < 16)

The two extraction instructions work like the dereference instruction: the address of the bitfield is in left, and the bitfield also gets extracted to left. The insertion instruction takes the value to insert in left, and the address to insert to in right (like the pop-store instruction without the pop).
Last edited by AWJ on Tue May 16, 2017 5:42 pm, edited 1 time in total.
User avatar
rainwarrior
Posts: 8731
Joined: Sun Jan 22, 2012 12:03 pm
Location: Canada
Contact:

Re: Koei bytecode

Post by rainwarrior »

This is really neat! Thanks for documenting it.
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Koei bytecode

Post by AWJ »

32 BIT INSTRUCTION SET

Code: Select all

code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 B700 | (illegal)      |  ---  | (illegal)
 B701 | LONG MULT      |   0   | left = left * right
 B702 | LONG SDIV      |   0   | left = (int32)left / (int32)right
 B703 | LONG ADD       |   0   | left = left + right
 B704 | LONG SUB       |   0   | left = left - right
 B705 | LONG MINUS     |   0   | left = -left
 B706 | LONG CMPEQ     |   0   | left = (left == right)
 B707 | LONG CMPNE     |   0   | left = (left != right)
 B708 | LONG SCMPLT    |   0   | left = ((int32)left < (int32)right)
 B709 | LONG SCMPLE    |   0   | left = ((int32)left <= (int32)right)
 B70A | LONG SCMPGT    |   0   | left = ((int32)left > (int32)right)
 B70B | LONG SCMPGE    |   0   | left = ((int32)left >= (int32)right)
 B70C | LONG LOADL far |   2   | left = <far>
 B70D | LONG LOADR far |   2   | right = <far>
 B70E | LONG STORE far |   2   | <far> = left
 B70F | LONG PUSH far  |   2   | push <far>
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 B710 | LONG LOADL abs |   2   | left = <abs>
 B711 | LONG LOADR abs |   2   | right = <abs>
 B712 | LONG STORE abs |   2   | <abs> = left
 B713 | LONG PUSH abs  |   2   | push <abs>
 B714 | LONG PUSHL     |   0   | push left
 B715 | LONG POPR      |   0   | pop right
 B716 | LONG DEREF     |   0   | left = *(int32 *)left
 B717 | LONG POPSTORE  |   0   | pop right; *(int32 *)right = left
 B718 | LONG LOADL imm4|   4   | left = <imm4>
 B719 | LONG LOADR imm4|   4   | right = <imm4>
 B71A | LONG SWAP      |   0   | swap left <-> right
 B71B | LONG INC       |   0   | ++left
 B71C | LONG DEC       |   0   | --left
 B71D | LONG BOOL      |   0   | left = ((int32)left != 0)
 B71E | LONG SMOD      |   0   | left = (int32)left % (int32)right
 B71F | LONG LSHIFT    |   0   | left = left << right
------+----------------+-------+-------------------------------------
code  | nickname       | args  | operation
------+----------------+-------+-------------------------------------
 B720 | LONG SRSHIFT   |   0   | left = (int32)left >> right
 B721 | LONG COMPL     |   0   | left = ~left
 B722 | LONG AND       |   0   | left = left & right
 B723 | LONG OR        |   0   | left = left | right
 B724 | LONG XOR       |   0   | left = left ^ right
 B725 | LONG SEXTEND   |   0   | left = (int32)(int16)left
 B726 | LONG UEXTEND   |   0   | left = (uint32)(uint16)left
 B727 | LONG NOP       |   0   | no operation
 B728 | LONG NOT (?)   |   0   | apparently bugged (see below)
 B729 | LONG UCMPLT    |   0   | left = ((uint32)left < (uint32)right)
 B72A | LONG UCMPLE    |   0   | left = ((uint32)left <= (uint32)right)
 B72B | LONG UCMPGT    |   0   | left = ((uint32)left > (uint32)right)
 B72C | LONG UCMPGE    |   0   | left = ((uint32)left >= (uint32)right)
 B72D | LONG URSHIFT   |   0   | left = (uint32)left >> right
 B72E | LONG UDIV      |   0   | left = (uint32)left / (uint32)right
 B72F | LONG UMOD      |   0   | left = (uint32)left % (uint32)right
Extension and bool conversion
16-bit instructions only use the lower halves of the registers and leave stale data in the upper halves, so it is necessary to sign-extend or zero-extend when doing arithmetic on mixtures of 16-bit and 32-bit data. Conversely, the bool conversion instruction is needed because the conditional jump instructions only check the (non-)zeroness of the lower half of left.

Instruction $B728
Instruction $B728 is probably supposed to be the long version of logical NOT ($CA) but actually has the same effect as instruction $B71D. None of the games I have inspected uses this instruction (the binary sequence $B7 $28 is nowhere to be found in the ROMs) so if it is a bug it seemingly went undetected for as long as Koei used this interpreter.
Last edited by AWJ on Tue May 16, 2017 7:58 pm, edited 1 time in total.
habstinat
Posts: 3
Joined: Wed Jul 31, 2013 10:45 am

Re: Koei bytecode

Post by habstinat »

This is really cool and I think it definitely at least deserves a page on the NESdev wiki for posterity. Thanks for documenting it.
Drag
Posts: 1615
Joined: Mon Sep 27, 2004 2:57 pm
Contact:

Re: Koei bytecode

Post by Drag »

Wiki page seconded, it'll be easier to organize and maintain.
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: Koei bytecode

Post by Myask »

Neat!

It seems more appropriate for Datacrystal or Zophar as to where to document, but yes to archival.
User avatar
dustmop
Posts: 136
Joined: Wed Oct 16, 2013 7:55 am

Re: Koei bytecode

Post by dustmop »

This is so awesome! Thanks so much for doing all the hard work.

Looking forward to working on a disassembler or decompiler, unless someone else gets around to it first.
User avatar
B00daW
Posts: 586
Joined: Thu Jan 03, 2008 1:48 pm

Re: Koei bytecode

Post by B00daW »

I guess someone should write an open source interpreter too. :X
User avatar
Myask
Posts: 965
Joined: Sat Jul 12, 2014 3:04 pm

Re: Koei bytecode

Post by Myask »

^^^ I guess you just volunteered?
Bitfields
There are three bitfield instructions: one to extract (load from) a signed bitfield, one to extract an unsigned bitfield, and one to insert (store to) a bitfield. For all three bitfield instructions, the first byte after the instruction is the size of the bitfield in bits, and the following byte is the bit position counting from the LSB. The entire bitfield must fit into a 16-bit word (i.e. size + pos < 16)
I was going to ask if the fields were little- or big-endian, but I suppose unless you're trying to make machines interoperable with the states of each other (hardly necessary) it doesn't matter.
CA | NOT | 0 | left = !left
CB | MINUS | 0 | left = -left
CC | COMPL | 0 | left = ~left
with all three existing, I'm curious what NOT does. Is it LEFT[15:0] = (LEFT[15:0] ==0 ? 1 : 0)?

ed: fixed to only write 16 bits to answer.
Last edited by Myask on Tue May 30, 2017 7:32 pm, edited 2 times in total.
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Koei bytecode

Post by AWJ »

Myask wrote:^^^ I guess you just volunteered?
Bitfields
There are three bitfield instructions: one to extract (load from) a signed bitfield, one to extract an unsigned bitfield, and one to insert (store to) a bitfield. For all three bitfield instructions, the first byte after the instruction is the size of the bitfield in bits, and the following byte is the bit position counting from the LSB. The entire bitfield must fit into a 16-bit word (i.e. size + pos < 16)
I was going to ask if the fields were little- or big-endian, but I suppose unless you're trying to make machines interoperable with the states of each other (hardly necessary) it doesn't matter.
Little-endian. That's what I meant by "counting from the LSB". A bitfield with a position of 0 is aligned with the LSB of the word it's in, so no shifting is needed on insertion or extraction.
AWJ
Posts: 433
Joined: Mon Nov 10, 2008 3:09 pm

Re: Koei bytecode

Post by AWJ »

Myask wrote:
CA | NOT | 0 | left = !left
CB | MINUS | 0 | left = -left
CC | COMPL | 0 | left = ~left
with all three existing, I'm curious what NOT does. Is it LEFT = (LEFT[15:0] ==0 ? 1 : 0)?
Yes.
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Koei bytecode

Post by psycopathicteen »

I've been thinking about something similar for the 65816, but with the "word-code" being a list of addresses to jump to.
Oziphantom
Posts: 1565
Joined: Tue Feb 07, 2017 2:03 am

Re: Koei bytecode

Post by Oziphantom »

Why not look at and extract the 16bit KOEI titles to see if they already have the above in 16bit?
Garth
Posts: 246
Joined: Wed Nov 30, 2016 4:45 pm
Location: Southern California
Contact:

Re: Koei bytecode

Post by Garth »

psycopathicteen wrote:I've been thinking about something similar for the 65816, but with the "word-code" being a list of addresses to jump to.
That of course is what Forth normally does, at least in the indirect-threaded code (ITC) and direct-threaded code (DTC) models. On the 6502.org forum, Bruce Clark presents the idea of using the 16-bit S (stack-pointer) register as the instruction pointer in DTC Forth on the '816, at http://forum.6502.org/viewtopic.php?t=586 . Basically the program itself is on the hardware stack! The earlier idea of a two-instruction NEXT in ITC Forth on the '816, which he mentions at the top of the head post, is discussed at http://forum.6502.org/viewtopic.php?t=584 . Just when you get all comfy with some method, along comes Bruce with some wild idea to turn everything upside down and get you to consider a very different approach that may be a lot better in some situations.
http://WilsonMinesCo.com/ lots of 6502 resources
psycopathicteen
Posts: 3140
Joined: Wed May 19, 2010 6:12 pm

Re: Koei bytecode

Post by psycopathicteen »

If you use the stack, you can easily do long addressing.

plx
plb
inc $0000,x
rts
Post Reply