// controller.h // The SplitBit Memory Controller. // Written by Anachronaut #ifndef CONTROLLER_H #define CONTROLLER_H #include // ---- Ports ---- // // Sixteen registers, one to a port, written and read directly. A register file behind a // single port would be smaller but stateful, and losing your place in a device that // moves memory corrupts memory rather than an answer. #define CONTROLLER_PORT_BASE 0xE0 #define CONTROLLER_PORT_TOP 0xEF #define CTRL_SOURCE_BANK 0xE0 #define CTRL_SOURCE_HIGH 0xE1 #define CTRL_SOURCE_LOW 0xE2 #define CTRL_DEST_BANK 0xE3 #define CTRL_DEST_HIGH 0xE4 #define CTRL_DEST_LOW 0xE5 #define CTRL_LENGTH_HIGH 0xE6 #define CTRL_LENGTH_LOW 0xE7 #define CTRL_COMMAND 0xE8 #define CTRL_DATA 0xE9 #define CTRL_STATUS 0xEA #define CTRL_GUARD_BANK 0xEB #define CTRL_GUARD_START_HIGH 0xEC #define CTRL_GUARD_START_LOW 0xED #define CTRL_GUARD_END_HIGH 0xEE #define CTRL_GUARD_END_LOW 0xEF // ---- Commands ---- // // Written to the Command port, which performs them at once. A blit is instantaneous from // the CPU's point of view: waiting belongs to the peripheral that has something to wait // for, not to the moving of bytes. #define COMMAND_BLIT 0x01 #define COMMAND_FILL 0x02 // Gives a bank number to the memory owned by a device. How big it is comes from the // device, not from software: a program asserting a hardware fact could only ever be // wrong about it. #define COMMAND_REGISTER_BANK 0x03 // Raises and lowers the fence over the bank named by GuardBank. Any program may do // either: this is a fence rather than a wall, and nobody is ever told no. What it stops // is walking into something by accident, not walking into it on purpose. #define COMMAND_GUARD_ON 0x10 #define COMMAND_GUARD_OFF 0x11 // ---- Banks ---- // // Program and Data are banks like any other; being banks 0 and 1 is the only thing // special about them. Bank 2 is the controller's own memory, and the bank table lives // in it, which is how anything finds out what banks exist without a second protocol. #define BANK_PROGRAM 0 #define BANK_DATA 1 #define BANK_TABLE 2 #define BANK_COUNT 256 // Eight bytes each, so bank n's record begins at n * 8. // // 0 Flags // 1 The port that owns it, or the machine itself for banks 0 to 2 // 2 - 3 Capacity, where zero means the whole 64K // 4 - 5 First guarded address // 6 - 7 Last guarded address // // What is published is a description. The pointer a bank really holds is never in here: // 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 #define BANK_FLAG_READ_ONLY 0x02 #define BANK_FLAG_GUARDED 0x04 // Banks 0 to 2 belong to the machine rather than to any device. #define BANK_OWNER_MACHINE 0xFF // ---- What the controller's work costs ---- // // The controller moves memory, and memory takes time to move: a byte has to be read from // somewhere and written somewhere else. A blit is not free just because the machine issues // it with one instruction, and pretending otherwise made a quarter of a millisecond of // work look like ten cycles. // // Banks are separate memories, which is what decides the rate. A move between two of them // can overlap its read and its write - fetch the next word while the last one is stored - // while a move WITHIN one bank cannot and costs twice as much. A fill has nothing to read // and costs the same as a move between banks. // // AND THE PATH IS SIXTEEN BITS WIDE, so a transfer whose source, destination and length are // all even moves two bytes a cycle between banks and one within a bank. Anything odd falls // back to the byte a cycle this had before it was widened: lining up bytes across word // boundaries is a second design, and this is not it. See wideRun in controller.c. // // Returned and cleared, so the caller adds it to whatever it is charging for. The CPU picks // it up after each port access, which makes the transfer a stall: the machine issues a blit // 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. // ---- 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; unsigned long controllerTakeCycles(Controller *c); void initializeController(Controller *c, uint8_t *programMemory, uint8_t *dataMemory); uint8_t controllerWrite(Controller *c, uint8_t value, uint8_t port); uint8_t controllerRead(Controller *c, uint8_t port); #endif // CONTROLLER_H