Skip to content

Commit

Permalink
Update the letter to contain the new interrupt system and new instruc…
Browse files Browse the repository at this point in the history
…tion system
  • Loading branch information
joskuijpers committed Apr 9, 2012
1 parent e34929e commit 8fb687c
Showing 1 changed file with 240 additions and 54 deletions.
294 changes: 240 additions & 54 deletions MISC/Open_Letter_Notch.txt
Expand Up @@ -17,68 +17,254 @@ We never had this much fun with game that isn't even released yet. Keep up the g

- The #0x10c-std community.

*** DCPU16 SPECIFICATION CHANGES FOLLOWS ***

====== Changes to opcodes (changes are idented) ======

Values: (6 bits)
0x00-0x08: register (A, B, C, X, Y, Z, I, J or SP, in that order)
0x09-0x11: [register]
0x12-0x1a: [next word + register]
0x1b: CR - a control register: 16 control bits, usable for a lot of things like IO.
0x1c: PC
0x1d: O
0x1e: [next word]
0x1f: next word (literal)
0x20-0x3f: literal value 0x00-0x1f (literal)


Non-basic opcodes: (6 bits)
0x00: reserved for future expansion
0x01: JSR a - pushes the address of the next instruction to the stack, then sets PC to a
0x02: PUSH a - effectively the same as SET [--SP],a but one cycle
0x03: POP a - effectively the same as SET a,[SP++] but one cycle
0x04: INT a - in case of an implementation of interrupts, this fires
an interrupt with interrupt code in a and the registers BC for more options
0x05-0x3f: reserved

====== Interrupts and control register ======

Control Register: (16 bits)
0: IF - interrupt flag. 1 to turn on interrupts
1: Reserved
2-15: interrupt handler PC: must be 4word aligned.
*** DCPU16 SPECIFICATION FOLLOWS ***

DCPU-16 Specification
Copyright 2012 Mojang
Version 1.2
Edited by: 0x10c Standards Committee (rmmh, Jarvix, tpw_rules, et al)

* 16 bit words
* 0x10000 words of ram
* 8 registers (A, B, C, X, Y, Z, I, J)
* program counter (PC)
* stack pointer (SP)
* extra/excess (EX)
* control register (CR) internal

In this document, anything within [brackets] is shorthand for "the value of the RAM at the location of the value inside the brackets".
For example, SP means stack pointer, but [SP] means the value of the RAM at the location the stack pointer is pointing at.

Whenever the CPU needs to read a word, it reads [PC], then increases PC by one. Shorthand for this is [PC++].
In some cases, the CPU will modify a value before reading it, in this case the shorthand is [++PC].

Instructions are 1-3 words long and are fully defined by the first word.
In a basic instruction, the lower five bits of the first word of the instruction are the opcode,
and the remaining eleven bits are split into a five bit value a and a six bit value b.
a is always handled by the processor after b, and is the lower five bits.
In bits (with the least significant being last), a basic instruction has the format: bbbbbbaaaaaooooo



Values: (5/6 bits)
0x00-0x07: register (A, B, C, X, Y, Z, I or J, in that order)
0x08-0x0f: [register]
0x10-0x17: [register + next word]
0x18: (PUSH / [--SP]) if in a, or (POP / [SP++]) if in b
0x19: [SP] / PEEK
0x1a: [SP + next word] / PICK n
0x1b: SP
0x1c: PC
0x1d: EX
0x1e: [next word]
0x1f: next word (literal)
0x20-0x3f: literal value 0xffff-0x1e (-1..30) (literal) (only for b)

* "next word" really means "[PC++]". These increase the word length of the instruction by 1.
* All values that read a word (0x10-0x17, 0x1e, and 0x1f) take 1 cycle to look up. The rest take 0 cycles.
* By using 0x18, 0x19, 0x1a as PEEK, POP/PUSH, and PICK there's a reverse stack starting at memory location 0xffff. Example: "SET PUSH, 10", "SET X, POP"



