diff --git a/Source/Emulator/controller.c b/Source/Emulator/controller.c index 9df4ca2..94279de 100644 --- a/Source/Emulator/controller.c +++ b/Source/Emulator/controller.c @@ -14,152 +14,130 @@ #include "../Assembler/assembly.h" // For the fault vector numbers. #include -typedef struct { - uint8_t *memory; // Never published. See the note in controller.h. - uint32_t capacity; // In bytes. A full bank is 65536, which is why this is not 16 bit. - uint8_t flags; - uint8_t ownerPort; - uint16_t guardStart; - uint16_t guardEnd; -} Bank; - -static Bank banks[BANK_COUNT]; - -// Bank 2's contents: the description of every bank, for anything that wants to read it. -static uint8_t bankTable[BANK_TABLE_BYTES]; - -// The registers, exactly as the ports name them. -static uint8_t sourceBank, destBank, guardBank; -static uint16_t sourceAddress, destAddress, length; -static uint16_t guardStart, guardEnd; -static uint8_t status; // Writes a bank's description into the table that bank 2 publishes. Called whenever // anything about a bank changes, so the published table and the real one cannot drift. -static void publishBank(int number) { - uint8_t *record = bankTable + number * BANK_RECORD_BYTES; - record[0] = banks[number].flags; - record[1] = banks[number].ownerPort; +static void publishBank(Controller *c, int number) { + uint8_t *record = c->bankTable + number * BANK_RECORD_BYTES; + record[0] = c->banks[number].flags; + record[1] = c->banks[number].ownerPort; // Zero means the whole 64K, the same convention Length uses, because a capacity of // nothing is never what anyone meant. - record[2] = (uint8_t)((banks[number].capacity >> 8) & 0xFF); - record[3] = (uint8_t)(banks[number].capacity & 0xFF); - record[4] = (uint8_t)(banks[number].guardStart >> 8); - record[5] = (uint8_t)(banks[number].guardStart & 0xFF); - record[6] = (uint8_t)(banks[number].guardEnd >> 8); - record[7] = (uint8_t)(banks[number].guardEnd & 0xFF); + record[2] = (uint8_t)((c->banks[number].capacity >> 8) & 0xFF); + record[3] = (uint8_t)(c->banks[number].capacity & 0xFF); + record[4] = (uint8_t)(c->banks[number].guardStart >> 8); + record[5] = (uint8_t)(c->banks[number].guardStart & 0xFF); + record[6] = (uint8_t)(c->banks[number].guardEnd >> 8); + record[7] = (uint8_t)(c->banks[number].guardEnd & 0xFF); } -static void defineBank(int number, uint8_t *memory, uint32_t capacity, uint8_t flags, uint8_t owner) { - banks[number].memory = memory; - banks[number].capacity = capacity; - banks[number].flags = flags | BANK_FLAG_PRESENT; - banks[number].ownerPort = owner; - banks[number].guardStart = 0; - banks[number].guardEnd = 0; - publishBank(number); +static void defineBank(Controller *c, int number, uint8_t *memory, uint32_t capacity, uint8_t flags, uint8_t owner) { + c->banks[number].memory = memory; + c->banks[number].capacity = capacity; + c->banks[number].flags = flags | BANK_FLAG_PRESENT; + c->banks[number].ownerPort = owner; + c->banks[number].guardStart = 0; + c->banks[number].guardEnd = 0; + publishBank(c, number); } -void initializeController(uint8_t *programMemory, uint8_t *dataMemory) { - memset(banks, 0, sizeof(banks)); - memset(bankTable, 0, sizeof(bankTable)); +void initializeController(Controller *c, uint8_t *programMemory, uint8_t *dataMemory) { + memset(c->banks, 0, sizeof(c->banks)); + memset(c->bankTable, 0, sizeof(c->bankTable)); for (int i = 0; i < BANK_COUNT; i++) { - banks[i].ownerPort = BANK_OWNER_MACHINE; - publishBank(i); + c->banks[i].ownerPort = BANK_OWNER_MACHINE; + publishBank(c, i); } - defineBank(BANK_PROGRAM, programMemory, 0x10000, 0, BANK_OWNER_MACHINE); - defineBank(BANK_DATA, dataMemory, 0x10000, 0, BANK_OWNER_MACHINE); + defineBank(c, BANK_PROGRAM, programMemory, 0x10000, 0, BANK_OWNER_MACHINE); + defineBank(c, BANK_DATA, dataMemory, 0x10000, 0, BANK_OWNER_MACHINE); // The table describes itself, so a program that walks it finds bank 2 in there along // with everything else. It is read only, which is what keeps RegisterBank the only // way to change what the controller routes through. - defineBank(BANK_TABLE, bankTable, BANK_TABLE_BYTES, BANK_FLAG_READ_ONLY, BANK_OWNER_MACHINE); + defineBank(c, BANK_TABLE, c->bankTable, BANK_TABLE_BYTES, BANK_FLAG_READ_ONLY, BANK_OWNER_MACHINE); - sourceBank = destBank = guardBank = 0; - sourceAddress = destAddress = length = 0; - guardStart = guardEnd = 0; - status = 0; + c->sourceBank = c->destBank = c->guardBank = 0; + c->sourceAddress = c->destAddress = c->length = 0; + c->guardStart = c->guardEnd = 0; + c->status = 0; } // Refuses, remembering why so that Status can be read afterwards. -static void refuse(uint8_t faultVector) { - status = faultVector; +static void refuse(Controller *c, uint8_t faultVector) { + c->status = faultVector; refuseAccess(faultVector); } // Is this somewhere the controller can read? A bank has to be there, and the address has // to be inside it. -static int canRead(uint8_t bank, uint16_t address) { - if (!(banks[bank].flags & BANK_FLAG_PRESENT) || address >= banks[bank].capacity) { - refuse(VECTOR_BANK_FAULT); +static int canRead(Controller *c, uint8_t bank, uint16_t address) { + if (!(c->banks[bank].flags & BANK_FLAG_PRESENT) || address >= c->banks[bank].capacity) { + refuse(c, VECTOR_BANK_FAULT); return 0; } return 1; } // The same, and then the two reasons a write in particular gets turned away. -static int canWrite(uint8_t bank, uint16_t address) { - if (!canRead(bank, address)) { +static int canWrite(Controller *c, uint8_t bank, uint16_t address) { + if (!canRead(c, bank, address)) { return 0; } - if (banks[bank].flags & BANK_FLAG_READ_ONLY) { - refuse(VECTOR_GUARD_VIOLATION); + if (c->banks[bank].flags & BANK_FLAG_READ_ONLY) { + refuse(c, VECTOR_GUARD_VIOLATION); return 0; } - if ((banks[bank].flags & BANK_FLAG_GUARDED) - && address >= banks[bank].guardStart && address <= banks[bank].guardEnd) { - refuse(VECTOR_GUARD_VIOLATION); + if ((c->banks[bank].flags & BANK_FLAG_GUARDED) + && address >= c->banks[bank].guardStart && address <= c->banks[bank].guardEnd) { + refuse(c, VECTOR_GUARD_VIOLATION); return 0; } return 1; } -// A length of zero means the whole 64K, because a transfer of no bytes is never what +// A c->length of zero means the whole 64K, because a transfer of no bytes is never what // anyone meant, and 65536 does not fit in the two bytes that carry it. -static uint32_t transferLength(void) { - return (length == 0) ? 0x10000u : (uint32_t)length; +static uint32_t transferLength(Controller *c) { + return (c->length == 0) ? 0x10000u : (uint32_t)c->length; } // Everything a transfer will touch is checked before any of it moves. A blit that ran // out of bank halfway would leave memory in a state no program asked for, and the // diagnostic would arrive after the damage rather than instead of it. So these answer // for the whole range or refuse the whole thing. -static int rangeReadable(uint8_t bank, uint16_t address, uint32_t count) { - if (!(banks[bank].flags & BANK_FLAG_PRESENT) - || (uint32_t)address + count > banks[bank].capacity) { - refuse(VECTOR_BANK_FAULT); +static int rangeReadable(Controller *c, uint8_t bank, uint16_t address, uint32_t count) { + if (!(c->banks[bank].flags & BANK_FLAG_PRESENT) + || (uint32_t)address + count > c->banks[bank].capacity) { + refuse(c, VECTOR_BANK_FAULT); return 0; } return 1; } -static int rangeWritable(uint8_t bank, uint16_t address, uint32_t count) { - if (!rangeReadable(bank, address, count)) { +static int rangeWritable(Controller *c, uint8_t bank, uint16_t address, uint32_t count) { + if (!rangeReadable(c, bank, address, count)) { return 0; } - if (banks[bank].flags & BANK_FLAG_READ_ONLY) { - refuse(VECTOR_GUARD_VIOLATION); + if (c->banks[bank].flags & BANK_FLAG_READ_ONLY) { + refuse(c, VECTOR_GUARD_VIOLATION); return 0; } - if (banks[bank].flags & BANK_FLAG_GUARDED) { + if (c->banks[bank].flags & BANK_FLAG_GUARDED) { uint32_t last = (uint32_t)address + count - 1; // Any overlap at all with the fence, not just a write that starts inside it. - if (!(last < banks[bank].guardStart || address > banks[bank].guardEnd)) { - refuse(VECTOR_GUARD_VIOLATION); + if (!(last < c->banks[bank].guardStart || address > c->banks[bank].guardEnd)) { + refuse(c, VECTOR_GUARD_VIOLATION); return 0; } } return 1; } -// What the moves below have cost since anybody last asked. -static unsigned long pendingCycles = 0; - // ---- Sixteen bits wide, when the addresses let it be ---- // // The controller reaches bank memory two bytes at a time, so an aligned transfer moves two // bytes in the time a misaligned one moves one. A word is read at an even address and -// written at an even address, which is why the source, the destination AND the length must +// written at an even address, which is why the source, the destination AND the c->length must // all be even: an odd anything would have the controller shifting bytes across word // boundaries to line them up, and that is a second design rather than this one. // @@ -173,140 +151,140 @@ static int wideRun(uint32_t addressesAndLength) { return (addressesAndLength & 1u) == 0; } -unsigned long controllerTakeCycles(void) { - unsigned long taken = pendingCycles; - pendingCycles = 0; +unsigned long controllerTakeCycles(Controller *c) { + unsigned long taken = c->pendingCycles; + c->pendingCycles = 0; return taken; } -static void doBlit(void) { - uint32_t count = transferLength(); - if (!rangeReadable(sourceBank, sourceAddress, count)) { +static void doBlit(Controller *c) { + uint32_t count = transferLength(c); + if (!rangeReadable(c, c->sourceBank, c->sourceAddress, count)) { return; } - if (!rangeWritable(destBank, destAddress, count)) { + if (!rangeWritable(c, c->destBank, c->destAddress, count)) { return; } // memmove rather than memcpy, because source and destination may be the same bank // and may overlap. Sliding a buffer along itself is an ordinary thing to want, and // getting it silently wrong is exactly the sort of failure this machine keeps // designing against. - memmove(banks[destBank].memory + destAddress, - banks[sourceBank].memory + sourceAddress, count); - // A word read and a word written. Two banks are two memories and the pair overlaps; + memmove(c->banks[c->destBank].memory + c->destAddress, + c->banks[c->sourceBank].memory + c->sourceAddress, count); + // A word read and a word written. Two c->banks are two memories and the pair overlaps; // one bank is one memory and they do not. The odd cycle is the pipeline filling. // - // Wide when everything is even, so an aligned move between banks settles at two bytes a + // Wide when everything is even, so an aligned move between c->banks settles at two bytes a // cycle and an aligned move within one at a byte a cycle - each twice what it was. - unsigned long moves = wideRun(sourceAddress | destAddress | count) ? count / 2 : count; - pendingCycles += (sourceBank == destBank) ? 2 * moves + 1 : moves + 1; - sourceAddress = (uint16_t)(sourceAddress + count); - destAddress = (uint16_t)(destAddress + count); - status = 0; + unsigned long moves = wideRun(c->sourceAddress | c->destAddress | count) ? count / 2 : count; + c->pendingCycles += (c->sourceBank == c->destBank) ? 2 * moves + 1 : moves + 1; + c->sourceAddress = (uint16_t)(c->sourceAddress + count); + c->destAddress = (uint16_t)(c->destAddress + count); + c->status = 0; } -static void doFill(void) { - uint32_t count = transferLength(); - if (!rangeWritable(destBank, destAddress, count)) { +static void doFill(Controller *c) { + uint32_t count = transferLength(c); + if (!rangeWritable(c, c->destBank, c->destAddress, count)) { return; } // A fill has nowhere to read from, only a value, so SourceLow carries the byte and the // rest of the source registers mean nothing here - including for the alignment, which // asks only about where the bytes are going and how many there are. - pendingCycles += (wideRun(destAddress | count) ? count / 2 : count) + 1; - memset(banks[destBank].memory + destAddress, (int)(sourceAddress & 0xFF), count); - destAddress = (uint16_t)(destAddress + count); - status = 0; + c->pendingCycles += (wideRun(c->destAddress | count) ? count / 2 : count) + 1; + memset(c->banks[c->destBank].memory + c->destAddress, (int)(c->sourceAddress & 0xFF), count); + c->destAddress = (uint16_t)(c->destAddress + count); + c->status = 0; } // DestBank is the number being given out, and SourceLow says which port owns the memory. // The capacity is asked of the device rather than supplied, because how big a bank is // was settled when the machine was built. -static void doRegisterBank(void) { - if (destBank <= BANK_TABLE) { +static void doRegisterBank(Controller *c) { + if (c->destBank <= BANK_TABLE) { // Banks 0 to 2 are the machine's own and are not anybody's to hand out. - refuse(VECTOR_BANK_FAULT); + refuse(c, VECTOR_BANK_FAULT); return; } - uint8_t port = (uint8_t)(sourceAddress & 0xFF); + uint8_t port = (uint8_t)(c->sourceAddress & 0xFF); uint32_t capacity = 0; uint8_t *memory = deviceMemory(port, &capacity); if (memory == NULL) { // Either nothing is on that port or what is there brings no memory. Registering // it would put a bank in the table that leads nowhere. - refuse(VECTOR_BANK_FAULT); + refuse(c, VECTOR_BANK_FAULT); return; } // Registering over a bank that already has something in it is allowed. Which number // a device's memory answers to is the OS's business, and nothing was allocated that // could be lost by changing its mind. - defineBank(destBank, memory, capacity, 0, port); - status = 0; + defineBank(c, c->destBank, memory, capacity, 0, port); + c->status = 0; } // The guard registers stage a range; this is what commits it. Raising a fence over a // bank that is not there would protect nothing while looking like it protected // something, so it is refused rather than quietly accepted. -static void doGuardOn(void) { - if (!(banks[guardBank].flags & BANK_FLAG_PRESENT)) { - refuse(VECTOR_BANK_FAULT); +static void doGuardOn(Controller *c) { + if (!(c->banks[c->guardBank].flags & BANK_FLAG_PRESENT)) { + refuse(c, VECTOR_BANK_FAULT); return; } - if (guardStart > guardEnd) { + if (c->guardStart > c->guardEnd) { // No address can be inside a range that ends before it starts, so this fence // would catch nothing. A program that raised one would believe it was protected // and would not be, which is worse than having no fence at all. - refuse(VECTOR_BANK_FAULT); + refuse(c, VECTOR_BANK_FAULT); return; } - banks[guardBank].guardStart = guardStart; - banks[guardBank].guardEnd = guardEnd; - banks[guardBank].flags |= BANK_FLAG_GUARDED; - publishBank(guardBank); - status = 0; + c->banks[c->guardBank].guardStart = c->guardStart; + c->banks[c->guardBank].guardEnd = c->guardEnd; + c->banks[c->guardBank].flags |= BANK_FLAG_GUARDED; + publishBank(c, c->guardBank); + c->status = 0; } -static void doGuardOff(void) { - if (!(banks[guardBank].flags & BANK_FLAG_PRESENT)) { - refuse(VECTOR_BANK_FAULT); +static void doGuardOff(Controller *c) { + if (!(c->banks[c->guardBank].flags & BANK_FLAG_PRESENT)) { + refuse(c, VECTOR_BANK_FAULT); return; } - banks[guardBank].flags &= (uint8_t)~BANK_FLAG_GUARDED; - publishBank(guardBank); - status = 0; + c->banks[c->guardBank].flags &= (uint8_t)~BANK_FLAG_GUARDED; + publishBank(c, c->guardBank); + c->status = 0; } -uint8_t controllerWrite(uint8_t value, uint8_t port) { +uint8_t controllerWrite(Controller *c, uint8_t value, uint8_t port) { switch (port) { - case CTRL_SOURCE_BANK: sourceBank = value; break; - case CTRL_SOURCE_HIGH: sourceAddress = (uint16_t)(value << 8) | (sourceAddress & 0x00FF); break; - case CTRL_SOURCE_LOW: sourceAddress = (sourceAddress & 0xFF00) | value; break; - case CTRL_DEST_BANK: destBank = value; break; - case CTRL_DEST_HIGH: destAddress = (uint16_t)(value << 8) | (destAddress & 0x00FF); break; - case CTRL_DEST_LOW: destAddress = (destAddress & 0xFF00) | value; break; - case CTRL_LENGTH_HIGH: length = (uint16_t)(value << 8) | (length & 0x00FF); break; - case CTRL_LENGTH_LOW: length = (length & 0xFF00) | value; break; + case CTRL_SOURCE_BANK: c->sourceBank = value; break; + case CTRL_SOURCE_HIGH: c->sourceAddress = (uint16_t)(value << 8) | (c->sourceAddress & 0x00FF); break; + case CTRL_SOURCE_LOW: c->sourceAddress = (c->sourceAddress & 0xFF00) | value; break; + case CTRL_DEST_BANK: c->destBank = value; break; + case CTRL_DEST_HIGH: c->destAddress = (uint16_t)(value << 8) | (c->destAddress & 0x00FF); break; + case CTRL_DEST_LOW: c->destAddress = (c->destAddress & 0xFF00) | value; break; + case CTRL_LENGTH_HIGH: c->length = (uint16_t)(value << 8) | (c->length & 0x00FF); break; + case CTRL_LENGTH_LOW: c->length = (c->length & 0xFF00) | value; break; - case CTRL_GUARD_BANK: guardBank = value; break; - case CTRL_GUARD_START_HIGH: guardStart = (uint16_t)(value << 8) | (guardStart & 0x00FF); break; - case CTRL_GUARD_START_LOW: guardStart = (guardStart & 0xFF00) | value; break; - case CTRL_GUARD_END_HIGH: guardEnd = (uint16_t)(value << 8) | (guardEnd & 0x00FF); break; - case CTRL_GUARD_END_LOW: guardEnd = (guardEnd & 0xFF00) | value; break; + case CTRL_GUARD_BANK: c->guardBank = value; break; + case CTRL_GUARD_START_HIGH: c->guardStart = (uint16_t)(value << 8) | (c->guardStart & 0x00FF); break; + case CTRL_GUARD_START_LOW: c->guardStart = (c->guardStart & 0xFF00) | value; break; + case CTRL_GUARD_END_HIGH: c->guardEnd = (uint16_t)(value << 8) | (c->guardEnd & 0x00FF); break; + case CTRL_GUARD_END_LOW: c->guardEnd = (c->guardEnd & 0xFF00) | value; break; case CTRL_DATA: // A byte into the destination, and the address steps on so that writing a // run of bytes is a loop over one instruction rather than four. - if (canWrite(destBank, destAddress)) { - pendingCycles++; // The byte itself, beyond reaching the port. - banks[destBank].memory[destAddress] = value; - if (destBank == BANK_TABLE) { + if (canWrite(c, c->destBank, c->destAddress)) { + c->pendingCycles++; // The byte itself, beyond reaching the port. + c->banks[c->destBank].memory[c->destAddress] = value; + if (c->destBank == BANK_TABLE) { // Unreachable while the table is read only, and here so that it stays // true if that ever changes: the published bytes are a description, // and nothing may write through them into a real bank. - publishBank(BANK_TABLE); + publishBank(c, BANK_TABLE); } - destAddress++; - status = 0; + c->destAddress++; + c->status = 0; } break; @@ -314,16 +292,16 @@ uint8_t controllerWrite(uint8_t value, uint8_t port) { // Both leave the addresses past whatever they touched and Length as it was, // so asking again carries straight on from where the last one stopped. switch (value) { - case COMMAND_BLIT: doBlit(); break; - case COMMAND_FILL: doFill(); break; - case COMMAND_REGISTER_BANK: doRegisterBank(); break; - case COMMAND_GUARD_ON: doGuardOn(); break; - case COMMAND_GUARD_OFF: doGuardOff(); break; + case COMMAND_BLIT: doBlit(c); break; + case COMMAND_FILL: doFill(c); break; + case COMMAND_REGISTER_BANK: doRegisterBank(c); break; + case COMMAND_GUARD_ON: doGuardOn(c); break; + case COMMAND_GUARD_OFF: doGuardOff(c); break; default: // Refusing an unknown command is better than ignoring it, since a // program that asked for something is entitled to find out that it // did not happen. - refuse(VECTOR_BANK_FAULT); + refuse(c, VECTOR_BANK_FAULT); break; } break; @@ -335,33 +313,33 @@ uint8_t controllerWrite(uint8_t value, uint8_t port) { return 0; } -uint8_t controllerRead(uint8_t port) { +uint8_t controllerRead(Controller *c, uint8_t port) { switch (port) { - case CTRL_SOURCE_BANK: return sourceBank; - case CTRL_SOURCE_HIGH: return (uint8_t)(sourceAddress >> 8); - case CTRL_SOURCE_LOW: return (uint8_t)(sourceAddress & 0xFF); - case CTRL_DEST_BANK: return destBank; - case CTRL_DEST_HIGH: return (uint8_t)(destAddress >> 8); - case CTRL_DEST_LOW: return (uint8_t)(destAddress & 0xFF); - case CTRL_LENGTH_HIGH: return (uint8_t)(length >> 8); - case CTRL_LENGTH_LOW: return (uint8_t)(length & 0xFF); - case CTRL_STATUS: return status; + case CTRL_SOURCE_BANK: return c->sourceBank; + case CTRL_SOURCE_HIGH: return (uint8_t)(c->sourceAddress >> 8); + case CTRL_SOURCE_LOW: return (uint8_t)(c->sourceAddress & 0xFF); + case CTRL_DEST_BANK: return c->destBank; + case CTRL_DEST_HIGH: return (uint8_t)(c->destAddress >> 8); + case CTRL_DEST_LOW: return (uint8_t)(c->destAddress & 0xFF); + case CTRL_LENGTH_HIGH: return (uint8_t)(c->length >> 8); + case CTRL_LENGTH_LOW: return (uint8_t)(c->length & 0xFF); + case CTRL_STATUS: return c->status; - case CTRL_GUARD_BANK: return guardBank; - case CTRL_GUARD_START_HIGH: return (uint8_t)(guardStart >> 8); - case CTRL_GUARD_START_LOW: return (uint8_t)(guardStart & 0xFF); - case CTRL_GUARD_END_HIGH: return (uint8_t)(guardEnd >> 8); - case CTRL_GUARD_END_LOW: return (uint8_t)(guardEnd & 0xFF); + case CTRL_GUARD_BANK: return c->guardBank; + case CTRL_GUARD_START_HIGH: return (uint8_t)(c->guardStart >> 8); + case CTRL_GUARD_START_LOW: return (uint8_t)(c->guardStart & 0xFF); + case CTRL_GUARD_END_HIGH: return (uint8_t)(c->guardEnd >> 8); + case CTRL_GUARD_END_LOW: return (uint8_t)(c->guardEnd & 0xFF); case CTRL_DATA: { // A byte out of the source, stepping on the same way a write does. - if (!canRead(sourceBank, sourceAddress)) { + if (!canRead(c, c->sourceBank, c->sourceAddress)) { return 0; } - pendingCycles++; // As above, the other way round. - uint8_t value = banks[sourceBank].memory[sourceAddress]; - sourceAddress++; - status = 0; + c->pendingCycles++; // As above, the other way round. + uint8_t value = c->banks[c->sourceBank].memory[c->sourceAddress]; + c->sourceAddress++; + c->status = 0; return value; } } diff --git a/Source/Emulator/controller.h b/Source/Emulator/controller.h index 5d8f83e..45f6d77 100644 --- a/Source/Emulator/controller.h +++ b/Source/Emulator/controller.h @@ -76,6 +76,17 @@ // a program that could write one would be setting a host address, which means nothing on // hardware and everything to the emulator running it. #define BANK_RECORD_BYTES 8 + +// What the controller knows about one bank. The memory pointer is never published - see the +// note above the record layout. +typedef struct { + uint8_t *memory; + uint32_t capacity; // In bytes. A full bank is 65536, which is why this is not 16 bit. + uint8_t flags; + uint8_t ownerPort; + uint16_t guardStart; + uint16_t guardEnd; +} Bank; #define BANK_TABLE_BYTES (BANK_COUNT * BANK_RECORD_BYTES) #define BANK_FLAG_PRESENT 0x01 @@ -107,12 +118,37 @@ // and waits for it. Whether real hardware would let the two run at once is a live question - // the memories are separate, so it plausibly could - and the answer wants measuring before // it is designed. -unsigned long controllerTakeCycles(void); +// ---- One of these to a bus ---- +// +// A controller is the most stateful thing on this machine: a source bank and address, a +// destination, a length, a command, five guard registers and a table describing every bank +// it can reach. TWO PROCESSORS SHARING ONE WOULD INTERLEAVE INTO NONSENSE - one sets a +// source, the other sets a destination, the first issues a blit and moves the wrong bytes +// somewhere else again. No amount of arbitration fixes that, because there is nothing to +// arbitrate: both writes were legal and the result belongs to neither of them. +// +// So a peripheral core gets its own, describing its own memories. The fields are here rather +// than hidden in the source file because a device that contains a core has to be able to hold +// one; nothing outside reaches into them. +typedef struct { + Bank banks[BANK_COUNT]; + // Bank 2's contents: the description of every bank, for anything that wants to read it. + uint8_t bankTable[BANK_TABLE_BYTES]; + // The registers, exactly as the ports name them. + uint8_t sourceBank, destBank, guardBank; + uint16_t sourceAddress, destAddress, length; + uint16_t guardStart, guardEnd; + uint8_t status; + // What the moves have cost since anybody last asked. + unsigned long pendingCycles; +} Controller; -void initializeController(uint8_t *programMemory, uint8_t *dataMemory); +unsigned long controllerTakeCycles(Controller *c); -uint8_t controllerWrite(uint8_t value, uint8_t port); +void initializeController(Controller *c, uint8_t *programMemory, uint8_t *dataMemory); -uint8_t controllerRead(uint8_t port); +uint8_t controllerWrite(Controller *c, uint8_t value, uint8_t port); + +uint8_t controllerRead(Controller *c, uint8_t port); #endif // CONTROLLER_H diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index a869df3..044013f 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -626,6 +626,26 @@ static uint8_t consoleStatus(void) { // entire reason these are a struct rather than an array sitting here. static InterruptLines machineLines; +// And its own controller, for the same reason. Everything on this bus that moves memory means +// this one. +static Controller theMachinesController; + +Controller *machineController(void) { + return &theMachinesController; +} + +static uint8_t machineControllerWrite(uint8_t value, uint8_t port) { + return controllerWrite(&theMachinesController, value, port); +} + +static uint8_t machineControllerRead(uint8_t port) { + return controllerRead(&theMachinesController, port); +} + +static unsigned long machineControllerCycles(void) { + return controllerTakeCycles(&theMachinesController); +} + // Whether somebody has asked the machine to start over, and taking that request away. static int resetWanted = 0; @@ -659,7 +679,7 @@ int takeResetRequest(void) { static const Bus theMachinesBus = { OutputHandler, InputHandler, - controllerTakeCycles, + machineControllerCycles, takeIdleCycles, nextPendingInterrupt, clearInterrupt, @@ -1001,7 +1021,7 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) { refusedPort = Address; // The controller answers on a block of ports, which is a range rather than a list. if (Address >= CONTROLLER_PORT_BASE && Address <= CONTROLLER_PORT_TOP) { - return controllerWrite(DataByte, Address); + return machineControllerWrite(DataByte, Address); } // And so does the screen. if (Address >= PORT_VIDEO && Address <= PORT_VIDEO_TOP) { @@ -1096,7 +1116,7 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) { uint8_t InputHandler(uint8_t Address) { refusedPort = Address; if (Address >= CONTROLLER_PORT_BASE && Address <= CONTROLLER_PORT_TOP) { - return controllerRead(Address); + return machineControllerRead(Address); } if (Address >= PORT_VIDEO && Address <= PORT_VIDEO_TOP) { return videoRead(Address); diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index fc0dfeb..d5c5e3d 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -8,6 +8,7 @@ #include #include "cpu.h" +#include "controller.h" // ---- Ports ---- // @@ -347,6 +348,10 @@ void linesClear(InterruptLines *lines, uint8_t port); // what happens next by reading the port numbers. int linesNext(const InterruptLines *lines); +// The machine's own controller, which is what every device on this bus means when it moves +// memory. A peripheral core's device holds its own. +Controller *machineController(void); + // The machine's own lines, which is what every device on this bus means. void raiseInterrupt(uint8_t port); diff --git a/Source/Emulator/machine.c b/Source/Emulator/machine.c index cf238e0..e5bb23b 100644 --- a/Source/Emulator/machine.c +++ b/Source/Emulator/machine.c @@ -187,7 +187,7 @@ uint8_t machineStart(Machine *m, const EmulatorOptions *options, const char *pro consoleHome(); // The controller has to know where the memories are before anything can reach // them through it. Banks 0 and 1 are those two arrays. - initializeController(Program, Data); + initializeController(machineController(), Program, Data); initializeCPU(&m->cpu, Program, Data); if (m->options.debug) { printRegisters(&m->cpu, Program, Data);