diff --git a/Makefile b/Makefile index d811943..df7d113 100644 --- a/Makefile +++ b/Makefile @@ -1,11 +1,14 @@ +CFLAGS := -std=c99 -Wall -Wextra +CC ?= gcc + all: dcpu a16 dcpu: emulator.c disassemble.c - gcc -Wall -o dcpu emulator.c disassemble.c + $(CC) $(CFLAGS) -o $@ emulator.c disassemble.c a16: assembler.c disassemble.c - gcc -Wall -o a16 assembler.c disassemble.c + $(CC) $(CFLAGS) -o $@ assembler.c disassemble.c clean: rm -f dcpu a16 diff --git a/assembler.c b/assembler.c index a9cdb2d..49c0415 100644 --- a/assembler.c +++ b/assembler.c @@ -37,6 +37,7 @@ #include #include #include +#include #include typedef uint16_t u16; @@ -145,26 +146,25 @@ enum tokens { tXXX, tSET, tADD, tSUB, tMUL, tDIV, tMOD, tSHL, tSHR, tAND, tBOR, tXOR, tIFE, tIFN, tIFG, tIFB, tJSR, - tPOP, tPEEK, tPUSH, tSP, tPC, tO, + tPOP, tPEEK, tPUSH, tSP, tPC, tO, tDAT, tWORD, - tCOMMA, tOBRACK, tCBRACK, tCOLON, tPLUS, - tSTRING, tNUMBER, tEOF, + tCOMMA, tOBRACK, tCBRACK, tCOLON, tPLUS, tQUOTE, + tSTRING, tDATA, tNUMBER, tEOL, tEOF, }; static const char *tnames[] = { "A", "B", "C", "X", "Y", "Z", "I", "J", "XXX", "SET", "ADD", "SUB", "MUL", "DIV", "MOD", "SHL", "SHR", "AND", "BOR", "XOR", "IFE", "IFN", "IFG", "IFB", "JSR", - "POP", "PEEK", "PUSH", "SP", "PC", "O", + "POP", "PEEK", "PUSH", "SP", "PC", "O", "DAT", "WORD", - ",", "[", "]", ":", "+", - "", "", "", + ",", "[", "]", ":", "+", "\"", + "", "", "", "", "", }; #define LASTKEYWORD tWORD int _next(void) { char c; -nextline: if (!*lineptr) { if (feof(fin)) return tEOF; if (fgets(linebuffer, 128, fin) == 0) return tEOF; @@ -172,7 +172,7 @@ int _next(void) { linenumber++; } while (*lineptr <= ' ') { - if (*lineptr == 0) goto nextline; + if (*lineptr == 0) return tEOL; lineptr++; } switch ((c = *lineptr++)) { @@ -180,8 +180,9 @@ int _next(void) { case '+': return tPLUS; case '[': return tOBRACK; case ']': return tCBRACK; + case '"': return tQUOTE; case ':': return tCOLON; - case '/': case ';': *lineptr = 0; goto nextline; + case '/': case ';': *lineptr = 0; return tEOL; default: if (isdigit(c) || ((c == '-') && isdigit(*lineptr))) { tnumber = strtoul(lineptr-1, &lineptr, 0); @@ -232,6 +233,50 @@ void assemble_imm_or_label(void) { } } +char unescape(char c) { + switch (c) { + case '\\': case '"': return c; + case '0': return 0x00; case 'b': return 0x08; + case 't': return 0x09; case 'n': return 0x0a; + case 'r': return 0x0d; case 'e': return 0x1b; + default: die("unknown escape sequence: \\%c", c); + } + return 0; +} + +void assemble_data(void) { + int argc = -1; +nextarg: + argc++; + next(); + + if (argc % 2) { + if (token == tEOL) return; + if (token == tCOMMA) goto nextarg; + die("expected comma or newline"); + } + + /* 0x0000 */ + if (token == tNUMBER) + image[PC++] = tnumber; + + else if (token == tQUOTE) { + /* "literal \"strings\" with \n escape chars" */ + while (*lineptr) { + char c = *lineptr++; + if (c == '"') goto nextarg; + /* check for escaped characters */ + if (c == '\\') + c = unescape(*lineptr++); + image[PC++] = (u16)c; + } + } else { + die("expecting or , found %s", tnames[token]); + } + + goto nextarg; +} + int assemble_operand(void) { u16 n; next(); @@ -311,10 +356,15 @@ void assemble(const char *fn) { switch (token) { case tEOF: goto done; + case tEOL: + continue; case tCOLON: expect(tSTRING); set_label(tstring, PC); continue; + case tDAT: + assemble_data(); + continue; case tWORD: assemble_imm_or_label(); continue; @@ -375,6 +425,7 @@ void emit(const char *fn, enum outformat format) { static void usage(int argc, char **argv) { + (void) argc; fprintf(stderr, "usage: %s [-o output] [-O output_format] \n", argv[0]); fprintf(stderr, "\toutput_format can be one of: pretty, hex, binary\n"); } diff --git a/emulator.c b/emulator.c index 7544c8d..5719713 100644 --- a/emulator.c +++ b/emulator.c @@ -37,6 +37,10 @@ #include #include +#define SCREEN_REGION (0x8000) +#define SCREEN_WIDTH (36) +#define SCREEN_HEIGHT (12) + typedef uint8_t u8; typedef uint16_t u16; typedef uint32_t u32; @@ -104,7 +108,7 @@ void dcpu_skip(struct dcpu *d) { d->pc += skiptable[(op >> 4) & 31]; } -void dcpu_step(struct dcpu *d) { +int dcpu_step(struct dcpu *d) { u16 op = d->m[d->pc++]; u16 dst; u32 res; @@ -128,14 +132,14 @@ void dcpu_step(struct dcpu *d) { case 0x9: res = a & b; break; case 0xA: res = a | b; break; case 0xB: res = a ^ b; break; - case 0xC: if (a!=b) dcpu_skip(d); return; - case 0xD: if (a==b) dcpu_skip(d); return; - case 0xE: if (a<=b) dcpu_skip(d); return; - case 0xF: if ((a&b)==0) dcpu_skip(d); return; + case 0xC: if (a!=b) dcpu_skip(d); return 1; + case 0xD: if (a==b) dcpu_skip(d); return 1; + case 0xE: if (a<=b) dcpu_skip(d); return 1; + case 0xF: if ((a&b)==0) dcpu_skip(d); return 1; } if (dst < 0x1f) *aa = res; - return; + return 1; extended: a = *dcpu_opr(d, op >> 10); @@ -143,11 +147,12 @@ void dcpu_step(struct dcpu *d) { case 0x01: d->m[--(d->sp)] = d->pc; d->pc = a; - return; + return 1; default: fprintf(stderr, "< ILLEGAL OPCODE >\n"); - exit(0); + return 0; } + return 1; } void dumpheader(void) { @@ -156,6 +161,27 @@ void dumpheader(void) { "---- ---- ---- ---- ---- ---- ---- ---- ---- ---- ---- -----------\n"); } +void dumpboxhl(void) { + fputc('+', stderr); + for (int i = SCREEN_WIDTH; i--;) + fputc('-', stderr); + fprintf(stderr, "+\n"); +} + +void dumpscreen(struct dcpu *d) { + dumpboxhl(); + for (int y = 0; y < SCREEN_HEIGHT; y++) { + fputc('|', stderr); + for (int x = 0; x < SCREEN_WIDTH; x++) { + u16 c = d->m[SCREEN_REGION + (y * SCREEN_WIDTH) + x]; + c = c & 0xff; + fputc(isprint(c) ? c : ' ', stderr); + } + fprintf(stderr, "|\n"); + } + dumpboxhl(); +} + void dumpstate(struct dcpu *d) { char out[128]; disassemble(d->m + d->pc, out); @@ -191,10 +217,13 @@ int main(int argc, char **argv) { load(&d, argc > 1 ? argv[1] : "out.hex"); dumpheader(); - for (;;) { - dumpstate(&d); - dcpu_step(&d); - } + do { + //dumpscreen(&d); + dumpstate(&d); + } while (dcpu_step(&d)); + + dumpscreen(&d); + return 0; } diff --git a/tests/colours.dc b/tests/colours.dc new file mode 100644 index 0000000..350de55 --- /dev/null +++ b/tests/colours.dc @@ -0,0 +1,41 @@ +; Notch's second "hello word" program. +; http://i.imgur.com/XIXc4.jpg +; Supposed to show formatting. +:start + set i, 0 + set j, 0 + set b, 0xf100 + +:nextchar + set a, [data+i] + ife a, 0 + set PC, end + ifg a, 0xff + set PC, setcolor + bor a, b + set [0x8000+j], a + add i, 1 + add j, 1 + set PC, nextchar + +:setcolor + set b, a + and b, 0xff + shl b, 8 + ifg a, 0x1ff + add b, 0x80 ; Add high bit to each character we color. So the character must be 7-bit ASCII + add i, 1 + set PC, nextchar + +:data + dat 0x170, "Hello ", 0x2e1, "world", 0x170, ", how are you?", 0 + ; Color format: + ; After processing: + ; 0x170 -> b = 0x7000 -> 0111 0000 0XXX XXXX = white(grey) on black + ; 0x2e1 -> b = 0xe180 -> 1110 0001 1XXX XXXX = yellow on blue + ; b gets OR'd with each character. + ; ANSI says: black is 0, white is 7, yellow is 3, blue is 4 + ; If black is 0 and grey is 7, it's or +:end + ;set PC, start + WORD 0xeee0 diff --git a/tests/example.s b/tests/example.dc similarity index 100% rename from tests/example.s rename to tests/example.dc diff --git a/tests/helloworld.dc b/tests/helloworld.dc new file mode 100644 index 0000000..15d19bb --- /dev/null +++ b/tests/helloworld.dc @@ -0,0 +1,42 @@ +; 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, print ; 7dc1 001a [*] + +:testsub SHL X, 4 ; 9037 + SET PC, POP ; 61c1 + +; "Hello, world!" +; Set 0x8000 - 0x8180 to an ASCII value to output to console +:print + SET [0x8000], 72 + SET [0x8001], 101 + SET [0x8002], 108 + SET [0x8003], 108 + SET [0x8004], 111 + SET [0x8005], 44 + SET [0x8006], 32 + SET [0x8007], 119 + SET [0x8008], 111 + SET [0x8009], 114 + SET [0x800a], 108 + SET [0x800b], 100 + SET [0x800c], 33 + +:crash + WORD 0xeee0