commit 887e637e28b5d7e312410cc62aa01464ff064ee8
parent 984ced33960044aa5c5f43f4f917cc48d43a2cd0
Author: Luxferre <lux@ferre>
Date: Sun, 14 Aug 2022 22:18:38 +0300
Attempt to somehow streamline the readme
Diffstat:
M | README.md | | | 107 | ++++++++++++++++++++++++++++++++++++++++++++++++------------------------------- |
M | equi.c | | | 3 | +-- |
2 files changed, 66 insertions(+), 44 deletions(-)
diff --git a/README.md b/README.md
@@ -11,48 +11,53 @@ Main features of an Equi machine:
- Instruction bus: 8-bit;
- Data bus: 16-bit;
- Address bus: 16-bit;
-- Two 256-byte (128-word) stacks, main and return;
-- One 32-byte literal stack;
-- 16-bit program counter (PC);
-- 16-bit compilation buffer pointer (CBP);
-- 16-bit compilation lookup table pointer (CLTP);
-- Interpretation mode (IM) flag;
-- Compilation mode (CM) flag;
-- Instruction ignore (II) flag;
-- Minification mode (MM) flag;
-- 16-bit input buffer pointer;
-- 42752 bytes of main RAM (0x0000-0xa6ff), 41984 bytes of which are available for the program and data and 768 bytes hold the three stacks and necessary service registers listed above;
+- Up to 65536 bytes of RAM;
- Up to 64 MiB flat persistent storage (tape, disk, flash etc);
- Serial terminal input and output;
-- Up to 65535 peripheral extension ports, including several virtual ports.
+- Up to 65535 peripheral extension ports, including several virtual ports;
+- Multitasking support with up to 8 concurrently running tasks (by default);
+- Two 256-byte (128-word) stacks, main and return, per task;
+- One 32-byte literal stack per task;
+- 16-bit input buffer pointer and global and individual mode flags.
The default Equi RAM layout is:
-Address range|Size (bytes)|Purpose
--------------|------------|---------------------
-0x0000-0x0001|2 |Main/return stack size in words
-0x0002 |1 |Literal stack size in bytes (up to 255)
-0x0003 |1 |LSP (literal stack pointer)
-0x0004-0x0005|2 |MSP (main stack pointer)
-0x0006-0x0007|2 |RSP (return stack pointer)
-0x0008-0x0009|2 |CLT start address
-0x000a-0x000b|2 |GPD area start address
-0x000c-0x000d|2 |Command buffer start address
-0x000e-0x000f|2 |Command buffer size in bytes
-0x0010-0x0011|2 |PC (program counter)
-0x0012-0x0013|2 |CBP (compilation buffer pointer)
-0x0014-0x0015|2 |CLTP (compilation lookup table pointer)
-0x0016-0x0017|2 |IBP (input buffer pointer)
-0x0018 |1 |II (instruciton ignore mode flag)
-0x0019 |1 |CM (compilation mode flag)
-0x001a |1 |IM (interpretation mode flag)
-0x001b |1 |MM (minification/bypass pseudo-mode flag)
-0x001c-... |varies |Main stack - 256 bytes by default
-... |varies |Return stack - 256 bytes by default
-... |varies |Literal stack - 32 bytes by default
-... |varies |Compilation lookup table (CLT) - up to 1024 compiled words by default
-... |varies |General purpose data (GPD) area
-...-0x7dff |varies |Command buffer area
+Size (bytes)|Purpose
+------------|---------------------
+2 |Main/return stack size in words
+1 |Literal stack size in bytes (up to 255)
+2 |Command buffer start address
+2 |Command buffer size in bytes
+2 |IBP - input buffer pointer
+1 |II - instruciton ignore mode flag
+1 |MM - minification/bypass pseudo-mode flag
+2 |Currently running task ID
+varies |Task context table (see the next layout)
+varies |Command buffer area
+
+And the Equi program task context layout in the task context table is:
+
+Size (bytes)|Purpose
+------------|------------
+2 |Task ID
+1 |Active flag
+1 |Privileged flag
+1 |CM - compilation mode flag
+1 |LSP - literal stack pointer
+2 |MSP - main stack pointer
+2 |RSP - return stack pointer
+2 |CLTP - compilation lookup table pointer
+2 |CBP - compilation buffer pointer
+2 |Task's GPD start address
+2 |Task's command buffer start address
+2 |Task's command buffer length in bytes
+2 |PC - program counter
+varies |Main stack
+varies |Return stack
+varies |Literal stack
+varies |Compilation lookup table
+varies |General purpose data (GPD) area
+
To preserve runtime integrity, Equi implementations are allowed to (but not required to) restrict all writes to the addresses below the GPD area start. Note that command buffer area must not be write-restricted, i.e. Equi programs can be self-modifying. Also, when reading 16-bit from the area below GPD start, only the values at offsets 0x0000 (stack size), 0x0008 (CLT start address), 0x000c (command buffer start address) and 0x000e (command buffer size) are guaranteed to be populated by the runtime in big-endian, all others will appear in the host endian byte order so the program must not rely on their values in any way. For GPD area start address, better use `G` instruction exclusively.
@@ -62,16 +67,14 @@ All whitespace characters (space, tabulation, CR or LF) are discarded in Equi up
The interpreter can run in one of the four modes: command (default), interpretation (IM), compilation (CM) and instruction ignore (II) mode. An Equi machine always starts in the command mode. The latter three are triggered by certain instructions that set the corresponding flags. The semantics of the compilation mode is similar to that of Forth, and will be covered in detail here later on.
-In the command mode, the interpreter doesn't perform any instruction execution and doesn't manipulate program counter (PC). Instead, it accumulates all characters typed from the standard input into the so-called command buffer. The only instruction Equi must react to in this mode is Q, the quit instruction, that sets PC to the command buffer start, sets the IM flag, **clears the CLT** and starts execution in the interpretation mode. Note that this also means that every Equi program file, even when run in a non-interactive environment, must end with a Q character, and as long as every program has a halting `Q` instruction, you can safely concatenate several Equi programs in a single file to be executed sequentially.
+In the command mode, the interpreter doesn't perform any instruction execution and doesn't manipulate program counter (PC). Instead, it accumulates all characters typed from the standard input into the so-called command buffer. The only instruction Equi must react to in this mode is Q, the quit instruction, that loads the currently input command buffer contents into a task context and starts its execution in the interpretation mode. Note that this also means that every Equi program file, even when run in a non-interactive environment, must end with a Q character, and as long as every program has a halting `Q` instruction, you can safely concatenate several Equi programs in a single file to be executed sequentially.
In the instruction ignore more (II flag set), all instructions or arbitrary characters except `)` (that unsets the II flag), are skipped and discarded. This can be used to write comments. In a well-formed Equi program, the characters braced in the II instructions `(` and `)`, as well as any whitespace characters, will never enter the command buffer upon loading.
-In the interpretation mode (IM flag set), when the interpreter encounters any of the following characters - `_0-9A-Fa-z` (not including `-`) - it pushes their ASCII values bytewise onto the literal stack (32-byte long). When any other character (except `:`, `"` or `'`) is encountered when the literal stack is not empty, the `#` instruction logic (see below) is performed automatically. If `:` is encountered, compilation mode logic is performed instead. If a `Q` instruction or a on-printable character is encountered, Equi returns to the command mode immediately.
+In the interpretation mode, when the interpreter encounters any of the following characters - `_0-9A-Fa-z` (not including `-`) - it pushes their ASCII values bytewise onto the literal stack (32-byte long). When any other character (except `:`, `"` or `'`) is encountered when the literal stack is not empty, the `#` instruction logic (see below) is performed automatically. If `:` is encountered, compilation mode logic is performed instead. If a `Q` instruction or a on-printable character is encountered, Equi returns to the command mode immediately.
In the compilation mode, all instructions except `;` are skipped while the CM flag is set. When the interpreter encounters `;` instruction, it performs the finalizing logic to save the compiled word into CLT (see below) and returns to the interpretation mode.
-Note that II flag has the precedence over IM and CM flags and CM flag has the precedence over IM flag. I.e. you cannot exit the interpretation mode while being in the compilation mode, and you can't exit any other mode while being in the II mode. And surely enough you can't exit the command mode (interpreter shell itself) unless all three mode flags are unset.
-
Equi's core instruction set is:
Op |Stack state |Meaning
@@ -132,7 +135,7 @@ See [FizzBuzz](examples/fizzbuzz.equi) for a more thorough example of how differ
Being a purely PC-oriented low-level runtime/programming environment, Equi has the reference implementation emulator/VM written in C (ANSI C89 standard), `equi.c`, compilable and runnable on all the systems supporting standard I/O. Note that, for portability reasons, this emulator:
- accepts the programs from standard input only,
-- only implements three ports for `P` instruction: 0 as an echo port (returns passed parameters as corresponding result values), 1 as a random port (returns two random values in the results in the range between the two parameter values) and 2 as a CRC16 calculation port for a given memory location and its length, for any other port value it outputs its parameters to the standard error stream and puts three 0x0000 values back onto the stack,
+- only implements four ports for `P` instruction: 0 as an echo port (returns passed parameters as corresponding result values), 1 as a random port (returns two random values in the results in the range between the two parameter values) 2 as a CRC16 calculation port for a given memory location and its length, and 3 for task control (see below), for any other port value it outputs its parameters to the standard error stream and puts three 0x0000 values back onto the stack,
- implements `s` command line parameter that runs the emulator in the silent mode without printing any welcome banners or interactive prompts,
- doesn't implement non-blocking key input (the `,` instruction is identical to blocking key input instruction `?`),
- sandboxes the `{` and `}` operations using the file with the name you supply on the compile time to the `PERSIST_FILE` constant. The file must already be created and accessible. If it doesn't exist, these operations will effectively do nothing except putting 0x0000 (success status) onto the stack.
@@ -196,6 +199,26 @@ You can also add a 96K-sized `PERS.DAT` file shipped in the repo to use the pers
java -jar platform-build-tools/apple2/ac.jar -dos equi.dsk PERS.DAT bin < platform-build-tools/PERS.DAT
```
+## Multitasking in Equi
+
+Equi supports running several tasks concurrently scheduled instruction-by-instruction in a round-robin fashion. The general rules are as follows:
+
+1. Every task context has an ID, starting from 0 and ending with `EQUI_TASKS_MAX - 1`, and two specific attributes - `active` and `privileged`. The `active` attribute determines whether or not the task is running, the `privileged` attribute determines whether or not the task can write to the command buffer area not belonging to itself.
+2. The program code passed into Equi on start is loaded into task 0 and its `privileged` attribute is always set. This way, any code initially run in the machine can act as a loader and launcher for other tasks.
+3. A privileged task can spawn either another privileged task or non-privileged task. A non-privileged task can only spawn another non-privileged task.
+4. No task, whether privileged or not, can write into any RAM area outside its own GPD area and the command buffer. Non-privileged tasks are additionally limited to the command buffer area they already take and cannot write anywhere else.
+5. When a task has ended, its `active` flag is unset. Equi runtime then may use its task slot to allocate another task when necessary.
+6. Equi machine halts/quits when no active task is left.
+
+New tasks are created (and instantly activated) with `Y` instruciton that accepts the code address, code length and privileged flag from the stack, and returns the task ID on top of the stack. Using this task ID, you can further control the status of the task using system port 3, passing the task ID as `p1` parameter and one of the following operation codes as `p2` parameter to the `P` instruction:
+
+- 0: get task status (active or not) as `r1`,
+- 1: set active status of the task (start/resume it) if your own task is privileged,
+- 2: unset active status of the task (pause/terminate it) if your own task is privileged,
+- 3: get the privilege status of the task as `r1`.
+
+See [this snippet](examples/multitask.equi) for a very simple example of using `Y` instruciton to allocate new tasks from existing code.
+
## FAQ
### Why does the world need another Forth-like system?
diff --git a/equi.c b/equi.c
@@ -100,10 +100,9 @@ struct EquiCtx { /* one Equi program context */
uchar active; /* 0 - inactive/quit, 1 - active */
uchar privileged; /* whether or not the task is allowed to write to the entire command buffer */
uchar CM; /* compilation mode flag */
- uchar IM; /* interpretation mode flag */
+ uchar lsp; /* literal stack pointer */
ushort msp; /* main stack pointer */
ushort rsp; /* return stack pointer */
- ushort lsp; /* literal stack pointer */
ushort cltp; /* compilation lookup table pointer */
ushort cbp; /* compilation buffer pointer */
ushort gpd_start; /* GPD area start for this task */