Basic opcodes: (5 bits)
0x0: special instruction - see below
0x1: SET a, b - sets a to b
0x2: ADD a, b - sets a to a+b, sets EX to 0x0001 if there's an overflow, 0x0 otherwise
0x3: SUB a, b - sets a to a-b, sets EX to 0xffff if there's an underflow, 0x0 otherwise
0x4: MUL a, b - sets a to a*b, sets EX to ((a*b)>>16)&0xffff (treats a, b as unsigned)
0x5: MLI a, b - like MUL, but treat a, b as signed
0x6: DIV a, b - sets a to a/b, sets EX to ((a<<16)/b)&0xffff. if b==0, sets a and EX to 0 instead. (treats a, b as unsigned)
0x7: DVI a, b - like DIV, but treat a, b as signed
0x8: MOD a, b - sets a to a%b. if b==0, sets a to 0 instead.
0x9: AND a, b - sets a to a&b
0xa: BOR a, b - sets a to a|b
0xb: XOR a, b - sets a to a^b
0xc: SHR a, b - sets a to a>>>b, sets EX to ((a<<16)>>b)&0xffff (logical shift)
0xd: ASR a, b - sets a to a>>b, sets EX to ((a<<16)>>>b)&0xffff (arithmetic shift) (treats a as signed)
0xe: SHL a, b - sets a to a<<b, sets EX to ((a<<b)>>16)&0xffff
0x10: special instruction - see below
0x11: IFB a, b - performs next instruction only if (a&b)!=0 (Bit set)
0x12: IFE a, b - performs next instruction only if a==b (Equal)
0x13: IFN a, b - performs next instruction only if a!=b (Not equal)
0x14: IFG a, b - performs next instruction only if a>b (signed) (Greater)
0x15: IFA a, b - performs next instruction only if a>b (unsigned) (Above)
0x16: IFL a, b - performs next instruction only if a<b (signed) (Less)
0x17: IFU a, b - performs next instruction only if a<b (unsigned) (Under)

* SET, AND, BOR and XOR take 1 cycle, plus the cost of a and b
* ADD, SUB, MUL, MLI, SHR, SHL, and ASR take 2 cycles, plus the cost of a and b
* DIV, DVI, and MOD take 3 cycles, plus the cost of a and b
* IFs take 2 cycles, plus the cost of a and b, plus 1 if the test fails
* JSR takes 2 cycles, plus the cost of a.
* Signed numbers are represented using two's complement.



Special opcodes always have their lower four bits unset, have one value and a seven bit opcode.
In binary, they have the format: aaaaaooooooo0000
The value (a) is in the same five bit format as defined earlier.

Special opcodes: (7 bits)
0x00: reserved for future expansion
0x01: JSR a - pushes the address of the next instruction to the stack, then sets PC to a
0x02: INT n - Sends an interrupt with number n
0x03: CLI - Clears the interrupt flag
0x04: STI - Sets the interrupt flag
0x05: LCR a - Loads the CR into a
0x06: SCR a - Stores the value of a into CR
0x07-0x7f: reserved

The CR register: (16 bits, word)
bit 0: IF - interrupt flag, 1 = interrupts enabled
bit 1-15: Reserved

FAQ:

Q: Why is there no JMP or RET?
A: They're not needed! "SET PC, <target>" is a one-instruction JMP.
For small relative jumps in a single word, you can even do "ADD PC, <dist>" or "SUB PC, <dist>".
For RET, simply do "SET PC, POP"

Q: How does the extra register (EX) work?
A: EX is set by certain instructions (see above), but never automatically read. You can use its value in instructions, however.
For example, to do a 32 bit add of 0x12345678 and 0xaabbccdd, do this:
SET [0x1000], 0x5678 ; low word
SET [0x1001], 0x1234 ; high word
ADD [0x1000], 0xccdd ; add low words, sets EX to either 0 or 1 (in this case 1)
ADD [0x1001], EX ; add EX to the high word
ADD [0x1001], 0xaabb ; add high words, sets EX again (to 0, as 0xaabb+0x1235 is lower than 0x10000)

Q: How do I do 32 or 64 bit division using EX?
A: This is left as an exercise for the reader.

Q: How about a quick example?
A: Sure! Here's some sample assembler, and a memory dump of the compiled code:

; Try some basic stuff
SET A, 0x30 ; 7c01 0030
SET [0x1000], 0x20 ; 7de1 1000 0020
SUB A, [0x1000] ; 7803 1000
IFN A, 0x10 ; c00d
SET PC, crash ; 7dc1 001a [*]

; Do a loopy thing
SET I, 10 ; a861
SET A, 0x2000 ; 7c01 2000
:loop SET [0x2000+I], [A] ; 2161 2000
SUB I, 1 ; 8463
IFN I, 0 ; 806d
SET PC, loop ; 7dc1 000d [*]

