commit aedf6873a788c93d5dcb16ff80cbe0234a59bf29 Author: Maurizio Porrato Date: Sat Oct 19 09:31:07 2019 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f031d1e --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +*% +*~ +*.o +*.orig +dsim diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..c12a8bd --- /dev/null +++ b/Makefile @@ -0,0 +1,15 @@ +.PHONY: all clean reformat + +CFLAGS=-Wall -Werror -pedantic -std=c99 -O2 -mtune=native +REFORMAT=astyle --style=linux + +all: dsim + +clean: + $(RM) *~ *% *.o *.orig + $(RM) dsim + +reformat: $(wildcard *.c) + $(REFORMAT) $^ + +# vim:noet:sw=8:sts=8: diff --git a/data/c64-80x25-font.png b/data/c64-80x25-font.png new file mode 100644 index 0000000..a09e9ea Binary files /dev/null and b/data/c64-80x25-font.png differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..470dac4 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,39 @@ +# Hardware specifications for 0x10c + +May 2014 + +This repository serves as a historical archive containing specifications for the fictional hardware of the game 0x10c. The game was to be a multiplayer sandbox game set in space, with a fully programmable CPU controlling a ship. The game was [cancelled in 2013][1] to much dismay of fans. A number of fan projects appeared aiming at continuing development, but they also appear to be abandoned. + +There are a large number of fan works on GitHub, mainly implementations of the DCPU-16 hardware or code to run on it. GitHub still has a list of [DCPU-16 ASM trending repositories][2]. These usually included links to the official specifications which were either hosted on Pastebin or 0x10c.com. The later has been been offline since February 2014 (weirdly the domain was renewed for another year in April 2014), so this is my attempt to archive them for future reference. + +## Official Specifications + +* [DCPU-16](dcpu16.txt) - 16bit CPU. +* [LEM1802](lem1802.txt) - 128x96 pixel color display. +* [SPED-3](sped3.txt) - 3D vector display. +* [M35FD](m35fd.txt) - Floppy disk drive. +* [SPC2000](spc2000.txt) - Deep sleep chamber. This is part of 0x10c's backstory (the number of years to sleep was entered in the wrong byte order). +* [Clock](clock.txt) - Basic real time clock. +* [Keyboard](keyboard.txt) - Keyboard interface. + +## Community Adopted Specifications + +The floppy drive specification was released approximately six months after the release of the initial specifications. Before this time a fan work specification for the [HMD2043 floppy disk drive](https://gist.github.com/DanielKeep/2495578) was released. A number of emulators continue to use this rather than the official M35FD. + +Although the specification of the LEM1802 says it must be initialised before use, most emulators start with the device already initialised with video memory mapped to 0x8000. + +Although there is a specification for a keyboard device, Notch's alpha releases of 0x10c which included a functional DCPU-16 system did not follow this. Instead the keyboard is interfaced through a 16 letter ring buffer mapped at 0x9000 (non configurable). An address is 0 before a key is pressed, and should be written as 0 again after being read so it can be checked later. Most emulators follow this functionality. + +The official specifications don't go too in depth into the format of the assembly language, and there has been no official assembler released. The code in the specification is similar to NASM and community assemblers are also based on this. Most support labels, and some add macros. The language has come to be known as DASM (DCPU-16 Assembly) and typically has the extension `dasm` or `dasm16`. Object code has the extension `bin`, `dcpu` or `dcpu16`. + +## Copyright + +The copyright notices in the specifications are assumed to be fictional due to the copyright dates being dated when the game was set (1980s), not when the fictional works were written (2012/2013). Even so, these fictional works are still assumed to be under the copyright of Mojang. They are being reproduced here to serve as a non-commerical archive which is permitted under most copyright laws around the world (including the US where GitHub is based, and the UK where I am based). + +The implementation of these specifications in your own commerical work is permitted (it is just a specification and is not patented), however you probably should not reproduce these specification documents as is. Care should be taken with hardware and manufacturer names, as they may also be under the copyright of Mojang. + +[Notch has said on Reddit][3] that Mojang didn't want these files to be distributed originally, as they didn't want the hardware to become fragmented before the game was finished. He has also said that implementing the hardware in your own games is allowed. + +[1]: http://www.rockpapershotgun.com/2013/08/19/0x10c-cancelled-for-good-but-fans-plan-to-do-it-anyway/ +[2]: https://github.com/trending?l=dcpu-16-asm +[3]: http://www.reddit.com/r/dcpu16/comments/1zykmx/hey_guys_what_sort_of_copyright_is_the_dcpu16/cfy7igf diff --git a/docs/clock.txt b/docs/clock.txt new file mode 100644 index 0000000..71bbc8b --- /dev/null +++ b/docs/clock.txt @@ -0,0 +1,17 @@ +Name: Generic Clock (compatible) +ID: 0x12d0b402 +Version: 1 + +Interrupts do different things depending on contents of the A register: + + A | BEHAVIOR +---+---------------------------------------------------------------------------- + 0 | The B register is read, and the clock will tick 60/B times per second. + | If B is 0, the clock is turned off. + 1 | Store number of ticks elapsed since last call to 0 in C register + 2 | If register B is non-zero, turn on interrupts with message B. If B is zero, + | disable interrupts +---+---------------------------------------------------------------------------- + +When interrupts are enabled, the clock will trigger an interrupt whenever it +ticks. diff --git a/docs/dcpu16.txt b/docs/dcpu16.txt new file mode 100644 index 0000000..3cdaaf2 --- /dev/null +++ b/docs/dcpu16.txt @@ -0,0 +1,212 @@ +DCPU-16 Specification +Copyright 1985 Mojang +Version 1.7 + + + +=== SUMMARY ==================================================================== + +* 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) +* interrupt address (IA) + +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]. + +For stability and to reduce bugs, it's strongly suggested all multi-word +operations use little endian in all DCPU-16 programs, wherever possible. + + + +=== INSTRUCTIONS =============================================================== + +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 b +and a six bit value a. +b is always handled by the processor after a, and is the lower five bits. +In bits (in LSB-0 format), a basic instruction has the format: aaaaaabbbbbooooo + +In the tables below, C is the time required in cycles to look up the value, or +perform the opcode, VALUE is the numerical value, NAME is the mnemonic, and +DESCRIPTION is a short text that describes the opcode or value. + + + +--- Values: (5/6 bits) --------------------------------------------------------- + C | VALUE | DESCRIPTION +---+-----------+---------------------------------------------------------------- + 0 | 0x00-0x07 | register (A, B, C, X, Y, Z, I or J, in that order) + 0 | 0x08-0x0f | [register] + 1 | 0x10-0x17 | [register + next word] + 0 | 0x18 | (PUSH / [--SP]) if in b, or (POP / [SP++]) if in a + 0 | 0x19 | [SP] / PEEK + 1 | 0x1a | [SP + next word] / PICK n + 0 | 0x1b | SP + 0 | 0x1c | PC + 0 | 0x1d | EX + 1 | 0x1e | [next word] + 1 | 0x1f | next word (literal) + 0 | 0x20-0x3f | literal value 0xffff-0x1e (-1..30) (literal) (only for a) + --+-----------+---------------------------------------------------------------- + +* "next word" means "[PC++]". Increases the word length of the instruction by 1. +* 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" +* Attempting to write to a literal value fails silently + + + +--- Basic opcodes (5 bits) ---------------------------------------------------- + C | VAL | NAME | DESCRIPTION +---+------+----------+--------------------------------------------------------- + - | 0x00 | n/a | special instruction - see below + 1 | 0x01 | SET b, a | sets b to a + 2 | 0x02 | ADD b, a | sets b to b+a, sets EX to 0x0001 if there's an overflow, + | | | 0x0 otherwise + 2 | 0x03 | SUB b, a | sets b to b-a, sets EX to 0xffff if there's an underflow, + | | | 0x0 otherwise + 2 | 0x04 | MUL b, a | sets b to b*a, sets EX to ((b*a)>>16)&0xffff (treats b, + | | | a as unsigned) + 2 | 0x05 | MLI b, a | like MUL, but treat b, a as signed + 3 | 0x06 | DIV b, a | sets b to b/a, sets EX to ((b<<16)/a)&0xffff. if a==0, + | | | sets b and EX to 0 instead. (treats b, a as unsigned) + 3 | 0x07 | DVI b, a | like DIV, but treat b, a as signed. Rounds towards 0 + 3 | 0x08 | MOD b, a | sets b to b%a. if a==0, sets b to 0 instead. + 3 | 0x09 | MDI b, a | like MOD, but treat b, a as signed. (MDI -7, 16 == -7) + 1 | 0x0a | AND b, a | sets b to b&a + 1 | 0x0b | BOR b, a | sets b to b|a + 1 | 0x0c | XOR b, a | sets b to b^a + 1 | 0x0d | SHR b, a | sets b to b>>>a, sets EX to ((b<<16)>>a)&0xffff + | | | (logical shift) + 1 | 0x0e | ASR b, a | sets b to b>>a, sets EX to ((b<<16)>>>a)&0xffff + | | | (arithmetic shift) (treats b as signed) + 1 | 0x0f | SHL b, a | sets b to b<>16)&0xffff + + 2+| 0x10 | IFB b, a | performs next instruction only if (b&a)!=0 + 2+| 0x11 | IFC b, a | performs next instruction only if (b&a)==0 + 2+| 0x12 | IFE b, a | performs next instruction only if b==a + 2+| 0x13 | IFN b, a | performs next instruction only if b!=a + 2+| 0x14 | IFG b, a | performs next instruction only if b>a + 2+| 0x15 | IFA b, a | performs next instruction only if b>a (signed) + 2+| 0x16 | IFL b, a | performs next instruction only if b +#include +#include +#include + +uint16_t ram[0x10000]; +uint16_t ra, rb, rc, rx, ry, rz, ri, rj; +uint16_t rpc, rsp, rex, ria; +bool skip_next; +uint64_t ticks; +bool running; +bool intq_en; +#define MAX_INTQ_SIZE 256 +uint16_t intq[MAX_INTQ_SIZE]; +unsigned int intq_size; +uint8_t intq_head; + +void intq_push(uint16_t v) +{ + if (intq_size < MAX_INTQ_SIZE) { + intq[intq_head] = v; + intq_head = (intq_head + 1) % MAX_INTQ_SIZE; + intq_size++; + } +} + +uint16_t intq_pop() +{ + if (intq_size > 0) { + return intq[(intq_head-intq_size--) % MAX_INTQ_SIZE]; + } else + return 0xffff; +} + +void reset() +{ + ra = rb = rc = rx = ry = rz = ri = rj = 0; + rpc = rsp = rex = ria = 0; + + intq_en = false; + intq_size = 0; + intq_head = 0; + + skip_next = false; + ticks = 0; + + running = true; +} + +typedef void (*dev_t)(void); + +struct dev_entry { + uint32_t vendor; + uint32_t product; + uint16_t version; + void (*irqh)(); + void (*init)(); + void (*free)(); + void (*tick)(); +}; + +struct dev_entry iodevs[] = { + { 0, 0, 0, NULL, NULL, NULL, NULL} +}; + +uint16_t lit[] = { + 0xffff, 0, 1, 2, 3, 4, 5, 6, + 7, 8, 9, 10, 11, 12, 13, 14, + 15, 16, 17, 18, 19, 20, 21, 22, + 23, 24, 25, 26, 27, 28, 29, 30 +}; + +uint16_t *val(int operand, bool is_a) +{ + switch (operand) { + case 0x00: + return &ra; + case 0x01: + return &rb; + case 0x02: + return &rc; + case 0x03: + return ℞ + case 0x04: + return &ry; + case 0x05: + return &rz; + case 0x06: + return &ri; + case 0x07: + return &rj; + case 0x08: + return &ram[ra]; + case 0x09: + return &ram[rb]; + case 0x0a: + return &ram[rc]; + case 0x0b: + return &ram[rx]; + case 0x0c: + return &ram[ry]; + case 0x0d: + return &ram[rz]; + case 0x0e: + return &ram[ri]; + case 0x0f: + return &ram[rj]; + case 0x10: + ticks++; + return &ram[ra+ram[rpc++]]; + case 0x11: + ticks++; + return &ram[rb+ram[rpc++]]; + case 0x12: + ticks++; + return &ram[rc+ram[rpc++]]; + case 0x13: + ticks++; + return &ram[rx+ram[rpc++]]; + case 0x14: + ticks++; + return &ram[ry+ram[rpc++]]; + case 0x15: + ticks++; + return &ram[rz+ram[rpc++]]; + case 0x16: + ticks++; + return &ram[ri+ram[rpc++]]; + case 0x17: + ticks++; + return &ram[rj+ram[rpc++]]; + case 0x18: + return (is_a ? &ram[rsp++] : &ram[--rsp]); + case 0x19: + return &ram[rsp]; + case 0x1a: + ticks++; + return &ram[rsp+ram[rpc++]]; + case 0x1b: + return &rsp; + case 0x1c: + return &rpc; + case 0x1d: + return &rex; + case 0x1e: + ticks++; + return &ram[ram[rpc++]]; + case 0x1f: + ticks++; + return &ram[rpc++]; /* FIXME: write to literal */ + default: + return &lit[operand-0x20]; + } +} + +void oNOP(uint16_t *pa, uint16_t *pb) +{ +} + +void oSET(uint16_t *a, uint16_t *b) +{ + *b = *a; +} + +void oADD(uint16_t *a, uint16_t *b) +{ + uint16_t pre = *b; + + *b += *a; + rex = (pre>*b ? 0x0001 : 0x0000); + ticks++; +} + +void oSUB(uint16_t *a, uint16_t *b) +{ + uint16_t pre = *b; + + *b -= *a; + rex = (pre<*b ? 0xffff : 0x0000); + ticks++; +} + +void oMUL(uint16_t *a, uint16_t *b) +{ + uint32_t res = *b * *a; + + *b = res & 0xffff; + rex = (res>>16) & 0xffff; + ticks++; +} + +void oMLI(uint16_t *a, uint16_t *b) +{ + int32_t res = (int16_t)*a * (int16_t)*b; + + *b = (uint32_t)res & 0xffff; + rex = ((uint32_t)res>>16) & 0xffff; + ticks++; +} + +void oDIV(uint16_t *a, uint16_t *b) +{ + if (*a) { + rex = ((uint32_t)*b<<16) / *a; + *b /= *a; + } else { + *b = rex = 0; + } +} + +void oDVI(uint16_t *a, uint16_t *b) +{ + if (*a) { + rex = ((int32_t)*b<<16) / (int16_t)*a; + *b = (int16_t)*b / (int16_t)*a; + } else { + *b = rex = 0; + } +} + +void oMOD(uint16_t *a, uint16_t *b) +{ + if (*a) { + *b %= *a; + } else { + *b = 0; + } +} + +void oMDI(uint16_t *a, uint16_t *b) +{ + if (*a) { + *b = (int16_t)*b / (int16_t)*a; + } else { + *b = 0; + } +} + +void oAND(uint16_t *a, uint16_t *b) +{ + *b &= *a; +} + +void oBOR(uint16_t *a, uint16_t *b) +{ + *b |= *a; +} + +void oXOR(uint16_t *a, uint16_t *b) +{ + *b ^= *a; +} + +void oSHR(uint16_t *a, uint16_t *b) +{ + uint16_t t; + + t = (uint32_t)*b >> *a; + rex = ((uint64_t)*b << 16) >> *a; + *b = t; +} + +void oASR(uint16_t *a, uint16_t *b) +{ + uint16_t t; + + t = *b >> *a; + rex = ((uint32_t)*b << 16) >> *a; + *b = t; +} + +void oSHL(uint16_t *a, uint16_t *b) +{ + uint16_t t; + + t = *b << *a; + rex = ((uint64_t)*b << *a) >> 16; + *b = t; +} + +#define DOIF(c) if (!(c)) skip_next = true + +void oIFB(uint16_t *a, uint16_t *b) +{ + DOIF((*b & *a) != 0); +} + +void oIFC(uint16_t *a, uint16_t *b) +{ + DOIF((*b & *a) == 0); +} + +void oIFE(uint16_t *a, uint16_t *b) +{ + DOIF(*b == *a); +} + +void oIFN(uint16_t *a, uint16_t *b) +{ + DOIF(*b != *a); +} + +void oIFG(uint16_t *a, uint16_t *b) +{ + DOIF(*b > *a); +} + +void oIFA(uint16_t *a, uint16_t *b) +{ + DOIF((int16_t)*b > (int16_t)*a); +} + +void oIFL(uint16_t *a, uint16_t *b) +{ + DOIF(*b < *a); +} + +void oIFU(uint16_t *a, uint16_t *b) +{ + DOIF((int16_t)*b < (int16_t)*a); +} + +void oADX(uint16_t *a, uint16_t *b) +{ + uint16_t t = *b; + + *b += *a + rex; + rex = (t>*b ? 0x0001 : 0x0000); + ticks += 2; +} + +void oSBX(uint16_t *a, uint16_t *b) +{ + uint16_t t = *b; + + *b -= *a - rex; + rex = (t<*b ? 0xffff : 0x0000); + ticks += 2; +} + +void oSTI(uint16_t *a, uint16_t *b) +{ + *b = *a; + ri++; + rj++; + ticks++; +} + +void oSTD(uint16_t *a, uint16_t *b) +{ + *b = *a; + ri--; + rj--; + ticks++; +} + +void oJSR(uint16_t *a, uint16_t *b) +{ + ram[--rsp] = rpc; + rpc = *a; +} + +void oINT(uint16_t *a, uint16_t *b) +{ + intq_push(*a); + ticks += 3; +} + +void oIAG(uint16_t *a, uint16_t *b) +{ + *a = ria; +} + +void oIAS(uint16_t *a, uint16_t *b) +{ + ria = *a; +} + +void oRFI(uint16_t *a, uint16_t *b) +{ + intq_en = false; + ra = ram[rsp++]; + rpc = ram[rsp++]; +} + +void oIAQ(uint16_t *a, uint16_t *b) +{ + intq_en = (*a != 0); +} + +void oHWN(uint16_t *a, uint16_t *b) +{ + *a = sizeof(iodevs) / sizeof(struct dev_entry); +} + +void oHWQ(uint16_t *a, uint16_t *b) +{ + uint16_t idx = *a; + + if (idx < sizeof(iodevs) / sizeof(struct dev_entry)) { + ra = iodevs[idx].product & 0xffff; + rb = (iodevs[idx].product >> 16) & 0xffff; + rc = iodevs[idx].version; + rx = iodevs[idx].vendor & 0xffff; + ry = (iodevs[idx].vendor >> 16) & 0xffff; + } else { + ra = rb = rc = rx = ry = 0; + } +} + +void oHWI(uint16_t *a, uint16_t *b) +{ + uint16_t idx = *a; + + if (idx < sizeof(iodevs) / sizeof(struct dev_entry)) { + if (iodevs[idx].irqh) + iodevs[idx].irqh(); + } +} + +void oHLT(uint16_t *a, uint16_t *b) +{ + running = false; +} + +typedef void (*op_t)(uint16_t *, uint16_t *); + +const op_t ops[] = { + oNOP, oSET, oADD, oSUB, oMUL, oMLI, oDIV, oDVI, /* 00-07 */ + oMOD, oMDI, oAND, oBOR, oXOR, oSHR, oASR, oSHL, /* 08-0f */ + oIFB, oIFC, oIFE, oIFN, oIFG, oIFA, oIFL, oIFU, /* 10-17 */ + oNOP, oNOP, oADX, oSBX, oNOP, oNOP, oSTI, oSTD /* 18-1f */ +}; + +const op_t sops[] = { + oNOP, oJSR, oNOP, oNOP, oNOP, oNOP, oNOP, oNOP, /* 00-07 */ + oINT, oIAG, oIAS, oRFI, oIAQ, oNOP, oNOP, oNOP, /* 08-0f */ + oHWN, oHWQ, oHWI, oNOP, oNOP, oNOP, oNOP, oNOP, /* 10-17 */ + oNOP, oNOP, oNOP, oNOP, oNOP, oNOP, oNOP, oHLT /* 18-1f */ +}; + +void dumpregs() +{ + printf("%4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s %4s\n", + "PC","SP","A","B","C","X","Y","Z","I","J","EX","IA"); + printf("%04hx %04hx %04hx %04hx %04hx %04hx %04hx %04hx %04hx %04hx %04hx %04hx\n", + rpc, rsp, ra, rb, rc, rx, ry, rz, ri, rj, rex, ria); +} + +void next() +{ + uint16_t ir; + int opcode, a, b; + uint16_t *pa, *pb; + op_t f; + uint16_t i; + + if ((!intq_en) && (intq_size > 0)) { + i = intq_pop(); + if (ria != 0) { + intq_en = true; + ram[--rsp] = rpc; + ram[--rsp] = ra; + rpc = ria; + ra = i; + } + } + + dumpregs(); + + ir = ram[rpc++]; + + opcode = ir & 0x001f; + a = (ir >> 5) & 0x001f; + b = (ir >> 10) & 0x003f; + + if (opcode == 0) { /* special instruction */ + f = sops[b]; + pa = val(a, true); + pb = NULL; + } else { + f = ops[opcode]; + pa = val(a, true); + pb = val(b, false); + } + + if (!skip_next) + f(pa, pb); + else + skip_next = false; + + ticks++; +} + +int main() +{ + reset(); + while (running) next(); + return EXIT_SUCCESS; +}