docs: correct opcode documentation against emulator implementation
authorSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Oct 2026 07:27:31 +0000 (10:27 +0300)
committerSvjatoslav Agejenko <svjatoslav@svjatoslav.eu>
Sun, 11 Oct 2026 07:27:31 +0000 (10:27 +0300)
Full audit of all 48 opcode entries against emulator/*.asm; every doc
claim now matches the silicon. Highlights:

- opcodes_20_29.org: fix c@/c! cross-references (opcodes 12/13, not
  16/17), fix inverted ">" bytecode example result, drop wrong swap in
  division pattern, replace broken <=/<> formulas with verified ones
  (> not; 2dup < rot rot > or), add 2dup to underflowing sort
  patterns, note idiv quotient-overflow crash.
- opcodes_40_49.org: vidmap copies 16 KB per granule (4096 dwords),
  not 4 KB; mouse@ returns int33h motion deltas, not absolute
  coordinates; cmove overlap direction was inverted and its example
  pushed args in wrong order; image structure offset 8 is inline
  pixels, not a data pointer; charput example had x/y swapped; title
  range corrected to 40-47; dangling TODO removed.
- opcodes_10_19.org: disk@/disk! parameter bullets swapped which
  argument is TOS; disk file is disk.raw; past-EOF reads return stale
  data, not zeros.
- opcodes_30_39.org: fix i2/i3 example results (2 and 3, not 1), fix
  false shr-by-31 corner case, remove stray unclosed #+begin_example,
  correct or/xor TOS labels, note cprt@ port width and zero-extension.
- opcodes_00_09.org: kbd@ writes only the low byte of the pushed cell;
  document host DOS keyboard-buffer drain side effect.
- virtual machine.org: mouse@ row shows deltas; precise opcode count
  (48 slots, 16 unused).
- emulator comments: fix the wrong comments the docs had copied
  (opcode numbers, backwards ">" examples, broken <> formula, kbd@
  "zero-extended" claim, misleading IMUL edx note).

HTML re-exports clean for all 6 changed pages (incremental exporter).

Documentation/opcodes_00_09.org
Documentation/opcodes_10_19.org
Documentation/opcodes_20_29.org
Documentation/opcodes_30_39.org
Documentation/opcodes_40_49.org
Documentation/virtual machine.org
emulator/opcodes_00_09.asm
emulator/opcodes_20_29.asm

index 7610959..797fbb0 100644 (file)
@@ -144,9 +144,8 @@ onto the data stack. This is a non-blocking operation: if no key is
 pending, it pushes 0 immediately.
 
 Only the low byte of the pushed stack cell is written; the upper three
-bytes keep whatever the stack memory previously held. Read the result
-with =c@=-style byte semantics (e.g. mask with =FF and=) before
-comparing as a full 32-bit value.
+bytes keep whatever the stack memory previously held. Compare or mask
+only the low byte (=FF=) rather than the full 32-bit cell.
 
 ** Scan Code Behavior
 
index 336b62b..0a07955 100644 (file)
@@ -410,8 +410,8 @@ specified address.
 
 *Parameters:*
 
-- *sector* (top of stack): Sector number to read from (0-based)
-- *addr* (second): Destination memory address for the data
+- *sector* (second on stack): Sector number to read from (0-based)
+- *addr* (top of stack): Destination memory address for the data
 
 *When to Use:*
 
@@ -435,12 +435,15 @@ Read sector 16 into memory at 0x2000:
 - 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
+- The virtual disk is a file named =disk.raw= in the emulator's working
+  directory
 
 ** 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
+- Reading beyond the end of the disk file is not detected — the DOS
+  read result is never checked, so the destination receives stale data
+  left over from the previous sector read
 - The disk is accessed through DOS file handles, not BIOS interrupts
 
 * 19: disk! (Write Disk Sector)
@@ -464,8 +467,8 @@ specified sector number.
 
 *Parameters:*
 
-- *addr* (top of stack): Source memory address
-- *sector* (second): Sector number to write to (0-based)
+- *addr* (second on stack): Source memory address
+- *sector* (top of stack): Sector number to write to (0-based)
 
 *When to Use:*
 
index 35adc9b..ce8b280 100644 (file)
@@ -101,7 +101,7 @@ In Fifth source code:
 
 ** Important Notes
 
-- This is the 32-bit equivalent of =c@= (opcode 16)
+- This is the 32-bit equivalent of =c@= (opcode 12)
 - Virtual address translation provides memory management
 - Reads full 32-bit words, not bytes
 - The address is consumed and replaced with the value
@@ -179,8 +179,9 @@ In Fifth source code:
 
 ** Important Notes
 
-- This is the 32-bit equivalent of =c!= (opcode 17)
-- Virtual address translation provides memory protection
+- This is the 32-bit equivalent of =c!= (opcode 13)
+- Virtual address translation relocates VM addresses into the XMS
+  memory block (it is not memory protection)
 - Order is value then addr (Forth standard: value to address)
 - Both arguments are consumed (stack cleaned up)
 - No bounds checking is performed
@@ -270,12 +271,6 @@ In Fifth source code:
 
 ** Common Patterns
 
-*** Compare Without Consuming
-
-#+BEGIN_SRC fifth
-a b over >      \ compare a with b, keeping both on stack
-#+END_SRC
-
 *** Use Value Twice
 
 #+BEGIN_SRC fifth
@@ -288,7 +283,6 @@ a b over +      \ compute a + b, keeping a on stack
 addr over @     \ read from addr, keeping addr for later use
 #+END_SRC
 
----------------------------------------------------------------------
 
 * 23: swap (Swap)
 :PROPERTIES:
@@ -721,12 +715,13 @@ total parts /   \ divide total into equal parts
 *** Correct Division Order
 
 #+BEGIN_SRC fifth
-dividend divisor swap /  \ compute dividend / divisor
+dividend divisor /  \ compute dividend / divisor (divisor on top)
 #+END_SRC
 
 ** Edge Cases
 
 - Division by zero: CPU exception (crash)
+- Quotient overflow (e.g. =80000000 / -1=) also raises a CPU exception
 - =Anything / 1 = anything=
 - =0 / anything = 0=
 - Negative division follows standard signed rules
@@ -784,7 +779,7 @@ Compare two numbers:
 #+BEGIN_SRC
 03 05 00 00 00  ; num 5    - stack: [ 5 ]
 03 03 00 00 00  ; num 3    - stack: [ 5 3 ]
-1C              ; >        - stack: [ 0 ] (false, 5 is not > 3)
+1C              ; >        - stack: [ -1 ] (true, 5 > 3)
 #+END_SRC
 
 In Fifth source code:
@@ -821,7 +816,7 @@ value max > abort" Value too large"
 *** Sorting Logic
 
 #+BEGIN_SRC fifth
-a b > if swap then  \ ensure smaller value is on top
+a b 2dup > if swap then  \ ensure smaller value is on top
 #+END_SRC
 
 ** Edge Cases
@@ -920,7 +915,7 @@ value min < abort" Value too small"
 *** Sorting Logic
 
 #+BEGIN_SRC fifth
-a b < if swap then  \ ensure larger value is on top
+a b 2dup < if swap then  \ ensure larger value is on top
 #+END_SRC
 
 *** Equality Check
@@ -942,12 +937,14 @@ then
 - Equal values return 0 (false)
 - Negative numbers compared correctly
 - Works with full 32-bit signed range
-- Relationship to other comparisons: =a b <= is equivalent to= =b a > not=
+- Relationship to other comparisons: =a b <= is equivalent to=
+  =a b > not=
 
 ** Relationship to Other Comparisons
 
 The less than operation is complementary to greater than:
 
-- =a b < is equivalent to= =b a >=
-- =a b <= (less than or equal) can be implemented as=: =over over < > or=
-- =a b <> (not equal) can be implemented as=: =over over <> = drop=
\ No newline at end of file
+- =a b < is equivalent to= =b a >
+- =a b <= (less than or equal) can be implemented as=: => not=
+  (=a b > not=)
+- =a b <> (not equal) can be implemented as=: =2dup < rot rot > or=
index 3d87fda..55242f9 100644 (file)
@@ -68,7 +68,9 @@
 - *Stack Effect:* =port -- byte=
 - *Description:* Reads a byte from the specified hardware I/O port.
 - *Notes:* Uses IN instruction to read from the port. Replaces the port
-  address with the read byte.
+  address with the read byte. The port number is a 16-bit I/O address
+  (only the low 16 bits are used); the read byte is zero-extended to a
+  full 32-bit cell.
 - *Example:*
   #+begin_example
   0x60  ; Keyboard port
   push push
   i2
   #+end_example
-  After execution, data stack contains 1.
+  After execution, data stack contains 2 (the return stack top is 1,
+  its second item is 2).
 
 * 35: i3
 :PROPERTIES:
   push push push
   i3
   #+end_example
-  After execution, data stack contains 1.
+  After execution, data stack contains 3 (the return stack top is 1,
+  its third item is 3).
 
 * 36: shl
 :PROPERTIES:
@@ -166,7 +170,6 @@ shl
 #+end_example
 Result: FF0 (0x00000FF0 = 4080 decimal). The low 4 bits (F) were
 shifted up, and the upper bits remain zero.
-#+begin_example
 
 * 37: shr
 :PROPERTIES:
@@ -192,7 +195,8 @@ shifted up, and the upper bits remain zero.
   =count mod 32=. For example, shift by 32 results in no change,
   shift by 33 shifts by 1.
 - =value = 0=: Result is always 0 regardless of count.
-- Shifting by 31 always results in 0 (all bits shifted out).
+- Shifting by 31 leaves only the original bit 31 as bit 0 of the result
+  (e.g. =80000000 >> 31 = 1=); all other bits are shifted out.
 
 *Examples:*
 #+begin_example
@@ -216,7 +220,7 @@ Result: 0. The single bit was shifted out and discarded.
 :END:
 - *Stack Effect:* =n1 n2 -- n1|n2=
 - *Description:* Performs a bitwise OR operation on the top two values.
-- *Notes:* The top value (n1) is ORed with the second value (n2).
+- *Notes:* The top value (n2) is ORed with the second value (n1).
 - *Example:*
   #+begin_example
   1 2
@@ -231,7 +235,7 @@ Result: 0. The single bit was shifted out and discarded.
 :END:
 - *Stack Effect:* =n1 n2 -- n1^n2=
 - *Description:* Performs a bitwise XOR operation on the top two values.
-- *Notes:* The top value (n1) is XORed with the second value (n2).
+- *Notes:* The top value (n2) is XORed with the second value (n1).
 - *Example:*
   #+begin_example
   1 2
index 1186994..98a87a7 100644 (file)
@@ -1,5 +1,5 @@
 #+SETUPFILE: ~/.emacs.d/org-styles/html/darksun.theme
-#+TITLE: Fifth - virtual machine opcodes 40 -- 49
+#+TITLE: Fifth - virtual machine opcodes 40 -- 47
 #+AUTHOR: Svjatoslav Agejenko
 #+LANGUAGE: en
 
@@ -50,14 +50,15 @@ Parameters:
      graphics mode).
    - Initialize VESA-related variables (e.g., =gra= for page number).
 2. *VESA Page Mapping*:
-   - For each VESA page (up to 19 pages for 640x480x8 mode):
+   - For each of the 19 VESA pages (hardcoded for 640x480x8 mode):
      - Set VESA page using interrupt =0x10= with =AX = 0x4F05=.
      - The page number is stored in =gra=.
 3. *Memory Copying*:
    - For each page:
-     - Copy 4096 bytes (4KB) from source address to video memory.
+     - Copy 16384 bytes (16 KB, 4096 dwords) from source address to
+       video memory.
      - Use =STOSD= to write 32-bit words for efficiency.
-   - The source address is incremented by 4KB after each page.
+   - The source address is incremented by 16 KB after each page.
 4. *Final Cleanup*:
    - Restore original segments and clean up stack.
 
@@ -72,11 +73,12 @@ vidmap
 ** Notes
 
 - The operation assumes the video mode is set to VESA 640x480x8 (mode
-  =0x101=).
-- The exact number of pages copied depends on the video mode (19 pages
-  for 640x480x8).
-- Requires VESA BIOS support and proper initialization via
-  =set_vesa_mode=.
+  =0x101=), which the emulator sets once at startup (there is no
+  separate =set_vesa_mode= routine).
+- The page count is hardcoded to 19. Total transfer is
+  19 x 16384 = 311,296 bytes — that is 4,096 bytes PAST the end of a
+  307,200-byte 640x480x8 image, so the source buffer must be padded
+  accordingly.
 
 * 41: mouse@
 :PROPERTIES:
@@ -84,31 +86,37 @@ vidmap
 :ID:       b4bce653-d38f-45ed-82ec-13ca78c9860b
 :END:
 Stack Footprint:
-: -- x y buttons
+: -- dx dy buttons
 
 *Description:*
 
-Reads the current mouse position and button states from the mouse
-driver. Returns three values on the stack:
-- X-coordinate,
-- Y-coordinate,
+Reads mouse motion and button states from the DOS mouse driver
+(=int 33h=). Returns three values on the stack:
+- horizontal motion delta,
+- vertical motion delta,
 - button states.
 
 
 - *Returns*:
-  - *x* (32-bit): Current X-coordinate of the mouse cursor (0-based).
-  - *y* (32-bit): Current Y-coordinate of the mouse cursor (0-based).
+  - *dx* (32-bit): Horizontal motion since the last call, in mickeys
+    (signed; negative = moved left).
+  - *dy* (32-bit): Vertical motion since the last call, in mickeys
+    (signed; negative = moved up).
   - *buttons* (32-bit): Bitmask of button states:
     - Bit 0: Left button (1 = pressed)
     - Bit 1: Right button (1 = pressed)
     - Bit 2: Middle button (1 = pressed)
 
+Note: the values are motion DELTAS (read via mouse function =0Bh=), not
+absolute cursor coordinates. Accumulate them to track a cursor
+position.
+
 *Example:*
 
 #+begin_example
 mouse@
 #+end_example
-After execution, stack contains buttons (top), y, x (bottom).
+After execution, stack contains buttons (top), dy, dx (bottom).
 
 * 42: vidput - put image1 into image2, at location x, y
 :PROPERTIES:
@@ -116,7 +124,7 @@ After execution, stack contains buttons (top), y, x (bottom).
 :ID:       238e8b03-57b6-424d-bfee-b6bb652cefbc
 :END:
 Stack Footprint:
-: addr1 addr2 x y --=
+: addr1 addr2 x y --
 
 Copies a portion of image1 to image2 at the specified (x, y)
 coordinates with clipping. Unlike =tvidput=, there is no transparency
@@ -125,9 +133,9 @@ support - every pixel is copied regardless of value.
 *Parameters:*
 
 - *addr1* (32-bit): Address of the source image structure (width,
-  height, data address).
+  height, inline pixel data).
 - *addr2* (32-bit): Address of the destination image structure (width,
-  height, data address).
+  height, inline pixel data).
 - *x* (32-bit): X-coordinate in the destination image where the copy
   starts.
 - *y* (32-bit): Y-coordinate in the destination image where the copy
@@ -138,7 +146,9 @@ support - every pixel is copied regardless of value.
 Identical to =tvidput=:
 - *Offset 0*: Image width (32-bit)
 - *Offset 4*: Image height (32-bit)
-- *Offset 8*: Base address of image data (32-bit pointer)
+- *Offset 8*: Pixel data, stored INLINE — one byte per pixel,
+  row-major, starting immediately after the 8-byte header (this is NOT
+  a pointer)
 
 *Example:*
 #+begin_example
@@ -161,13 +171,14 @@ vidput
 Copies =len= bytes from =addr1= to =addr2=. Handles overlapping memory
 regions correctly.
 
-- *Notes:* If =addr1 > addr2=, copies backwards to avoid data
-  corruption; otherwise copies forwards.
+- *Notes:* Copy direction is chosen for overlap safety: if =addr1 >
+  addr2= (source above destination) it copies FORWARDS; otherwise it
+  copies BACKWARDS.
 - *Example:*
   #+begin_example
-  100  ; Length
-  0x2000  ; Destination address
   0x1000  ; Source address
+  0x2000  ; Destination address
+  100  ; Length
   cmove
   #+end_example
 
@@ -205,9 +216,9 @@ destination.
 *Parameters:*
 
 - *addr1* (32-bit): Address of the source image structure (contains
-  width, height, and data address).
-- *addr2* (32-bit): Address of the destination image structure (contains
-  width, height, and data address).
+  width, height, and inline pixel data).
+- *addr2* (32-bit): Address of the destination image structure
+  (contains width, height, and inline pixel data).
 - *x* (32-bit): X-coordinate in the destination image where the copy
   starts.
 - *y* (32-bit): Y-coordinate in the destination image where the copy
@@ -218,7 +229,9 @@ destination.
 Each image structure is stored in memory as follows:
 - *Offset 0*: Image width (32-bit integer)
 - *Offset 4*: Image height (32-bit integer)
-- *Offset 8*: Base address of image data (32-bit pointer)
+- *Offset 8*: Pixel data, stored INLINE — one byte per pixel,
+  row-major, starting immediately after the 8-byte header (this is NOT
+  a pointer)
 
 *Example:*
 #+begin_example
@@ -267,7 +280,8 @@ Draws an 8x8 character from a source memory buffer to a destination
 image buffer at the specified (x, y) coordinates. Each byte in the
 source buffer represents one row of the character (8 bits per row),
 where each bit determines whether to use the foreground or background
-color for that pixel.
+color for that pixel. Bit 7 (the MSB) of each byte is the leftmost
+pixel of the row; glyphs are fixed at 8x8 pixels.
 
 *Parameters:*
 
@@ -276,8 +290,10 @@ color for that pixel.
 - *colorbg* (1 byte): Background color value (0-255).
 - *addrsrc* (32-bit): Memory address pointing to 8 bytes of character
   data. Each byte represents one row of the character (8 bits per row).
-- *addrdest* (32-bit): Base address of the destination image buffer in
-  memory.
+- *addrdest* (32-bit): Address of the destination image STRUCTURE —
+  i.e. width/height header followed by inline pixel data, as used by
+  =vidput=. NOT a raw framebuffer address: the width field is read as
+  the row stride.
 - *x* (32-bit): X-coordinate (0-based) where the character's left edge
   starts.
 - *y* (32-bit): Y-coordinate (0-based) where the character's top edge
@@ -285,16 +301,13 @@ color for that pixel.
 
 *Example:*
 
-TODO: Create dedicated page that explains this instruction in
-depth. Link to this new page from current one.
-
 #+begin_example
 0xFF  ; White foreground
 0x00  ; Black background
 0x3000  ; Character data address
-0x5000  ; Video memory address
-20      ; y
+0x5000  ; Destination image structure
 10      ; x
+20      ; y
 charput
 #+end_example
 
index fddabf3..05eda38 100644 (file)
@@ -36,8 +36,9 @@ virtual CPU. The virtual CPU emulator runs as a COM executable
 (emulator.com) under MS-DOS.
 
 The virtual machine is designed to be simple yet powerful, featuring
-two stacks (data stack and return stack), approximately 50
-instructions (opcodes), and a theoretical 4GB flat address space. It
+two stacks (data stack and return stack), 48 opcode slots (opcodes
+0-47; opcode 16 is currently unused), and a theoretical 4GB flat
+address space. It
 serves as the foundation for the Fifth language — a Forth-inspired,
 interactive programming environment where users can define, extend,
 and modify system behavior at runtime.
@@ -153,7 +154,7 @@ in FIFTH programming language):
 | 38 | [[file:opcodes_30_39.org::#opcode-or][or]]       | n1 n2 -- result       | Bitwise OR of top two items on data stack                |                                                                     |
 | 39 | [[file:opcodes_30_39.org::#opcode-xor][xor]]      | n1 n2 -- result       | Bitwise XOR of top two items on data stack               |                                                                     |
 | 40 | [[file:opcodes_40_49.org::#opcode-vidmap][vidmap]]   | addr --               | Copy memory to video memory                              |                                                                     |
-| 41 | [[file:opcodes_40_49.org::#opcode-mouse][mouse@]]   | -- x y buttons        | Read mouse coordinates and button states                 |                                                                     |
+| 41 | [[file:opcodes_40_49.org::#opcode-mouse][mouse@]]   | -- dx dy buttons      | Read mouse motion deltas (mickeys) and button states     |                                                                     |
 | 42 | [[file:opcodes_40_49.org::#opcode-vidput][vidput]]   | addr1 addr2 x y --    | Blit image1 to image2 at (x,y) without transparency      |                                                                     |
 | 43 | [[file:opcodes_40_49.org::#opcode-cmove][cmove]]    | addr1 addr2 len --    | Copy memory block from addr1 to addr2                    |                                                                     |
 | 44 | [[file:opcodes_40_49.org::#opcode-cfill][cfill]]    | byte addr len --      | Fill memory with specified byte value                    |                                                                     |
index bb59fb4..9f53b5e 100644 (file)
 ;   - 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:
+; Returns (in the LOW BYTE of the stack cell only; upper bytes are stale):
 ;   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 (as 32-bit value, zero-extended)
+       mov         [es:edi], dl        ; push scancode byte onto data stack (upper 3 bytes of the cell are NOT cleared)
 
     ; Drain the DOS keyboard buffer to prevent key bleeding into the emulator.
     ; Without this, keys pressed during emulation would appear in the DOS
index b92a840..91b6d06 100644 (file)
@@ -27,7 +27,7 @@ op_20_x@:                       ; Fetch 32-bit: ( addr -- value )
 ;   - Replaces address with value on stack
 ;
 ; Notes:
-;   - This is the 32-bit equivalent of c@ (opcode 16)
+;   - This is the 32-bit equivalent of c@ (opcode 12)
 ;   - Virtual address translation allows memory management
 ;   - Reads full 32-bit words, not bytes
        mov     eax, [es:edi]           ; eax = virtual address from top of data stack
@@ -52,8 +52,8 @@ op_21_x!:                       ; Store 32-bit: ( value addr -- )
 ;   - Removes both arguments from stack
 ;
 ; Notes:
-;   - This is the 32-bit equivalent of c! (opcode 17)
-;   - Virtual address translation provides memory protection
+;   - This is the 32-bit equivalent of c! (opcode 13)
+;   - Virtual address translation relocates VM addresses into the XMS block
 ;   - Order is value then addr (Forth standard: value to address)
 ;   - Both arguments are consumed (stack cleaned up)
        mov         eax, [es:edi]       ; eax = virtual address (top of stack)
@@ -184,7 +184,8 @@ op_26_xmul:                     ; Multiply (signed): ( n1 n2 -- n1*n2 )
 ; Operation:
 ;   - Reads top item (n2) into eax
 ;   - Pops n2 off stack (n1 becomes new TOS)
-;   - Clears edx (required for signed multiply)
+;   - Clears edx (not strictly required: one-operand IMUL overwrites
+;     edx:eax anyway)
 ;   - Performs signed multiply: edx:eax = eax * n1
 ;   - Stores low 32 bits (eax) as result
 ;
@@ -277,8 +278,8 @@ op_28_xgreat:                   ; Greater Than: ( a b -- flag )
 ;   -  0 = false (zero)
 ;
 ; Common usage:
-;   5 3 > -> 0 (false, 5 is not greater than 3)
-;   3 5 > -> -1 (true, 3 is greater than 5)
+;   5 3 > -> -1 (true, 5 is greater than 3)
+;   3 5 > -> 0 (false, 3 is not greater than 5)
 ;
 ; Edge cases:
 ;   - Equal values return 0 (false)
@@ -330,7 +331,7 @@ op_29_xless:                    ; Less Than: ( a b -- flag )
 ;
 ; Relationship to other comparisons:
 ;   a b <  is equivalent to  b a >
-;   a b <> (not equal) can be implemented as: over over <> = drop
+;   a b <> (not equal) can be implemented as: 2dup < rot rot > or
        mov         eax, [es:edi]       ; eax = b (top of stack)
        add         edi, 4              ; pop b off the stack; a is now the new top
        mov         edx, 0              ; edx = 0 (default: false)