Mercurial > repos > simple16
diff src/cpu.c @ 0:7e44f7d5810b
Initial commit. CPU working well enough for simple hello world program.
author | Michael Pavone <pavone@retrodev.com> |
---|---|
date | Tue, 22 Mar 2016 22:44:02 -0700 |
parents | |
children | 6204c81e2933 |
line wrap: on
line diff
--- /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 <stdint.h> +#include <stdlib.h> +#include <string.h> +#include <stdio.h> +#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