sd:isa
Differences
This shows you the differences between two versions of the page.
| Both sides previous revisionPrevious revisionNext revision | Previous revision | ||
| sd:isa [2026/06/16 08:10] – appledog | sd:isa [2026/09/08 04:07] (current) – external edit 127.0.0.1 | ||
|---|---|---|---|
| Line 3: | Line 3: | ||
| == ISA Overview | == ISA Overview | ||
| The SD-8516 instruction set is organized into four tiers. Numbering is canonical | The SD-8516 instruction set is organized into four tiers. Numbering is canonical | ||
| - | in decimal (as defined in '' | + | in decimal (as defined in '' |
| * **Tier 1 - Core** behaves like a RISC instruction set: small, orthogonal, and complete on its own. Target this first. Everything above it can be synthesized from the core if you have to. | * **Tier 1 - Core** behaves like a RISC instruction set: small, orthogonal, and complete on its own. Target this first. Everything above it can be synthesized from the core if you have to. | ||
| * **Tier 2 - Extended** are quality-of-life instructions for hand assemblers. '' | * **Tier 2 - Extended** are quality-of-life instructions for hand assemblers. '' | ||
| * **Tier 3 - CISC** are heavier, mostly VAX- and 680x0-flavored instructions: | * **Tier 3 - CISC** are heavier, mostly VAX- and 680x0-flavored instructions: | ||
| - | * **Tier 4 - Acceleration** are instructions added primarily to speed up a specific consumer: the LLVM backend ('' | + | * **Tier 4 - Acceleration** are instructions added primarily to speed up a specific consumer: the LLVM backend ('' |
| A handful of opcodes carry aliases (e.g. '' | A handful of opcodes carry aliases (e.g. '' | ||
| Line 27: | Line 27: | ||
| | 7 | $07 | [[# | | 7 | $07 | [[# | ||
| | 9 | $09 | [[# | | 9 | $09 | [[# | ||
| - | | 11 | $0B | PUSH | PUSH A | Push register onto stack | + | | 11 | $0B | [[#PUSH]] | PUSH A | Push register onto stack |
| - | | 12 | $0C | POP | POP Y | Pop stack into register | + | | 12 | $0C | [[#POP]] | POP Y | Pop stack into register |
| - | | 15 | $0F | PUSHF | PUSHF | Push flags | + | | 15 | $0F | [[#PUSHF]] | PUSHF | Push flags |
| - | | 16 | $10 | POPF | POPF | Pop flags | * | | + | | 16 | $10 | [[#POPF]] | POPF | Pop flags | * | |
| - | | 23 | $17 | INC | INC X | Increment register by 1 (any width) | + | | 23 | $17 | [[#INC]] | INC X | Increment register by 1 (any width) |
| - | | 24 | $18 | DEC | DEC Y | Decrement register by 1 (any width) | + | | 24 | $18 | [[#DEC]] | DEC Y | Decrement register by 1 (any width) |
| - | | 30 | $1E | ADD | ADD X, Y | X = X + Y | + | | 30 | $1E | [[#ADD]] | ADD X, Y | X = X + Y |
| - | | 31 | $1F | SUB | SUB X, Y | X = X - Y | + | | 31 | $1F | [[#SUB]] | SUB X, Y | X = X - Y |
| - | | 50 | $32 | AND | AND dst, src | Bitwise AND | | | + | | 50 | $32 | [[#AND]] | AND dst, src | Bitwise AND | | |
| - | | 51 | $33 | OR | OR dst, src | Bitwise OR | + | | 51 | $33 | [[#OR]] | OR dst, src | Bitwise OR |
| - | | 52 | $34 | XOR | XOR dst, src | Bitwise XOR | | | + | | 52 | $34 | [[#XOR]] | XOR dst, src | Bitwise XOR | | |
| - | | 53 | $35 | NOT | NOT reg | Bitwise NOT (invert all bits) | | | + | | 53 | $35 | [[#NOT]] | NOT reg | Bitwise NOT (invert all bits) | | |
| - | | 70 | $46 | SHL | SHL A | Shift left | Z N C | | + | | 70 | $46 | [[#SHL]] | SHL A | Shift left | Z N C | |
| - | | 71 | $47 | SHR | SHR A | Shift right | Z N C | | + | | 71 | $47 | [[#SHR]] | SHR A | Shift right | Z N C | |
| - | | 90 | $5A | CMP | CMP A, B | Compare (subtract, discard result) | + | | 90 | $5A | [[#CMP]] | CMP A, B | Compare (subtract, discard result) |
| - | | 91 | $5B | CMP_IMM | + | | 91 | $5B | [[#CMP_IMM]] | CMP A, $1 | Compare against immediate |
| - | | 100 | $64 | JMP | JMP @label | + | | 100 | $64 | [[#JMP]] | JMP @label |
| - | | 101 | $65 | JZ | JZ @label | + | | 101 | $65 | [[#JZ]] | JZ @label |
| - | | 102 | $66 | JNZ | JNZ @label | + | | 102 | $66 | [[#JNZ]] | JNZ @label |
| - | | 105 | $69 | JC | JC @label | + | | 105 | $69 | [[#JC]] | JC @label |
| - | | 106 | $6A | JNC | JNC @label | + | | 106 | $6A | [[#JNC]] | JNC @label |
| | 130 | $82 | [[# | | 130 | $82 | [[# | ||
| - | | 133 | $85 | RET | RET | Return from subroutine (pop IP) | | | + | | 133 | $85 | [[#RET]] | RET | Return from subroutine (pop IP) | | |
| - | | 134 | $86 | INT | INT $10 | Software interrupt | + | | 134 | $86 | [[#INT]] | INT $10 | Software interrupt |
| - | | 135 | $87 | RTI | RTI | Return from interrupt | + | | 135 | $87 | [[#RTI]] | RTI | Return from interrupt |
| - | | 182 | $B6 | SETF | SETF $80 | Set bits in the flags register | + | | 182 | $B6 | [[#SETF]] | SETF $80 | Set bits in the flags register |
| - | | 183 | $B7 | CLRF | CLRF $80 | Clear bits in the flags register | + | | 183 | $B7 | [[#CLRF]] | CLRF $80 | Clear bits in the flags register |
| - | | 184 | $B8 | TESTF | TESTF $80 | Non-destructive AND against flags | Z C | | + | | 184 | $B8 | [[#TESTF]] | TESTF $80 | Non-destructive AND against flags | Z C | |
| - | | 254 | $FE | NOP | NOP | No operation | + | | 254 | $FE | [[#NOP]] | NOP | No operation |
| - | | 255 | $FF | HALT | HALT | Halt CPU (sets HALT flag) | | | + | | 255 | $FF | [[#HALT]] | HALT | Halt CPU (sets HALT flag) | | |
| + | |||
| + | Although this is intended as a RISC core, there are some extras here, primarily LD-8bit hot path, which is done to speed up execution of 8 bit loads by about 30%. Additionally, | ||
| + | |||
| + | POPF however, is a strong canidate for removal. It would really only be needed for a kind of protected mode, interrupt mode or kernal mode, and we dont use it currently. | ||
| + | |||
| + | Finally, it may be interesting to fuse CMP and JZ. Imagine, any CMP and conditional jump could be a CJMP. ex. '' | ||
| + | |||
| + | Consider: | ||
| + | * No CMP exists in isolation | ||
| + | * No JZ exists without responding to a change in the Z flag. | ||
| + | * CJZ A, A can test for zero after a load if we really need one. | ||
| + | |||
| + | Further investigate: | ||
| + | * Does the cost of flag calculation outweight dispatch for how often a CMP occurrs? | ||
| === Tier 2: Extended (Quality of Life) | === Tier 2: Extended (Quality of Life) | ||
| Line 164: | Line 178: | ||
| === Tier 4: Acceleration (LLVM / Forth / Hardware) | === Tier 4: Acceleration (LLVM / Forth / Hardware) | ||
| - | Instructions added to remove work from a specific hot path rather than to add | + | Instructions added to remove work from a specific hot path rather than to add expressiveness. Each one has a primary consumer noted below. |
| - | expressiveness. Each one has a primary consumer noted below. | + | |
| + | Canidates for inclusion: LEA, fused CMP-Bcc and CMP-Jcc, conditional move, LD_IDX16, [[# | ||
| | # | hex | Mnemonic | Example | | # | hex | Mnemonic | Example | ||
| | 28 | $1C | [[# | | 28 | $1C | [[# | ||
| | 29 | $1D | [[# | | 29 | $1D | [[# | ||
| + | | 60 | $3C | [[# | ||
| + | | 61 | $3D | [[# | ||
| | 210 | $D2 | [[# | | 210 | $D2 | [[# | ||
| | 211 | $D3 | [[# | | 211 | $D3 | [[# | ||
| Line 339: | Line 356: | ||
| <wrap #jc /> | <wrap #jc /> | ||
| **''# | **''# | ||
| - | Jump if carry set (alias JAE for unsigned >=). '' | + | Jump if carry set (alias JAE for unsigned |
| <wrap #jnc /> | <wrap #jnc /> | ||
| Line 374: | Line 391: | ||
| <wrap #cmpc /> | <wrap #cmpc /> | ||
| **''# | **''# | ||
| - | Non-zero byte compare, useful for strings. Compares up to C characters; C returns | + | Non-zero byte compare, useful for strings. Compares up to C characters; C returns either the index of the first mismatch or the matched length. Sets ZERO on a full match; otherwise CARRY distinguishes the -1 / +1 ordering. |
| - | either the index of the first mismatch or the matched length. Sets ZERO on a full | + | |
| - | match; otherwise CARRY distinguishes the -1 / +1 ordering. | + | |
| - | CMPC allows early termination when '' | + | CMPC allows early termination when '' |
| - | semantics. If both strings reach a terminator at the same position with all prior | + | |
| - | bytes equal, the loop exits matched (Z=1, C=1). Only '' | + | |
| - | that point '' | + | |
| - | '' | + | |
| - | null-terminated strcmp in one instruction. | + | |
| <wrap #skpc /> | <wrap #skpc /> | ||
| Line 479: | Line 489: | ||
| <wrap #casetab /> | <wrap #casetab /> | ||
| **''# | **''# | ||
| - | Jump table held at a base address rather than inline. Indexes an address from the | + | Jump table held at a base address rather than inline. Indexes an address from the table at '' |
| - | table at '' | + | |
| - | //The precise indexing and out-of-bounds policy should be confirmed against the | + | |
| - | current emulator handler: the older CASE3 (computed table at base) and CASEB | + | |
| - | (scanned '' | + | |
| - | table-at-base form.// | + | |
| <wrap #cvtan /> | <wrap #cvtan /> | ||
| **''# | **''# | ||
| - | Convert ASCII to number. Maps ' | + | Convert ASCII to number. Maps ' |
| - | a decimal digit (0-9) and Carry if it is not a hex digit (0-15). Also a fast digit | + | |
| - | test: '' | + | |
| - | Designed for zoned decimal, and works for zoned hex. | + | |
| <wrap #cvtna /> | <wrap #cvtna /> | ||
| Line 499: | Line 501: | ||
| <wrap #idx /> | <wrap #idx /> | ||
| - | **''# | + | **''# |
| - | Indexed load/store: a 24-bit pointer register plus a displacement. The '' | + | |
| - | take a signed byte immediate (-128..+127); | + | |
| - | These back the compiler' | + | |
| - | pointer base must be a 24-bit register; the effective address reads the base at its | + | |
| true width before applying the offset. | true width before applying the offset. | ||
| Line 521: | Line 519: | ||
| <codify armasm> | <codify armasm> | ||
| - | LDFLX $F000 ; loop start | + | LDFLX $F000 ; |
| - | LDA #1 | + | LDA #1 ; start at 1 |
| - | STA [FLX] | + | STA [FLX] ; write starting counter at first four bytes |
| - | LDA #1000 | + | LDA #1000 ; go until 1000 |
| - | STA [FLX+4] | + | STA [FLX+4] |
| loop: | loop: | ||
| - | LSTEPM | + | LSTEPM |
| JNZ @loop | JNZ @loop | ||
| </ | </ | ||
| - | Unlike a DEC loop it counts upward and supports an arbitrary start. | + | Unlike a DEC loop it counts upward and supports an arbitrary start. |
| - | for Forth and has been generalized by [[# | + | |
| - | LSTEPM | + | Only used by Forth. It is recommended to use LSTEP instead; this instruction |
| <wrap #ttos /> | <wrap #ttos /> | ||
| Line 554: | Line 552: | ||
| LSTEP C, @loop | LSTEP C, @loop | ||
| </ | </ | ||
| + | |||
| + | |||
| + | <wrap #mac /> | ||
| + | **''# | ||
| + | Multiply-accumulate: | ||
| + | written. The result takes the accumulator' | ||
| + | read at their own -- so '' | ||
| + | |||
| + | **Why it exists.** Every tile lookup in a tile game computes | ||
| + | '' | ||
| + | rendered cell and every walkability test; it was the single most executed | ||
| + | arithmetic sequence in the program. '' | ||
| + | and the scratch register the product needed disappears with it. | ||
| + | |||
| + | <codify armasm> | ||
| + | ; ELM = tile_base + (y * width + x) * 2 | ||
| + | LDB [@level_dim] | ||
| + | MOV Z, X ; Z = column | ||
| + | MAC Z, Y, BL ; + row * width | ||
| + | ADD Z, Z ; two bytes per tile | ||
| + | LDELM [@level_tiles] | ||
| + | ADD ELM, Z | ||
| + | </ | ||
| + | |||
| + | **Where else it helps.** Anything shaped '' | ||
| + | two MACs and no temporary at all: | ||
| + | |||
| + | <codify armasm> | ||
| + | LDA #0 | ||
| + | MAC A, I, I ; A = dx*dx | ||
| + | MAC A, J, J ; A += dy*dy | ||
| + | </ | ||
| + | |||
| + | Also dot products, fixed-point scaling, Horner' | ||
| + | inner loop of any filter, matrix or checksum routine. | ||
| + | |||
| + | **Recognising the pattern.** Look for a '' | ||
| + | to something and then never used again, or a '' | ||
| + | scratch register. If that register exists only to carry the product from the | ||
| + | multiply to the add, MAC removes the register and an instruction together. | ||
| + | |||
| + | **Measured.** Adding MAC to rogueima' | ||
| + | 5.0% off the cost of a turn. | ||
| + | |||
| + | |||
| + | <wrap #absd /> | ||
| + | **''# | ||
| + | Absolute difference: '' | ||
| + | always clear and Z is set exactly when the two operands are equal. '' | ||
| + | read at its own width. | ||
| + | |||
| + | **Why it exists.** Distance work needs the size of a difference, not its sign, | ||
| + | and getting one without this instruction costs four to six instructions of | ||
| + | compare, branch, and subtract-the-other-way round -- **per axis**. Rogueima' | ||
| + | line-of-sight does it twice for every square it examines, a few thousand times | ||
| + | a turn. | ||
| + | |||
| + | <codify armasm> | ||
| + | ; dx = |x - px|, without a branch in sight | ||
| + | LDA #0 | ||
| + | LDAL [@PX] | ||
| + | LDI #0 | ||
| + | MOV IL, XL | ||
| + | ABSD I, A ; I = |x - px| | ||
| + | </ | ||
| + | |||
| + | **Where else it helps.** Manhattan and Chebyshev distance, "are these two within | ||
| + | N of each other", | ||
| + | audio or images. It doubles as a branch-free equality test: Z is set precisely | ||
| + | when the operands match, so you get '' | ||
| + | |||
| + | **Recognising the pattern.** Look for a compare followed by two subtractions in | ||
| + | opposite orders down the two arms of a branch -- any | ||
| + | '' | ||
| + | |||
| + | **Measured.** 7.2% off the cost of a turn in rogueima -- more than MAC, because | ||
| + | the branches it removes were unpredictable ones inside the hottest loop. | ||
| + | |||
| + | |||
| + | <wrap #ptrace /> | ||
| + | **'' | ||
| + | //No opcode assigned. This is a specification for review, not a description of | ||
| + | the machine.// | ||
| + | |||
| + | Walk a line through a tile array, stopping at the first tile that blocks, and | ||
| + | optionally marking every tile visited on the way. A PPU instruction in the same | ||
| + | family as '' | ||
| + | |||
| + | ^ ^ ^ | ||
| + | | **in** | '' | ||
| + | | ::: | '' | ||
| + | | ::: | '' | ||
| + | | ::: | '' | ||
| + | | ::: | '' | ||
| + | | **out** | '' | ||
| + | | ::: | '' | ||
| + | | ::: | '' | ||
| + | |||
| + | At every tile along the line it ORs '' | ||
| + | '' | ||
| + | the edge of the array. A '' | ||
| + | projectile or a "can this monster see me" test wants. | ||
| + | |||
| + | <codify armasm> | ||
| + | ; one line-of-sight ray, marking SEEN|VISIBLE and stopping at anything opaque | ||
| + | LDELM [@level_tiles] | ||
| + | LDB [@level_dim] | ||
| + | MOV K, B | ||
| + | LDBL #2 ; two bytes per tile | ||
| + | LDBH #1 ; the flags are the second of them | ||
| + | LDCL @TF_OPAQUE | ||
| + | LDCH $0C ; TF_SEEN | TF_VISIBLE | ||
| + | LDXL [@PX] | ||
| + | LDYL [@PY] | ||
| + | LDI [@target_x] | ||
| + | LDJ [@target_y] | ||
| + | PTRACE | ||
| + | </ | ||
| + | |||
| + | **Why.** Rogueima' | ||
| + | 45,000 of the 62,577 instructions a turn costs -- 48 ms at 1.3 MIPS. Each ray | ||
| + | is roughly 540 instructions of Bresenham stepping and per-tile testing. As one | ||
| + | instruction, | ||
| + | instead of 540, and a turn drops to roughly 15 ms. | ||
| + | |||
| + | **Where else.** The masks are what make it general. Projectile and thrown-object | ||
| + | paths, wand and breath-weapon beams, monster targeting, swept collision in a | ||
| + | tile platformer, and post-pathfinding line smoothing ("can I walk straight from | ||
| + | here to there" | ||
| + | '' | ||
| + | |||
| + | The application that makes it a platform capability rather than one game's | ||
| + | helper is a **Wolfenstein-style raycaster**: | ||
| + | tile map, 320 rays a frame at 60 Hz. In software that is several million | ||
| + | instructions a second, more than this machine has, so the genre is off the | ||
| + | table. As an instruction it is about 19,000 a second plus the column drawing. | ||
| + | |||
| + | **A design fork worth settling first.** Two different things wear similar | ||
| + | clothes here: | ||
| + | |||
| + | * **Bresenham** visits the tiles a line passes through. Symmetric, integer, | ||
| + | and what line of sight, projectiles, | ||
| + | * **DDA** steps to each grid // | ||
| + | distance. That is what a perspective raycaster needs -- Wolfenstein used it | ||
| + | because Bresenham' | ||
| + | a wrong distance is a wrong wall height and a fisheye. | ||
| + | |||
| + | '' | ||
| + | wants a sibling -- '' | ||
| + | of the tile was struck, for texture mapping. Trying to make one instruction do | ||
| + | both would make both worse. | ||
| + | |||
| + | The name deliberately says the job rather than the algorithm, so that the pair | ||
| + | reads '' | ||
| + | |||
| + | **Open questions.** | ||
| + | - Six registers in is a lot. Is a descriptor block at '' | ||
| + | '' | ||
| + | level-scoped rather than per-call? | ||
| + | - Should it also report the tile it stopped ON versus the last clear tile | ||
| + | before it? Line of sight wants the blocker marked; a projectile wants the | ||
| + | square in front of it. | ||
| + | - Should a zero-length line (start == target) mark the start square? | ||
| + | |||
| + | **Precedent.** No CPU precedent that I know of, but a strong coprocessor one: | ||
| + | this is what blitters did. The Amiga blitter (1985) had a hardware Bresenham | ||
| + | line mode, and the TMS34010 (1986) was a graphics processor with '' | ||
| + | instruction. It belongs to that tradition, which is where the rest of the PPU | ||
| + | already lives. | ||
| + | |||
sd/isa.1781597433.txt.gz · Last modified: by appledog
