From c4b2c27a2d556630a49bfacc5e2207c80c8dda12 Mon Sep 17 00:00:00 2001 From: Anachronaut Date: Sat, 15 Aug 2026 01:01:21 -0400 Subject: [PATCH] Device table added to IO Port 0xFF. --- Programs/testPrograms/registryTest.asm | 50 ++++++++++++++++++ Source/Emulator/io.c | 72 ++++++++++++++++++++++++-- Source/Emulator/io.h | 30 +++++++++++ SplitBit Programming Manual.md | 45 ++++++++++++++-- Tests/expected/registryTest.out | 6 +++ Tests/manifest | 3 ++ 6 files changed, 199 insertions(+), 7 deletions(-) create mode 100644 Programs/testPrograms/registryTest.asm create mode 100644 Tests/expected/registryTest.out diff --git a/Programs/testPrograms/registryTest.asm b/Programs/testPrograms/registryTest.asm new file mode 100644 index 0000000..f7b5eda --- /dev/null +++ b/Programs/testPrograms/registryTest.asm @@ -0,0 +1,50 @@ +; Asks the bus registry what this machine is made of. +; +; Writing a port number to the registry selects the port being asked about. Reading +; gives that port's record a byte at a time: its device class, then its flags, then +; zeroes once the record has run out. Nothing here touches the device being asked +; about, which is the whole point: reading port 0x00 to find out what it is would take +; a character off standard input and wait for one that never comes. +; +; Port 0x05 has nothing on it, and reads as class 0. That is the same answer a machine +; with no registry at all would give, so software finds out whether it can enumerate by +; enumerating. +; +; Correct output is: +; 00 02 00 the console, class 2, no flags +; 10 10 00 the test device, class 0x10, no flags +; FF 01 00 the registry itself, class 1, no flags +; 05 00 00 nothing there + +#Include print.asm + +#Program + +start: + INIA 0x00 + CALL reportPort + INIA 0x10 + CALL reportPort + INIA 0xFF + CALL reportPort + INIA 0x05 + CALL reportPort + HALT + +; Prints the port number, then the two bytes of its record. A is the port to ask about. +reportPort: + PSHA + CALL printByteHex ; The port we are asking about. + CALL blankSpace + + POPA + OUTA 0xFF ; Select it. This is the only write the registry accepts. + + INA 0xFF ; The device class. + CALL printByteHex + CALL blankSpace + + INA 0xFF ; The flags. + CALL printByteHex + CALL lineFeed + RET diff --git a/Source/Emulator/io.c b/Source/Emulator/io.c index 7164ce0..1c08ed1 100644 --- a/Source/Emulator/io.c +++ b/Source/Emulator/io.c @@ -37,22 +37,81 @@ int nextPendingInterrupt(void) { return -1; } +// ---- The bus registry ---- +// +// What is plugged into this machine. The table is fixed when the machine is built: a +// program cannot write to it, because writing would only let a program lie to itself +// about what hardware exists. Which routine handles a device is a different question, +// and the vector table already answers it. +// +// Nothing here touches the device being asked about. That matters more than it looks: +// reading a port is a real operation, and asking the console what it is by reading it +// would take a character off standard input and block waiting for one. + +typedef struct { + uint8_t port; + uint8_t deviceClass; + uint8_t flags; +} DeviceRecord; + +static const DeviceRecord deviceTable[] = { + { PORT_CONSOLE, DEVICE_CONSOLE, 0 }, + { PORT_TEST, DEVICE_TEST, 0 }, + { PORT_REGISTRY, DEVICE_REGISTRY, 0 }, +}; +static const int deviceCount = (int)(sizeof(deviceTable) / sizeof(deviceTable[0])); + +// Which port the registry is currently being asked about, and how far through that +// port's record it has been read. Selecting a port starts the record again. +static uint8_t registrySelected = 0; +static uint8_t registryCursor = 0; + +static const DeviceRecord *deviceOnPort(uint8_t port) { + for (int i = 0; i < deviceCount; i++) { + if (deviceTable[i].port == port) { + return &deviceTable[i]; + } + } + return NULL; +} + +// One byte of the selected port's record. Everything about a port that is not there +// reads as zero, which is the same answer an absent registry would give. +static uint8_t readRegistry(void) { + const DeviceRecord *device = deviceOnPort(registrySelected); + uint8_t answer = 0; + if (device != NULL && registryCursor < DEVICE_RECORD_BYTES) { + answer = (registryCursor == 0) ? device->deviceClass : device->flags; + } + if (registryCursor < DEVICE_RECORD_BYTES) { + registryCursor++; + } + return answer; +} + uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) { // This function sends the DataByte to the appropriate place based on the Port Address. switch(Address) { - case 0x00: + case PORT_CONSOLE: // If data is sent here, it should be written to STDOUT. // For now, I'll implement this so it simply writes each byte out as it comes in. // Later, I'll want to use a buffer for this for performance, probably. putchar(DataByte); break; - case 0x10: + case PORT_REGISTRY: + // Names the port the registry is being asked about. This is the only thing + // that can be written to the registry, and it changes nothing about the + // machine: it selects a question, it does not give an answer. + registrySelected = DataByte; + registryCursor = 0; + break; + case PORT_TEST: // A test device, and about the simplest one that can exist: writing to it // puts its own line up. It stands in for the shape a real device has, where // the CPU asks for something and is interrupted once the answer is ready, // with the waiting taken out so that a test runs the same way every time. // The byte written is ignored; only the asking matters. - raiseInterrupt(0x10); + raiseInterrupt(PORT_TEST); break; default: // Writes to unused Output Ports are ignored. @@ -64,10 +123,15 @@ uint8_t OutputHandler(uint8_t DataByte, uint8_t Address) { uint8_t InputHandler(uint8_t Address) { switch(Address) { - case 0x00: + case PORT_CONSOLE: // If data is sent here, it should be read from STDIN. return getchar(); break; + case PORT_REGISTRY: + // One byte of the selected port's record, then the next, and zero once the + // record has run out. + return readRegistry(); + break; default: // Reading from an unused port is ignored. return 0; diff --git a/Source/Emulator/io.h b/Source/Emulator/io.h index 04e2f50..6748c3e 100644 --- a/Source/Emulator/io.h +++ b/Source/Emulator/io.h @@ -9,6 +9,36 @@ #include #include "cpu.h" +// ---- Ports ---- +// +// Which port a device answers on is a property of the machine rather than of any +// program, so the numbers live here and everything else refers to them by name. + +#define PORT_CONSOLE 0x00 +#define PORT_TEST 0x10 +#define PORT_REGISTRY 0xFF + +// ---- Device classes ---- +// +// What kind of thing is plugged into a port. Class 0 is not a device: reading an +// unimplemented port already gives zero, so "nothing there" needs no special case and a +// machine with no registry at all answers correctly by doing nothing. +// +// Classes 0x01 to 0x0F belong to the machine itself. Peripherals start at 0x10. + +#define DEVICE_NONE 0x00 +#define DEVICE_REGISTRY 0x01 +#define DEVICE_CONSOLE 0x02 +#define DEVICE_TEST 0x10 + +// What a device brings besides itself. The memory controller will want the first of +// these to find out which ports own memory it can reach. +#define DEVICE_FLAG_HAS_MEMORY 0x01 + +// How many bytes a device's entry in the registry runs to. Reading past the end gives +// zero, so the record can grow later without anything already written having to change. +#define DEVICE_RECORD_BYTES 2 + uint8_t OutputHandler(uint8_t DataByte, uint8_t Address); uint8_t InputHandler(uint8_t Address); diff --git a/SplitBit Programming Manual.md b/SplitBit Programming Manual.md index ca47c69..bf325ad 100644 --- a/SplitBit Programming Manual.md +++ b/SplitBit Programming Manual.md @@ -122,10 +122,49 @@ If a device interrupts and its vector is empty, that is a fault: the machine sto ## Devices: -| Port | Device | +| Port | Device | Class | +| --- | --- | --- | +| 0x00 | The console. Writing sends a byte to standard output, reading takes one from standard input. | 0x02 | +| 0x10 | A test device. Writing anything to it puts its own line up, so that interrupt handling can be exercised without waiting on anything. The byte written is ignored. | 0x10 | +| 0xFF | The bus registry. See Asking What Is There below. | 0x01 | + +## Asking What Is There: + +A program that only ever runs on one machine can be told where everything is. A program meant to run on more than one has to ask, and the bus registry on port 0xFF is what it asks. + +Write a port number to the registry to say which port you are asking about, then read to get that port's record a byte at a time: + +| Byte | Meaning | | --- | --- | -| 0x00 | The console. Writing sends a byte to standard output, reading takes one from standard input. | -| 0x10 | A test device. Writing anything to it puts its own line up, so that interrupt handling can be exercised without waiting on anything. The byte written is ignored. | +| 0 | The device class. Zero means there is nothing on that port. | +| 1 | Flags. Bit 0 means the device brings memory of its own. | + +Reading past the end of a record gives zero, so a record can grow later without anything already written having to change. Selecting a port starts its record again from the beginning. + +``` + INIA 0x03 + OUTA 0xFF ; Ask about port 3. + INA 0xFF ; A is now the class of whatever is on port 3. +``` + +The registry answers on the device's behalf and never touches it. That is the reason it exists rather than programs simply reading each port to see what answers: reading a port is a real operation with real consequences, and reading the console to find out what it is would take a character off standard input and then wait for one that may never come. + +The registry is read only. A program cannot tell it that a device exists, because saying so would not make one exist, and once a program could write to it nothing reading it could tell what is really there from what has merely been claimed. Which routine handles a device is a separate question, and the vector table already answers it: installing a driver for the device on port 3 means writing hardware vector 3. + +Absence describes itself. Reading a port with nothing on it gives zero, so class zero means nothing is there. A machine with no registry at all answers zero when asked about port 0xFF, which correctly says that it cannot be enumerated. A program finds out whether it can ask by asking. + +One thing to be careful of: the registry remembers which port it was asked about, so an interrupt handler that enumerates in the middle of an enumeration will lose the caller's place. Enumerate with the Interrupt Flag down, or do it before any device is enabled. + +### Device Classes: + +| Class | Device | +| --- | --- | +| 0x00 | Nothing. | +| 0x01 | Bus registry. | +| 0x02 | Console. | +| 0x03 - 0x0F | Reserved for the machine itself. | +| 0x10 | Test device. | +| 0x11 - 0xFF | Peripherals. | ## Faults: diff --git a/Tests/expected/registryTest.out b/Tests/expected/registryTest.out new file mode 100644 index 0000000..e421c18 --- /dev/null +++ b/Tests/expected/registryTest.out @@ -0,0 +1,6 @@ +00 02 00 +10 10 00 +FF 01 00 +05 00 00 +Execution halted after 440 cycles. +[exit 0] diff --git a/Tests/manifest b/Tests/manifest index a06edbd..09c2200 100644 --- a/Tests/manifest +++ b/Tests/manifest @@ -46,6 +46,9 @@ dispatchTest | testPrograms/dispatchTest.asm | run | - # ---- Moving an ALU result back into an operand register ---- moveQTest | testPrograms/moveQTest.asm | run | - | - +# ---- Asking the machine what it is made of ---- +registryTest | testPrograms/registryTest.asm | run | - | - + # ---- Moving the cursor along ---- paddingTest | testPrograms/paddingTest.asm | run | - | -