; Call a subroutine
SET X, 0x4 ; 9031
JSR testsub ; 7c10 0018 [*]
SET PC, crash ; 7dc1 001a [*]

:testsub SHL X, 4 ; 9037
SET PC, POP ; 61c1

; Hang forever. X should now be 0x40 if everything went right.
:crash SET PC, crash ; 7dc1 001a [*]

; [*]: Note that these can be one word shorter and one cycle faster by using the short form (0x00-0x1f) of literals,
; but my assembler doesn't support short form labels yet.

Full memory dump:

0000: 7c01 0030 7de1 1000 0020 7803 1000 c00d
0008: 7dc1 001a a861 7c01 2000 2161 2000 8463
0010: 806d 7dc1 000d 9031 7c10 0018 7dc1 001a
0018: 9037 61c1 7dc1 001a 0000 0000 0000 0000

====== Interrupts ======

== Enabling interrupts
To enable interrupts, the handler address has to be set.
The handler should be 4-word aligned.
To enable interrupts, the address of your handler and the interrupt flag have to be set.
The address of the handler should be put into a -to-be-defined-location-.

The program will do something along the lines:

SET X, CR ; read the control register
SET Y, myHandler ; get the address of the handler
AND Y, 0xFFFC ; remove lower 2 bits (should already be 0, so this could be ommitted)
OR X, Y ; or it to X, the copy of CR
OR X, 1 ; set the interrupt flag
SET CR, X ; put the new data into the control register
SET [0xFFFE], myHandler ; set the address of the handler into the predefined location (0xFFFE is an example)
XOR CR, 1 ; enable the interrupt flag

== An interrupt occurs
The virtual machine received an interrupt (or creates one).
The VM will do the following:

1. Verify the Interrupt Flag is 1 by AND-ing CR with 1 and comparing.
If it is 0, skip interrupt handling
2. It reads the handler from the Control register by AND-in the CR with 0xFFFC
3. It stores all registers except CR (as it will not change)
4. It put the interrupt code in A, and additional information in other registers
; 5. Optional: set IF to 0
6. It calls the handler (JSR)
The handler performs the action
7. When the handler returned, the vm restores all registers
; 8. Optional: set IF to 1

If clearing IF by the VM is ommitted, the handler has to do it to prevent interruption of interrupts.
As the VM can do this more easily than the emulated code, making the VM do it is probably easier and faster.

An Interrupt Vector Table is considered and would be faster when we reach more than 2 interrupts.
A simple implementation would be a 4word aligned IVT into the CR. The VM then reads the A-th word
(A is the register) from this table and uses that as handler address instead.
If it is 1, set it to 0 and continue interrupt handling
2. It reads the handler address from the predefined location in memory
3. If the address equals 0xFFFF, handling the interrupts is skipped
5. Pushes the PC onto the stack
6. Pushes the argument onto the stack
7. Pushes the interrupt number onto the stack
8. It calls the handler with SET PC,handler
The handler performs the action and returns directly to the interrupted code

The INT opcode does the same as a hardware interrupt. argument 2 is 0, argument 1 is the interrupt code.

=== Example that would be possible with this system
; Code by Jarvix

;There was an interrupt by the timer!

;;;VM

; check the IF and clear it

; This is how the stack looks
SET PUSH, PC ; is current PC
SET PUSH, 0 ; timer has no argument
SET PUSH, 1 ; timer is interrupt 1

; vm does the lookup job
; jumps to the handler

;;;HANDLER
:interruptHandler
; arg is in [SP]
; arg2 is in [SP+1]
; PC is in [SP+2]

; Save all registers
SET PUSH, EX
SET PUSH, A
SET PUSH, B
SET PUSH, C
SET PUSH, X
SET PUSH, Y
SET PUSH, I
SET PUSH, J

; Get new stack address somehow
; Each program has its own stack.
; Switching the stack pointer continues running
; the next program

; Set new stack address
SET SP, newStack

; We now are on the new stack of another task,
; that also pushed their registers
SET J, POP
SET I, POP
SET Y, POP
SET X, POP
SET C, POP
SET B, POP
SET A, POP
SET EX, POP

; We restore the program

; Remove the interrupt info without clobbering
SUB SP,2

; Turn on interrupts
STI

; in notch-code this is SET PC,POP
SET PC, POP

; We now run not in the VM but the actual code again, skipping thus the VM.

0 comments on commit 8fb687c

Please sign in to comment.