commit ec56fd7a9e09a880795885ae82862eb06499d874 Author: Jim Date: Mon Apr 18 00:00:00 2005 -0400 Initial version of Prime emulator for Mac OSX on PowerPC diff --git a/em.c b/em.c new file mode 100644 index 0000000..81254f0 --- /dev/null +++ b/em.c @@ -0,0 +1,1372 @@ +/* Prime R-mode emulator, Jim Wilcoxson (jim@meritnet.com), April 4, 2005 + Copyright (C) 2005, Jim Wilcoxson (jim@meritnet.com). All Rights Reserved. + + Restores a Prime R-mode .save image from stdin to memory and + emulates execution. + + PLEASE NOTE: this is a very rough prototype still in development. + The main goal for the prototype is to get an understanding of the + kinds of design issues faced by an emulator, before doing a "real" + Prime 50-series emulator. + + You are welcome to pass this along to friends for fun and + amusement, but please don't publish it. Comments, suggestions, + corrections, and general notes that you're interested in a Prime + emulation project are also welcome and appreciated. + + Usage: + + $ ./em /dev/null + + Lots of instruction details are spewed to stderr. + + + Left to do (way more to do ... this just gives an idea): + + - only runs on a big-endian machine, like the Prime + + - nothing has been systematically verified/checked + + - many instructions are left to implement + + - only 1 32R program has been executed, and it may have issues; + no other CPU modes have been tested at all + + - svc doesn't handle alternate returns, LOC(blah) args might not be + handled correctly, and it doesn't handle missing or extra arguments + correctly (by looking for the terminating zero) + + - instructions don't update the C-bit, L-bit, and condition codes + + - restricted-mode instruction checking is missing, as well as all + restricted-mode instructions and environment (for example, no page + mapping) + +*/ + +#include +#include + +/* Prime CPU registers are mapped to memory locations 0-'37, but only + 0-7 are accessible in SR user mode. In VI user mode, locations + 0-'17 are trapped and map to the live register file (p 5-17, Sys Arch) +*/ + +#define X 0 +#define A 1 +#define B 2 +#define S 3 +#define FLTH 4 +#define FLTL 5 +#define FEXP 6 +#define P 7 +#define PMAR 010 +#define FCODE 011 +#define PFAR 012 +#define DMA0 020 +#define DMA1 022 +#define DMA2 024 +#define DMA3 026 +#define DMA4 030 +#define DMA5 032 +#define DMA6 034 +#define DMA7 036 + +/* Locations '40-'57 are reserved for 8 DMC channels, 2 words each. + Locations '60-'77 are interrupt vectors + Locations '100-'177 are for external device interrupts + see p. A-8 of Sys Arch +*/ + +unsigned short mem[64*1024]; /* 64K words of main memory */ +signed short tempa; +unsigned short utempa; +signed int templ,templ2; +unsigned int utempl; + +unsigned short prevpc,keys; /* program counter, prev pc, keys */ +unsigned short amask; /* address mask */ +unsigned short ea; /* effective address (word) */ +unsigned short opcode; +short i,x; +unsigned short class; +unsigned short xx; +int d; /* displacement */ +int verbose; +int memdump; /* -memdump arg */ +int scount; /* shift count */ +int instcount=0; + +/* I/O device map table, containing function pointers to handle device I/O */ + +#include "emdev.h" + +void (*devmap[64])(unsigned short, unsigned short) = { + 0,0,0,0,devasr,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0, + 0,0,0,0,0,0,0,0}; + +/* read a short (16-bit) integer in big-endian from stdin */ + +unsigned short readshort () { + + return getchar()<<8 | getchar(); +} + + +/* set new processor keys */ + +newkeys (unsigned short new) { + + keys = new; + switch ((keys & 016000) >> 10) { + case 0: /* 16S */ + fprintf(stderr,"Entering 16S mode, keys=%o\n", keys); + amask = 037777; + break; + case 1: /* 32S */ + fprintf(stderr,"Entering 32S mode, keys=%o\n", keys); + amask = 077777; + break; + case 2: /* 64R */ + fprintf(stderr,"Entering 64R mode, keys=%o\n", keys); + amask = 0177777; + break; + case 3: /* 32R */ + fprintf(stderr,"Entering 32R mode, keys=%o\n", keys); + amask = 077777; + break; + case 4: /* 32I */ + fprintf(stderr,"Entering 32I mode, keys=%o\n", keys); + amask = 0177777; + break; + case 6: /* 64V */ + fprintf(stderr,"Entering 64V mode, keys=%o\n", keys); + amask = 0177777; + break; + default: /* invalid */ + fprintf(stderr,"Invalid CPU mode: %o\n", keys); + exit(1); + } +} + + + +/* NOTE: PMA 16S flowcharts say P should point to the current instruction, + but it has already been incremented. Should be using prevpc here instead. + This code is untested! +*/ + +unsigned short ea16s (unsigned short inst, short i, short x) { + + unsigned short ea; + + if (inst & 001000) + ea = (mem[P] & 037000) | (inst & 0777); /* current sector */ + else + ea = (inst & 0777); /* sector 0 */ + while (1) { + if (x) /* indexed */ + ea += mem[X]; + if (!i) /* not indirect */ + break; + i = mem[ea] & 0100000; + x = mem[ea] & 040000; + ea = mem[ea] & 037777; /* go indirect */ + } + return ea; +} + + +/* NOTE: PMA 32S flowcharts say P should point to the current instruction, + but it has already been incremented. Should be using prevpc here instead. + This code is untested! +*/ + +unsigned short ea32s (unsigned short inst, short i, short x) { + + unsigned short ea; + + if (inst & 001000) + ea = (mem[P] & 077000) | (inst & 0777); /* current sector */ + else { + ea = (inst & 0777); /* sector 0 */ + if (ea < 0100 && x) { /* preindex by X */ + ea += mem[X]; + x = 0; + } + } + while (i) { + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; /* go indirect */ + } + if (x) /* postindex */ + ea += mem[X]; + return ea & amask; +} + + +/* NOTE: PMA 32R flowcharts say P should point to the current instruction, + but 64R says it should point after the current instruction. Tests show + that using incremented P is correct. +*/ + +unsigned short ea32r (unsigned short inst, short i, short x, short *opcode) { + + unsigned short ea; + + fprintf(stderr," ea32r: i=%o, x=%o\n", i!= 0, x!=0); + if (inst & 001000) /* sector bit 7 set? */ + if ((inst & 0760) != 0400) { /* PC relative? */ + ea = mem[P] + (((short) (inst << 7)) >> 7); /* yes, sign extend D */ + fprintf(stderr," PC relative, P=%o, new ea=%o\n", mem[P], ea); + } + else + goto special; /* special cases */ + else { + ea = (inst & 0777); /* sector 0 */ + fprintf(stderr," Sector 0, new ea=%o\n", ea); + if (ea < 0100 && x) { /* preindex by X */ + fprintf(stderr," Preindex, ea=%o, X='%o/%d\n", ea, mem[X], *(short *)(mem+X)); + ea += mem[X]; + fprintf(stderr," Preindex, new ea=%o\n", ea); + x = 0; + } + } + while (i) { + fprintf(stderr," Indirect, old ea=%o, [ea]=%o\n", ea, mem[ea]); + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; /* go indirect */ + fprintf(stderr," Indirect, new i=%d, new ea=%o, [ea]=%o\n", i!=0, ea, mem[ea]); + } + if (x) { + fprintf(stderr," Postindex, old ea=%o, X='%o/%d\n", ea, mem[X], *(short *)(mem+X)); + ea += mem[X]; + fprintf(stderr," Postindex, new ea=%o\n", ea); + } + return ea & amask; + +special: + class = inst & 3; /* class bits = 15 & 16 */ + *opcode = *opcode | ((inst >> 2) & 3); /* opcode extension */ + fprintf(stderr," special, new opcode=%5#0o, class=%d\n", *opcode, class); + + if (class < 2) { /* class 0/1 */ + ea = mem[mem[P]++]; /* get A from next word */ + fprintf(stderr," Class %d, new ea=%o\n", class, ea); + if (class == 1) + ea += mem[S]; + if (x) { + fprintf(stderr," Preindex, ea=%o, X='%o/%d\n", ea, mem[X], *(short *)(mem+X)); + ea += mem[X]; + fprintf(stderr," Preindex, new ea=%o\n", ea); + } + while (i) { + fprintf(stderr," Indirect, old ea=%o, [ea]=%o\n", ea, mem[ea]); + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; + fprintf(stderr," Indirect, new i=%d, new ea=%o, [ea]=%o\n", i!=0, ea, mem[ea]); + } + + } else if (i && x) { /* class 2/3, ix=11 */ + fprintf(stderr," class 2/3, ix=11\n"); + ea = mem[mem[P]++]; /* get A from next word */ + fprintf(stderr," ea=%o\n", ea); + if (class == 3) + ea += (short) mem[S]; + while (i) { + fprintf(stderr," Indirect, ea=%o, [ea]=%o\n", ea, mem[ea]); + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; + fprintf(stderr," Indirect, new i=%d, new ea=%o, [ea]=%o\n", i!=0, ea, mem[ea]); + } + fprintf(stderr," Postindex, old ea=%o, X='%o/%d\n", ea, mem[X], *(short *)(mem+X)); + ea += (short) mem[X]; + fprintf(stderr," Postindex, new ea=%o\n", ea); + + } else { /* class 2/3, ix != 11 */ + if (class == 2) + ea = mem[S]++; + else + ea = --mem[S]; + fprintf(stderr," Class 2/3, new ea=%o, new S=%o\n", ea, mem[S]); + exit(1); + if (x) { + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; + } + while (i) { + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; + } + if (x) + ea += mem[X]; + } + return ea & 077777; +} + +unsigned short ea64r (unsigned short inst, short i, short x, short *opcode) { + + unsigned short ea; + + fprintf(stderr,"Mode 64R not implemented\n"); +} + +unsigned short ea64v (unsigned short inst, short i, short x, short *opcode) { + fprintf(stderr,"Mode 64V not implemented\n"); +} + +unsigned short ea32i (unsigned short inst, short i, short x) { + fprintf(stderr,"Mode 32I not implemented\n"); +} + + + + +main (int argc, char **argv) { + int i; + int nw; + unsigned short rvec[9]; /* SA, EA, P, A, B, X, keys, dummy, dummy */ + unsigned short inst; + + + verbose = 0; + memdump = 0; + + /* check args */ + + for (i=1; i rvec[1]) { + fprintf(stderr,"Program start > EA: runfile is trashed\n"); + exit(1); + } + + /* read memory image from SA to EA inclusive */ + + nw = rvec[1]-rvec[0]+1; + if (fread(mem+rvec[0], sizeof(short), nw, stdin) != nw) { + perror("Error reading memory image"); + exit(1); + } + + /* setup execution (registers, keys, address mask, etc.) from rvec */ + + mem[A] = rvec[3]; + mem[B] = rvec[4]; + mem[X] = rvec[5]; + newkeys(rvec[6]); + mem[P] = rvec[2]; + + if (memdump) { + + /* dump sector zero for debugging */ + + fprintf(stderr,"\nSector 0:\n"); + for (i=0; i<01000; i=i+8) + if (mem[i]|mem[i+1]|mem[i+2]|mem[i+3]|mem[i+4]|mem[i+5]|mem[i+6]|mem[i+7]) + fprintf(stderr,"%3o: %6o %6o %6o %6o %6o %6o %6o %6o\n", i, mem[i], mem[i+1], mem[i+2], mem[i+3], mem[i+4], mem[i+5], mem[i+6], mem[i+7]); + + /* dump main memory for debugging */ + + fprintf(stderr,"\nMain memory:\n"); + for (i=rvec[2]; i rvec[1]) { /* hack for testing */ + fprintf(stderr,"\nOOPS! Program counter %o > EA %o\n", mem[P], rvec[1]); + exit(1); + } + + prevpc = mem[P]; + inst = mem[mem[P]]; + instcount++; + fprintf(stderr,"\n%o: %o A='%o/%:0d, B='%o/%d, X=%o/%d instcount=%d\n", mem[P], inst, mem[A], *(short *)(mem+A), mem[B], *(short *)(mem+B), mem[X], *(short *)(mem+X), instcount); + mem[P]++; + + /* generic? */ + + if ((inst & 036000) == 0) { + fprintf(stderr," generic\n"); + class = inst>>14; + if (class == 0 || class == 3) { + fprintf(stderr," generic class %d\n", class); + + if (inst == 000005) { /* SGL */ + fprintf(stderr," SGL\n"); + newkeys(keys & ~040000); + continue; + } + + if (inst == 000011) { /* E16S */ + fprintf(stderr," E16S\n"); + newkeys(keys & 0161777); + continue; + } + + if (inst == 000013) { /* E32S */ + fprintf(stderr," E32S\n"); + newkeys((keys & 0161777) | 1<<10); + continue; + } + + if (inst == 001013) { /* E32R */ + fprintf(stderr," E32R\n"); + newkeys((keys & 0161777) | 3<<10); + continue; + } + + if (inst == 001011) { /* E64R */ + fprintf(stderr," E64R\n"); + newkeys((keys & 0161777) | 2<<10); + continue; + } + + if (inst == 000010) { /* E64V */ + fprintf(stderr," E64V\n"); + newkeys((keys & 0161777) | 6<<10); + continue; + } + + if (inst == 001010) { /* E32I */ + fprintf(stderr," E32I\n"); + newkeys((keys & 0161777) | 4<<10); + continue; + } + + if (inst == 000505) { /* SVC */ + fprintf(stderr," SVC\n"); + svc(); + continue; + } + + if (inst == 0141206) { /* A1A */ + fprintf(stderr," A1A\n"); + mem[A]++; + continue; + } + + if (inst == 0140110) { /* S1A */ + fprintf(stderr," S1A\n"); + mem[A]--; + continue; + } + + if (inst == 0140310) { /* S2A */ + fprintf(stderr," S2A\n"); + mem[A] -= 2; + continue; + } + + if (inst == 0141050) { /* CAL */ + fprintf(stderr," CAL\n"); + mem[A] &= 0xFF; + continue; + } + + if (inst == 0141044) { /* CAR */ + fprintf(stderr," CAR\n"); + mem[A] &= 0xFF00; + continue; + } + + if (inst == 0140040) { /* CRA */ + fprintf(stderr," CRA\n"); + mem[A] = 0; + continue; + } + + if (inst == 0140010) { /* CRL */ + fprintf(stderr," CRL\n"); + mem[A] = 0; mem[B] = 0; + continue; + } + + if (inst == 0140214) { /* CAZ */ + fprintf(stderr," CAZ\n"); + if (mem[A] == 0) + mem[P]++; + else if (*(short *)(mem+A) < 0) + mem[P] += 2; + continue; + } + + /* NOTE: using "if mem[X]++ == 0" doesn't work because of unsigned + short type promotion! */ + + if (inst == 0140114) { /* IRX */ + fprintf(stderr," IRX\n"); + mem[X]++; + if (mem[X] == 0) + mem[P]++; + continue; + } + + + if (inst == 0140210) { /* DRX */ + fprintf(stderr," DRX\n"); + mem[X]--; + if (mem[X] == 0) + mem[P]++; + continue; + } + + /* NOTE: needs to check for various CPU modes */ + + if (inst == 000111) { + fprintf(stderr," CEA\n"); + i = mem[A] & 0100000; + ea = mem[A] & amask; + while (i) { + fprintf(stderr," Indirect, old ea=%o, [ea]=%o\n", ea, mem[ea]); + i = mem[ea] & 0100000; + ea = mem[ea] & 077777; /* go indirect */ + fprintf(stderr," Indirect, new i=%d, new ea=%o, [ea]=%o\n", i!=0, ea, mem[ea]); + } + mem[A] = ea; + continue; + } + + if (inst == 000000) { /* HLT :( */ + fprintf(stderr," HLT\n"); + fprintf(stderr,"\nProgram halt at %o\n", prevpc); + exit(1); + } + + if (inst == 0141240) { /* ICR */ + fprintf(stderr," ICR\n"); + mem[A] = mem[A] << 8; + continue; + } + + if (inst == 000205) { /* PIM (R-mode) */ + fprintf(stderr," PIM\n"); + mem[A] = mem[B] | (mem[A] << 15); + continue; + } + + if (inst == 000211) { /* PID (R-mode) */ + fprintf(stderr," PID\n"); + mem[B] = mem[A] & 0x7fff; + if (mem[A] & 0x8000) + mem[A] = -1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140417) { /* LT */ + fprintf(stderr," LT\n"); + mem[A] = 1; + continue; + } + + if (inst == 0140416) { /* LF */ + fprintf(stderr," LF\n"); + mem[A] = 0; + continue; + } + + if (inst == 0140410) { /* LLT */ + fprintf(stderr," LLT\n"); + if (*(short *)(mem+A) < 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140411) { /* LLE */ + fprintf(stderr," LLE\n"); + if (*(short *)(mem+A) <= 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140412) { /* LNE */ + fprintf(stderr," LNE\n"); + if (*(short *)(mem+A) != 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140413) { /* LEQ */ + fprintf(stderr," LEQ\n"); + if (*(short *)(mem+A) == 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140414) { /* LGE */ + fprintf(stderr," LGE\n"); + if (*(short *)(mem+A) >= 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 0140411) { /* LGT */ + fprintf(stderr," LGT\n"); + if (*(short *)(mem+A) > 0) + mem[A] = 1; + else + mem[A] = 0; + continue; + } + + if (inst == 000007) { /* DBL */ + fprintf(stderr," DBL\n"); + newkeys(keys | 040000); + continue; + } + + if (inst == 0141034) { /* TXA */ + fprintf(stderr," TXA\n"); + mem[A] = mem[X]; + continue; + } + + if (inst == 0140407) { + fprintf(stderr," TCA\n"); + *(short *)(mem+A) = - (*(short *)(mem+A)); + continue; + } + + if (inst == 0140200) { + fprintf(stderr," RCB\n"); + newkeys(keys & 077777); + continue; + } + + fprintf(stderr," unrecognized generic class 0/3 instruction!\n"); + exit(1); + + } + + if (class == 1) { + fprintf(stderr," shift group\n"); + scount = -inst & 077; + switch (inst & 01700) { + + case 00000: /* LRL */ + fprintf(stderr," LRL %d\n", scount); + utempl = *(unsigned int *)(mem+A); + utempl = utempl >> scount; + *(int *)(mem+A) = utempl; + break; + + case 00100: /* LRS (s.b. different in R/V modes) */ + fprintf(stderr," LRS %d\n", scount); + templ = *(int *)(mem+A); + templ = templ >> scount; + *(int *)(mem+A) = templ; + break; + + case 00200: /* LRR */ + fprintf(stderr," LRR %d\n", scount); + exit(1); + break; + + case 00400: /* ARL */ + fprintf(stderr," ARL %d\n", scount); + utempa = mem[A]; + utempa = utempa >> scount; + mem[A] = utempa; + break; + + case 00500: /* ARS */ + fprintf(stderr," ARS %d\n", scount); + tempa = *(short *)(mem+A); + tempa = tempa >> scount; + *(short *)(mem+A) = tempa; + break; + + case 00600: /* ARR */ + fprintf(stderr," ARR %d\n", scount); + exit(1); + break; + + case 01000: /* LLL */ + fprintf(stderr," LLL %d\n", scount); + utempl = *(unsigned int *)(mem+A); + utempl = utempl << scount; + *(unsigned int *)(mem+A) = utempl; + break; + + case 01100: /* LLS (s.b. different in R/V modes) */ + fprintf(stderr," LLS %d\n", scount); + templ = *(int *)(mem+A); + templ = templ << scount; + *(int *)(mem+A) = templ; + break; + + case 01200: /* LLR */ + fprintf(stderr," LLR %d\n", scount); + exit(1); + break; + + case 01400: /* ALL */ + case 01500: /* ALS */ + fprintf(stderr," ALL/ALS %d\n", scount); + utempa = *(short *)(mem+A); + utempa = utempa << scount; + *(short *)(mem+A) = utempa; + break; + + case 01600: /* ALR */ + fprintf(stderr," ALR %d\n", scount); + exit(1); + break; + + default: + fprintf(stderr," unrecognized shift instruction\n"); + exit(1); + } + continue; + } + + if (class == 2) { + fprintf(stderr," skip group\n"); + + if (inst == 0101000) { + fprintf(stderr," NOP\n"); + continue; + } + + if (inst == 0100000) { + fprintf(stderr," SKP\n"); + mem[P]++; + continue; + } + + if (inst == 0101400) { + fprintf(stderr," SMI\n"); + if (*(short *)(mem+A) < 0) + mem[P]++; + continue; + } + + if (inst == 0100400) { + fprintf(stderr," SPL\n"); + if (*(short *)(mem+A) >= 0) + mem[P]++; + continue; + } + + if (inst == 0101100) { + fprintf(stderr," SLN\n"); + if (mem[A] & 1) + mem[P]++; + continue; + } + + if (inst == 0100100) { + fprintf(stderr," SLZ\n"); + if (!(mem[A] & 1)) + mem[P]++; + continue; + } + + if (inst == 0101040) { + fprintf(stderr," SNZ\n"); + if (mem[A] != 0) + mem[P]++; + continue; + } + + if (inst == 0100040) { + fprintf(stderr," SZE\n"); + if (mem[A] == 0) + mem[P]++; + continue; + } + + if (inst == 0101220) { + fprintf(stderr," SLE\n"); + if (*(short *)(mem+A) <= 0) + mem[P]++; + continue; + } + + if (inst == 0100220) { + fprintf(stderr," SGT\n"); + if (*(short *)(mem+A) > 0) + mem[P]++; + continue; + } + + fprintf(stderr," unrecognized skip instruction\n"); + exit(1); + + } + + } + + /* here for non-generic instructions: memory references or pio */ + + if (keys & 010000) { + fprintf(stderr," VI-mode MR decode\n"); + exit(1); + } + + fprintf(stderr," SR-mode MR decode\n"); + + /* pio? */ + + if ((inst & 036000) == 030000) { + pio(inst); + continue; + } + + /* get ix bits and adjust opcode so that PMA manual opcode + references can be used directly, ie, if the PMA manual says the + opcode is '15 02, then 01502 can be used here. If the PMA + manual says the opcode is '11, then use 01100 (the XX extended + opcode bits are zero) */ + + i = inst & 0100000; /* indirect is bit 1 (left/MS bit) */ + x = inst & 040000; /* indexed is bit 2 */ + opcode = (inst & 036000) >> 4; /* isolate opcode bits */ + + /* fix ldx/stx (opcode '15): these instructions cannot be indexed + by X, so if an instruction specifies indexing by X, it acts + like an opcode extension. Opcodes listed as '35 02 for example + (sty in V-mode, jdx in R-mode) have X=1 with the 4 opcode bits + 1101 ('15) + + x=0, opcode='15 -> stx (SRV) + x=1, opcode='15 -> ldx (SRV) (aka '35) + + x=0, opcode='15 01 -> flx (RV) + x=1, opcode='15 01 -> ldy (V) (aka '35 01) + + x=0, opcode='15 02 -> dflx (V) + x=1, opcode='15 02 -> sty (V) (aka '35 02) + x=1, opcode='15 02 -> jdx (R) (aka '35 02) + + x=0, opcode='15 03 -> jix (R) + x=1, opcode='15 03 -> jsx (RV) (aka '35 03) + */ + + if (opcode == 01500) { /* '15 = ldx/stx form, depending on X */ + opcode = opcode | ((inst & 040000)>>4); /* if X set, expand opcode */ + x = 0; /* clear X bit (these can't be indexed) */ + fprintf(stderr," ldx/stx opcode adjusted\n"); + } + + fprintf(stderr," opcode=%5#0o, i=%o, x=%o\n", opcode, i, x); + + switch ((keys & 016000) >> 10) { + case 0: /* 16S */ + ea = ea16s(inst, i, x); + break; + case 1: /* 32S */ + ea = ea32s(inst, i, x); + break; + case 2: /* 64R */ + ea = ea64r(inst, i, x, &opcode); + exit(1); + break; + case 3: /* 32R */ + ea = ea32r(inst, i, x, &opcode); + break; + case 4: /* 32I */ + ea = ea32i(inst, i, x); + exit(1); + break; + case 6: /* 64V */ + ea = ea64v(inst, i, x, &opcode); + exit(1); + break; + default: + fprintf(stderr,"Bad CPU mode in EA calculation, keys = %o\n", keys); + exit(1); + } + + fprintf(stderr," ea=%o, [ea]='%o/%d\n", ea, mem[ea], *(short *)(mem+ea)); + + if (opcode == 00100) { + fprintf(stderr," JMP\n"); + mem[P] = ea; + continue; + } + + if (opcode == 00200) { + if (keys & 040000) { + fprintf(stderr," DLD\n"); + mem[A] = mem[ea]; + mem[B] = mem[ea+1]; + } else { + fprintf(stderr," LDA\n"); + mem[A] = mem[ea]; + } + continue; + } + + if (opcode == 00300) { + fprintf(stderr," ANA\n"); + mem[A] &= mem[ea]; + continue; + } + + if (opcode == 00400) { + if (keys & 040000) { + fprintf(stderr," DST\n"); + mem[ea] = mem[A]; + mem[ea+1] = mem[B]; + } else { + fprintf(stderr," STA\n"); + mem[ea] = mem[A]; + } + continue; + } + + if (opcode == 00500) { + fprintf(stderr," ERA\n"); + mem[A] ^= mem[ea]; + continue; + } + + if (opcode == 00600) { + fprintf(stderr," ADD\n"); + tempa = mem[A]; + tempa += mem[ea]; + mem[A] = tempa; + continue; + } + + if (opcode == 00700) { + fprintf(stderr," SUB\n"); + tempa = mem[A]; + tempa -= mem[ea]; + mem[A] = tempa; + continue; + } + + if (opcode == 01000) { + fprintf(stderr," JST\n"); + mem[ea] = mem[P]; + mem[P] = ea+1; + continue; + } + + if (opcode == 01100) { + fprintf(stderr," CAS\n"); + if (mem[A] == mem[ea]) + mem[P]++; + else if (*(short*)(mem+A) < *(short *)(mem+ea)) + mem[P] += 2; + continue; + } + + if (opcode == 01200) { + fprintf(stderr," IRS\n"); + mem[ea]++; + if (mem[ea] == 0) + mem[P]++; + continue; + } + + if (opcode == 01300) { + fprintf(stderr," IMA\n"); + tempa = mem[ea]; + mem[ea] = mem[A]; + mem[A] = tempa; + continue; + } + + if (opcode == 01400) { + /* if V-mode, JSY, else LDX in R-mode */ + fprintf(stderr," JSY/LDX\n"); + exit(1); + } + + if (opcode == 01500) { + fprintf(stderr," STX\n"); + mem[ea] = mem[X]; + continue; + } + + if (opcode == 01600) { + fprintf(stderr," MPY\n"); + templ = *(short *)(mem+A) * *(short *)(mem+ea); + if (keys & 010000) { /* V/I mode */ + *(int *)(mem+A) = templ; + } else { /* R/S mode */ + mem[A] = (templ >> 15); + mem[B] = templ & 077777; + } + continue; + } + + if (opcode == 01700) { + fprintf(stderr," DIV\n"); + if (keys & 010000) { /* V/I mode */ + templ = *(int *)(mem+A); + } else { /* R/S mode */ + if (mem[A] != 0 && mem[A] != 0xffff) { + fprintf(stderr," Register A should be 0 or -1 before R-mode DIV\n"); + exit(1); + } + if (mem[A]) { /* dividend was negative before PID */ + tempa = mem[B] | 0x8000; /* make 16-bit negative again */ + templ = tempa; /* extend to 32-bit negative */ + } else + templ = *(short *)(mem+B);/* make 32-bit positive */ + } + mem[A] = templ / *(short *)(mem+ea); + mem[B] = templ % *(short *)(mem+ea); + continue; + } + + if (opcode == 03500) { + fprintf(stderr," LDX instruction, new value=%o\n", mem[ea]); + mem[X] = mem[ea]; + continue; + } + + if (opcode == 00101) { + fprintf(stderr," EAA\n"); + mem[A] = ea; + continue; + } + + if (opcode == 00203) { + fprintf(stderr," JEQ\n"); + if (*(short *)(mem+A) == 0) + mem[P] = ea; + continue; + } + + if (opcode == 00703) { + fprintf(stderr," JGE\n"); + if (*(short *)(mem+A) >= 0) + mem[P] = ea; + continue; + } + + if (opcode == 00503) { + fprintf(stderr," JGT\n"); + if (*(short *)(mem+A) > 0) + mem[P] = ea; + continue; + } + + if (opcode == 00403) { + fprintf(stderr," JLE\n"); + if (*(short *)(mem+A) <= 0) + mem[P] = ea; + continue; + } + + if (opcode == 00603) { + fprintf(stderr," JLT\n"); + if (*(short *)(mem+A) < 0) + mem[P] = ea; + continue; + } + + if (opcode == 00303) { + fprintf(stderr," JNE\n"); + if (*(short *)(mem+A) != 0) + mem[P] = ea; + continue; + } + + fprintf(stderr," UNKNOWN OPCODE: %o\n", opcode); + exit(1); + } +} + + +/* Handle SVC instruction. For real hardware emulation on an R-mode + such as the P300, SVC would interrupt (JST*) through location '75 + (in vectored mode) or would fault on the P400. + + Since we're running programs without an OS underneath us, handle + the SVC's here. + + Typical usage: + + CALL TNOUA ('MESSAGE', 7) + JST TNOUA + DAC =C'MESSAGE' + DAC =7 + OCT 0 + ... + + The library would resolve the TNOUA reference like this: + + TNOUA DAC ** RETURN ADDRESS + SVC ENTER THE OS + OCT 140703 + JMP# BADCALL HERE FOR BAD SVC + ... + + The SVC code word follows the SVC instruction: + + '100000 = this is an interlude; the actual arguments are found + at the address preceeding the SVC instruction (ie, the caller) + + '040000 = there is an instruction following the SVC code, before + the argument list. If there is an SVC error, like a bad function + code, the SVC handler will return to the location following the + code word. + + Bits 3-4 are ignored + + Bits 5-10 are the SVC group, 1-15. + Bits 11-16 are the SVC function within the group. + + Interludes are used because at the time a program is compiled, the + compiler doesn't know whether a call is going to the OS or not. So the + call is compiled like any other. It's dumb for the library TNOUA to + muck around with all the arguments, so bit 1 is set as a shortcut and + the address of the argument list is just before the SVC itself. + + If a program knows it's going to call the OS, a direct SVC can be used, + with bit 1 clear: + + SVC + OCT '040703 + JMP# BADSVC + DAC =C'MESSAGE' + DAC =7 + OCT 0 + GOOD ... + + BADSVC EQU * + +*/ + + +svc() { + + unsigned short code; /* code word following SVC instruction */ + unsigned short argl; /* address of argument list */ + unsigned short a1,a2,a3,a4,a5,a6; /* args */ + short i; + char *bp; + short class; /* SVC class, from bits 5-10 */ + short func; /* SVC func within class, bits 11-16 */ + + code = mem[mem[P]]; + if (code & 0100000) + argl = mem[mem[P]-2]; + else if (code & 040000) + argl = mem[P]+2; + else + argl = mem[P]+1; + class = (code >> 6) & 077; + func = code & 077; + fprintf(stderr," code=%o, class=%o, func=%o, arglist=%o\n", code, class, func, argl); + + switch (class) { + case 0: /* same as class 1 */ + case 1: /* funcs 0-'15 */ + switch (func) { + case 5: /* EXIT */ + fprintf(stderr,"\nProgram called EXIT\n"); + exit(1); + default: + goto unimp; + } + break; + + case 2: /* funcs 0-3 */ + goto unimp; + break; + + case 3: /* func 0 = PRWFIL */ + goto unimp; + break; + + case 4: /* func 0 = FAMSVC (obsolete) */ + goto unimp; + break; + + case 5: /* funcs 0-'17 */ + goto unimp; + break; + + case 6: /* funcs 0-5 */ + switch (func) { + case 0: /* comanl, 0 args */ + goto unimp; + case 1: /* c1in, 1 arg */ + fprintf(stderr," c1in - enter a character!\n"); + goto badsvc; + a1 = mem[argl++]; + tempa=getchar() | 0x80; /* WRONG - needs to read from /dev/tty */ + mem[a1] = tempa; + break; + default: + goto unimp; + } + break; + + case 7: + switch (func) { + case 0: + fprintf(stderr," t1in\n"); + exit(1); + break; + case 1: + fprintf(stderr," t1ou\n"); + a1 = mem[argl++]; /* get address of arg1 */ + a1 = mem[a1]; /* get the character to print */ + putchar(a1 & 0x7f); /* write the character w/o parity */ + break; + case 2: /* TNOU */ + case 3: /* TNOUA */ + a1 = mem[argl++]; + a2 = mem[argl++]; + bp = (char *)(mem+a1); + fprintf(stderr," tnou/a, arg1 @ %o, arg2 @ %o\n", a1, a2); + for (i=0; i < mem[a2]; i++,bp++) + putchar(*bp & 0x7f); + if (func == 2) + putchar('\n'); + fflush(stdout); + break; + case 4: + fprintf(stderr," tooct\n"); + exit(1); + break; + case 5: + fprintf(stderr," duplx$\n"); + exit(1); + break; + default: + goto unimp; + } + break; + + case 010: + goto unimp; + break; + + case 011: + goto unimp; + break; + + case 012: + goto unimp; + break; + + case 013: + goto unimp; + break; + + case 014: + goto unimp; + break; + + case 015: + switch (func) { + case 024: /* ERKL$$(KEY,ERASECH,KILLCH,CODE) */ + fprintf(stderr," erlk$$\n"); + a1 = mem[argl++]; + a2 = mem[argl++]; + a3 = mem[argl++]; + a4 = mem[argl++]; + if (mem[a1] == 1) { /* arg 1 = k$read? */ + mem[a2] = 0210; + mem[a3] = 0377; + } + mem[a4] = 0; + break; + default: + goto unimp; + } + break; + + default: + goto unimp; /* bad class */ + } + + /* after the SVC, argl is the return address; look for zero arg list + terminator and ignore it) */ + + if (mem[argl] == 0) + argl++; + fprintf(stderr," returning from SVC to %o\n", argl); + mem[P] = argl; + return; + + +unimp: + + fprintf(stderr," svc class '%o function '%o is not implemented\n", class, func); + exit(1); + + /* here on a bad svc; if the bounce bit (bit 2) is set in the code word, + jump to the location following the code word (which is typically a + JMP instruction). If the bounce bit isn't set, we have to halt */ + +badsvc: + + if (code & 040000) { + mem[P]++; + fprintf(stderr," bouncing svc error to address %o\n", mem[P]); + return; + } + + fprintf(stderr," halting on bad svc\n"); + exit(1); +} + + + +/* here for PIO instructions: OCP, SKS, INA, OTA. The instruction + word is passed in as an argument to handle EIO (Execute I/O) in + V-mode. +*/ + +pio(unsigned int inst) { + unsigned short class; + unsigned short func; + unsigned short device; + + class = inst >> 14; + func = (inst >> 6) & 017; + device = inst & 077; + fprintf(stderr," pio, class=%d, func=%o, device=%o\n", class, func, device); + if (devmap[device]) + devmap[device](class, func); + else + fprintf(stderr," no handler for device; instruction ignored\n"); +} diff --git a/emdev.h b/emdev.h new file mode 100644 index 0000000..89c148e --- /dev/null +++ b/emdev.h @@ -0,0 +1,84 @@ +/* Device '4: system console + + OCP '0004 = initialize for input only, echoplex, 110 baud, 8-bit, no parity + OCP '0104 = same, but for output only + OCP '1004 = Full duplex; software must echo + OCP '1204 = Prime normal, independent xmit and recv w/echoplex + OCP '1304 = Self test mode (internally connects transmitter to receiver) + OCP '1704 = same as above, but clears interrupt masks and dma/c enables + + +*/ + +#include + +void devasr (unsigned short class, unsigned short func) { + + static int ttydev=-1; + unsigned char ch; + int n; + + if (ttydev < 0) { + ttydev = open("/dev/tty", O_RDWR+O_NONBLOCK, 0); + if (ttydev < 0) { + perror(" error opening /dev/tty"); + exit(1); + } + } + + switch (class) { + + case 0: + fprintf(stderr," OCP '%o04\n", func); + break; + + case 1: + fprintf(stderr," SKS\n"); + if (func <= 7) + mem[P]++; /* assume it's always ready */ + else { + fprintf(stderr," unimplemented SKS '04 function\n"); + exit(1); + } + break; + + case 2: + fprintf(stderr," INA\n"); + if (func == 0 || func == 010) { + usleep(100000); + n = read(ttydev, &ch, 1); + if (n < 0) { + if (errno != EAGAIN) { + perror(" error reading from tty"); + exit(1); + } + } else if (n == 1) { + mem[A] = ch | 0x80; + mem[P]++; + } else if (n != 0) { + fprintf(stderr," unexpected error reading from tty, n=%d", n); + exit(1); + } + } else if (func == 012) { /* no idea what this does... */ + mem[P]++; + } else { + fprintf(stderr," unimplemented INA '04 function\n"); + exit(1); + } + break; + + case 3: + fprintf(stderr," OTA\n"); + if (func == 0 | func == 1) { + fprintf(stderr," char to write=%o\n", mem[A]); + putchar(mem[A] & 0x7f); + fflush(stdout); + mem[P]++; /* OTA '0004 always works on Unix */ + } else { + fprintf(stderr," unimplemented OTA '04 function\n"); + exit(1); + } + break; + } +} + diff --git a/makefile b/makefile new file mode 100644 index 0000000..f1dcb7a --- /dev/null +++ b/makefile @@ -0,0 +1,6 @@ +em: em.o + cc -O -o em em.c + +debug: + cc -g -o em em.c +