/* XCPU.c * Main source file for XCPU. * */ #include #include #include #include #include /* memory.c */ void *xmalloc (size_t amount); void xfree (void *mem); void free_all (void); void mem_trace (void); char *disassemble_instruction (uint8_t *mem); void xcpu_print_cpu_state (void); /* something really bad just happened and it's time to bail */ #define PANIC panic (__LINE__, __FILE__) void panic (long line, char *file); #define BOOTSTRAP_FILE "bootstrap.xcpu" typedef struct { void (*seek)(uint32_t); uint32_t (*tell)(void); uint32_t (*read)(void); void (*write)(uint32_t); } _ioport, *ioport; static ioport ioports[8] = { NULL, NULL, NULL, NULL, NULL, NULL, NULL, NULL }; static int active_ioport; static char *dis = NULL; #define N_IOPORTS 8 typedef void (*instruction_function)(uint8_t); static instruction_function instructions[16]; typedef struct { uint32_t IP; uint32_t SP; uint32_t A; uint32_t B; uint32_t C; uint32_t D; uint32_t M; uint8_t status; uint32_t rom_start; uint32_t rom_size; uint32_t memory_start; uint32_t memory_size; uint32_t stack_start; uint32_t stack_size; uint32_t ioport_start; int halted; } _XCPU, *XCPU; #define XCPU_SIZE sizeof (_XCPU) #define ROM_SIZE 256 #define REG_A 1 #define REG_B 2 #define REG_C 4 #define REG_D 8 #define XCPU_JUMP1 0 #define XCPU_JUMP2 1 #define XCPU_MEMOP 2 #define XCPU_SLR 3 #define XCPU_SRC 4 #define XCPU_MOVE 5 #define XCPU_DEBUG 7 #define XCPU_GET_STATUS_BIT(b) ((cpu->status & (1 << (b))) >> (b)) #define XCPU_SET_STATUS_BIT(b) cpu->status |= 1 << (b) #define XCPU_CLEAR_STATUS_BIT(b) cpu->status &= ~(1 << (b)) #define JUMP_UNCOND 0 #define JUMP_EQ 1 #define JUMP_NEQ 2 #define JUMP_LT 3 #define JUMP_GT 4 #define CALL 7 static FILE *infp; static XCPU cpu; static uint8_t *rom = NULL; static uint8_t *memory = NULL; int icount = 0; void panic (long line, char *file) { fprintf (stderr, "XCPU: PANIC at %ld of %s: %s\n", line, file, strerror (errno)); abort (); } void exception (char *m) { fprintf (stderr, "XCPU: An exception occurred: %s\n", m); xcpu_print_cpu_state (); } #define byte_p(n) ((uint8_t *)(&(n))) uint32_t bswap4 (uint32_t in) { uint32_t out; *(byte_p (out) + 0) = *(byte_p (in) + 3); *(byte_p (out) + 1) = *(byte_p (in) + 2); *(byte_p (out) + 2) = *(byte_p (in) + 1); *(byte_p (out) + 3) = *(byte_p (in) + 0); return out; } void push_stack (uint32_t arg) { *(uint32_t *)(&memory[cpu->SP - cpu->memory_start]) = bswap4 (arg); cpu->SP += 4; } uint32_t pop_stack (void) { uint32_t arg; cpu->SP -= 4; arg = bswap4 (*(uint32_t *)(&memory[cpu->SP - cpu->memory_start])); return arg; } void in_config (uint8_t ia) { switch (ia) { case 0: /* memory */ memory = xmalloc (cpu->B); cpu->memory_start = cpu->A; cpu->memory_size = cpu->B; break; case 1: /* stack */ cpu->stack_start = cpu->A; cpu->stack_size = (cpu->memory_size - cpu->memory_start - cpu->stack_start); cpu->SP = cpu->stack_start; break; case 2: /* ioports */ cpu->ioport_start = cpu->A; break; default: fprintf (stderr, "XCPU: invalid config arg %02x\n", ia); } } void in_jump (uint8_t ia) { uint32_t jump_addr, new_addr; new_addr = cpu->IP; jump_addr = pop_stack (); switch (ia) { case CALL: push_stack (cpu->IP); case JUMP_UNCOND: new_addr = jump_addr; break; case JUMP_EQ: if (XCPU_GET_STATUS_BIT (XCPU_JUMP1)) new_addr = jump_addr; break; case JUMP_NEQ: if (!XCPU_GET_STATUS_BIT (XCPU_JUMP1)) new_addr = jump_addr; break; case JUMP_LT: if (!XCPU_GET_STATUS_BIT (XCPU_JUMP2) && XCPU_GET_STATUS_BIT (XCPU_JUMP1)) new_addr = jump_addr; break; case JUMP_GT: if (XCPU_GET_STATUS_BIT (XCPU_JUMP2) && XCPU_GET_STATUS_BIT (XCPU_JUMP1)) new_addr = jump_addr; break; default: exception ("Invalid jump type"); } cpu->IP = new_addr; } void in_push (uint8_t ia) { uint32_t push_val; switch (ia) { case REG_A: push_val = cpu->A; break; case REG_B: push_val = cpu->B; break; case REG_C: push_val = cpu->C; break; case REG_D: push_val = cpu->D; break; default: exception ("Invalid register specification"); return; } push_stack (push_val); } void in_pop (uint8_t ia) { uint32_t pop_val; pop_val = pop_stack (); switch (ia) { case REG_A: cpu->A = pop_val; break; case REG_B: cpu->B = pop_val; break; case REG_C: cpu->C = pop_val; break; case REG_D: cpu->D = pop_val; break; default: exception ("Invalid register specification"); } } void in_memop (uint8_t ia) { uint32_t addr, val = 0; addr = pop_stack (); if (XCPU_GET_STATUS_BIT (XCPU_MEMOP)) /* LOAD */ { if (addr >= cpu->rom_start && addr < cpu->rom_start + cpu->rom_size) val = bswap4 (*(uint32_t *)(&rom[addr - cpu->rom_start])); else if (addr >= cpu->memory_start && addr < cpu->memory_start + cpu->memory_size) val = bswap4 (*(uint32_t *)(&memory[addr - cpu->memory_start])); else if (addr >= cpu->ioport_start && addr < cpu->ioport_start + 12) { if (addr == cpu->ioport_start) val = (uint32_t)active_ioport; else if (addr == cpu->ioport_start + 4) { if (active_ioport >= N_IOPORTS) exception ("I/O port value greater than available I/O ports"); else if (ioports[active_ioport] == NULL) exception ("Selected I/O port does not exist"); else val = ioports[active_ioport]->tell (); } else if (addr == cpu->ioport_start + 8) { if (active_ioport >= N_IOPORTS) exception ("I/O port value greater than available I/O ports"); else if (ioports[active_ioport] == NULL) exception ("Selected I/O port does not exist"); else val = ioports[active_ioport]->read (); } else exception ("MEMOP attempted unaligned load from I/O ports"); } else exception ("MEMOP attempted load in unmapped memory"); switch (ia) { case REG_A: cpu->A = val; break; case REG_B: cpu->B = val; break; case REG_C: cpu->C = val; break; case REG_D: cpu->D = val; break; default: exception ("Invalid register specification"); } } else /* STORE */ { switch (ia) { case REG_A: val = cpu->A; break; case REG_B: val = cpu->B; break; case REG_C: val = cpu->C; break; case REG_D: val = cpu->D; break; default: exception ("Invalid register specification"); } if (addr >= cpu->rom_start && addr < cpu->rom_start + cpu->rom_size) exception ("MEMOP attempted store in ROM"); else if (addr >= cpu->memory_start && addr < cpu->memory_start + cpu->memory_size) *(uint32_t *)(&memory[addr - cpu->memory_start]) = bswap4 (val); else if (addr >= cpu->ioport_start && addr < cpu->ioport_start + 12) { if (addr == cpu->ioport_start) { if (val >= N_IOPORTS) exception ("I/O port value greater than available I/O ports"); else if (ioports[val] == NULL) exception ("Selected I/O port does not exist"); else active_ioport = (int)val; } else if (addr == cpu->ioport_start + 4) { if (active_ioport >= N_IOPORTS) exception ("I/O port value greater than available I/O ports"); else if (ioports[active_ioport] == NULL) exception ("Selected I/O port does not exist"); else ioports[active_ioport]->seek (val); } else if (addr == cpu->ioport_start + 8) { if (active_ioport >= N_IOPORTS) exception ("I/O port value greater than available I/O ports"); else if (ioports[active_ioport] == NULL) exception ("Selected I/O port does not exist"); else ioports[active_ioport]->write (val); } else exception ("MEMOP attempted unaligned store to I/O ports"); } else exception ("MEMOP attempted store in unmapped memory"); } } void in_cpuop (uint8_t ia) { uint8_t mask = 0, bit; bit = (ia & 0x8) >> 3; mask = 1 << (ia & 0x7); if (bit) cpu->status |= mask; else cpu->status &= ~mask; } void in_assign (uint8_t ia) { uint32_t val; if (cpu->IP > cpu->rom_start && cpu->IP < cpu->rom_start + cpu->rom_size) val = bswap4 (*(uint32_t *)(&rom[cpu->IP - cpu->rom_start])); else if (cpu->IP > cpu->memory_start && cpu->IP < cpu->memory_start + cpu->memory_size) val = bswap4 (*(uint32_t *)(&memory[cpu->IP - cpu->memory_start])); else { fprintf (stderr, "XCPU: IP points to lala land for assign\n"); return; } cpu->IP += 4; switch (ia) { case REG_A: cpu->A = val; break; case REG_B: cpu->B = val; break; case REG_C: cpu->C = val; break; case REG_D: cpu->D = val; break; default: exception ("Invalid register specification"); } } void in_add (uint8_t ia) { switch (ia) { case REG_A: cpu->A += cpu->A; break; case REG_B: cpu->B += cpu->A; break; case REG_C: cpu->C += cpu->A; break; case REG_D: cpu->D += cpu->A; break; default: exception ("Invalid register specification"); } } void in_shift (uint8_t ia) { uint32_t val, sh; sh = cpu->A; switch (ia) { case REG_A: val = cpu->A; break; case REG_B: val = cpu->B; break; case REG_C: val = cpu->C; break; case REG_D: val = cpu->D; break; default: val = 0; exception ("Invalid register specification"); } if (XCPU_GET_STATUS_BIT (XCPU_SLR)) { if (XCPU_GET_STATUS_BIT (XCPU_SRC)) /* right circular */ val = val >> sh | val << (32 - sh); else /* right regular */ val >>= sh; } else { if (XCPU_GET_STATUS_BIT (XCPU_SRC)) /* left circular */ val = val << sh | val >> (32 - sh); else /* left regular */ val <<= sh; } switch (ia) { case REG_A: cpu->A = val; break; case REG_B: cpu->B = val; break; case REG_C: cpu->C = val; break; case REG_D: cpu->D = val; break; default: exception ("Invalid register specification"); } } void in_and (uint8_t ia) { switch (ia) { case REG_A: cpu->A &= cpu->A; break; case REG_B: cpu->B &= cpu->A; break; case REG_C: cpu->C &= cpu->A; break; case REG_D: cpu->D &= cpu->A; break; default: exception ("Invalid register specification"); } } void in_or (uint8_t ia) { switch (ia) { case REG_A: cpu->A |= cpu->A; break; case REG_B: cpu->B |= cpu->A; break; case REG_C: cpu->C |= cpu->A; break; case REG_D: cpu->D |= cpu->A; break; default: exception ("Invalid register specification"); } } void in_not (uint8_t ia) { switch (ia) { case REG_A: cpu->A = ~cpu->A; break; case REG_B: cpu->B = ~cpu->B; break; case REG_C: cpu->C = ~cpu->C; break; case REG_D: cpu->D = ~cpu->D; break; default: exception ("Invalid register specification"); } } void in_move (uint8_t ia) { if (XCPU_GET_STATUS_BIT (XCPU_MOVE)) { /* moving from M */ switch (ia) { case REG_A: cpu->A = cpu->M; break; case REG_B: cpu->B = cpu->M; break; case REG_C: cpu->C = cpu->M; break; case REG_D: cpu->D = cpu->M; break; default: exception ("Invalid register specification"); } } else { /* moving to M */ switch (ia) { case REG_A: cpu->M = cpu->A; break; case REG_B: cpu->M = cpu->B; break; case REG_C: cpu->M = cpu->C; break; case REG_D: cpu->M = cpu->D; break; default: exception ("Invalid register specification"); } } } void in_compare (uint8_t ia) { uint32_t val; switch (ia) { case REG_A: val = cpu->A; break; case REG_B: val = cpu->B; break; case REG_C: val = cpu->C; break; case REG_D: val = cpu->D; break; default: val = 0; exception ("Invalid register specification"); } XCPU_CLEAR_STATUS_BIT (XCPU_JUMP1); XCPU_CLEAR_STATUS_BIT (XCPU_JUMP2); if (val == cpu->A) { XCPU_CLEAR_STATUS_BIT (XCPU_JUMP1); } else if (val < cpu->A) { XCPU_CLEAR_STATUS_BIT (XCPU_JUMP2); XCPU_SET_STATUS_BIT (XCPU_JUMP1); } else if (val > cpu->A) { XCPU_SET_STATUS_BIT (XCPU_JUMP2); XCPU_SET_STATUS_BIT (XCPU_JUMP1); } } void in_halt (uint8_t ia) { cpu->halted = 1; } void in_nop (uint8_t ia) { /* do nothing */ } uint8_t xcpu_get_instruction (void) { uint32_t aip; /* actual IP */ uint8_t is; /* instruction */ /* find out where IP is pointing */ if (cpu->IP >= cpu->rom_start && cpu->IP < cpu->rom_start + cpu->rom_size) { /* IP is in ROM */ aip = cpu->IP - cpu->rom_start; is = rom[aip]; dis = disassemble_instruction (rom + aip); } else if (cpu->IP >= cpu->memory_start && cpu->IP < cpu->memory_start + cpu->memory_size) { /* IP is in main memory */ aip = cpu->IP - cpu->memory_start; is = memory[aip]; dis = disassemble_instruction (memory + aip); } else { /* IP is in lala land - throw exception and return halt instruction */ exception ("IP points outside of memory and ROM"); is = 0xe0; dis = NULL; } cpu->IP++; return is; } void xcpu_dispatch_instruction (uint8_t instruction) { uint8_t it; /* instruction type */ uint8_t ia; /* instruction args */ /* split the instruction and the arg */ it = (instruction & 0xf0) >> 4; ia = instruction & 0x0f; /* call the instruction */ instructions[it](ia); } void xcpu_print_cpu_state (void) { printf ("Current CPU state\n" "IP %08x SP %08x M %08x Status %02x\n" "A %08x B %08x C %08x D %08x\n" "\n" "Memory state\n" "Rom %08x - %08x\n" "IO Ports %08x - %08x\n" "Memory %08x - %08x\n" "Stack %08x - %08x\n" "\n", cpu->IP, cpu->SP, cpu->M, cpu->status, cpu->A, cpu->B, cpu->C, cpu->D, cpu->rom_start, cpu->rom_start + cpu->rom_size, cpu->ioport_start, cpu->ioport_start + 12, cpu->memory_start, cpu->memory_start + cpu->memory_size, cpu->stack_start, cpu->stack_start + cpu->stack_size); if (cpu->stack_start > 0) { uint32_t sa; sa = cpu->SP - cpu->memory_start; if (cpu->SP > cpu->stack_start) printf ("Stack-4: %02x%02x%02x%02x\n", memory[sa-4], memory[sa-3], memory[sa-2], memory[sa-1]); else printf ("Stack-4: 00000000\n"); if (cpu->SP > cpu->stack_start + 4) printf ("Stack-8: %02x%02x%02x%02x\n", memory[sa-8], memory[sa-7], memory[sa-6], memory[sa-5]); else printf ("Stack-8: 00000000\n"); if (cpu->SP > cpu->stack_start + 8) printf ("Stack-c: %02x%02x%02x%02x\n", memory[sa-12], memory[sa-11], memory[sa-10], memory[sa-9]); else printf ("Stack-c: 00000000\n"); if (cpu->SP > cpu->stack_start + 12) printf ("Stack-10: %02x%02x%02x%02x\n", memory[sa-16], memory[sa-15], memory[sa-14], memory[sa-13]); else printf ("Stack-10: 00000000\n"); } } int xcpu_halted (void) { return cpu->halted; } void xcpu_console_seek (uint32_t data) { /* console device doesn't seek (maybe it will in the future) */ } uint32_t xcpu_console_tell (void) { /* doesn't tell either */ return 0; } uint32_t xcpu_console_read (void) { uint32_t data; data = (uint32_t)getchar (); return bswap4 (data); } void xcpu_console_write (uint32_t data) { putchar ((int)data); } void xcpu_graphics_seek (uint32_t data) { } uint32_t xcpu_graphics_tell (void) { return 0; } uint32_t xcpu_graphics_read (void) { return 0; } void xcpu_graphics_write (uint32_t data) { } void xcpu_exe_seek (uint32_t pos) { fseek (infp, (long)pos, SEEK_SET); } uint32_t xcpu_exe_tell (void) { uint32_t p; p = (uint32_t)ftell (infp); return p; } uint32_t xcpu_exe_read (void) { uint32_t data; fread (&data, sizeof (uint32_t), 1, infp); return bswap4 (data); } void xcpu_exe_write (uint32_t data) { /* EXE is a read only device */ } /* xcpu_init * Initialize XCPU to a known state and load the bootstrap "ROM" into memory. */ void xcpu_init (void) { long offset; FILE *bs; /* assign instructions to their slots for dispatching */ instructions[0] = in_nop; instructions[1] = in_jump; instructions[2] = in_push; instructions[3] = in_pop; instructions[4] = in_memop; instructions[5] = in_cpuop; instructions[6] = in_assign; instructions[7] = in_add; instructions[8] = in_shift; instructions[9] = in_and; instructions[10] = in_or; instructions[11] = in_not; instructions[12] = in_move; instructions[13] = in_compare; instructions[14] = in_halt; instructions[15] = in_config; /* allocate the CPU and ROM */ cpu = xmalloc (XCPU_SIZE); rom = xmalloc (ROM_SIZE); /* load the bootstrap into ROM */ bs = fopen (BOOTSTRAP_FILE, "rb"); if (bs == NULL) { fprintf (stderr, "XCPU: Unable to open bootstrap file `%s'\n", BOOTSTRAP_FILE); exit (1); } fseek (bs, 4, SEEK_SET); fread (&offset, 4, 1, bs); offset = (long)bswap4 ((uint32_t)offset); fseek (bs, offset, SEEK_SET); fread (rom, sizeof (uint8_t), ROM_SIZE, bs); fclose (bs); /* initialize devices */ /* console I/O */ ioports[0] = xmalloc (sizeof (_ioport)); ioports[0]->seek = xcpu_console_seek; ioports[0]->tell = xcpu_console_tell; ioports[0]->read = xcpu_console_read; ioports[0]->write = xcpu_console_write; /* graphics I/O */ ioports[1] = xmalloc (sizeof (_ioport)); ioports[1]->seek = xcpu_graphics_seek; ioports[1]->tell = xcpu_graphics_tell; ioports[1]->read = xcpu_graphics_read; ioports[1]->write = xcpu_graphics_write; /* exe I/O - this is the program to be executed */ ioports[7] = xmalloc (sizeof (_ioport)); ioports[7]->seek = xcpu_exe_seek; ioports[7]->tell = xcpu_exe_tell; ioports[7]->read = xcpu_exe_read; ioports[7]->write = xcpu_exe_write; /* initialize CPU state */ cpu->IP = 0; cpu->rom_start = 0; cpu->rom_size = ROM_SIZE; cpu->halted = 0; } /* xcpu_cleanup * Clean up the operating environment after execution finishes. */ void xcpu_cleanup (void) { int k; for (k = 0; k < N_IOPORTS; k++) if (ioports[k] != NULL) xfree (ioports[k]); if (memory != NULL) xfree (memory); xfree (rom); xfree (cpu); free_all (); } /* xcpu_load_file * Load the file to be executed and place the file pointer into the * device structure so that the bootstrap code can access it. */ void xcpu_load_file (char *filename) { char header[5]; infp = fopen (filename, "rb"); if (infp == NULL) { fprintf (stderr, "XCPU: Unable to open input file `%s'\n", filename); exit (1); } /* verify that this is a good file */ fread (header, sizeof (char), 4, infp); header[4] = '\0'; if (strcmp (header, "XCPU")) { fprintf (stderr, "XCPU: `%s' is not a correct executable image\n", filename); fclose (infp); exit (1); } fseek (infp, 0, SEEK_SET); } int main (int argc, char *argv[]) { if (argc != 2) { fprintf (stderr, "XCPU: must specify input file\n"); exit (1); } xcpu_init (); xcpu_load_file (argv[1]); do { uint8_t instruction; instruction = xcpu_get_instruction (); xcpu_dispatch_instruction (instruction); icount++; if (XCPU_GET_STATUS_BIT (XCPU_DEBUG)) { printf ("%d: %02x: %s\n", icount, instruction, dis); xcpu_print_cpu_state (); getchar (); } xfree (dis); } while (!xcpu_halted ()); xcpu_cleanup (); return 0; }