shithub: 8080

Download patch

ref: 0d4a0e8b087756ce01fdfd7527409baff200281b
parent: eee9501cb9a975d6b9c507db70feb338fa604b26
author: Alex Musolino <alex@musolino.id.au>
date: Thu May 11 10:07:24 EDT 2023

misc. updates improvements

--- a/8080.c
+++ b/8080.c
@@ -1,6 +1,7 @@
 #include <u.h>
 #include <libc.h>
 #include <bio.h>
+#include <ctype.h>
 #include "dat.h"
 #include "fns.h"
 
@@ -27,7 +28,7 @@
 static void
 usage(void)
 {
-	fprint(2, "usage: %s [-b addr] rom\n", argv0);
+	fprint(2, "usage: %s [-b addr] [-i script] [-t addr] [-T addr]\n", argv0);
 	exits("usage");
 }
 
@@ -77,16 +78,20 @@
 				tracing++;
 			}
 
-	for(i = 0; i < nbrkpts; i++)
-		if(brkpts[i].enabled && cpu.PC == brkpts[i].addr){
-			brkpts[i].enabled = 0;
+	for(i = 0; i < nbrkpts; i++){
+		if(brkpts[i].enabled && brkpts[i].hit == 0 && cpu.PC == brkpts[i].addr){
+			Bprint(stderr, "hit breakpoint %d @ pc=%#.4uhx\n", i+1, cpu.PC);
+			Bflush(stderr);
+			brkpts[i].hit = 1;
 			trap();
 		}
+	}
 
 	insn.pc = cpu.PC;
 	insn.op = decodeop(buf[0] = ifetch(&cpu));
 	if(insn.op == -1){
-		Bprint(stderr, "illegal opcode %#.2uhhx @ pc=%#.4uhx\n", buf[0], insn.pc);
+		Bprint(stderr, "illegal opcode %#.2uhhx @ pc=%#.4uhx\n", buf[0], cpu.PC);
+		Bflush(stderr);
 		trap();
 	}
 	switch(ilen = insnlen(insn.op)){
@@ -114,7 +119,7 @@
 
 	for(i = 0; i < nbrkpts; i++)
 		if(cpu.PC == brkpts[i].addr)
-			brkpts[i].enabled = 1;
+			brkpts[i].hit = 0;
 
 	for(i = 0; i < ntraceops; i++)
 		if(cpu.PC == traceops[i].addr)
@@ -126,61 +131,217 @@
 	ocpu = cpu;
 }
 
-static void
-prompt(void)
+enum
 {
-	static char prev[256] = "";
+	Cerror = -1,
+	Cnone,
+	Cdas,
+	Cbpls,
+	Cbpset,
+	Cbpdel,
+	Cload,
+	Creg,
+	Crun,
+	Cstep,
+	Cexit,
+	Creset,
+};
+
+static char *cmdtab[] = {
+	[Cnone] "",
+	[Cdas] "das",
+	[Cbpls] "bpls",
+	[Cbpset] "bpset",
+	[Cbpdel] "bpdel",
+	[Cload] "load",
+	[Creg] "reg",
+	[Crun] "run",
+	[Cstep] "step",
+	[Cexit] "exit",
+	[Creset] "reset",
+};
+
+typedef struct Cmd Cmd;
+struct Cmd
+{
 	char *buf;
+	int argc;
+	char *argv[16];
+};
 
-	trapinit();
-	if(interactive){
-		Bprint(stdout, "8080> ");
-		Bflush(stdout);
+static int
+cmd(char *s)
+{
+	int i;
+
+	for(i = 0; i < nelem(cmdtab); i++){
+		if(strcmp(s, cmdtab[i]) == 0)
+			return i;
 	}
-	buf = Brdstr(stdin, '\n', 1);
-	if(Blinelen(stdin) <= 0)
-		exits("eof");
-	if(strcmp(buf, "") == 0){
-		if(strcmp(prev, "") == 0)
-			return;
-		strcpy(buf, prev);
-	}
-	if(strcmp(buf, "das") == 0){
-		//das(&insn, mem);
-	}else if(strcmp(buf, "bpset") == 0){
-	}else if(strcmp(buf, "load") == 0){
-		if(loadrom("invaders.rom", 0) < 0)
-			Bprint(stderr, "load failed: %r\n");
-	}else if(strcmp(buf, "reg") == 0){
+	return Cerror;
+}
+
+static void
+docmd(Cmd *c)
+{
+	int i;
+
+	switch(cmd(c->argv[0])){
+	case Cerror:
+		Bprint(stderr, "unknown command: %s\n", c->argv[0]);
+		Bflush(stderr);
+		break;
+	case Cnone:
+		break;
+	case Cdas:
+		das(mem+cpu.PC,mem+nelem(mem), -1);
+		break;
+	case Cbpls:
+		if(c->argc != 1){
+			Bprint(stderr, "usage: bpls\n");
+			Bflush(stderr);
+			break;
+		}
+		for(i = 0; i < nbrkpts; i++){
+			if(brkpts[i].enabled == 0)
+				continue;
+			Bprint(stdout, "bp %d @ %#.4uhx\n", i+1, brkpts[i].addr);
+		}
+		Bflush(stdout);
+		break;
+	case Cbpset:
+		if(c->argc != 2){
+			Bprint(stderr, "usage: bpset ADDR\n");
+			Bflush(stderr);
+			break;
+		}
+		if(nbrkpts >= MAXBRKPTS){
+			Bprint(stderr, "too many breakpoints");
+			Bflush(stderr);
+			break;
+		}
+		brkpts[nbrkpts].addr = strtoul(c->argv[1], 0, 0);
+		brkpts[nbrkpts].enabled = 1;
+		nbrkpts++;
+		break;
+	case Cbpdel:
+		if(c->argc != 2){
+			Bprint(stderr, "usage: bpdel NUM\n");
+			Bflush(stderr);
+			break;
+		}
+		i = strtoul(c->argv[1], 0, 0);
+		if(i < 1 || i > nbrkpts){
+			Bprint(stderr, "bad breakpoint number\n");
+			Bflush(stderr);
+			break;
+		}
+		if(brkpts[i-1].enabled == 0){
+			Bprint(stderr, "no such breakpoint\n");
+			Bflush(stderr);
+			break;
+		}
+		brkpts[i-1].enabled = 0;
+		break;
+	case Cload:
+		if(c->argc != 2){
+			Bprint(stderr, "usage: load ROM\n");
+			Bflush(stderr);
+			break;
+		}
+		if(loadrom(c->argv[1], 0) < 0){
+			Bprint(stderr, "loading %s failed: %r\n", c->argv[1]);
+			Bflush(stderr);
+		}
+		break;
+	case Creg:
 		dumpregs();
-	}else if(strcmp(buf, "run") == 0){
-		if(wastrap())
+		break;
+	case Crun:
+		if(c->argc != 1){
+			Bprint(stderr, "usage: run\n");
+			Bflush(stderr);
+			break;
+		}
+		if(wastrap()){
+			Bflush(stdout);
 			cpu = ocpu;
-		else
+		}else{
 			for(;;){
-				Bprint(stdout, "%#.4uhx\t", cpu.PC);
-				das(mem+cpu.PC,nelem(mem));
+				//Bprint(stdout, "%#.4uhx\t", cpu.PC);
+				//das1(mem+cpu.PC,nelem(mem)-cpu.PC);
 				cpustep();
 			}
-	}else if(strcmp(buf, "step") == 0){
-		if(wastrap())
+		}
+		break;
+	case Cstep:
+		if(wastrap()){
 			cpu = ocpu;
-		else{
+		}else{
 			print("%#.4uhx\t", cpu.PC);
-			das(mem+cpu.PC,nelem(mem));
+			das1(mem+cpu.PC,nelem(mem)-cpu.PC);
 			cpustep();
 		}
-	}else if(strcmp(buf, "exit") == 0){
+		break;
+	case Cexit:
 		exits(0);
-	}else if(strcmp(buf, "reset") == 0){
+		break;
+	case Creset:
 		cpureset();
+		break;
+	}
+}
+
+static Cmd prev;
+
+static int
+proc(Biobuf *in)
+{
+	char *buf;
+	Cmd cmd;
+
+	trapinit();
+	buf = Brdstr(in, '\n', 0);
+	Bflush(stderr);
+	if(Blinelen(in) <= 0)
+		return Blinelen(in);
+	while(isspace(*buf))
+		buf++;
+	if(*buf == '#')
+		return Blinelen(in);
+	cmd.argc = tokenize(buf, cmd.argv, nelem(cmd.argv));
+	if(cmd.argc == 0){
+		free(buf);
+		if(prev.argc > 0)
+			docmd(&prev);
 	}else{
-		Bprint(stderr, "unknown command: %s\n", buf);
-		buf[0] = 0;
+		cmd.buf = buf;
+		docmd(&cmd);
+		free(prev.buf);
+		prev = cmd;
 	}
-	strcpy(prev, buf);
+	return Blinelen(in);
 }
 
+static void
+procfile(char *file)
+{
+	Biobuf *b;
+
+	b = Bopen(file, OREAD);
+	if(b == nil)
+		sysfatal("could not open %s: %r", file);
+	for(;;){
+		switch(proc(b)){
+		case -1:
+			sysfatal("error processing %s", file);
+		case 0:
+			Bterm(b);
+			return;
+		}
+	}
+}
+
 static int
 isatty(void)
 {
@@ -194,6 +355,10 @@
 void
 main(int argc, char **argv)
 {
+	stdin = Bfdopen(0, OREAD);
+	stdout = Bfdopen(1, OWRITE);
+	stderr = Bfdopen(2, OWRITE);
+
 	ARGBEGIN{
 	case 'b':
 		if(nbrkpts >= MAXBRKPTS)
@@ -205,6 +370,9 @@
 	case 'd':
 		debug++;
 		break;
+	case 'i':
+		procfile(EARGF(usage()));
+		break;
 	case 't':
 	case 'T':
 		if(ntraceops >= MAXTRACEOPS)
@@ -218,12 +386,19 @@
 	}ARGEND;
 	if(argc != 0)
 		usage();
-	stdin = Bfdopen(0, OREAD);
-	stdout = Bfdopen(1, OWRITE);
-	stderr = Bfdopen(2, OWRITE);
 	interactive = isatty();
 	fmtinstall('I', insnfmt);
 	cpureset();
-	for(;;)
-		prompt();
+	for(;;){
+		if(interactive){
+			Bprint(stdout, "8080> ");
+			Bflush(stdout);
+		}
+		switch(proc(stdin)){
+		case -1:
+			sysfatal("error reading stdin: %r");
+		case 0:
+			exits(0);
+		}
+	}
 }
--- a/das.c
+++ b/das.c
@@ -4,56 +4,57 @@
 #include "dat.h"
 #include "fns.h"
 
-static void Xshld(CPU*, Insn*);
-static void Xsbi(CPU*, Insn*);
-static void Xjm(CPU*, Insn*);
-static void Xdaa(CPU*, Insn*);
-static void Xcz(CPU*, Insn*);
-static void Xpchl(CPU*, Insn*);
-static void Xnop(CPU*, Insn*);
-static void Xjmp(CPU*, Insn*);
-static void Xlxi(CPU*, Insn*);
-static void Xmvi(CPU*, Insn*);
-static void Xpop(CPU*, Insn*);
-static void Xcall(CPU*, Insn*);
-static void Xldax(CPU*, Insn*);
-static void Xmov(CPU*, Insn*);
-static void Xinx(CPU*, Insn*);
-static void Xdcr(CPU*, Insn*);
-static void Xret(CPU*, Insn*);
-static void Xdad(CPU*, Insn*);
-static void Xsta(CPU*, Insn*);
-static void Xxra(CPU*, Insn*);
-static void Xout(CPU*, Insn*);
-static void Xora(CPU*, Insn*);
-static void Xlda(CPU*, Insn*);
+static void Xadi(CPU*, Insn*);
 static void Xana(CPU*, Insn*);
-static void Xpush(CPU*, Insn*);
-static void Xxchg(CPU*, Insn*);
-static void Xinr(CPU*, Insn*);
 static void Xani(CPU*, Insn*);
-static void Xrar(CPU*, Insn*);
-static void Xori(CPU*, Insn*);
+static void Xcall(CPU*, Insn*);
 static void Xcmp(CPU*, Insn*);
-static void Xrlc(CPU*, Insn*);
-static void Xrim(CPU*, Insn*);
-static void Xrrc(CPU*, Insn*);
-static void Xdcx(CPU*, Insn*);
-static void Xstax(CPU*, Insn*);
 static void Xcpi(CPU*, Insn*);
-static void Xadi(CPU*, Insn*);
-static void Xei(CPU*, Insn*);
+static void Xcz(CPU*, Insn*);
+static void Xdaa(CPU*, Insn*);
+static void Xdad(CPU*, Insn*);
+static void Xdcr(CPU*, Insn*);
+static void Xdcx(CPU*, Insn*);
 static void Xdi(CPU*, Insn*);
+static void Xei(CPU*, Insn*);
 static void Xin(CPU*, Insn*);
+static void Xinr(CPU*, Insn*);
+static void Xinx(CPU*, Insn*);
 static void Xjc(CPU*, Insn*);
-static void Xjz(CPU*, Insn*);
+static void Xjm(CPU*, Insn*);
+static void Xjmp(CPU*, Insn*);
 static void Xjnc(CPU*, Insn*);
 static void Xjnz(CPU*, Insn*);
+static void Xjp(CPU*, Insn*);
+static void Xjz(CPU*, Insn*);
+static void Xlda(CPU*, Insn*);
+static void Xldax(CPU*, Insn*);
+static void Xlxi(CPU*, Insn*);
+static void Xmov(CPU*, Insn*);
+static void Xmvi(CPU*, Insn*);
+static void Xnop(CPU*, Insn*);
+static void Xora(CPU*, Insn*);
+static void Xori(CPU*, Insn*);
+static void Xout(CPU*, Insn*);
+static void Xpchl(CPU*, Insn*);
+static void Xpop(CPU*, Insn*);
+static void Xpush(CPU*, Insn*);
+static void Xrar(CPU*, Insn*);
 static void Xrc(CPU*, Insn*);
+static void Xret(CPU*, Insn*);
+static void Xrim(CPU*, Insn*);
+static void Xrlc(CPU*, Insn*);
 static void Xrnc(CPU*, Insn*);
 static void Xrnz(CPU*, Insn*);
+static void Xrrc(CPU*, Insn*);
 static void Xrz(CPU*, Insn*);
+static void Xsbi(CPU*, Insn*);
+static void Xshld(CPU*, Insn*);
+static void Xsta(CPU*, Insn*);
+static void Xstax(CPU*, Insn*);
 static void Xsui(CPU*, Insn*);
+static void Xxchg(CPU*, Insn*);
+static void Xxra(CPU*, Insn*);
 static void Xxthl(CPU*, Insn*);
 
 static int dec0(Insn*, uchar*, long);
@@ -116,6 +117,7 @@
 	[Ojmp]{"JMP", Taddr, Xjmp},
 	[Ojnc]{"JNC", Taddr, Xjnc},
 	[Ojnz]{"JNZ", Taddr, Xjnz},
+	[Ojp]{"JP", Taddr, Xjp},
 	[Ojpo]{"JPO", Taddr},
 	[Ojz]{"JZ", Taddr, Xjz},
 	[Olda]{"LDA", Taddr, Xlda},
@@ -464,18 +466,43 @@
 }
 
 int
-das(uchar *mem, long mlen)
+das1(uchar *s, long len)
 {
 	int n;
 	Insn insn;
 
-	if((n = decode(&insn, mem, mlen)) < 0)
+	if((n = decode(&insn, s, len)) < 0)
 		return -1;
-	Bprint(stdout, "\t%I\n", &insn);
+	Bprint(stdout, "%#.4ullx:\t%I\n", s - mem, &insn);
+	//Bflush(stdout);
 	return n;
 }
 
 int
+das(uchar *s, uchar *e, int max)
+{
+	int n, isz;
+
+	n = 0;
+	for(;;){
+		if(s >= e)
+			break;
+		if(max >= 0 && n >= max)
+			break;
+		if((isz = das1(s, e-s)) < 0){
+			n = -1;
+			break;
+		}
+		if(isz == 0)
+			break;
+		n++;
+		s += isz;
+	}
+	Bflush(stdout);
+	return n;
+}
+
+int
 dasfile(char *file)
 {
 	Biobuf *r;
@@ -522,11 +549,12 @@
 		}
 	}
 	while(buflen > 0){
-		print("%#.4x\t???\t%#.2x\n", addr, buf[0]);
+		Bprint(stdout, "%#.4x\t???\t%#.2x\n", addr, buf[0]);
 		addr++;
 		bp++;
 		buflen--;
 	}
+	Bflush(stdout);
 	return 0;
 }
 
@@ -535,6 +563,7 @@
 {
 	if(isa[insn->op].exec == nil){
 		Bprint(stderr, "%s (%#.2uhhx) not implemented!\n", opstr(insn->op), insn->op);
+		Bflush(stderr);
 		trap();
 	}
 	itrace(opstr(insn->op));
@@ -544,10 +573,18 @@
 static u16int
 rpair(CPU *cpu, u8int rp)
 {
+	u16int x;
+
 	switch(rp){
 	case BC: return cpu->r[B]<<8 | cpu->r[C];
 	case DE: return cpu->r[D]<<8 | cpu->r[E];
-	case HL: return cpu->r[H]<<8 | cpu->r[L];
+	case HL:
+		x = cpu->r[H]<<8 | cpu->r[L];
+		if(rp == HL && x == 0){
+			Bprint(stderr, "HL = 0 @ pc=%#.4uhx\n", cpu->PC);
+			Bflush(stderr);
+		}
+		return x;
 	}
 	fatal("unknown register pair %d", rp);
 	return 0;
@@ -556,6 +593,10 @@
 static void
 wpair(CPU *cpu, u8int rp, u16int x)
 {
+	if(rp == HL && x == 0){
+		Bprint(stderr, "HL ← 0 @ pc=%#.4uhx\n", cpu->PC);
+		Bflush(stderr);
+	}
 	cpu->r[(rp<<1)+B] = x>>8;
 	cpu->r[(rp<<1)+B+1] = x;
 }
@@ -566,6 +607,13 @@
 }
 
 static void
+Xjp(CPU *cpu, Insn *insn)
+{
+	if((cpu->flg & Fsign) == 0)
+		cpu->PC = insn->addr;
+}
+
+static void
 Xjmp(CPU *cpu, Insn *insn)
 {
 	cpu->PC = insn->addr;
@@ -596,6 +644,8 @@
 static void
 Xcall(CPU *cpu, Insn *insn)
 {
+	//Bprint(stderr, "CALL %#.4uhx @ pc=%#.4uhx\n", insn->addr, insn->pc);
+	//Bflush(stderr);
 	push16(cpu, cpu->PC);
 	cpu->PC = insn->addr;
 }
@@ -669,8 +719,8 @@
 	x += rpair(cpu, HL);
 	if(x>>16 > 0)
 		cpu->flg |= Fcarry;
-	//else
-		//cpu->flg &= ~Fcarry;
+	else
+		cpu->flg &= ~Fcarry;
 	wpair(cpu, HL, x);
 }
 
@@ -705,11 +755,6 @@
 	cpu->flg &= ~(Fcarry|Fhcarry);
 }
 
-void
-iow(u16int a, u8int v)
-{
-}
-
 static void
 Xout(CPU *cpu, Insn *insn)
 {
@@ -775,21 +820,37 @@
 static void
 Xpush(CPU *cpu, Insn *insn)
 {
-	if(insn->rp == 3){
+	u16int x;
+
+	if(insn->rp == MM){
 		push8(cpu, cpu->r[A]);
 		push8(cpu, cpu->flg);
-	}else
-		push16(cpu, rpair(cpu, insn->rp));
+	}else{
+		x = rpair(cpu, insn->rp);
+		if(insn->rp == HL && x == 0){
+			Bprint(stderr, "pushed %uhd from %s @ %#.4uhx\n", x, rpnam(insn->rp), insn->pc);
+			Bflush(stderr);
+		}
+		push16(cpu, x);
+	}
 }
 
 static void
 Xpop(CPU *cpu, Insn *insn)
 {
-	if(insn->rp == 3){
+	u16int x;
+
+	if(insn->rp == MM){
 		cpu->flg = pop8(cpu);
 		cpu->r[A] = pop8(cpu);
-	}else
-		wpair(cpu, insn->rp, pop16(cpu));
+	}else{
+		x = pop16(cpu);
+		if(insn->rp == HL && x == 0){
+			Bprint(stderr, "popped %uhd into %s @ %#.4uhx\n", x, rpnam(insn->rp), insn->pc);
+			Bflush(stderr);
+		}
+		wpair(cpu, insn->rp, x);
+	}
 }
 
 static void
@@ -830,6 +891,19 @@
 	cpu->flg &= ~Fhcarry;
 }
 
+static int
+evenpar(u8int x)
+{
+	u8int r;
+
+	r = 1;
+	while(x != 0){
+		r ^= x&1;
+		x >>= 1;
+	}
+	return r;
+}
+
 static void
 Xani(CPU *cpu, Insn *insn)
 {
@@ -845,6 +919,11 @@
 	else
 		cpu->flg &= ~Fsign;
 
+	if(evenpar(cpu->r[A]))
+		cpu->flg |= Fparity;
+	else
+		cpu->flg &= ~Fparity;
+
 	cpu->flg &= ~(Fcarry|Fhcarry);
 }
 
@@ -1007,9 +1086,9 @@
 }
 
 static void
-Xin(CPU *cpu, Insn*)
+Xin(CPU *cpu, Insn *insn)
 {
-	cpu->r[A] = 0;
+	cpu->r[A] = ior(insn->addr);
 }
 
 static void
--- a/dat.h
+++ b/dat.h
@@ -1,3 +1,6 @@
+typedef struct Biobuf Biobuf;
+typedef struct Fmt Fmt;
+
 typedef struct CPU CPU;
 typedef struct Insn Insn;
 typedef struct ISA ISA;
@@ -27,6 +30,7 @@
 	BC,
 	DE,
 	HL,
+	MM,
 };
 
 enum{
@@ -184,6 +188,7 @@
 {
 	u16int addr;
 	uchar enabled;
+	uchar hit;
 };
 
 enum{
--- a/debug.c
+++ b/debug.c
@@ -59,13 +59,20 @@
 	return "XX";
 }
 
-void
-dumpinst(void)
+static void
+dumpinst0(void)
 {
 	Bprint(stderr, "op: %#.2x [%#.2x %#.2x]\n",
 		mem[insn.pc+0], mem[insn.pc+1], mem[insn.pc+2]);
 }
 
+void
+dumpinst(void)
+{
+	dumpinst0();
+	Bflush(stderr);
+}
+
 static char
 flagchar(int f)
 {
@@ -79,8 +86,8 @@
 	return 0;
 }
 
-void
-dumpregs(void)
+static void
+dumpregs0(void)
 {
 	int i;
 
@@ -105,6 +112,13 @@
 }
 
 void
+dumpregs(void)
+{
+	dumpregs0();
+	Bflush(stderr);
+}
+
+void
 dumpmem(u16int s, u16int e)
 {
 	while(s < e){
@@ -111,6 +125,7 @@
 		Bprint(stderr, "%.4x: %.2x\n", s, mem[s]);
 		s++;
 	}
+	Bflush(stderr);
 }
 
 void
@@ -122,6 +137,7 @@
 	Bprint(stderr, "%#.4x ", insn.pc);
 	Bvprint(stderr, fmt, args);
 	Bprint(stderr, "\n");
+	Bflush(stderr);
 	va_end(args);
 }
 
