psx 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/CHANGELOG.md +15 -0
- data/LICENSE +21 -0
- data/README.md +129 -0
- data/exe/psx +86 -0
- data/lib/psx/bios.rb +46 -0
- data/lib/psx/cdrom.rb +202 -0
- data/lib/psx/cop0.rb +161 -0
- data/lib/psx/cpu.rb +964 -0
- data/lib/psx/disasm.rb +226 -0
- data/lib/psx/display.rb +200 -0
- data/lib/psx/dma.rb +406 -0
- data/lib/psx/gpu.rb +1116 -0
- data/lib/psx/gte.rb +775 -0
- data/lib/psx/interrupts.rb +79 -0
- data/lib/psx/memory.rb +382 -0
- data/lib/psx/ram.rb +47 -0
- data/lib/psx/sio0.rb +261 -0
- data/lib/psx/spu.rb +110 -0
- data/lib/psx/timers.rb +175 -0
- data/lib/psx/version.rb +5 -0
- data/lib/psx.rb +354 -0
- metadata +114 -0
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PSX
|
|
4
|
+
# Interrupt Controller
|
|
5
|
+
# Manages hardware interrupts via I_STAT and I_MASK registers
|
|
6
|
+
class Interrupts
|
|
7
|
+
# Interrupt bits
|
|
8
|
+
IRQ_VBLANK = 0x001 # Vertical blank
|
|
9
|
+
IRQ_GPU = 0x002 # GPU ready
|
|
10
|
+
IRQ_CDROM = 0x004 # CD-ROM
|
|
11
|
+
IRQ_DMA = 0x008 # DMA transfer complete
|
|
12
|
+
IRQ_TIMER0 = 0x010 # Timer 0
|
|
13
|
+
IRQ_TIMER1 = 0x020 # Timer 1
|
|
14
|
+
IRQ_TIMER2 = 0x040 # Timer 2
|
|
15
|
+
IRQ_CONTROLLER = 0x080 # Controller/memory card
|
|
16
|
+
IRQ_SIO = 0x100 # Serial I/O
|
|
17
|
+
IRQ_SPU = 0x200 # Sound
|
|
18
|
+
IRQ_LIGHTPEN = 0x400 # Lightpen (directly active)
|
|
19
|
+
|
|
20
|
+
attr_reader :stat, :mask
|
|
21
|
+
|
|
22
|
+
def initialize
|
|
23
|
+
@stat = 0 # I_STAT - interrupt status (active interrupts)
|
|
24
|
+
@mask = 0 # I_MASK - interrupt mask (enabled interrupts)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def read_stat
|
|
28
|
+
@stat
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def read_mask
|
|
32
|
+
@mask
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def write_stat(value)
|
|
36
|
+
# Writing to I_STAT acknowledges (clears) interrupts.
|
|
37
|
+
# On PSX: write 0 to a bit clears it (ack), write 1 leaves it unchanged.
|
|
38
|
+
@stat &= value
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def write_mask(value)
|
|
42
|
+
@mask = value & 0x7FF
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Request an interrupt
|
|
46
|
+
def request(irq)
|
|
47
|
+
@stat |= irq
|
|
48
|
+
end
|
|
49
|
+
|
|
50
|
+
# Check if any unmasked interrupt is pending
|
|
51
|
+
def pending?
|
|
52
|
+
(@stat & @mask) != 0
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Trigger VBlank interrupt
|
|
56
|
+
def vblank!
|
|
57
|
+
request(IRQ_VBLANK)
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Trigger GPU interrupt
|
|
61
|
+
def gpu!
|
|
62
|
+
request(IRQ_GPU)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
# Trigger DMA interrupt
|
|
66
|
+
def dma!
|
|
67
|
+
request(IRQ_DMA)
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Trigger timer interrupt
|
|
71
|
+
def timer!(n)
|
|
72
|
+
case n
|
|
73
|
+
when 0 then request(IRQ_TIMER0)
|
|
74
|
+
when 1 then request(IRQ_TIMER1)
|
|
75
|
+
when 2 then request(IRQ_TIMER2)
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
data/lib/psx/memory.rb
ADDED
|
@@ -0,0 +1,382 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PSX
|
|
4
|
+
class Memory
|
|
5
|
+
# Memory regions (physical addresses)
|
|
6
|
+
RAM_START = 0x0000_0000
|
|
7
|
+
RAM_SIZE = 0x0020_0000 # 2 MB (mirrored 4x in 8 MB region)
|
|
8
|
+
RAM_MIRROR_MASK = 0x001F_FFFF
|
|
9
|
+
|
|
10
|
+
SCRATCHPAD_START = 0x1F80_0000
|
|
11
|
+
SCRATCHPAD_SIZE = 0x0000_0400 # 1 KB
|
|
12
|
+
|
|
13
|
+
IO_START = 0x1F80_1000
|
|
14
|
+
IO_SIZE = 0x0000_2000
|
|
15
|
+
|
|
16
|
+
BIOS_START = 0x1FC0_0000
|
|
17
|
+
BIOS_SIZE = 0x0008_0000 # 512 KB
|
|
18
|
+
|
|
19
|
+
# Region masks for KSEG translation
|
|
20
|
+
REGION_MASK = [
|
|
21
|
+
0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, 0xFFFF_FFFF, # KUSEG: 0x0000_0000 - 0x7FFF_FFFF
|
|
22
|
+
0x7FFF_FFFF, # KSEG0: 0x8000_0000 - 0x9FFF_FFFF (cached)
|
|
23
|
+
0x1FFF_FFFF, # KSEG1: 0xA000_0000 - 0xBFFF_FFFF (uncached)
|
|
24
|
+
0xFFFF_FFFF, 0xFFFF_FFFF # KSEG2: 0xC000_0000 - 0xFFFF_FFFF
|
|
25
|
+
].freeze
|
|
26
|
+
|
|
27
|
+
attr_accessor :cache_isolated, :dma, :gpu, :cdrom, :sio0, :spu
|
|
28
|
+
|
|
29
|
+
def initialize(bios:, ram:, interrupts: nil, dma: nil, timers: nil, cdrom: nil, sio0: nil, spu: nil)
|
|
30
|
+
@bios = bios
|
|
31
|
+
@ram = ram
|
|
32
|
+
@interrupts = interrupts
|
|
33
|
+
@dma = dma
|
|
34
|
+
@timers = timers
|
|
35
|
+
@cdrom = cdrom
|
|
36
|
+
@sio0 = sio0
|
|
37
|
+
@spu = spu
|
|
38
|
+
@gpu = nil # Set later when GPU is created
|
|
39
|
+
@scratchpad = ("\x00" * SCRATCHPAD_SIZE).b # Force binary encoding
|
|
40
|
+
@cache_isolated = false
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def tick_dma
|
|
44
|
+
@dma&.tick(self, gpu: @gpu)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
def read8(addr)
|
|
48
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
49
|
+
|
|
50
|
+
# RAM (most common)
|
|
51
|
+
return @ram.read8(phys & RAM_MIRROR_MASK) if phys < 0x0080_0000
|
|
52
|
+
|
|
53
|
+
# BIOS
|
|
54
|
+
return @bios.read8(phys - BIOS_START) if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
55
|
+
|
|
56
|
+
# Scratchpad
|
|
57
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
58
|
+
return @scratchpad.getbyte(phys - SCRATCHPAD_START)
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
# I/O
|
|
62
|
+
return io_read8(phys - IO_START) if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
63
|
+
|
|
64
|
+
# Expansion regions
|
|
65
|
+
return 0xFF if phys >= 0x1F00_0000 && phys < 0x1F80_0000
|
|
66
|
+
return 0xFF if phys >= 0x1F80_2000 && phys < 0x1F80_2100
|
|
67
|
+
|
|
68
|
+
0
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def read16(addr)
|
|
72
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
73
|
+
|
|
74
|
+
# RAM (most common)
|
|
75
|
+
return @ram.read16(phys & RAM_MIRROR_MASK) if phys < 0x0080_0000
|
|
76
|
+
|
|
77
|
+
# BIOS
|
|
78
|
+
return @bios.read16(phys - BIOS_START) if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
79
|
+
|
|
80
|
+
# Scratchpad
|
|
81
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
82
|
+
offset = phys - SCRATCHPAD_START
|
|
83
|
+
return @scratchpad.getbyte(offset) | (@scratchpad.getbyte(offset + 1) << 8)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# I/O
|
|
87
|
+
return io_read16(phys - IO_START) if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
88
|
+
|
|
89
|
+
0
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
def read32(addr)
|
|
93
|
+
# Fast path: translate virtual to physical
|
|
94
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
95
|
+
|
|
96
|
+
# Fast path for RAM (most common case)
|
|
97
|
+
if phys < 0x0080_0000
|
|
98
|
+
return @ram.read32(phys & RAM_MIRROR_MASK)
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
# Fast path for BIOS
|
|
102
|
+
if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
103
|
+
return @bios.read32(phys - BIOS_START)
|
|
104
|
+
end
|
|
105
|
+
|
|
106
|
+
# Scratchpad
|
|
107
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
108
|
+
offset = phys - SCRATCHPAD_START
|
|
109
|
+
return @scratchpad.getbyte(offset) |
|
|
110
|
+
(@scratchpad.getbyte(offset + 1) << 8) |
|
|
111
|
+
(@scratchpad.getbyte(offset + 2) << 16) |
|
|
112
|
+
(@scratchpad.getbyte(offset + 3) << 24)
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# I/O
|
|
116
|
+
return io_read32(phys - IO_START) if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
117
|
+
|
|
118
|
+
# Cache control
|
|
119
|
+
return 0 if phys >= 0xFFFE_0000 && phys < 0xFFFE_0200
|
|
120
|
+
|
|
121
|
+
# Expansion regions
|
|
122
|
+
return 0xFFFF_FFFF if phys >= 0x1F00_0000 && phys < 0x1F80_0000
|
|
123
|
+
return 0xFFFF_FFFF if phys >= 0x1F80_2000 && phys < 0x1F80_2100
|
|
124
|
+
|
|
125
|
+
0
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
def write8(addr, value)
|
|
129
|
+
return if @cache_isolated # Writes go to cache, not memory
|
|
130
|
+
|
|
131
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
132
|
+
|
|
133
|
+
# RAM (most common)
|
|
134
|
+
if phys < 0x0080_0000
|
|
135
|
+
@ram.write8(phys & RAM_MIRROR_MASK, value)
|
|
136
|
+
return
|
|
137
|
+
end
|
|
138
|
+
|
|
139
|
+
# Scratchpad
|
|
140
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
141
|
+
@scratchpad.setbyte(phys - SCRATCHPAD_START, value & 0xFF)
|
|
142
|
+
return
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
# I/O
|
|
146
|
+
if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
147
|
+
io_write8(phys - IO_START, value)
|
|
148
|
+
return
|
|
149
|
+
end
|
|
150
|
+
|
|
151
|
+
# BIOS (read-only)
|
|
152
|
+
warn format("Write to BIOS at 0x%08X", addr) if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
153
|
+
end
|
|
154
|
+
|
|
155
|
+
def write16(addr, value)
|
|
156
|
+
return if @cache_isolated
|
|
157
|
+
|
|
158
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
159
|
+
|
|
160
|
+
# RAM (most common)
|
|
161
|
+
if phys < 0x0080_0000
|
|
162
|
+
@ram.write16(phys & RAM_MIRROR_MASK, value)
|
|
163
|
+
return
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Scratchpad
|
|
167
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
168
|
+
offset = phys - SCRATCHPAD_START
|
|
169
|
+
@scratchpad.setbyte(offset, value & 0xFF)
|
|
170
|
+
@scratchpad.setbyte(offset + 1, (value >> 8) & 0xFF)
|
|
171
|
+
return
|
|
172
|
+
end
|
|
173
|
+
|
|
174
|
+
# I/O
|
|
175
|
+
if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
176
|
+
io_write16(phys - IO_START, value)
|
|
177
|
+
return
|
|
178
|
+
end
|
|
179
|
+
|
|
180
|
+
# BIOS (read-only)
|
|
181
|
+
warn format("Write to BIOS at 0x%08X", addr) if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
def write32(addr, value)
|
|
185
|
+
return if @cache_isolated
|
|
186
|
+
|
|
187
|
+
# Fast path: translate virtual to physical
|
|
188
|
+
phys = addr & REGION_MASK[(addr >> 29) & 0x7]
|
|
189
|
+
|
|
190
|
+
# Fast path for RAM (most common case)
|
|
191
|
+
if phys < 0x0080_0000
|
|
192
|
+
@ram.write32(phys & RAM_MIRROR_MASK, value)
|
|
193
|
+
return
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Scratchpad
|
|
197
|
+
if phys >= 0x1F80_0000 && phys < 0x1F80_0400
|
|
198
|
+
offset = phys - SCRATCHPAD_START
|
|
199
|
+
@scratchpad.setbyte(offset, value & 0xFF)
|
|
200
|
+
@scratchpad.setbyte(offset + 1, (value >> 8) & 0xFF)
|
|
201
|
+
@scratchpad.setbyte(offset + 2, (value >> 16) & 0xFF)
|
|
202
|
+
@scratchpad.setbyte(offset + 3, (value >> 24) & 0xFF)
|
|
203
|
+
return
|
|
204
|
+
end
|
|
205
|
+
|
|
206
|
+
# I/O
|
|
207
|
+
if phys >= 0x1F80_1000 && phys < 0x1F80_3000
|
|
208
|
+
io_write32(phys - IO_START, value)
|
|
209
|
+
return
|
|
210
|
+
end
|
|
211
|
+
|
|
212
|
+
# BIOS (read-only)
|
|
213
|
+
if phys >= 0x1FC0_0000 && phys < 0x1FC8_0000
|
|
214
|
+
warn format("Write to BIOS at 0x%08X", addr)
|
|
215
|
+
return
|
|
216
|
+
end
|
|
217
|
+
|
|
218
|
+
# Cache control and expansion regions - ignore writes
|
|
219
|
+
end
|
|
220
|
+
|
|
221
|
+
private
|
|
222
|
+
|
|
223
|
+
# I/O register stubs - will be expanded later
|
|
224
|
+
def io_read8(offset)
|
|
225
|
+
case offset
|
|
226
|
+
when 0x0040..0x004F
|
|
227
|
+
@sio0 ? @sio0.read8(offset) : 0xFF
|
|
228
|
+
when 0x0800..0x0803
|
|
229
|
+
@cdrom ? @cdrom.read8(offset - 0x0800) : 0
|
|
230
|
+
when 0x1040...0x1050
|
|
231
|
+
# Expansion 2 (POST/debug) - return 0
|
|
232
|
+
0
|
|
233
|
+
else
|
|
234
|
+
# warn format("IO read8 at 0x%08X", IO_START + offset)
|
|
235
|
+
0
|
|
236
|
+
end
|
|
237
|
+
end
|
|
238
|
+
|
|
239
|
+
def io_read16(offset)
|
|
240
|
+
case offset
|
|
241
|
+
when 0x0040..0x004F
|
|
242
|
+
@sio0 ? @sio0.read16(offset) : 0
|
|
243
|
+
when 0x005A
|
|
244
|
+
# SIO1 CTRL
|
|
245
|
+
0
|
|
246
|
+
when 0x0070
|
|
247
|
+
# I_STAT (low halfword) — BIOS uses 16-bit access.
|
|
248
|
+
(@interrupts&.read_stat || 0) & 0xFFFF
|
|
249
|
+
when 0x0074
|
|
250
|
+
# I_MASK (low halfword) — BIOS uses 16-bit access.
|
|
251
|
+
(@interrupts&.read_mask || 0) & 0xFFFF
|
|
252
|
+
when 0x0100...0x0130
|
|
253
|
+
@timers&.read(offset - 0x0100) || 0
|
|
254
|
+
when 0x0C80...0x0D00
|
|
255
|
+
# SPU voice registers - read back 0
|
|
256
|
+
0
|
|
257
|
+
when 0x0D80...0x0E00
|
|
258
|
+
@spu ? @spu.read16(offset) : 0
|
|
259
|
+
else
|
|
260
|
+
# warn format("IO read16 at 0x%08X", IO_START + offset)
|
|
261
|
+
0
|
|
262
|
+
end
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def io_read32(offset)
|
|
266
|
+
case offset
|
|
267
|
+
when 0x0000...0x0024
|
|
268
|
+
# Memory control 1
|
|
269
|
+
0
|
|
270
|
+
when 0x0040..0x004F
|
|
271
|
+
@sio0 ? @sio0.read32(offset) : 0
|
|
272
|
+
when 0x0060
|
|
273
|
+
# RAM size register
|
|
274
|
+
0x0000_0B88
|
|
275
|
+
when 0x0070
|
|
276
|
+
# I_STAT - Interrupt status
|
|
277
|
+
@interrupts&.read_stat || 0
|
|
278
|
+
when 0x0074
|
|
279
|
+
# I_MASK - Interrupt mask
|
|
280
|
+
@interrupts&.read_mask || 0
|
|
281
|
+
when 0x0080...0x00F0
|
|
282
|
+
# DMA channel registers
|
|
283
|
+
@dma&.read(offset - 0x0080) || 0
|
|
284
|
+
when 0x00F0
|
|
285
|
+
# DMA DPCR - control
|
|
286
|
+
@dma&.dpcr || 0x0765_4321
|
|
287
|
+
when 0x00F4
|
|
288
|
+
# DMA DICR - interrupt control
|
|
289
|
+
@dma&.dicr || 0
|
|
290
|
+
when 0x0100...0x0130
|
|
291
|
+
# Timers
|
|
292
|
+
@timers&.read(offset - 0x0100) || 0
|
|
293
|
+
when 0x0810
|
|
294
|
+
# GPU GPUREAD
|
|
295
|
+
@gpu&.read_data || 0
|
|
296
|
+
when 0x0814
|
|
297
|
+
# GPU GPUSTAT - return ready, display enabled
|
|
298
|
+
@gpu&.status || 0x1C00_0000
|
|
299
|
+
else
|
|
300
|
+
# warn format("IO read32 at 0x%08X", IO_START + offset)
|
|
301
|
+
0
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def io_write8(offset, value)
|
|
306
|
+
case offset
|
|
307
|
+
when 0x0040..0x004F
|
|
308
|
+
@sio0&.write8(offset, value)
|
|
309
|
+
when 0x0800..0x0803
|
|
310
|
+
@cdrom&.write8(offset - 0x0800, value)
|
|
311
|
+
when 0x1040...0x1050
|
|
312
|
+
# POST/debug output - could display but ignore for now
|
|
313
|
+
else
|
|
314
|
+
# warn format("IO write8 at 0x%08X = 0x%02X", IO_START + offset, value)
|
|
315
|
+
end
|
|
316
|
+
end
|
|
317
|
+
|
|
318
|
+
def io_write16(offset, value)
|
|
319
|
+
case offset
|
|
320
|
+
when 0x0040..0x004F
|
|
321
|
+
@sio0&.write16(offset, value)
|
|
322
|
+
when 0x0070
|
|
323
|
+
# I_STAT ack via 16-bit write: the BIOS ack writes the low halfword.
|
|
324
|
+
# Preserve the upper bits of stat (real I_STAT is 11 bits anyway).
|
|
325
|
+
if @interrupts
|
|
326
|
+
high = @interrupts.stat & ~0xFFFF
|
|
327
|
+
@interrupts.write_stat((value & 0xFFFF) | high)
|
|
328
|
+
end
|
|
329
|
+
when 0x0074
|
|
330
|
+
# I_MASK via 16-bit write — BIOS uses this in SetIntMask.
|
|
331
|
+
@interrupts&.write_mask(value & 0xFFFF)
|
|
332
|
+
when 0x0100...0x0130
|
|
333
|
+
@timers&.write(offset - 0x0100, value)
|
|
334
|
+
when 0x0C80...0x0D80
|
|
335
|
+
# SPU voice registers - drop
|
|
336
|
+
when 0x0D80...0x0E00
|
|
337
|
+
@spu&.write16(offset, value)
|
|
338
|
+
else
|
|
339
|
+
# warn format("IO write16 at 0x%08X = 0x%04X", IO_START + offset, value)
|
|
340
|
+
end
|
|
341
|
+
end
|
|
342
|
+
|
|
343
|
+
def io_write32(offset, value)
|
|
344
|
+
case offset
|
|
345
|
+
when 0x0000...0x0024
|
|
346
|
+
# Memory control 1 - ignore for now
|
|
347
|
+
when 0x0040..0x004F
|
|
348
|
+
@sio0&.write32(offset, value)
|
|
349
|
+
when 0x0060
|
|
350
|
+
# RAM size config - ignore
|
|
351
|
+
when 0x0070
|
|
352
|
+
# I_STAT - Interrupt status (write acknowledges)
|
|
353
|
+
@interrupts&.write_stat(value)
|
|
354
|
+
when 0x0074
|
|
355
|
+
# I_MASK - Interrupt mask
|
|
356
|
+
@interrupts&.write_mask(value)
|
|
357
|
+
when 0x0080...0x00F0
|
|
358
|
+
# DMA channel registers
|
|
359
|
+
@dma&.write(offset - 0x0080, value)
|
|
360
|
+
# Check if this triggered a DMA transfer
|
|
361
|
+
tick_dma
|
|
362
|
+
when 0x00F0
|
|
363
|
+
# DMA DPCR
|
|
364
|
+
@dma&.write(0x70, value)
|
|
365
|
+
when 0x00F4
|
|
366
|
+
# DMA DICR
|
|
367
|
+
@dma&.write(0x74, value)
|
|
368
|
+
when 0x0100...0x0130
|
|
369
|
+
# Timers
|
|
370
|
+
@timers&.write(offset - 0x0100, value)
|
|
371
|
+
when 0x0810
|
|
372
|
+
# GPU GP0
|
|
373
|
+
@gpu&.gp0(value)
|
|
374
|
+
when 0x0814
|
|
375
|
+
# GPU GP1
|
|
376
|
+
@gpu&.gp1(value)
|
|
377
|
+
else
|
|
378
|
+
# warn format("IO write32 at 0x%08X = 0x%08X", IO_START + offset, value)
|
|
379
|
+
end
|
|
380
|
+
end
|
|
381
|
+
end
|
|
382
|
+
end
|
data/lib/psx/ram.rb
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module PSX
|
|
4
|
+
class RAM
|
|
5
|
+
SIZE = 2 * 1024 * 1024 # 2 MB
|
|
6
|
+
MASK = SIZE - 1
|
|
7
|
+
|
|
8
|
+
def initialize
|
|
9
|
+
@data = ("\x00" * SIZE).b # Force binary encoding
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
def read8(offset)
|
|
13
|
+
@data.getbyte(offset & MASK)
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
def read16(offset)
|
|
17
|
+
offset &= MASK
|
|
18
|
+
@data.getbyte(offset) | (@data.getbyte(offset + 1) << 8)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read32(offset)
|
|
22
|
+
offset &= MASK
|
|
23
|
+
@data.getbyte(offset) |
|
|
24
|
+
(@data.getbyte(offset + 1) << 8) |
|
|
25
|
+
(@data.getbyte(offset + 2) << 16) |
|
|
26
|
+
(@data.getbyte(offset + 3) << 24)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def write8(offset, value)
|
|
30
|
+
@data.setbyte(offset & MASK, value & 0xFF)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def write16(offset, value)
|
|
34
|
+
offset &= MASK
|
|
35
|
+
@data.setbyte(offset, value & 0xFF)
|
|
36
|
+
@data.setbyte(offset + 1, (value >> 8) & 0xFF)
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def write32(offset, value)
|
|
40
|
+
offset &= MASK
|
|
41
|
+
@data.setbyte(offset, value & 0xFF)
|
|
42
|
+
@data.setbyte(offset + 1, (value >> 8) & 0xFF)
|
|
43
|
+
@data.setbyte(offset + 2, (value >> 16) & 0xFF)
|
|
44
|
+
@data.setbyte(offset + 3, (value >> 24) & 0xFF)
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|