(2023-05-05) An attempt to standardize memory-mapped I/O in M/OISC machines --------------------------------------------------------------------------- When it comes to interacting with the outer world, all authors of "esoteric" ISAs (that don't have dedicated I/O instructions, that is) have their own ways of doing it. Someone reserves the last virtual memory cell as a standard I/O cell, someone (like me with NRJ) reserves the first three cells for input, output and port context, someone allocates entire non-overlapping blocks or interrupt vectors... It definitely is hard to adapt to a new approach every single time you learn a new ISA or even a different variant of the same ISA. Like Subleq, for example - it's obvious that the howerj's variant of Subleq-16, under which eForth can be run, only provides the last-cell standard I/O option and it's not sufficient for the purposes of Dawn OS that was based on the same Subleq instruction (although of a greater bitness) but had full-featured graphics, disk I/O, non-blocking keyboard, mouse and even touchscreen controls. And that's just a single example, there are many others I haven't even seen in action yet. But I guess you get my point already - with so many different approaches to the same dead-simple problem, it's hard to focus on more essential stuff. So, while this attempt may be totally fruitless, I'd like to try and propose a bitness-agnostic spec of memory-mapped I/O in such machines. Let's call it EsoIO. 0. Introduction The EsoIO specification defines the rules to interact with five entities (if any of them is present on the target system): 1) standard I/O (as/if defined by the host OS); 2) serial I/O (keyboard, mouse, touchscreen etc); 3) raw video memory; 4) block-oriented I/O (disk, sound, network etc); 5) timers. While raw video memory and timers can just be considered special cases of block-oriented and serial I/O respectively, these cases do have special requirements to processing speed, so it is recommended to implement them with separate logic as described below. Note that the system is EsoIO-compatible if and only if all the listed parts it actually implements are implemented according to this spec. If the target system doesn't implement any of these five entities, corresponding memory blocks may be used for any other purpose and the system still will be EsoIO-compatible. Also, EsoIO tries to be as non-invasive as possible and is based on some of already existing conventions. All the numbers and addresses refer to cells, not bytes, unless explicitly stated otherwise. The size of a cell is one machine word, which can be anything by the VM author's design. In this specification, addresses are always signed integers and the indexing is zero-based (the first memory cell is 0). Negative cell addresses refer to the cells at the upper part of the virtual memory space, e.g. cell -1 is the last cell in memory, cell -2 is the second last cell and so on. 1. Memory layout in EsoIO Provided the entire memory space consists of N cells and a single cell address itself takes S cells, the layout is as follows: Entity | Address(es) | Description -------|-----------------------|--------------------------------------------- Program| 0..N/2 - 1 | Program/data space (all positive addresses) Video | -N/2 | Screen width W Video | -N/2 + 1 | Screen height H Video | -N/2 + 2 | Color depth C (how many cells a pixel takes) Video | -N/2 + 3..BK- 1 | Video memory itself Block | BK = -N/2 + 3 + W*H*C | Block I/O port number Block | BK + 1 | Block I/O buffer size Block | BK + 2..BK + 1 + S | Block I/O buffer address (S cells long) Block | BK + 2 + S | Block I/O trigger Unused | BK + 2 + S..-4 | Unused space, can be used for anything else Timers | -3 - (S+2)*T..-3 | T timer vectors (address and delay value) Ser/Std| -2 | Serial I/O port number (0 for standard) Ser/Std| -1 | Single-cell standard/serial input/output There might not be any "unused" part and the block I/O memory and timer vector memory areas may be adjacent to each other but they must not overlap. 2. Standard and serial I/O EsoIO views standard I/O as a special case of serial I/O. If the current operation refers to standard I/O, the cell at the -2 address must be set to 0. Otherwise, it's set to an implementation-specific port number that defines which device to interact with. In case with standard or serial I/O, input operation is always triggered by reading from the cell at the -1 address, and output operation is always triggered by writing to this cell. During a single serial operation, only a single cell of data can be transferred. The -2 cell should be set to 0 upon program start, but in general it's recommended to explicitly set it before every input or output operation (unless it's performed in a loop). 3. Video output EsoIO specifies pixel-based video output with three mandatory parameters at the start of the upper half of the virtual memory: screen width, screen height and color depth. Every one of this parameters must fit into a single cell. In this case, color depth parameter only serves the purpose of indication how many memory cells (again, not bytes, but cells) a single pixel would take. Thus, the video memory itself that starts immediately after these three parameters, is W * H * C cells long. It is fully up to the implementation on how the screen contents refresh is triggered after updating the video memory: it can be immediate, it can be an internal 60 Hz interval timer and so on. The semantics of the video memory cell contents is also fully on the implementation: whether it's RGB, YCbCr, HSL or something else. The rationale behind populating screen width, screen height and color depth in the virtual memory is to give programs a possibility to distinguish between different graphical environments and adjust their logic accordingly. 4. Block I/O For block I/O, there are three parameters to set: implementation-specific port number, input/output buffer size and input/output buffer address. After these parameters are set, an read or write operation on the trigger cell is required, which starts block input or block output operation respectively. The result of this action is expected to appear in the buffer we have pointed to in our parameters. If there are more parameters to the block operation than can fit into a single trigger cell (like file name, sector number or network address and port), they must be encapsulated in the buffer itself. 5. Timers In EsoIO, timers are defined at the end of the memory before the standard/serial I/O cells. Every timer consists of S cells for target routine address and 2 cells for the delay and parameters. The timing units (seconds, milliseconds, nanoseconds, CPU cycles etc) and the parameters (interval/one-off etc) are fully implementation-specific, as well as the condition under which the timer is considered armed. When a timer is triggered, the control is expected to be passed to the routine at the corresponding address. EsoIO does not limit the amount of timers to be created, but a specific implementation can. Now, that's it. I hope it helps at least a bit to streamline all the different models into something interoperable, and promise myself to develop the next ISAs in accordance to this proposal too. Have fun! --- Luxferre ---