# HG changeset patch # User Michael Pavone # Date 1458711842 25200 # Node ID 7e44f7d5810b4ee55e59ac6c7d320353c39e63c8 Initial commit. CPU working well enough for simple hello world program. diff -r 000000000000 -r 7e44f7d5810b .hgignore --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/.hgignore Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,3 @@ +glob +debug/* +release/* \ No newline at end of file diff -r 000000000000 -r 7e44f7d5810b Makefile --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/Makefile Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,17 @@ +ifdef DEBUG +CFLAGS:=-ggdb $(CFLAGS) +LDFLAGS:=-ggdb $(LDFLAGS) +TARGETDIR:=debug +else +CFLAGS:=-O2 +TARGETDIR:=release +endif #DEBUG + +all : $(TARGETDIR)/s16 + + +$(TARGETDIR)/s16 : $(TARGETDIR)/main.o $(TARGETDIR)/cpu.o + $(CC) -o $@ $^ $(LDFLAGS) + +$(TARGETDIR)/%.o : src/%.c + $(CC) $(CFLAGS) -c -o $@ $< diff -r 000000000000 -r 7e44f7d5810b helloworld.bin Binary file helloworld.bin has changed diff -r 000000000000 -r 7e44f7d5810b helloworld.s16 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/helloworld.s16 Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,13 @@ + ldim message, r0 + ldim 0, r1 +loop + ld8 r0, r1, r2 + ori 0, r2 + beq done + outi 11, r2 + bra loop +done + bra done + +message: + dc.b "Hello, world!", 0 \ No newline at end of file diff -r 000000000000 -r 7e44f7d5810b simple_console.txt --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/simple_console.txt Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,115 @@ +Key: +1 = literal 1 bit +0 = literal 0 bit +O = opcode bit +D = destination register bit +A = source A register bit +B = source B register bit + +DDDD AAAA BBBB OOOO + +0: ldim + D = destination reg + A and B form 8-bit immediate value +1: ldimh + D = destination reg + A and B form 8-bit immediate value +2: ld8 +3: ld16 +4: str8 +5: str16 +6: add +7: adc +8: and +9: or +A: xor +B: lsl +C: lsr +D: asr +E: bcc +F: single source + + + +DDDD AAAA OOOO 1111 + +single source + +0: mov +1: neg +2: not +3: cmp +4: call + A = register containing pointer to function + D = register that will store PC value +5: swap +6: in +7: out +8: ini +9: outi +A: addi +B: andi +C: ori +D: ls[lr]i + MSB of AAAA determines direction + LS 3 bits determines magnitude +E: asri +F: single reg + + +DDDD OOOO 1111 1111 + +0: reti - return from interrupt, D = register to restore from uer +1: trap +2: trapi +3: getepc +4: setepc +5: getesr +6: setesr +7: getenum +8: setenum +9: getuer +A: setuer +B: getenum +C: setenum +E: invalid +F: invalid + + +Registers: + +r0 - r12 : general purpose +r13 : technically general purpose, but canonically the stack register +r14 : PC +r15 : status register + +IO: Ports + +0: Controller 1 +1: Controller 2 +2: Controller 3 +3: Controller 4 + +4: Channel A Freq + Load value for a 16-bit down-counter + Polarity of output is switched on transition from 1 to 0 + Value is loaded on cycles where counter is 0 + Special case value of 0 in this register forces polarity to positive +5: Channel B Freq +6: Channel C Freq +7: Channel D Freq + +8: Channel A/B Vol +9: Channel C/D Vol + +A: Timer Freq +B: "Serial" Debug Port + +D: Write Pallette Address : Read Vertical Position +E: Write Name Table Address : Read Horizontal Position +F: Write Sprite Table Address : Read Status? + + + + + diff -r 000000000000 -r 7e44f7d5810b src/cpu.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cpu.c Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,492 @@ +#include +#include +#include +#include +#include "cpu.h" + +enum { + EXCEPTION_INTERRUPT_0, + EXCEPTION_INTERRUPT_1, + EXCEPTION_UNALIGNED_READ, + EXCEPTION_INVALID_INSTRUCTION +}; + +enum { + STATE_NEED_FETCH, + STATE_NORMAL, + STATE_EXCEPTION_START +}; + +#define STATUS_INT0_ENABLE 1 +#define STATUS_INT1_ENABLE 2 +#define FLAG_Z 4 +#define FLAG_C 8 +#define FLAG_N 16 + +#define REG_PC 14 +#define REG_SR 15 + +cpu* alloc_cpu(uint32_t clock_divider, uint32_t num_regions, memory_region *regions) +{ + size_t alloc_size = sizeof(cpu) + sizeof(memory_region) * num_regions; + cpu *context = malloc(alloc_size); + memset(context, 0, alloc_size); + context->clock_inc = clock_divider; + context->num_mem_regions = num_regions; + memcpy(context->mem_regions, regions, num_regions*sizeof(memory_region)); + + return context; +} + +uint16_t cpu_read_16(cpu *context, uint16_t address) +{ + context->cycles += context->clock_inc; + if (address & 1) { + context->exception = EXCEPTION_UNALIGNED_READ; + context->state = STATE_EXCEPTION_START; + return 0xFFFF; + } + memory_region *cur = context->mem_regions; + for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) + { + if (address >= cur->start && address <= cur->end && (cur->flags & MEM_READ)) { + return cur->base[address - cur->start] << 8 | cur->base[address - cur->start + 1]; + } + } + return 0xFFFF; +} + +uint8_t cpu_read_8(cpu *context, uint16_t address) +{ + context->cycles += context->clock_inc; + memory_region *cur = context->mem_regions; + for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) + { + if (address >= cur->start && address <= cur->end && (cur->flags & MEM_READ)) { + return cur->base[address - cur->start]; + } + } + return 0xFF; +} + +void cpu_write_16(cpu *context, uint16_t address, uint16_t value) +{ + context->cycles += context->clock_inc; + if (address & 1) { + context->exception = EXCEPTION_UNALIGNED_READ; + context->state = STATE_EXCEPTION_START; + return; + } + memory_region *cur = context->mem_regions; + for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) + { + if (address >= cur->start && address <= cur->end && (cur->flags & MEM_WRITE)) { + cur->base[address - cur->start] = value >> 8; + cur->base[address - cur->start + 1] = value; + break; + } + } +} + +void cpu_write_8(cpu *context, uint16_t address, uint8_t value) +{ + context->cycles += context->clock_inc; + memory_region *cur = context->mem_regions; + for (memory_region *end = cur + context->num_mem_regions; cur < end; cur++) + { + if (address >= cur->start && address <= cur->end && (cur->flags & MEM_WRITE)) { + cur->base[address - cur->start] = value; + break; + } + } +} + +uint16_t cpu_read_port(cpu *context, uint8_t port) +{ + port &= 0xF; + if (context->port_handlers[port].read) { + return context->port_handlers[port].read(context, port); + } + return 0xFFFF; +} + +void cpu_write_port(cpu *context, uint8_t port, uint16_t value) +{ + port &= 0xF; + if (context->port_handlers[port].write) { + context->port_handlers[port].write(context, port, value); + } +} + +void fetch_instruction(cpu *context) +{ + context->prefetch = cpu_read_16(context, context->regs[REG_PC]); + context->regs[REG_PC] += 2; + context->state = STATE_NORMAL; +} + +void vector_fetch(cpu *context) +{ + context->exception_pc = context->regs[REG_PC] - 2; + context->exception_sr = context->regs[REG_SR]; + context->regs[REG_SR] &= ~(STATUS_INT0_ENABLE | STATUS_INT1_ENABLE); + context->regs[REG_PC] = cpu_read_16(context, context->vector_base + context->exception); + context->state = STATE_NEED_FETCH; +} + +uint16_t sign_extend(uint16_t val) +{ + if (val & 0x80) { + return val | 0xFF00; + } + return val; +} + +void update_flags_arith(cpu *context, uint32_t result) +{ + context->regs[REG_SR] &= ~(FLAG_N|FLAG_C|FLAG_Z); + if (!(result & 0xFFFF)) { + context->regs[REG_SR] |= FLAG_Z; + } + if (result &= 0x8000) { + context->regs[REG_SR] |= FLAG_N; + } + if (result &= 0x10000) { + context->regs[REG_SR] |= FLAG_C; + } +} + +void update_flags_bitwise(cpu *context, uint32_t result) +{ + context->regs[REG_SR] &= ~(FLAG_N|FLAG_Z); + if (!(result & 0xFFFF)) { + context->regs[REG_SR] |= FLAG_Z; + } + if (result &= 0x8000) { + context->regs[REG_SR] |= FLAG_N; + } +} + +void run_bcc(cpu *context, uint8_t condition, uint8_t a, uint8_t b) +{ + + uint8_t doit = 0; + switch (condition) + { + case COND_ALWAYS: + doit = 1; + break; + case COND_NEVER: + break; + case COND_ZERO: + doit = context->regs[REG_SR] & FLAG_Z; + break; + case COND_NZERO: + doit = !(context->regs[REG_SR] & FLAG_Z); + break; + case COND_NEG: + doit = context->regs[REG_SR] & FLAG_N; + break; + case COND_POS: + doit = !(context->regs[REG_SR] & FLAG_N); + break; + case COND_CARRY: + doit = context->regs[REG_SR] & FLAG_C; + break; + case COND_NCARRY: + doit = context->regs[REG_SR] & FLAG_C; + break; + case COND_GREATER: + //not zero and not carry + doit = !(context->regs[REG_SR] & FLAG_Z) || !(context->regs[REG_SR] & FLAG_C); + break; + case COND_LEQ: + //zero or carry + doit = (context->regs[REG_SR] & FLAG_Z) || (context->regs[REG_SR] & FLAG_C); + break; + default: + context->exception = EXCEPTION_INVALID_INSTRUCTION; + context->state = STATE_EXCEPTION_START; + return; + } + + if (doit) { + context->regs[REG_PC] += sign_extend(a << 4 | b) * 2; + context->state = STATE_NEED_FETCH; + } +} + +uint16_t format_immediate_bitwise(uint16_t val) +{ + if (val & 8) { + val |= 0xFFF0; + } + return val; +} + +uint16_t format_immediate(uint16_t val) +{ + val = format_immediate_bitwise(val); + if (!val) { + val = 8; + } + return val; +} + +void run_single_reg(cpu *context, uint8_t dst, uint8_t op) +{ + switch(op) + { + case RETI: + context->regs[dst] = context->exception_ur; + context->regs[REG_PC] = context->exception_pc; + context->regs[REG_SR] = context->exception_sr; + context->state = STATE_NEED_FETCH; + return; + case TRAP: + context->state = STATE_EXCEPTION_START; + context->exception = context->regs[dst]; + return; + case TRAPI: + context->state = STATE_EXCEPTION_START; + context->exception = dst; + return; + case GETEPC: + context->regs[dst] = context->exception_pc; + break; + case SETEPC: + context->exception_pc = context->regs[dst]; + break; + case GETESR: + context->regs[dst] = context->exception_sr; + break; + case SETESR: + context->exception_sr = context->regs[dst]; + break; + case GETEUR: + context->regs[dst] = context->exception_ur; + break; + case SETEUR: + context->exception_ur = context->regs[dst]; + break; + case GETENUM: + context->regs[dst] = context->exception; + break; + case SETENUM: + context->exception = context->regs[dst]; + break; + default: + context->state = STATE_EXCEPTION_START; + context->exception = EXCEPTION_INVALID_INSTRUCTION; + return; + } + if (dst == REG_PC) { + context->state = STATE_NEED_FETCH; + } +} + +void run_single_source(cpu *context, uint8_t dst, uint8_t a, uint8_t op) +{ + uint32_t tmp; + uint8_t shift; + switch(op) + { + case MOVE: + context->regs[dst] = context->regs[a]; + break; + case NEG: + tmp = -context->regs[a]; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case NOT: + context->regs[dst] = ~context->regs[a]; + update_flags_bitwise(context, context->regs[dst]); + break; + case CMP: + tmp = context->regs[dst] - context->regs[a]; + update_flags_arith(context, tmp); + return; + case CALL: + context->regs[dst] = context->regs[REG_PC] - 2; + context->regs[REG_PC] = context->regs[a]; + context->state = STATE_NEED_FETCH; + return; + case SWAP: + tmp = context->regs[dst]; + context->regs[dst] = context->regs[a]; + context->regs[a] = tmp; + if (a == REG_PC) { + context->state = STATE_NEED_FETCH; + return; + } + break; + case IN: + context->regs[dst] = cpu_read_port(context, context->regs[a]); + break; + case OUT: + cpu_write_port(context, context->regs[a], context->regs[dst]); + return; + case INI: + context->regs[dst] = cpu_read_port(context, a); + break; + case OUTI: + cpu_write_port(context, a, context->regs[dst]); + return; + case ADDI: + tmp = context->regs[dst] + format_immediate(a); + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case ANDI: + context->regs[dst] = context->regs[dst] & format_immediate_bitwise(a); + update_flags_bitwise(context, context->regs[dst]); + break; + case ORI: + context->regs[dst] = context->regs[dst] | format_immediate_bitwise(a); + update_flags_bitwise(context, context->regs[dst]); + break; + case LSI: + shift = a & 7; + if (!shift) { + shift = 8; + } + if (a & 8) { + tmp = context->regs[dst] >> shift; + tmp |= (context->regs[dst] >> (shift - 1)) << 16 & 0x10000; + } else { + tmp = context->regs[dst] << (a & 7); + } + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case ASRI: + shift = a; + if (!shift) { + shift = 16; + } + tmp = context->regs[dst]; + if (tmp & 0x8000) { + tmp |= 0xFFFF0000; + } + tmp = tmp >> shift & 0xFFFF; + tmp |= (context->regs[dst] >> (context->regs[shift] - 1)) << 16 & 0x10000; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case SINGLE_REG: + run_single_reg(context, dst, a); + return; + } + if (dst == REG_PC) { + context->state = STATE_NEED_FETCH; + } +} + +char * mnemonics[] = { + "ldim", "ldimh", "ld8", "ld16", "str8", "str16", "add", "adc", "and", "or", "xor", "lsl", "lsr", "asr", "bcc", "single" +}; + +void run_instruction(cpu *context) +{ + uint16_t instruction = context->prefetch; + fetch_instruction(context); + uint8_t dst = instruction >> 12; + uint8_t a = instruction >> 8 & 0xF; + uint8_t b = instruction >> 4 & 0xF; + uint8_t op = instruction & 0xF; + uint32_t tmp; + switch (op) + { + case LDIM: + context->regs[dst] = sign_extend(a << 4 | b); + break; + case LDIMH: + context->regs[dst] &= 0xFF; + context->regs[dst] |= a << 12 | b << 8; + break; + case LD8: + context->regs[dst] = cpu_read_8(context, context->regs[a] + context->regs[b]); + break; + case LD16: + context->regs[dst] = cpu_read_16(context, context->regs[a] + context->regs[b]); + break; + case STR8: + cpu_write_8(context, context->regs[a] + context->regs[b], context->regs[dst]); + return; + case STR16: + cpu_write_16(context, context->regs[a] + context->regs[b], context->regs[dst]); + return; + case ADD: + tmp = context->regs[a] + context->regs[b]; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case ADC: + tmp = context->regs[a] + context->regs[b] + (context->regs[REG_SR] & FLAG_C ? 1 : 0); + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case AND: + context->regs[dst] = context->regs[a] & context->regs[b]; + update_flags_bitwise(context, context->regs[dst]); + break; + case OR: + context->regs[dst] = context->regs[a] | context->regs[b]; + update_flags_bitwise(context, context->regs[dst]); + break; + case XOR: + context->regs[dst] = context->regs[a] ^ context->regs[b]; + update_flags_bitwise(context, context->regs[dst]); + break; + case LSL: + tmp = context->regs[a] << context->regs[b]; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case LSR: + tmp = context->regs[a] >> context->regs[b]; + tmp |= (context->regs[a] >> (context->regs[b] - 1)) << 16 & 0x10000; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case ASR: + tmp = context->regs[a]; + if (tmp & 0x8000) { + tmp |= 0xFFFF0000; + } + tmp = tmp >> context->regs[b] & 0xFFFF; + tmp |= (context->regs[a] >> (context->regs[b] - 1)) << 16 & 0x10000; + context->regs[dst] = tmp; + update_flags_arith(context, tmp); + break; + case BCC: + run_bcc(context, dst, a, b); + return; + case SINGLE_SOURCE: + run_single_source(context, dst, a, b); + return; + } + if (dst == REG_PC) { + context->state = STATE_NEED_FETCH; + } +} + +void run_cpu(cpu *context, uint32_t target_cycle) +{ + while (context->cycles < target_cycle) + { + switch (context->state) + { + case STATE_NEED_FETCH: + fetch_instruction(context); + break; + case STATE_NORMAL: + run_instruction(context); + break; + case STATE_EXCEPTION_START: + vector_fetch(context); + break; + } + } +} \ No newline at end of file diff -r 000000000000 -r 7e44f7d5810b src/cpu.h --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/cpu.h Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,114 @@ +#ifndef CPU_H_ +#define CPU_H_ + + +typedef struct cpu cpu; + +typedef void (*port_write_fun) (cpu *context, uint8_t port, uint16_t value); +typedef uint16_t (*port_read_fun) (cpu *context, uint8_t port); + +typedef struct { + port_write_fun write; + port_read_fun read; +} port_handler; + + +#define MEM_READ 1 +#define MEM_WRITE 2 + +typedef struct { + uint8_t *base; + uint16_t start; + uint16_t end; + uint8_t flags; +} memory_region; + + +struct cpu { + uint32_t cycles; + uint32_t clock_inc; + uint32_t num_mem_regions; + uint16_t regs[16]; + uint16_t exception; + uint16_t exception_pc; + uint16_t exception_sr; + uint16_t exception_ur; + uint16_t vector_base; + + uint16_t prefetch; + + uint8_t state; + + port_handler port_handlers[16]; + memory_region mem_regions[]; +}; + +cpu* alloc_cpu(uint32_t clock_divider, uint32_t num_regions, memory_region *regions); +void run_cpu(cpu *context, uint32_t target_cycle); + +enum { + LDIM, + LDIMH, + LD8, + LD16, + STR8, + STR16, + ADD, + ADC, + AND, + OR, + XOR, + LSL, + LSR, + ASR, + BCC, + SINGLE_SOURCE +}; + +enum { + MOVE, + NEG, + NOT, + CMP, + CALL, + SWAP, + IN, + OUT, + INI, + OUTI, + ADDI, + ANDI, + ORI, + LSI, + ASRI, + SINGLE_REG +}; + +enum { + RETI, + TRAP, + TRAPI, + GETEPC, + SETEPC, + GETESR, + SETESR, + GETEUR, + SETEUR, + GETENUM, + SETENUM +}; + +enum { + COND_ALWAYS, + COND_NEVER, + COND_ZERO, + COND_NZERO, + COND_NEG, + COND_POS, + COND_CARRY, + COND_NCARRY, + COND_GREATER, + COND_LEQ +}; + +#endif //CPU_H_ diff -r 000000000000 -r 7e44f7d5810b src/main.c --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/src/main.c Tue Mar 22 22:44:02 2016 -0700 @@ -0,0 +1,74 @@ +#include +#include +#include +#include +#include "cpu.h" + +#define CYCLES_PER_FRAME ((48000*25)/60) + +uint8_t rom[48 * 1024]; +uint8_t ram[16 * 1024]; + +enum { + PORT_CONTROLLER_1, + PORT_CONTROLLER_2, + PORT_CONTROLLER_3, + PORT_CONTROLLER_4, + PORT_FREQUENCY_A, + PORT_FREQUENCY_B, + PORT_FREQUENCY_C, + PORT_FREQUENCY_D, + PORT_VOLUME_AB, + PORT_VOLUME_CD, + PORT_TIMER, + PORT_SERIAL, +}; + +void debug_port_write(cpu *context, uint8_t port, uint16_t value) +{ + putchar(value); +} + +uint16_t debug_port_read(cpu *context, uint8_t port) +{ + return getchar(); +} + +memory_region regions[] = { + {rom, 0, sizeof(rom)-1, MEM_READ}, + {ram, sizeof(rom), sizeof(rom)-1+sizeof(ram), MEM_READ}, +}; + +void run_console(cpu *context) +{ + for(;;) + { + run_cpu(context, CYCLES_PER_FRAME); + context->cycles -= CYCLES_PER_FRAME; + } +} + + + +int main(int argc, char **argv) +{ + if (argc < 2) { + fputs("usage: s16 FILE\n", stderr); + return 1; + } + FILE *f = fopen(argv[1], "rb"); + if (!f) { + fprintf(stderr, "Failed to open %s for reading\n", argv[1]); + return 1; + } + + size_t read; + if ((read = fread(rom, 1, sizeof(rom), f)) < sizeof(rom)) { + memset(rom + read, 0xFF, sizeof(rom)-read); + } + cpu *context = alloc_cpu(1, sizeof(regions)/sizeof(memory_region), regions); + context->port_handlers[PORT_SERIAL].write = debug_port_write; + context->port_handlers[PORT_SERIAL].read = debug_port_read; + run_console(context); + return 0; +}