gigatron/rom/Docs/gtemu.c
2025-01-28 19:17:01 +03:00

127 lines
3.8 KiB
C

#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
typedef struct { // TTL state that the CPU controls
uint16_t PC;
uint8_t IR, D, AC, X, Y, OUT, undef;
} CpuState;
uint8_t ROM[1<<16][2], RAM[1<<15], IN=0xff;
CpuState cpuCycle(const CpuState S)
{
CpuState T = S; // New state is old state unless something changes
T.IR = ROM[S.PC][0]; // Instruction Fetch
T.D = ROM[S.PC][1];
int ins = S.IR >> 5; // Instruction
int mod = (S.IR >> 2) & 7; // Addressing mode (or condition)
int bus = S.IR&3; // Busmode
int W = (ins == 6); // Write instruction?
int J = (ins == 7); // Jump instruction?
uint8_t lo=S.D, hi=0, *to=NULL; // Mode Decoder
int incX=0;
if (!J)
switch (mod) {
#define E(p) (W?0:p) // Disable AC and OUT loading during RAM write
case 0: to=E(&T.AC); break;
case 1: to=E(&T.AC); lo=S.X; break;
case 2: to=E(&T.AC); hi=S.Y; break;
case 3: to=E(&T.AC); lo=S.X; hi=S.Y; break;
case 4: to= &T.X; break;
case 5: to= &T.Y; break;
case 6: to=E(&T.OUT); break;
case 7: to=E(&T.OUT); lo=S.X; hi=S.Y; incX=1; break;
}
uint16_t addr = (hi << 8) | lo;
int B = S.undef; // Data Bus
switch (bus) {
case 0: B=S.D; break;
case 1: if (!W) B = RAM[addr&0x7fff]; break;
case 2: B=S.AC; break;
case 3: B=IN; break;
}
if (W) RAM[addr&0x7fff] = B; // Random Access Memory
uint8_t ALU; // Arithmetic and Logic Unit
switch (ins) {
case 0: ALU = B; break; // LD
case 1: ALU = S.AC & B; break; // ANDA
case 2: ALU = S.AC | B; break; // ORA
case 3: ALU = S.AC ^ B; break; // XORA
case 4: ALU = S.AC + B; break; // ADDA
case 5: ALU = S.AC - B; break; // SUBA
case 6: ALU = S.AC; break; // ST
case 7: ALU = -S.AC; break; // Bcc/JMP
}
if (to) *to = ALU; // Load value into register
if (incX) T.X = S.X + 1; // Increment X
T.PC = S.PC + 1; // Next instruction
if (J) {
if (mod != 0) { // Conditional branch within page
int cond = (S.AC>>7) + 2*(S.AC==0);
if (mod & (1 << cond)) // 74153
T.PC = (S.PC & 0xff00) | B;
} else
T.PC = (S.Y << 8) | B; // Unconditional far jump
}
return T;
}
void garble(uint8_t mem[], int len)
{
for (int i=0; i<len; i++) mem[i] = rand();
}
int main(int argc, char *argv[])
{
CpuState S;
srand(time(NULL)); // Initialize with randomized data
garble((void*)ROM, sizeof ROM);
garble((void*)RAM, sizeof RAM);
garble((void*)&S, sizeof S);
FILE *fp = fopen(argc>1 ? argv[1] : "gigatron.rom", "rb");
if (!fp) {
fprintf(stderr, "Error: failed to open ROM file\n");
exit(EXIT_FAILURE);
}
fread(ROM, 1, sizeof ROM, fp);
fclose(fp);
int vgaX=0, vgaY=0;
for (long long t=-2; ; t++) {
if (t < 0) S.PC = 0; // MCP100 Power-On Reset
CpuState T = cpuCycle(S); // Update CPU
int hSync = (T.OUT & 0x40) - (S.OUT & 0x40); // "VGA monitor" (use simple stdout)
int vSync = (T.OUT & 0x80) - (S.OUT & 0x80);
if (vSync < 0) vgaY = -36; // Falling vSync edge
if (vgaX++ < 200) {
if (hSync) putchar('|'); // Visual indicator of hSync
else if (vgaX == 200) putchar('>'); // Too many pixels
else if (~S.OUT & 0x80) putchar('^'); // Visualize vBlank pulse
else putchar(32 + (S.OUT & 63)); // Plot pixel
}
if (hSync > 0) { // Rising hSync edge
printf("%s line %-3d xout %02x t %0.3f\n",
vgaX!=200 ? "~" : "", // Mark horizontal cycle errors
vgaY, T.AC, t/6.250e+06);
vgaX = 0;
vgaY++;
T.undef = rand() & 0xff; // Change this once in a while
}
S=T;
}
return 0;
}