Class: PSX::CPU
- Inherits:
-
Object
- Object
- PSX::CPU
- Defined in:
- lib/psx/cpu.rb
Defined Under Namespace
Classes: ExecutionError
Constant Summary collapse
- RESET_VECTOR =
BIOS entry point
0xBFC0_0000- BIOS_A_DISPATCH =
Physical address of the A/B/C BIOS jump tables in low RAM
0x000000A0- BIOS_B_DISPATCH =
0x000000B0- BIOS_C_DISPATCH =
0x000000C0
Instance Attribute Summary collapse
-
#cop0 ⇒ Object
readonly
Returns the value of attribute cop0.
-
#gte ⇒ Object
readonly
Returns the value of attribute gte.
-
#hi ⇒ Object
readonly
Returns the value of attribute hi.
-
#lo ⇒ Object
readonly
Returns the value of attribute lo.
-
#memory ⇒ Object
readonly
Returns the value of attribute memory.
-
#pc ⇒ Object
Returns the value of attribute pc.
-
#regs ⇒ Object
readonly
Returns the value of attribute regs.
-
#step_cycles ⇒ Object
readonly
Returns the value of attribute step_cycles.
-
#tty_handler ⇒ Object
Returns the value of attribute tty_handler.
Instance Method Summary collapse
- #disassemble_current ⇒ Object
- #dump_registers ⇒ Object
-
#initialize(memory, interrupts: nil) ⇒ CPU
constructor
A new instance of CPU.
- #step ⇒ Object
Constructor Details
#initialize(memory, interrupts: nil) ⇒ CPU
Returns a new instance of CPU.
27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 |
# File 'lib/psx/cpu.rb', line 27 def initialize(memory, interrupts: nil) @memory = memory @interrupts = interrupts @regs = Array.new(32, 0) # R0 is always 0 @pc = RESET_VECTOR @next_pc = @pc + 4 @hi = 0 @lo = 0 # Coprocessors @cop0 = COP0.new @gte = GTE.new # Delayed load handling @load_delay_reg = 0 @load_delay_value = 0 # Branch delay slot tracking @in_delay_slot = false @branch_target = nil @current_pc = @pc # Interrupt check counter - only check every N cycles @interrupt_check_counter = 0 @interrupt_check_interval = 64 # Cycles consumed by the most recent step. Defaults to 1 per # instruction; loads bump it to reflect the R3000A load-delay slot plus # main-RAM access latency (the BIOS code path is mostly uncached). The # outer run loop reads this to drive VBlank/timer ticks at roughly the # right rate, so wait loops in the BIOS (e.g. VSync) complete before # their 0x8000-iteration timeout. @step_cycles = 1 # Whether the previous step set up a branch — i.e. the *current* step # is executing a delay-slot instruction. We can't infer this from # @next_pc vs @pc+4 because short branches happen to have target == # delay_slot+4; only the branch instruction itself knows. @next_in_delay_slot = false end |
Instance Attribute Details
#cop0 ⇒ Object (readonly)
Returns the value of attribute cop0.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def cop0 @cop0 end |
#gte ⇒ Object (readonly)
Returns the value of attribute gte.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def gte @gte end |
#hi ⇒ Object (readonly)
Returns the value of attribute hi.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def hi @hi end |
#lo ⇒ Object (readonly)
Returns the value of attribute lo.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def lo @lo end |
#memory ⇒ Object (readonly)
Returns the value of attribute memory.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def memory @memory end |
#pc ⇒ Object
Returns the value of attribute pc.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def pc @pc end |
#regs ⇒ Object (readonly)
Returns the value of attribute regs.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def regs @regs end |
#step_cycles ⇒ Object (readonly)
Returns the value of attribute step_cycles.
17 18 19 |
# File 'lib/psx/cpu.rb', line 17 def step_cycles @step_cycles end |
#tty_handler ⇒ Object
Returns the value of attribute tty_handler.
18 19 20 |
# File 'lib/psx/cpu.rb', line 18 def tty_handler @tty_handler end |
Instance Method Details
#disassemble_current ⇒ Object
130 131 132 133 |
# File 'lib/psx/cpu.rb', line 130 def disassemble_current instruction = @memory.read32(@pc) Disasm.disassemble(@pc, instruction) end |
#dump_registers ⇒ Object
135 136 137 138 139 140 141 142 143 144 |
# File 'lib/psx/cpu.rb', line 135 def dump_registers lines = ["Registers:"] (0...32).each_slice(4) do |slice| row = slice.map { |i| format("R%-2d=%08X", i, @regs[i]) }.join(" ") lines << row end lines << format("PC=%08X HI=%08X LO=%08X", @pc, @hi, @lo) lines << format("SR=%08X CAUSE=%08X EPC=%08X", @cop0.sr, @cop0.cause, @cop0.epc) lines.join("\n") end |
#step ⇒ Object
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
# File 'lib/psx/cpu.rb', line 68 def step @step_cycles = 1 # Snapshot pre-execute state so a pending interrupt (or any exception # raised during this step) records the right EPC/BD. The previous step # set @next_in_delay_slot iff it was a taken branch; that means *this* # step's instruction is in the delay slot. pc = @pc next_pc = @next_pc @current_pc = pc @in_delay_slot = @next_in_delay_slot @next_in_delay_slot = false # Check for pending interrupts (only every N cycles for performance). # If this fires, @pc/@next_pc/@current_pc are clobbered to the vector # and we continue with the vector's first instruction. @interrupt_check_counter += 1 if @interrupt_check_counter >= @interrupt_check_interval @interrupt_check_counter = 0 check_interrupts if @pc != pc # Exception was taken; rebind the loop variables to the vector. pc = @pc next_pc = @next_pc end end # Optional TTY hook: intercept BIOS A-table entry so PS-EXE programs # built against the standard BIOS putchar/puts functions can be tested # without depending on a working CD-ROM/Shell. Returns true when the # call has been handled and PC was advanced to the caller. if @tty_handler && intercept_bios_call(pc) return end # Fetch instruction instruction = @memory.read32(pc) # Advance PC (next_pc may have been redirected by a previous branch # epilogue, which is exactly what makes the current instruction a # delay slot — already captured in @in_delay_slot above). @pc = next_pc @next_pc = (next_pc + 4) & 0xFFFF_FFFF # Apply pending load (inlined for performance) load_reg = @load_delay_reg if load_reg != 0 @regs[load_reg] = @load_delay_value @load_delay_reg = 0 end # Execute (skip NOP) execute(instruction) if instruction != 0 # Handle branch delay (check @branch_target as instruction may have set new one) new_branch = @branch_target if new_branch @next_pc = new_branch @branch_target = nil @next_in_delay_slot = true end end |