minor cleanups, added docs and test files
authorJustin Wind <justin.wind@gmail.com>
Tue, 10 Apr 2012 19:06:24 +0000 (12:06 -0700)
committerJustin Wind <justin.wind@gmail.com>
Tue, 10 Apr 2012 19:06:24 +0000 (12:06 -0700)
.gitignore [new file with mode: 0644]
docs/dcpu-16.txt [new file with mode: 0644]
tests/test.s [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..c774e03
--- /dev/null
@@ -0,0 +1,3 @@
+as-dcpu16
+dcpu16
+*.o
diff --git a/docs/dcpu-16.txt b/docs/dcpu-16.txt
new file mode 100644 (file)
index 0000000..2e99ed2
--- /dev/null
@@ -0,0 +1,142 @@
+DCPU-16 Specification\r
+Copyright 2012 Mojang\r
+Version 1.1 (Check 0x10c.com for updated versions)\r
+\r
+* 16 bit unsigned words\r
+* 0x10000 words of ram\r
+* 8 registers (A, B, C, X, Y, Z, I, J)\r
+* program counter (PC)\r
+* stack pointer (SP)\r
+* overflow (O)\r
+\r
+In this document, anything within [brackets] is shorthand for "the value of the RAM at the location of the value inside the brackets".\r
+For example, SP means stack pointer, but [SP] means the value of the RAM at the location the stack pointer is pointing at.\r
+\r
+Whenever the CPU needs to read a word, it reads [PC], then increases PC by one. Shorthand for this is [PC++].\r
+In some cases, the CPU will modify a value before reading it, in this case the shorthand is [++PC].\r
+\r
+Instructions are 1-3 words long and are fully defined by the first word.\r
+In a basic instruction, the lower four bits of the first word of the instruction are the opcode,\r
+and the remaining twelve bits are split into two six bit values, called a and b.\r
+a is always handled by the processor before b, and is the lower six bits.\r
+In bits (with the least significant being last), a basic instruction has the format: bbbbbbaaaaaaoooo\r
+\r
+\r
+\r
+Values: (6 bits)\r
+    0x00-0x07: register (A, B, C, X, Y, Z, I or J, in that order)\r
+    0x08-0x0f: [register]\r
+    0x10-0x17: [next word + register]\r
+         0x18: POP / [SP++]\r
+         0x19: PEEK / [SP]\r
+         0x1a: PUSH / [--SP]\r
+         0x1b: SP\r
+         0x1c: PC\r
+         0x1d: O\r
+         0x1e: [next word]\r
+         0x1f: next word (literal)\r
+    0x20-0x3f: literal value 0x00-0x1f (literal)\r
+    \r
+* "next word" really means "[PC++]". These increase the word length of the instruction by 1. \r
+* If any instruction tries to assign a literal value, the assignment fails silently. Other than that, the instruction behaves as normal.\r
+* All values that read a word (0x10-0x17, 0x1e, and 0x1f) take 1 cycle to look up. The rest take 0 cycles.\r
+* By using 0x18, 0x19, 0x1a as POP, PEEK and PUSH, there's a reverse stack starting at memory location 0xffff. Example: "SET PUSH, 10", "SET X, POP"\r
+\r
+\r
+\r
+Basic opcodes: (4 bits)\r
+    0x0: non-basic instruction - see below\r
+    0x1: SET a, b - sets a to b\r
+    0x2: ADD a, b - sets a to a+b, sets O to 0x0001 if there's an overflow, 0x0 otherwise\r
+    0x3: SUB a, b - sets a to a-b, sets O to 0xffff if there's an underflow, 0x0 otherwise\r
+    0x4: MUL a, b - sets a to a*b, sets O to ((a*b)>>16)&0xffff\r
+    0x5: DIV a, b - sets a to a/b, sets O to ((a<<16)/b)&0xffff. if b==0, sets a and O to 0 instead.\r
+    0x6: MOD a, b - sets a to a%b. if b==0, sets a to 0 instead.\r
+    0x7: SHL a, b - sets a to a<<b, sets O to ((a<<b)>>16)&0xffff\r
+    0x8: SHR a, b - sets a to a>>b, sets O to ((a<<16)>>b)&0xffff\r
+    0x9: AND a, b - sets a to a&b\r
+    0xa: BOR a, b - sets a to a|b\r
+    0xb: XOR a, b - sets a to a^b\r
+    0xc: IFE a, b - performs next instruction only if a==b\r
+    0xd: IFN a, b - performs next instruction only if a!=b\r
+    0xe: IFG a, b - performs next instruction only if a>b\r
+    0xf: IFB a, b - performs next instruction only if (a&b)!=0\r
+    \r
+* SET, AND, BOR and XOR take 1 cycle, plus the cost of a and b\r
+* ADD, SUB, MUL, SHR, and SHL take 2 cycles, plus the cost of a and b\r
+* DIV and MOD take 3 cycles, plus the cost of a and b\r
+* IFE, IFN, IFG, IFB take 2 cycles, plus the cost of a and b, plus 1 if the test fails\r
+    \r
+\r
+    \r
+Non-basic opcodes always have their lower four bits unset, have one value and a six bit opcode.\r
+In binary, they have the format: aaaaaaoooooo0000\r
+The value (a) is in the same six bit format as defined earlier.\r
+\r
+Non-basic opcodes: (6 bits)\r
+         0x00: reserved for future expansion\r
+         0x01: JSR a - pushes the address of the next instruction to the stack, then sets PC to a\r
+    0x02-0x3f: reserved\r
+    \r
+* JSR takes 2 cycles, plus the cost of a.\r
+\r
+\r
+\r
+FAQ:\r
+\r
+Q: Why is there no JMP or RET?\r
+A: They're not needed! "SET PC, <target>" is a one-instruction JMP.\r
+   For small relative jumps in a single word, you can even do "ADD PC, <dist>" or "SUB PC, <dist>".\r
+   For RET, simply do "SET PC, POP"\r
+   \r
+Q: How does the overflow (O) work?\r
+A: O is set by certain instructions (see above), but never automatically read. You can use its value in instructions, however.\r
+   For example, to do a 32 bit add of 0x12345678 and 0xaabbccdd, do this:\r
+      SET [0x1000], 0x5678    ; low word\r
+      SET [0x1001], 0x1234    ; high word\r
+      ADD [0x1000], 0xccdd    ; add low words, sets O to either 0 or 1 (in this case 1)\r
+      ADD [0x1001], O         ; add O to the high word\r
+      ADD [0x1001], 0xaabb    ; add high words, sets O again (to 0, as 0xaabb+0x1235 is lower than 0x10000)\r
+\r
+Q: How do I do 32 or 64 bit division using O?\r
+A: This is left as an exercise for the reader.\r
+     \r
+Q: How about a quick example?\r
+A: Sure! Here's some sample assembler, and a memory dump of the compiled code:\r
+\r
+        ; Try some basic stuff\r
+                      SET A, 0x30              ; 7c01 0030\r
+                      SET [0x1000], 0x20       ; 7de1 1000 0020\r
+                      SUB A, [0x1000]          ; 7803 1000\r
+                      IFN A, 0x10              ; c00d \r
+                         SET PC, crash         ; 7dc1 001a [*]\r
+                      \r
+        ; Do a loopy thing\r
+                      SET I, 10                ; a861\r
+                      SET A, 0x2000            ; 7c01 2000\r
+        :loop         SET [0x2000+I], [A]      ; 2161 2000\r
+                      SUB I, 1                 ; 8463\r
+                      IFN I, 0                 ; 806d\r
+                         SET PC, loop          ; 7dc1 000d [*]\r
+        \r
+        ; Call a subroutine\r
+                      SET X, 0x4               ; 9031\r
+                      JSR testsub              ; 7c10 0018 [*]\r
+                      SET PC, crash            ; 7dc1 001a [*]\r
+        \r
+        :testsub      SHL X, 4                 ; 9037\r
+                      SET PC, POP              ; 61c1\r
+                        \r
+        ; Hang forever. X should now be 0x40 if everything went right.\r
+        :crash        SET PC, crash            ; 7dc1 001a [*]\r
+        \r
+        ; [*]: Note that these can be one word shorter and one cycle faster by using the short form (0x00-0x1f) of literals,\r
+        ;      but my assembler doesn't support short form labels yet.     \r
+\r
+  Full memory dump:\r
+  \r
+        0000: 7c01 0030 7de1 1000 0020 7803 1000 c00d\r
+        0008: 7dc1 001a a861 7c01 2000 2161 2000 8463\r
+        0010: 806d 7dc1 000d 9031 7c10 0018 7dc1 001a\r
+        0018: 9037 61c1 7dc1 001a 0000 0000 0000 0000\r
+        
\ No newline at end of file
diff --git a/tests/test.s b/tests/test.s
new file mode 100644 (file)
index 0000000..b67c81b
--- /dev/null
@@ -0,0 +1,33 @@
+;
+; dcpu16 test asm
+;
+
+; 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.     
+