@@ -134,7 +150,8 @@
 	Bvprint(stderr, fmt, args);
 	Bprint(stderr, "\n");
 	va_end(args);
-	dumpinst();
-	dumpregs();
+	dumpinst0();
+	dumpregs0();
+	Bflush(stderr);
 	exits("fatal");
 }
--- a/fns.h
+++ b/fns.h
@@ -1,3 +1,5 @@
+typedef struct Fmt Fmt;
+
 /* debug */
 void	dumpregs(void);
 void	dumpmem(u16int, u16int);
@@ -12,7 +14,8 @@
 /* disassembler */
 #pragma	   varargck    type  "I"   Insn*
 int	insnfmt(Fmt*);
-int	das(uchar*, long);
+int	das1(uchar*, long);
+int das(uchar*, uchar*, int);
 int dasfile(char*);
 
 /* isa */
@@ -34,3 +37,7 @@
 u16int	pop16(CPU*);
 void	push8(CPU*, u8int);
 void	push16(CPU*, u16int);
+
+/* i/o */
+u8int	ior(u16int);
+void	iow(u16int, u8int);
--- /dev/null
+++ b/io.c
@@ -1,0 +1,35 @@
+#include <u.h>
+#include "dat.h"
+#include "fns.h"
+
+typedef struct Shifter Shifter;
+struct Shifter {
+	u16int reg;
+	u8int off;
+};
+
+static Shifter shifter;
+
+u8int
+ior(u16int a)
+{
+	switch(a){
+	case 0x3:
+		return shifter.reg>>shifter.off;
+	}
+	return 0;
+}
+
+void
+iow(u16int a, u8int v)
+{
+	switch(a){
+	case 0x2:
+		shifter.off = v&0x7;
+		break;
+	case 0x4:
+		shifter.reg >>= 8;
+		shifter.reg |= ((u16int)v)<<8;
+		break;
+	}
+}
--- a/mem.c
+++ b/mem.c
@@ -9,6 +9,7 @@
 {
 	if(a >= MEMSZ){
 		Bprint(stderr, "memread failed addr=%#.4x op=%#0.2x pc=%#.4x\n", a, insn.op, ocpu.PC);
+		Bflush(stderr);
 		trap();
 	}
 	return mem[a];
@@ -19,6 +20,7 @@
 {
 	if(a < ROMSZ || a >= MEMSZ){
 		Bprint(stderr, "write failed addr=%#.4x op=%#0.2x pc=%#.4x\n", a, insn.op, ocpu.PC);
+		Bflush(stderr);
 		trap();
 	}
 	mem[a] = x;
@@ -29,6 +31,7 @@
 {
 	if(cpu->PC >= ROMSZ){
 		Bprint(stderr, "ifetch failed pc=%#.4x\n", cpu->PC);
+		Bflush(stderr);
 		trap();
 	}
 	return memread(cpu->PC++);
--- a/mkfile
+++ b/mkfile
@@ -6,6 +6,7 @@
 	mem.$O\
 	debug.$O\
 	das.$O\
+	io.$O\
 
 HFILES=\
 	dat.h\
--- a/test.rc
+++ b/test.rc
@@ -1,5 +1,7 @@
 #!/bin/rc
-mk && 6.out >out.log <<EOF
-load
-run
+mk && 6.out -i <{cat <<EOF
+load invaders.rom
+bpset 0x18dc
+bpset 0x18df
 EOF
+}