From: Svjatoslav Agejenko Date: Tue, 24 Feb 2026 21:36:26 +0000 (+0200) Subject: Expand opcode documentation (10–19): detailed descriptions, stack effects, usage... X-Git-Url: http://www2.svjatoslav.eu/gitweb/?a=commitdiff_plain;h=refs%2Fheads%2Fmaster;p=fifth.git Expand opcode documentation (10–19): detailed descriptions, stack effects, usage examples, technical notes, and visual formatting improvements. --- diff --git a/doc/opcodes_00_09.org b/doc/opcodes_00_09.org index d53d0c3..ddc530e 100644 --- a/doc/opcodes_00_09.org +++ b/doc/opcodes_00_09.org @@ -31,7 +31,7 @@ This document describes virtual machine opcodes 0 through 9. These are the fundamental instructions for program flow control, stack manipulation, and basic I/O operations. -** Quick Reference +*Quick Reference:* | Opcode | Name | Stack Effect | Bytecode Size | Purpose | |--------+------+------------------+---------------+---------------------------------| @@ -60,20 +60,20 @@ manipulation, and basic I/O operations. | Stack | ( -- ) | | Bytecode | Single byte: =00= | -** Description +*Description* Does nothing. Execution continues immediately to the next instruction. This opcode is handled as the entry point to the emulator's instruction fetch loop, meaning it effectively "falls through" to the next opcode. -** When to Use +*When to Use:* - Padding bytecode for alignment purposes - Placeholder during development or debugging - Safe target for jump instructions when you need a "do nothing" branch - Filling unused entries in jump tables -** Example +*Example:* #+BEGIN_SRC 00 ; nop - do nothing @@ -83,8 +83,6 @@ fetch loop, meaning it effectively "falls through" to the next opcode. No observable effect. The instruction pointer simply advances to the next opcode. ---------------------------------------------------------------------- - * 1: halt (Stop Execution) :PROPERTIES: :CUSTOM_ID: opcode-halt @@ -124,8 +122,6 @@ to exit the Fifth environment. When executed, the emulator: In Fifth source code, this is typically invoked via the =bye= word, which compiles to the halt opcode. ---------------------------------------------------------------------- - * 2: kbd@ (Keyboard Input) :PROPERTIES: :CUSTOM_ID: opcode-kbd-read @@ -202,8 +198,6 @@ Basic key reading: codes to FSCII characters - The buffer holds 128 scan codes; older codes are overwritten if full ---------------------------------------------------------------------- - * 3: num (Push Literal Number) :PROPERTIES: :CUSTOM_ID: opcode-num @@ -264,8 +258,6 @@ FF ; Compiles to: 03 FF 00 00 00 - All 32 bits are pushed; high bytes are zero-filled for small values - The value is stored in little-endian byte order in bytecode ---------------------------------------------------------------------- - * 4: jmp (Unconditional Jump) :PROPERTIES: :CUSTOM_ID: opcode-jmp @@ -332,8 +324,6 @@ Skip over code: - The emulator adds =xms_addr= to convert to a physical memory address - No return address is saved; use =call= for subroutines ---------------------------------------------------------------------- - * 5: call (Call Subroutine) :PROPERTIES: :CUSTOM_ID: opcode-call @@ -399,8 +389,6 @@ argument (i.e., the address of the next instruction). - Always balance =call= with =ret= to avoid return stack corruption - Nesting depth is limited by return stack size ---------------------------------------------------------------------- - * 6: 1+ (Increment) :PROPERTIES: :CUSTOM_ID: opcode-1plus @@ -450,8 +438,6 @@ Use =1+= instead of =1 += for better code density. - Works identically on signed and unsigned 32-bit values - For adding larger values, use =+= (opcode 24) ---------------------------------------------------------------------- - * 7: 1- (Decrement) :PROPERTIES: :CUSTOM_ID: opcode-1minus diff --git a/doc/opcodes_10_19.org b/doc/opcodes_10_19.org index 3651889..336b62b 100644 --- a/doc/opcodes_10_19.org +++ b/doc/opcodes_10_19.org @@ -25,154 +25,480 @@ #+end_export +* Overview -* 10: if +This document describes virtual machine opcodes 10 through 19. These +opcodes provide conditional branching, subroutine return, byte-level +memory access, stack-to-return-stack operations, stack rotation, and +disk I/O. + +*Quick Reference:* + +| Opcode | Name | Stack Effect | Bytecode Size | Purpose | +|--------+--------+----------------+---------------+-------------------------------------------------| +| 10 | if | flag -- | 5 bytes | Conditional jump (jump if zero) | +| 11 | ret | -- | 1 byte | Return from subroutine | +| 12 | c@ | addr -- byte | 1 byte | Read byte from memory | +| 13 | c! | byte addr -- | 1 byte | Write byte to memory | +| 14 | push | n -- | 1 byte | Move data to return stack | +| 15 | pop | -- n | 1 byte | Move data from return stack | +| 16 | unused | -- | 1 byte | Reserved for future use. Currently acts as nop. | +| 17 | rot | a b c -- b c a | 1 byte | Rotate three stack elements | +| 18 | disk@ | sector addr -- | 1 byte | Read 1KB sector from disk | +| 19 | disk! | addr sector -- | 1 byte | Write 1KB sector to disk | + +* 10: if (Conditional Jump) :PROPERTIES: :CUSTOM_ID: opcode-if :ID: d6f834b6-9a37-4414-91b3-62b3e1d920c1 :END: -- *Stack Effect:* =n --= -- *Description:* Pops the top value from the data stack. If the value is - zero, jumps to the address specified by the next 32-bit value. - Otherwise, skips the address. -- *Notes:* Used for conditional branching. The address is added to - =xms_addr= for physical address conversion. -- *Example:* - #+begin_example - 0 - 0x1000 ; Jump target if zero - if - #+end_example - -* 11: ret + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+------------------------------------------| +| Opcode | 10 (0x0A) | +| Name | if | +| Stack | ( flag -- ) | +| Bytecode | =0A= followed by 4-byte target address | + +*Description:* + +Pops the top value from the data stack. If the value is zero, jumps to +the address embedded in the instruction stream. If the value is +non-zero, skips the address and continues with the next instruction. + +This "jump if false" semantics is exactly what is needed to jump/skip +over code because comparison condition was not met: + +: #1: +: #2: +: #3: +: #4: + + +*Bytecode Format:* + +#+BEGIN_SRC +0A aa aa aa aa +│ └──────────┘ 32-bit target address (little-endian) +└ opcode +#+END_SRC + +*When to Use* + +- Implementing conditional branches in control structures +- Building if-then-else constructs (the "else" branch) +- Loop exit conditions + +*Important Notes:* + +- The jump is taken when flag is ZERO (false condition) +- This is actually "jump if false" semantics +- High-level Fifth words like =if= =then= =else= compile to this opcode +- The address is a virtual address in the VM's address space + +* 11: ret (Return from Subroutine) :PROPERTIES: :CUSTOM_ID: opcode-ret :ID: 6e683977-a985-4bb8-9d2c-c860d30e1df6 :END: -- *Stack Effect:* =--= -- *Description:* Returns from a subroutine by popping the return address - from the return stack and jumping to it. -- *Notes:* The return stack pointer is incremented after the jump. -- *Example:* - #+begin_example - ret - #+end_example - -* 12: c@ + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|----------+-------------------| +| Opcode | 11 (0x0B) | +| Name | ret | +| Stack | ( -- ) | +| R. Stack | ( ret-addr -- ) | +| Bytecode | Single byte: =0B= | + +*Description:* + +Returns from a subroutine by popping the return address from the +return stack and jumping to it. This is the counterpart to the =call= +opcode (5). + + +*When to Use:* + +- Ending subroutine/word definitions +- Early exit from subroutines + +*Example:* + +#+BEGIN_SRC +; Subroutine at address 0x2000: +03 2A 00 00 00 ; num 42 - push a value +0B ; ret - return to caller +#+END_SRC + +In Fifth source code: + +#+BEGIN_SRC fifth +: myword ( -- n ) + 2A \ push 42 +; \ compiles to ret +#+END_SRC + +** Important Notes + +- Each =ret= must have a matching =call= to maintain return stack balance +- Unbalanced call/ret pairs will corrupt the return stack +- The return stack is also used by =push= and =pop= opcodes +- Return addresses are virtual addresses (without xms_addr offset) + +* 12: c@ (Fetch Byte from Memory) :PROPERTIES: :CUSTOM_ID: opcode-cfetch :ID: a2ce44f7-b661-44e0-909b-644ff52aa38e :END: -- *Stack Effect:* =addr -- byte= -- *Description:* Reads a single byte from the specified memory address. - Replaces the address on the stack with the byte value. -- *Notes:* The address is adjusted by =xms_addr= to convert to physical - memory before reading. -- *Example:* - #+begin_example - 0x1000 ; Read from address 0x1000 - c@ - #+end_example - After execution, stack contains the byte at address 0x1000. - -* 13: c! + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------| +| Opcode | 12 (0x0C) | +| Name | c@ (c-fetch) | +| Stack | ( addr -- byte ) | +| Bytecode | Single byte: =0C= | + +*Description:* + +Reads a single byte from the specified memory address and pushes it +onto the data stack. The byte value is zero-extended to 32 bits +(padded with zeros in the upper 24 bits). + +*When to Use:* + +- Reading character data from strings +- Parsing binary data structures +- Reading individual pixels from image buffers + + +*Example:* + +Read a byte from address 0x1000: + +#+BEGIN_SRC +03 00 10 00 00 ; num 0x1000 - push address +0C ; c@ - read byte +; Stack now contains: byte-value (0-255, zero-extended) +#+END_SRC + + +*Important Notes:* + +- The byte is zero-extended: reading 0xFF gives 0x000000FF on stack +- The address is a virtual address (xms_addr is added internally) + +* 13: c! (Store Byte to Memory) :PROPERTIES: :CUSTOM_ID: opcode-cstore :ID: f129ef87-a31a-40fe-b8d3-984da2db90e2 :END: -- *Stack Effect:* =byte addr --= -- *Description:* Stores a byte value to the specified memory address. -- *Notes:* Takes two values from the stack: the byte to store and the - address. The address is adjusted by =xms_addr= before writing. -- *Example:* - #+begin_example - 0x42 ; Byte to store - 0x1000 ; Address to store to - c! - #+end_example - -* 14: push + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------| +| Opcode | 13 (0x0D) | +| Name | c! (c-store) | +| Stack | ( byte addr -- ) | +| Bytecode | Single byte: =0D= | + +*Description:* + +Stores a single byte to the specified memory address. The byte value +is taken from the low 8 bits of the value on the stack. The upper 24 +bits are ignored. + +*When to Use:* + +- Writing character data to strings +- Building binary data structures +- Drawing individual pixels to image buffers + +*Example:* + +Store byte 0x42 at address 0x1000: + +#+BEGIN_SRC +03 42 00 00 00 ; num 0x42 - byte value to store +03 00 10 00 00 ; num 0x1000 - destination address +0D ; c! - store byte +; Memory at 0x1000 now contains 0x42 +#+END_SRC + + +*Important Notes:* + +- Upper 24 bits are ignored (no error if value > 255) +- The address is a virtual address + +* 14: push (Move Data to Return Stack) :PROPERTIES: :CUSTOM_ID: opcode-push :ID: e4bcbaf1-7724-4051-b19c-1aa7cd06eae6 :END: -- *Stack Effect:* =n --= -- *Description:* Moves the top value from the data stack to the return - stack. -- *Notes:* Used for transferring values between the two stacks without - modifying the value. -- *Example:* - #+begin_example - 42 - push - #+end_example - After execution, data stack is empty and return stack contains 42. - -* 15: pop + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------| +| Opcode | 14 (0x0E) | +| Name | push | +| Stack | ( n -- ) | +| R. Stack | ( -- n ) | +| Bytecode | Single byte: =0E= | + +*Description:* + +Moves the top value from the data stack to the return stack. This +provides temporary storage that doesn't interfere with data stack +operations. + +*When to Use:* + +- Temporarily saving values during complex stack manipulations + +*Example:* + +Save a value for later retrieval: + +#+BEGIN_SRC +03 2A 00 00 00 ; num 42 - push value onto data stack +0E ; push - move 42 to return stack +; Data stack is now empty +; Return stack now contains 42 + +; ... do other work ... + +0F ; pop - retrieve 42 from return stack +; Data stack now contains 42 +#+END_SRC + + +*Important Notes:* + +- Must be balanced with =pop= to avoid return stack corruption +- Do not mix carelessly with =call= /=ret= pairs +- The return stack grows downward (like the data stack) +- Popping too many times can corrupt return addresses + +* 15: pop (Move Data from Return Stack) :PROPERTIES: :CUSTOM_ID: opcode-pop :ID: 21871d09-4d58-440f-8c94-231105aa4e3f :END: -- *Stack Effect:* =-- n= -- *Description:* Moves the top value from the return stack to the data - stack. -- *Notes:* Used for transferring values between the two stacks without - modifying the value. -- *Example:* - #+begin_example - pop - #+end_example - After execution, data stack contains the value that was on top of the return stack. - - -* 17: rot + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------| +| Opcode | 15 (0x0F) | +| Name | pop | +| Stack | ( -- n ) | +| R. Stack | ( n -- ) | +| Bytecode | Single byte: =0F= | + +*Description:* + +Moves the top value from the return stack to the data stack. This is +the inverse of the =push= opcode. + +*When to Use:* + +- Retrieving temporarily saved values + +*Example:* + +Retrieve a saved value: + +#+BEGIN_SRC +; Assuming return stack contains 42 (from previous 'push') +0F ; pop - move 42 from return stack to data stack +; Data stack now contains 42 +; Return stack top item removed +#+END_SRC + +Important Notes: +- Popping from an empty return stack causes undefined behavior +- Always ensure there's a value on the return stack before calling =pop= +- Balance every =push= with a corresponding =pop= + +** Related Opcode: i + +The =i= opcode (31) copies (rather than moves) the top of the return +stack to the data stack, leaving the return stack unchanged. + +| Opcode | Action | R. Stack Effect | +|--------+------------------------+-----------------| +| i | Copy top to data stack | | +| pop | Move top to data stack | n -- | + +* 16: (Unused/Reserved) +:PROPERTIES: +:CUSTOM_ID: opcode-unused +:END: + +Opcode 16 (0x10) is reserved for future use. Opcode currently behaves as *nop* instruction. + +* 17: rot (Rotate Three Stack Elements) :PROPERTIES: :CUSTOM_ID: opcode-rot :ID: 4cee73f7-c105-4b96-9380-ff89bd7fedad :END: -- *Stack Effect:* =n1 n2 n3 -- n2 n3 n1= -- *Description:* Rotates the top three values on the data stack. The - third item becomes the top, the top becomes the second, and the second - becomes the third. -- *Notes:* Useful for reordering stack items without temporary storage. -- *Example:* - #+begin_example - 1 2 3 - rot - #+end_example - After execution, stack contains 2 3 1. - -* 18: disk@ + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+----------------------------| +| Opcode | 17 (0x11) | +| Name | rot | +| Stack | ( n1 n2 n3 -- n2 n3 n1 ) | +| Bytecode | Single byte: =11= | + +*Description* + +Rotates the top three values on the data stack. The third item from +the top moves to the top, while the top two items shift down. + +*Visual Representation:* +#+BEGIN_SRC +Before: [ ... n1 n2 n3 ] (n3 is on top) +After: [ ... n2 n3 n1 ] (n1 is now on top) +#+END_SRC + +Rotation direction: the deepest of the three (n1) rotates to the top. + +*When to Use:* +- Accessing values buried deeper on the stack +- Swapping values in multi-item calculations + + +*Example:* + +#+BEGIN_SRC +03 01 00 00 00 ; num 1 - push n1 +03 02 00 00 00 ; num 2 - push n2 +03 03 00 00 00 ; num 3 - push n3 +; Stack: [ 1 2 3 ] with 3 on top + +11 ; rot +; Stack: [ 2 3 1 ] with 1 on top +#+END_SRC + +Important Notes: + +- Requires at least three items on the data stack +- Rotating with fewer items causes undefined behavior +- This is a rotation, not a swap - all three elements move + +* 18: disk@ (Read Disk Sector) :PROPERTIES: :CUSTOM_ID: opcode-disk-read :ID: bed1aa27-66ac-4c73-bbb9-e49ff2aa67c5 :END: -- *Stack Effect:* =sector addr --= -- *Description:* Reads 1KB of data from the virtual disk at the - specified sector into the specified memory address. -- *Notes:* The sector number is the top of the stack, followed by the - destination address. The disk is accessed via DOS interrupt calls. -- *Example:* - #+begin_example - 16 ; Sector 16 - 0x2000 ; Destination address - disk@ - #+end_example - -* 19: disk! + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------------| +| Opcode | 18 (0x12) | +| Name | disk@ (disk-fetch) | +| Stack | ( sector addr -- ) | +| Bytecode | Single byte: =12= | + +*Description:* + +Reads one sector (1024 bytes) from the virtual disk into memory at the +specified address. + +*Parameters:* + +- *sector* (top of stack): Sector number to read from (0-based) +- *addr* (second): Destination memory address for the data + +*When to Use:* + +- Loading program code from disk +- Reading data files +- Accessing filesystem structures + +*Example:* + +Read sector 16 into memory at 0x2000: + +#+BEGIN_SRC +03 10 00 00 00 ; num 16 - sector number +03 00 20 00 00 ; num 0x2000 - destination address +12 ; disk@ - read sector +; 1024 bytes from sector 16 now at address 0x2000 +#+END_SRC + +** Technical Details + +- One sector = 1024 bytes (1 KB) +- Sector numbering starts at 0 +- Uses DOS file I/O to access the virtual disk image +- The virtual disk is typically a file named =disk.img= or similar + +** Important Notes + +- The address is a virtual address in the VM's address space +- Reading beyond the end of the disk file may return zeros or cause errors +- The disk is accessed through DOS file handles, not BIOS interrupts + +* 19: disk! (Write Disk Sector) :PROPERTIES: :CUSTOM_ID: opcode-disk-write :ID: 02eda775-e483-4057-b809-de36d586579b :END: -- *Stack Effect:* =addr sector --= -- *Description:* Writes 1KB of data from the specified memory address to - the virtual disk at the specified sector. -- *Notes:* The destination address is on top of the stack, followed by - the sector number. Data is first copied to a temporary buffer before - writing. -- *Example:* - #+begin_example - 0x2000 ; Source address - 16 ; Sector 16 - disk! - #+end_example + +#+ATTR_HTML: :border 2 :rules all :frame border +| Property | Value | +|------------+--------------------------| +| Opcode | 19 (0x13) | +| Name | disk! (disk-store) | +| Stack | ( addr sector -- ) | +| Bytecode | Single byte: =13= | + +*Description* + +Writes one sector (1024 bytes) from memory to the virtual disk at the +specified sector number. + +*Parameters:* + +- *addr* (top of stack): Source memory address +- *sector* (second): Sector number to write to (0-based) + +*When to Use:* + +- Saving program data to disk +- Modifying filesystem structures +- Persisting state across emulator sessions + +*Example:* + +Write memory at 0x2000 to sector 16: + +#+BEGIN_SRC +03 00 20 00 00 ; num 0x2000 - source address +03 10 00 00 00 ; num 16 - sector number +13 ; disk! - write sector +; 1024 bytes from address 0x2000 written to sector 16 +#+END_SRC + +*Technical Details:* + +- One sector = 1024 bytes (1 KB) +- Data is first copied to a temporary buffer in conventional memory + before being written to disk (required for XMS memory access) +- Writing to sector 0 may corrupt the bootloader/kernel + +*Stack Order Note:* + +The stack order is =( addr sector -- )=, which is reversed from +=disk@= =( sector addr -- )=. This is because the data source (addr) +is "produced" first, then the destination (sector) is specified. + +*Important Notes* + +- The address is a virtual address +- Writing to the disk image is immediate (no buffering) +- Be careful not to overwrite critical sectors (kernel, filesystem) diff --git a/emulator/emulator.asm b/emulator/emulator.asm index bcacad2..a41408f 100644 --- a/emulator/emulator.asm +++ b/emulator/emulator.asm @@ -92,7 +92,7 @@ table: dw op_13_xc! ; 13 - store byte dw op_14_xpush ; 14 - data stack to return stack dw op_15_xpop ; 15 - return stack to data stack - dw 0 ; 16 - unused instruction + dw emu ; 16 - Unused instruction. Temporarily acts as nop. Can change in the future. dw op_17_xrot ; 17 - rotate top 3 dw op_18_xdisk@ ; 18 - load sector from disk dw op_19_xdisk! ; 19 - save sector to disk diff --git a/emulator/opcodes_10_19.asm b/emulator/opcodes_10_19.asm index 11e5d4a..31cdc06 100644 --- a/emulator/opcodes_10_19.asm +++ b/emulator/opcodes_10_19.asm @@ -1,18 +1,81 @@ -; Opcodes 10-19: if, ret, c@, c!, push, pop, (unused), rot, disk@, disk! +; ============================================================================= +; Virtual CPU Opcodes 10-19: if, ret, c@, c!, push, pop, (unused), rot, disk@, disk! +; ============================================================================= +; +; This file contains the implementation of virtual machine opcodes 10 through 19. +; Each opcode handler is responsible for: +; 1. Performing its operation +; 2. Jumping back to the emulation loop (emu label in emulator.asm) +; +; Register conventions used throughout: +; esi - Virtual CPU instruction pointer (points to next bytecode) +; edi - Virtual CPU data stack pointer (grows downward, pre-decrement) +; [resp] - Virtual CPU return stack pointer +; es - Set to 0 for flat memory access +; [xms_addr] - Base address of virtual RAM in host memory +; +; Data Stack Memory Layout: +; The data stack grows DOWNWARD from high memory toward low memory. +; edi points to the TOP element (most recently pushed). +; To push: decrement edi by 4, then store at [es:edi] +; To pop: read from [es:edi], then increment edi by 4 +; +; Return Stack Memory Layout: +; The return stack also grows DOWNWARD. +; [resp] points to the TOP element. +; Used for: return addresses (call/ret), loop counters, temporary storage (push/pop) -op_10_xif: ; Conditional Jump: ( flag -- ) +; ============================================================================= +; Opcode 10: if (Conditional Jump) +; ============================================================================= +; Stack effect: ( flag -- ) +; +; Pops the top value from the data stack. If the value is zero, jumps to the +; address specified by the 32-bit value in the instruction stream. Otherwise, +; skips the address and continues execution. +; +; Instruction format: [opcode 0A] [32-bit target address, little-endian] +; +; When to use: +; - Implementing conditional branches in control structures +; - Building if-then-else constructs +; - Loop exit conditions +; +; Example bytecode sequence: +; 03 01 00 00 00 ; num 1 - push flag (non-zero) +; 0A ; if opcode +; 00 10 00 00 ; target address 0x1000 +; ; Result: jump NOT taken, execution continues after the address +; +; Note: The jump is taken when flag is ZERO (false condition). +; This is opposite to typical "if" semantics - it's actually "jump if false". +; +op_10_xif: mov eax, [es:edi] ; eax = flag (top of data stack) add edi, 4 ; pop flag off the data stack cmp eax, 0 ; test if flag is zero - jne l2 ; if flag is non-zero, skip the jump + jne l_skip_jump ; if flag is non-zero, skip the jump mov esi, [es:esi] ; read 32-bit target address from instruction stream add esi, [xms_addr] ; convert virtual address to physical address jmp emu ; return to emulation loop -l2: +l_skip_jump: add esi, 4 ; skip past the 32-bit jump target in instruction stream jmp emu ; return to emulation loop -op_11_xret: ; Return: ( -- ) R:( ret-addr -- ) +; ============================================================================= +; Opcode 11: ret (Return from Subroutine) +; ============================================================================= +; Stack effect: ( -- ) Return stack: ( ret-addr -- ) +; +; Returns from a subroutine by popping the return address from the return +; stack and jumping to it. This is the counterpart to the 'call' opcode. +; +; When to use: +; - Ending subroutine/word definitions +; - Returning control to the caller +; - Must be paired with 'call' to maintain return stack balance +; +op_11_xret: mov eax, [resp] ; load return stack pointer mov esi, [es:eax] ; pop return address from return stack add esi, [xms_addr] ; convert virtual address to physical address @@ -20,7 +83,28 @@ op_11_xret: ; Return: ( -- ) R:( ret-addr -- ) mov [resp], eax ; store updated return stack pointer jmp emu ; return to emulation loop -op_12_xc@: ; Fetch Byte: ( addr -- byte ) +; ============================================================================= +; Opcode 12: c@ (Fetch Byte) +; ============================================================================= +; Stack effect: ( addr -- byte ) +; +; Reads a single byte from the specified memory address and pushes it onto +; the data stack. The byte is zero-extended to 32 bits. +; +; When to use: +; - Reading character data from strings +; - Parsing binary data structures +; - Reading individual pixels from image buffers +; +; Example: +; 03 00 10 00 00 ; num 0x1000 - push address +; 0C ; c@ - read byte at address 0x1000 +; ; Result: Data stack contains the byte value from address 0x1000 (zero-extended) +; +; Note: The address is a VIRTUAL address. The emulator adds xms_addr to +; convert it to a physical address for actual memory access. +; +op_12_xc@: mov eax, [es:edi] ; eax = virtual address from top of data stack add eax, [xms_addr] ; convert virtual address to physical address sub ecx, ecx ; clear ecx (zero-extend the byte) @@ -28,7 +112,28 @@ op_12_xc@: ; Fetch Byte: ( addr -- byte ) mov [es:edi], ecx ; replace address on stack with the fetched byte value jmp emu ; return to emulation loop -op_13_xc!: ; Store Byte: ( byte addr -- ) +; ============================================================================= +; Opcode 13: c! (Store Byte) +; ============================================================================= +; Stack effect: ( byte addr -- ) +; +; Stores a single byte to the specified memory address. The byte value is +; taken from the low 8 bits of the top stack item. +; +; When to use: +; - Writing character data to strings +; - Building binary data structures +; - Drawing individual pixels to image buffers +; +; Example: +; 03 42 00 00 00 ; num 0x42 - push byte value to store +; 03 00 10 00 00 ; num 0x1000 - push destination address +; 0D ; c! - store byte 0x42 at address 0x1000 +; ; Result: Virtual machine RAM at 0x1000 now contains 0x42, stack is empty +; +; Note: Stack order is ( byte addr -- ), meaning the address is on top. +; +op_13_xc!: mov ebx, [es:edi] ; ebx = virtual address (top of stack) add edi, 4 ; pop address off the data stack mov ecx, [es:edi] ; ecx = byte value (second on stack, only cl used) @@ -37,7 +142,26 @@ op_13_xc!: ; Store Byte: ( byte addr -- ) mov [es:ebx], cl ; store low byte of ecx to memory jmp emu ; return to emulation loop -op_14_xpush: ; Push to Return Stack: ( n -- ) R:( -- n ) +; ============================================================================= +; Opcode 14: push (Move to Return Stack) +; ============================================================================= +; Data stack effect: ( n -- ) Return stack: ( -- n ) +; +; Moves the top value from the data stack to the return stack. This is useful +; for temporary storage that doesn't interfere with data stack operations. +; +; When to use: +; - Temporarily saving values during complex stack manipulations +; +; Example: +; 03 2A 00 00 00 ; num 42 - push value onto data stack +; 0E ; push - move 42 to return stack +; ; Data stack is now empty, return stack contains 42 +; +; Note: Must be balanced with 'pop' to avoid return stack corruption. +; Do not mix with 'call'/'ret' without careful management. +; +op_14_xpush: mov ebx, [es:edi] ; ebx = top of data stack add edi, 4 ; pop value off the data stack mov eax, [resp] ; load return stack pointer @@ -46,7 +170,26 @@ op_14_xpush: ; Push to Return Stack: ( n -- ) R:( -- n ) mov [resp], eax ; store updated return stack pointer jmp emu ; return to emulation loop -op_15_xpop: ; Pop from Return Stack: ( -- n ) R:( n -- ) +; ============================================================================= +; Opcode 15: pop (Move from Return Stack) +; ============================================================================= +; Data stack effect: ( -- n ) Return stack: ( n -- ) +; +; Moves the top value from the return stack to the data stack. This is the +; inverse of the 'push' opcode. +; +; When to use: +; - Retrieving temporarily saved values +; +; Example: +; ; Assuming return stack contains 42 (from previous 'push') +; 0F ; pop - move 42 from return stack to data stack +; ; Data stack now contains 42, return stack top item removed +; +; Note: Popping from an empty return stack will cause undefined behavior. +; Always ensure there's a value on the return stack before calling 'pop'. +; +op_15_xpop: mov eax, [resp] ; load return stack pointer mov ebx, [es:eax] ; read value from top of return stack add eax, 4 ; adjust return stack pointer (pop) @@ -55,16 +198,74 @@ op_15_xpop: ; Pop from Return Stack: ( -- n ) R:( n -- ) mov [es:edi], ebx ; push value onto data stack jmp emu ; return to emulation loop -op_17_xrot: ; Rotate: ( a b c -- b c a ) - mov ebx, [es:edi] ; ebx = c (top of stack) - mov ecx, [es:edi+4] ; ecx = b (second on stack) - mov edx, [es:edi+8] ; edx = a (third on stack) - mov [es:edi+8], ecx ; third slot = b - mov [es:edi+4], ebx ; second slot = c - mov [es:edi], edx ; top slot = a +; ============================================================================= +; Opcode 16: (Unused) +; ============================================================================= +; Opcode 16 is reserved for future use. Currently it acts as nop. + +; ============================================================================= +; Opcode 17: rot (Rotate Three Elements) +; ============================================================================= +; Stack effect: ( n1 n2 n3 -- n2 n3 n1 ) +; +; Rotates the top three values on the data stack. The third item from the +; top moves to the top, while the top two items shift down. +; +; Visual representation: +; Before: [ ... n1 n2 n3 ] (n3 is on top) +; After: [ ... n2 n3 n1 ] (n1 is now on top) +; +; When to use: +; - Reordering stack items for complex operations +; - Accessing values buried deeper on the stack +; - Implementing Forth control structures +; - Swapping values in multi-item calculations +; +; Example: +; 03 01 00 00 00 ; num 1 - push n1 +; 03 02 00 00 00 ; num 2 - push n2 +; 03 03 00 00 00 ; num 3 - push n3 +; 11 ; rot - rotate +; ; Result: stack contains 2, 3, 1 (with 1 on top) +; +; Note: Requires at least three items on the data stack. +; Rotating with fewer items causes undefined behavior. +; +op_17_xrot: + mov ebx, [es:edi] ; ebx = n3 (top of stack) + mov ecx, [es:edi+4] ; ecx = n2 (second on stack) + mov edx, [es:edi+8] ; edx = n1 (third on stack) + mov [es:edi+8], ecx ; third slot = n2 + mov [es:edi+4], ebx ; second slot = n3 + mov [es:edi], edx ; top slot = n1 jmp emu ; return to emulation loop -op_18_xdisk@: ; Disk Load: ( sector mem -- ) +; ============================================================================= +; Opcode 18: disk@ (Disk Read) +; ============================================================================= +; Stack effect: ( sector addr -- ) +; +; Reads one sector (1024 bytes) from the virtual disk into memory at the +; specified address. The sector number and destination address are taken +; from the data stack. +; +; When to use: +; - Reading data files +; - Accessing filesystem structures +; +; Example: +; 03 10 00 00 00 ; num 16 - sector number +; 03 00 20 00 00 ; num 0x2000 - destination address +; 12 ; disk@ - read sector 16 to address 0x2000 +; ; Result: 1024 bytes from sector 16 are now at address 0x2000 +; +; Technical details: +; - One sector = 1024 bytes (1 KB) +; - Sector numbering starts at 0 +; - Uses DOS file I/O to access the virtual disk image +; - The disk file must be open (initialized during emulator startup) +; +op_18_xdisk@: mov ebx, [es:edi] ; ebx = destination memory address (virtual) add ebx, [xms_addr] ; convert virtual address to physical address mov ecx, [es:edi+4] ; ecx = sector number @@ -72,7 +273,34 @@ op_18_xdisk@: ; Disk Load: ( sector mem -- ) call diskload ; load 1024 bytes: ecx=sector, ebx=destination jmp emu ; return to emulation loop -op_19_xdisk!: ; Disk Save: ( mem sector -- ) +; ============================================================================= +; Opcode 19: disk! (Disk Write) +; ============================================================================= +; Stack effect: ( addr sector -- ) +; +; Writes one sector (1024 bytes) from memory to the virtual disk at the +; specified sector number. The source address and sector number are taken +; from the data stack. +; +; When to use: +; - Saving program data to disk +; - Writing data files +; - Modifying filesystem structures +; - Persisting state across emulator sessions +; +; Example: +; 03 00 20 00 00 ; num 0x2000 - source address +; 03 10 00 00 00 ; num 16 - sector number +; 13 ; disk! - write from 0x2000 to sector 16 +; ; Result: 1024 bytes from address 0x2000 written to sector 16 +; +; Technical details: +; - One sector = 1024 bytes (1 KB) +; - Data is first copied to a temporary buffer in conventional memory +; before being written to disk (required for XMS memory access) +; - The disk file must be open for writing +; +op_19_xdisk!: mov ecx, [es:edi] ; ecx = sector number (top of stack) call file_seek ; seek to the correct position in disk file mov ecx, 1024 ; number of bytes to write