From 2af980df018ed988e0edd9b55514d84abcde0b4f Mon Sep 17 00:00:00 2001 From: Svjatoslav Agejenko Date: Mon, 23 Feb 2026 01:36:34 +0200 Subject: [PATCH] Expand opcode documentation (0-9): refined descriptions, clarified stack effects, added examples, and improved readability. --- emulator/opcodes_00_09.asm | 245 +++++++++++++++++++++++++++++++------ 1 file changed, 207 insertions(+), 38 deletions(-) diff --git a/emulator/opcodes_00_09.asm b/emulator/opcodes_00_09.asm index eadc940..bb59fb4 100644 --- a/emulator/opcodes_00_09.asm +++ b/emulator/opcodes_00_09.asm @@ -1,33 +1,153 @@ -; Opcodes 0-9: nop, halt, kbd@, num, jmp, call, inc, dec, dup, drop -; Opcode 0 (xnop) is handled inline as the emu loop entry point. -; Opcode 1 (xhalt/quit) is handled in the main emulator file. +; ============================================================================= +; Virtual CPU Opcodes 0-9: nop, halt, kbd@, num, jmp, call, 1+, 1-, dup, drop +; ============================================================================= +; +; This file contains the implementation of virtual machine opcodes 0 through 9. +; 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 +; +; Note: Opcode 0 (nop) and Opcode 1 (halt) are handled inline in emulator.asm. +; - nop simply falls through to the next instruction fetch +; - halt jumps to the quit routine to exit the emulator -op_02_kbd@: ; Read Keyboard: ( -- scancode ) +; ============================================================================= +; Opcode 2: kbd@ (Keyboard Read) +; ============================================================================= +; Stack effect: ( -- scancode ) +; +; Reads a keyboard scancode from the internal ring buffer and pushes it onto +; the data stack. If no key is available, pushes 0. +; +; When to use: +; - Reading raw keyboard input for games, text editors, or interactive programs +; - Building custom keyboard drivers or input handlers +; - Detecting specific key presses or releases (each key generates two +; scan codes: one for press, one for release) +; +; Scancode behavior: +; - Each key press generates a make code (scancode 1-127 typically) +; - Each key release generates a break code (make code + 128, i.e., high bit set) +; - Example: Pressing 'A' might give scancode 1E, releasing gives 9E +; +; Returns: +; 0 if no key is pending +; Otherwise, the scancode byte (1-255) +; +op_02_kbd@: call KB_read ; read scancode from keyboard ring buffer into dl sub edi, 4 ; make room on data stack for one element - mov [es:edi], dl ; push scancode onto data stack + mov [es:edi], dl ; push scancode onto data stack (as 32-bit value, zero-extended) - mov ah, 0bh ; DOS: check if key waiting in keyboard buffer + ; Drain the DOS keyboard buffer to prevent key bleeding into the emulator. + ; Without this, keys pressed during emulation would appear in the DOS + ; command prompt after exit. + mov ah, 0bh ; DOS: check if key waiting in keyboard buffer int 21h ; call DOS interrupt cmp al, 0h ; al=0 means no key waiting je emu ; if no key waiting, return to emulation loop - mov ah, 0 ; BIOS: read key from keyboard buffer - int 16h ; call BIOS interrupt (consume the key) + mov ah, 0 ; BIOS: read key from keyboard buffer (consume it) + int 16h ; call BIOS interrupt jmp emu ; return to emulation loop -op_03_xnum: ; Push Literal: ( -- n ) +; ============================================================================= +; Opcode 3: num (Push Literal Number) +; ============================================================================= +; Stack effect: ( -- n ) +; +; Pushes a 32-bit literal value onto the data stack. The value is read from +; the instruction stream immediately following the opcode. +; +; Instruction format: [opcode 03] [32-bit value, little-endian] +; +; When to use: +; - Pushing constant values for calculations +; - Loading addresses into the stack +; - Setting up parameters for other operations +; +; Example bytecode sequence: +; 03 ; num opcode +; 78 56 34 12 ; 32-bit value 0x12345678 (little-endian) +; ; Result: stack contains 0x12345678 +; + +op_03_xnum: mov edx, dword [es:esi] ; read 32-bit literal from instruction stream sub edi, 4 ; make room on data stack for one element mov [es:edi], edx ; push the literal value onto data stack - add esi, 4 ; advance instruction pointer past the 32-bit literal + add esi, 4 ; advance instruction pointer past the literal jmp emu ; return to emulation loop -op_04_xjmp: ; Unconditional Jump: ( -- ) +; ============================================================================= +; Opcode 4: jmp (Unconditional Jump) +; ============================================================================= +; Stack effect: ( -- ) +; +; Jumps unconditionally to the specified address. The target address is read +; from the instruction stream immediately following the opcode. +; +; Instruction format: [opcode 04] [32-bit target address, little-endian] +; +; When to use: +; - Creating infinite loops +; - Skipping over code blocks +; - Implementing control flow structures (loops, conditionals with if) +; +; Example bytecode sequence: +; 04 ; jmp opcode +; 00 10 00 00 ; target address 0x1000 +; ; Result: instruction pointer set to 0x1000 +; +; Note: The address is a VIRTUAL address in the virtual CPU's address space. +; The emulator converts it to a physical address internally. +; +op_04_xjmp: mov esi, dword [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 -op_05_xcall: ; Call Subroutine: ( -- ) R:( -- ret-addr ) +; ============================================================================= +; Opcode 5: call (Call Subroutine) +; ============================================================================= +; Stack effect: ( -- ) Return stack: ( -- ret-addr ) +; +; Calls a subroutine at the specified address. The return address (address of +; the instruction after the call) is pushed onto the return stack. +; +; Instruction format: [opcode 05] [32-bit target address, little-endian] +; +; When to use: +; - Calling reusable code blocks (functions, procedures) +; - Implementing Forth words that call other words +; - Building modular programs with subroutines +; +; Example bytecode sequence: +; 05 ; call opcode +; 00 20 00 00 ; target address 0x2000 +; ... ; code here is skipped, return address points here +; +; After execution: +; - Return stack has the return address pushed +; - Instruction pointer is at the target address +; - Use 'ret' (opcode 11) to return +; +; Note: The return stack is separate from the data stack and is used +; exclusively for return addresses and loop control variables. +; +op_05_xcall: mov edx, dword [es:esi] ; read 32-bit target address from instruction stream mov eax, [resp] ; load return stack pointer sub eax, 4 ; make room on return stack for one element @@ -40,50 +160,99 @@ op_05_xcall: ; Call Subroutine: ( -- ) R:( -- ret-addr ) add esi, [xms_addr] ; convert virtual address to physical address jmp emu ; return to emulation loop -op_06_xinc: ; Increment: ( n -- n+1 ) -; Increments the top of the data stack by 1. - +; ============================================================================= +; Opcode 6: 1+ (Increment) +; ============================================================================= +; Stack effect: ( n -- n+1 ) +; +; Adds 1 to the value on top of the data stack, modifying it in place. +; +; When to use: +; - Incrementing counters and loop indices +; - Advancing pointers through arrays or buffers +; - Computing successor values +; +; Example: +; Stack before: [ ... 5 ] +; After 1+: [ ... 6 ] +; +; Note: This is an efficient single-increment operation. For adding larger +; values, use the '+' (add) opcode instead. +; +op_06_xinc: inc dword [es:edi] ; increment the top of the data stack in place jmp emu ; return to emulation loop -op_07_xdec: ; Decrement: ( n -- n-1 ) +; ============================================================================= +; Opcode 7: 1- (Decrement) +; ============================================================================= +; Stack effect: ( n -- n-1 ) ; -; Decrements the top of the data stack by 1. +; Subtracts 1 from the value on top of the data stack, modifying it in place. ; -; Memory layout before execution: -; [es:edi] = n +; When to use: +; - Decrementing counters and loop indices +; - Moving pointers backward through arrays or buffers +; - Computing predecessor values ; -; Memory layout after execution: -; [es:edi] = n-1 - +; Example: +; Stack before: [ ... 10 ] +; After 1-: [ ... 9 ] +; +; Note: This is an efficient single-decrement operation. For subtracting +; larger values, use the '-' (subtract) opcode instead. +; +op_07_xdec: dec dword [es:edi] ; decrement the top of the data stack in place jmp emu ; return to emulation loop -op_08_xdup: ; Duplicate: ( n -- n n ) +; ============================================================================= +; Opcode 8: dup (Duplicate) +; ============================================================================= +; Stack effect: ( n -- n n ) ; -; Duplicates the top element of the data stack. +; Duplicates the value on top of the data stack, pushing a copy. ; -; Memory layout before execution: -; [es:edi] = n +; When to use: +; - Preserving a value before modifying it +; - Using the same value multiple times in a calculation +; - Displaying or logging values without removing them ; -; Memory layout after execution: -; [es:edi] = n (new top) -; [es:edi+4] = n (original top) - +; Example: +; Stack before: [ ... 42 ] +; After dup: [ ... 42 42 ] +; +; Common patterns: +; - dup @ : read from an address while keeping the address on stack +; - dup . : display a value while keeping it for further use +; - dup 1+ : create a copy and increment the copy +; +op_08_xdup: mov eax, [es:edi] ; copy top element to eax sub edi, 4 ; make room on data stack for one element mov [es:edi], eax ; push copy of top element onto stack jmp emu ; return to emulation loop -op_09_xdrop: ; Drop: ( n -- ) +; ============================================================================= +; Opcode 9: drop (Discard) +; ============================================================================= +; Stack effect: ( n -- ) ; -; Removes the top element from the data stack. +; Removes the value from the top of the data stack, discarding it. ; -; Memory layout before execution: -; [es:edi] = n +; When to use: +; - Discarding values that are no longer needed +; - Cleaning up the stack after a calculation +; - Consuming values from operations whose results aren't needed ; -; Memory layout after execution: -; Stack pointer is adjusted; n is no longer on stack - - add edi, 4 ; pop top element off the stack +; Example: +; Stack before: [ ... 100 42 ] +; After drop: [ ... 100 ] +; +; Common patterns: +; - addr @ drop : read from memory, discard result (side effect only) +; - Used after conditionals when the test result is no longer needed +; +op_09_xdrop: + add edi, 4 ; pop top element off the data stack jmp emu ; return to emulation loop -- 2.20.1