diff --git a/Source/Emulator/cpu.c b/Source/Emulator/cpu.c index 20934a1..31e63e0 100644 --- a/Source/Emulator/cpu.c +++ b/Source/Emulator/cpu.c @@ -8,8 +8,6 @@ #include "controller.h" #include "../Assembler/assembly.h" // For the vector table layout, which both tools share. -uint16_t shiftRegister; - // Reads one entry out of a vector table. Most significant byte first, matching the // branch instructions and both file formats. // ---- Every touch of memory, and what it costs ---- @@ -38,20 +36,20 @@ static inline void writeData(CPURegisters *cpu, uint16_t at, uint8_t value) { // otherwise. static inline void portOut(CPURegisters *cpu, uint8_t value, uint8_t port) { cpu->busCycles++; - OutputHandler(value, port); + cpu->bus->out(value, port); // And whatever memory that made the controller move. The machine waits for it, which // is the conservative reading: a blit stalls the program that asked for one. - cpu->busCycles += controllerTakeCycles(); + cpu->busCycles += cpu->bus->takeStall(); } static inline uint8_t portIn(CPURegisters *cpu, uint8_t port) { cpu->busCycles++; - uint8_t value = InputHandler(port); - cpu->busCycles += controllerTakeCycles(); + uint8_t value = cpu->bus->in(port); + cpu->busCycles += cpu->bus->takeStall(); // And whatever time went by while the device kept the machine waiting. Idle rather than // bus, because a machine stopped on a port is not using memory - the same distinction // WAIT makes, arrived at from the other direction. - cpu->idleCycles += takeIdleCycles(); + cpu->idleCycles += cpu->bus->takeIdle(); return value; } @@ -131,6 +129,9 @@ static uint8_t answerRefusal(CPURegisters *cpu, uint16_t site) { } void initializeCPU(CPURegisters *cpu, uint8_t *programMemory, uint8_t *dataMemory) { + // The machine's own, which is what every processor here was on when there could only be + // one. Anything that wants a processor somewhere else changes this afterwards. + cpu->bus = machineBus(); cpu->A = 0; cpu->B = 0; cpu->Q = 0; @@ -249,17 +250,29 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { break; case 0x17: // SHL - Shift AB left. - shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B; + // + // A local, and it always was one in effect: written and read inside this one + // instruction and never carried to the next. It sat at file scope until there + // was a second processor to share it with, which is a poor time to find out. + { + uint16_t shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B; shiftRegister = (shiftRegister << 1) | (shiftRegister >> 15); cpu->A = shiftRegister >> 8; cpu->B = shiftRegister & 0xFF; + } break; case 0x18: // SHR - Shift AB right. - shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B; + // + // A local, and it always was one in effect: written and read inside this one + // instruction and never carried to the next. It sat at file scope until there + // was a second processor to share it with, which is a poor time to find out. + { + uint16_t shiftRegister = ((uint16_t)cpu->A << 8) | cpu->B; shiftRegister = (shiftRegister >> 1) | (shiftRegister << 15); cpu->A = shiftRegister >> 8; cpu->B = shiftRegister & 0xFF; + } break; // // 1x - Branch Operations: @@ -825,7 +838,7 @@ uint8_t executeOperation(uint8_t Instruction, CPURegisters *cpu) { // makes the ordinary idiom race-free: a program tests its device, finds it // busy, and waits. If the device finished in between, the line is standing // and this does nothing at all rather than sleeping through the answer. - if (nextPendingInterrupt() < 0) { + if (cpu->bus->nextInterrupt() < 0) { cpu->Waiting = 1; } break; @@ -858,7 +871,7 @@ void stepCPU(CPURegisters *cpu) { // handler for and simply read its status afterwards, which is the whole reason // this is worth having and is what the filesystem does with it. if (cpu->Waiting) { - if (nextPendingInterrupt() < 0) { + if (cpu->bus->nextInterrupt() < 0) { cpu->idleCycles++; return; } @@ -872,7 +885,7 @@ void stepCPU(CPURegisters *cpu) { // handler faults there the way it always has. Waiting changes what the CPU // does between instructions; it does not change interrupt policy. if (!(cpu->Status & STATUS_INTERRUPT)) { - clearInterrupt((uint8_t)nextPendingInterrupt()); + cpu->bus->clearInterrupt((uint8_t)cpu->bus->nextInterrupt()); } } // A device asking for attention is answered between instructions and never @@ -882,9 +895,9 @@ void stepCPU(CPURegisters *cpu) { // A line that is up while the Interrupt Flag is clear stays up. Masking holds a // device off; it does not lose what the device was asking for. if (cpu->Status & STATUS_INTERRUPT) { - int port = nextPendingInterrupt(); + int port = cpu->bus->nextInterrupt(); if (port >= 0) { - clearInterrupt((uint8_t)port); + cpu->bus->clearInterrupt((uint8_t)port); if (enterInterrupt(cpu, HARDWARE_VECTOR_BASE, (uint8_t)port, cpu->ProgramCounter)) { // The device asked and nobody was listening. enterInterrupt has // already stopped the machine; correct the cause, because the empty diff --git a/Source/Emulator/cpu.h b/Source/Emulator/cpu.h index a3791d7..65578ed 100644 --- a/Source/Emulator/cpu.h +++ b/Source/Emulator/cpu.h @@ -51,6 +51,29 @@ typedef enum { FAULT_DEVICE_REFUSED // A device refused, and nothing was installed to catch it. } FaultCause; +// ---- What a CPU is plugged into ---- +// +// Five things a CPU asks of the world outside itself, and every one of them was a call to a +// function there was exactly one of. That is fine for a machine with one processor and wrong +// for a machine with two: A PERIPHERAL CORE'S BUS IS ITS OWN. It sees the devices its own +// device gives it, raises its own interrupt lines, and stalls on its own controller - none +// of which are the host's. +// +// Gathered here rather than threaded through as a bus number, because a bus is a thing a +// device provides, and a device that provides one should hand over the answers rather than +// be looked up by an index somebody has to keep right. +typedef struct { + uint8_t (*out)(uint8_t value, uint8_t port); + uint8_t (*in)(uint8_t port); + // What the memory controller on this bus has just spent moving memory, and what the + // machine spent stopped waiting on a device. Both are taken and cleared. + unsigned long (*takeStall)(void); + unsigned long (*takeIdle)(void); + // The lowest port with its line up, or below zero for none, and putting one down. + int (*nextInterrupt)(void); + void (*clearInterrupt)(uint8_t port); +} Bus; + // The struct containing the CPU registers. typedef struct { uint8_t A; @@ -62,6 +85,9 @@ typedef struct { uint16_t StackPointer; uint8_t *Program; uint8_t *Data; + // Which bus this processor is on. initializeCPU puts the machine's own here, which is + // what every CPU had before there could be more than one. + const Bus *bus; // ---- What the machine has cost so far ---- // diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index a821816..309b276 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -653,6 +653,24 @@ int takeResetRequest(void) { return wanted; } +// ---- The bus this machine's processor is on ---- +// +// Everything a CPU asks of the world outside it, for the world this file is. A peripheral +// core is handed a different one of these by whatever device contains it, which is the whole +// of what a private bus is: not a number to be checked, a different set of answers. +static const Bus theMachinesBus = { + OutputHandler, + InputHandler, + controllerTakeCycles, + takeIdleCycles, + nextPendingInterrupt, + clearInterrupt, +}; + +const Bus *machineBus(void) { + return &theMachinesBus; +} + void raiseInterrupt(uint8_t port) { pendingInterrupts[port >> 3] |= (uint8_t)(1u << (port & 7)); } diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index 98dfb35..3090bed 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -330,6 +330,10 @@ void clearInterrupt(uint8_t port); // The lowest numbered port with its line up, or -1 if none of them are. int nextPendingInterrupt(void); +// The bus this machine's own processor is on: its devices, its lines, its controller. A +// peripheral core is given a different one by whatever device contains it. +const Bus *machineBus(void); + // ---- Refusing ---- // // A device can refuse what it was asked to do. Interrupting is a device asking for