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