changeset 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 a44e078d792b
files .hgignore Makefile helloworld.bin helloworld.s16 simple_console.txt src/cpu.c src/cpu.h src/main.c
diffstat 8 files changed, 828 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /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
--- /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 $@ $<
Binary file helloworld.bin has changed
--- /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
--- /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?
+
+
+
+
+
--- /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
--- /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_
--- /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 <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#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;
+}