(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 ---