Skip to main content

picoem_common/
memory.rs

1/// ROM size: 32 kB.
2pub const ROM_SIZE: usize = 32 * 1024;
3/// SRAM size: 520 kB (10 banks: SRAM0-7 striped 64 kB each, SRAM8-9 non-striped 4 kB each).
4pub const SRAM_SIZE: usize = 520 * 1024;
5
6/// Unified memory backing stores. Owns the actual byte arrays for ROM, SRAM,
7/// and flash (XIP). No bus fabric or timing — just raw storage.
8pub struct Memory {
9    rom: Vec<u8>,
10    sram: Vec<u8>,
11    xip: Vec<u8>,
12}
13
14impl Memory {
15    pub fn new() -> Self {
16        Self {
17            rom: vec![0u8; ROM_SIZE],
18            sram: vec![0u8; SRAM_SIZE],
19            xip: Vec::new(),
20        }
21    }
22
23    /// Construct a `Memory` with chip-specific ROM and SRAM sizes.
24    /// Used by `rp2040_emu` (16 KB ROM, 264 KB SRAM) and any future chip
25    /// crate that differs from the RP2350 defaults baked into `new()`.
26    /// XIP starts empty; populate via `load_flash`.
27    pub fn with_sizes(rom_size: usize, sram_size: usize) -> Self {
28        Self {
29            rom: vec![0u8; rom_size],
30            sram: vec![0u8; sram_size],
31            xip: Vec::new(),
32        }
33    }
34
35    /// Construct a `Memory` with chip-specific ROM, SRAM, and a fixed-size
36    /// flash window. Used by `rp2040_emu` for its 2 MB XIP window: the
37    /// bus decoder maps a fixed address range, so the flash buffer must
38    /// cover the whole window regardless of image size.
39    ///
40    /// Flash bytes are zero-initialised; populate via [`Self::load_flash`],
41    /// which clamps to `flash_size` and zeroes any remaining tail.
42    pub fn with_flash(rom_size: usize, sram_size: usize, flash_size: usize) -> Self {
43        Self {
44            rom: vec![0u8; rom_size],
45            sram: vec![0u8; sram_size],
46            xip: vec![0u8; flash_size],
47        }
48    }
49
50    /// Current flash (XIP) buffer size in bytes. Zero when constructed
51    /// via `new()` / `with_sizes()` and `load_flash` has not been called
52    /// yet (rp2350_emu dynamic-resize path).
53    pub fn flash_size(&self) -> usize {
54        self.xip.len()
55    }
56
57    // --- ROM ---
58
59    pub fn load_rom(&mut self, data: &[u8]) {
60        let len = data.len().min(self.rom.len());
61        self.rom[..len].copy_from_slice(&data[..len]);
62    }
63
64    pub fn rom_read8(&self, offset: u32) -> u8 {
65        self.rom.get(offset as usize).copied().unwrap_or(0)
66    }
67
68    pub fn rom_read16(&self, offset: u32) -> u16 {
69        let off = offset as usize;
70        if off + 1 < self.rom.len() {
71            u16::from_le_bytes([self.rom[off], self.rom[off + 1]])
72        } else {
73            0
74        }
75    }
76
77    pub fn rom_read32(&self, offset: u32) -> u32 {
78        let off = offset as usize;
79        if off + 3 < self.rom.len() {
80            u32::from_le_bytes([
81                self.rom[off],
82                self.rom[off + 1],
83                self.rom[off + 2],
84                self.rom[off + 3],
85            ])
86        } else {
87            0
88        }
89    }
90
91    // --- SRAM ---
92
93    pub fn sram_read8(&self, offset: u32) -> u8 {
94        self.sram.get(offset as usize).copied().unwrap_or(0)
95    }
96
97    pub fn sram_read16(&self, offset: u32) -> u16 {
98        let off = offset as usize;
99        if off + 1 < self.sram.len() {
100            u16::from_le_bytes([self.sram[off], self.sram[off + 1]])
101        } else {
102            0
103        }
104    }
105
106    pub fn sram_read32(&self, offset: u32) -> u32 {
107        let off = offset as usize;
108        if off + 3 < self.sram.len() {
109            u32::from_le_bytes([
110                self.sram[off],
111                self.sram[off + 1],
112                self.sram[off + 2],
113                self.sram[off + 3],
114            ])
115        } else {
116            0
117        }
118    }
119
120    pub fn sram_write8(&mut self, offset: u32, val: u8) {
121        let off = offset as usize;
122        if off < self.sram.len() {
123            self.sram[off] = val;
124        }
125    }
126
127    pub fn sram_write16(&mut self, offset: u32, val: u16) {
128        let off = offset as usize;
129        if off + 1 < self.sram.len() {
130            let bytes = val.to_le_bytes();
131            self.sram[off] = bytes[0];
132            self.sram[off + 1] = bytes[1];
133        }
134    }
135
136    pub fn sram_write32(&mut self, offset: u32, val: u32) {
137        let off = offset as usize;
138        if off + 3 < self.sram.len() {
139            let bytes = val.to_le_bytes();
140            self.sram[off] = bytes[0];
141            self.sram[off + 1] = bytes[1];
142            self.sram[off + 2] = bytes[2];
143            self.sram[off + 3] = bytes[3];
144        }
145    }
146
147    // --- XIP (flash) ---
148
149    /// Copy `data` into the flash buffer starting at offset 0.
150    ///
151    /// * If the buffer was pre-sized via [`Self::with_flash`], the copy
152    ///   clamps at the buffer length and any previously-loaded tail is
153    ///   zeroed so a re-load doesn't leak stale bytes past the new image.
154    /// * Otherwise (default / `with_sizes` path — rp2350_emu) the buffer
155    ///   is resized to match the new data. This preserves the pre-PicoGUS
156    ///   behaviour where callers treat XIP as a dynamically-sized image.
157    pub fn load_flash(&mut self, data: &[u8]) {
158        if self.xip.is_empty() {
159            self.xip = data.to_vec();
160        } else {
161            let n = data.len().min(self.xip.len());
162            self.xip[..n].copy_from_slice(&data[..n]);
163            for b in &mut self.xip[n..] {
164                *b = 0;
165            }
166        }
167    }
168
169    pub fn xip_read8(&self, offset: u32) -> u8 {
170        self.xip.get(offset as usize).copied().unwrap_or(0)
171    }
172
173    pub fn xip_read16(&self, offset: u32) -> u16 {
174        let off = offset as usize;
175        if off + 1 < self.xip.len() {
176            u16::from_le_bytes([self.xip[off], self.xip[off + 1]])
177        } else {
178            0
179        }
180    }
181
182    pub fn xip_read32(&self, offset: u32) -> u32 {
183        let off = offset as usize;
184        if off + 3 < self.xip.len() {
185            u32::from_le_bytes([
186                self.xip[off],
187                self.xip[off + 1],
188                self.xip[off + 2],
189                self.xip[off + 3],
190            ])
191        } else {
192            0
193        }
194    }
195
196    // --- Direct access (for test / debug, bypasses bus) ---
197
198    pub fn peek8(&self, addr: u32) -> u8 {
199        match addr >> 28 {
200            0x0 => self.rom_read8(addr & 0x0FFF_FFFF),
201            0x1 => self.xip_read8(addr & 0x0FFF_FFFF),
202            0x2 => self.sram_read8(addr & 0x00FF_FFFF), // strip SRAM alias bits
203            _ => 0,
204        }
205    }
206
207    pub fn poke8(&mut self, addr: u32, val: u8) {
208        // ROM and XIP are read-only, others unmapped.
209        if addr >> 28 == 0x2 {
210            self.sram_write8(addr & 0x00FF_FFFF, val);
211        }
212    }
213
214    pub fn peek32(&self, addr: u32) -> u32 {
215        match addr >> 28 {
216            0x0 => self.rom_read32(addr & 0x0FFF_FFFF),
217            0x1 => self.xip_read32(addr & 0x0FFF_FFFF),
218            0x2 => self.sram_read32(addr & 0x00FF_FFFF), // strip SRAM alias bits
219            _ => 0,
220        }
221    }
222
223    pub fn poke32(&mut self, addr: u32, val: u32) {
224        // ROM and XIP are read-only, others unmapped.
225        if addr >> 28 == 0x2 {
226            self.sram_write32(addr & 0x00FF_FFFF, val);
227        }
228    }
229
230    /// Consume the backing store, yielding `(rom, sram, xip)` Vec<u8>
231    /// triples. Used by the threading runtime (`rp2350_emu::threaded`) to
232    /// seed a `SharedMemory` from an existing `Emulator`'s `Bus::memory`
233    /// without bulk-reading every byte through the scalar accessors.
234    pub fn into_parts(self) -> (Vec<u8>, Vec<u8>, Vec<u8>) {
235        (self.rom, self.sram, self.xip)
236    }
237}
238
239impl Default for Memory {
240    fn default() -> Self {
241        Self::new()
242    }
243}
244
245#[cfg(test)]
246mod tests {
247    use super::*;
248
249    #[test]
250    fn with_flash_preallocates_zeroed_buffer() {
251        // `with_flash` is the pre-sized flash constructor used by chips
252        // with a fixed-capacity flash window (e.g. rp2040_emu's 2 MB XIP).
253        // Pre-allocated bytes must read back as zero.
254        let mem = Memory::with_flash(16 * 1024, 264 * 1024, 2 * 1024 * 1024);
255        assert_eq!(mem.flash_size(), 2 * 1024 * 1024);
256        assert_eq!(mem.xip_read8(0), 0);
257        assert_eq!(mem.xip_read8(2 * 1024 * 1024 - 1), 0);
258        assert_eq!(mem.xip_read32(0), 0);
259    }
260
261    #[test]
262    fn with_flash_load_clamps_into_fixed_buffer() {
263        // Loading data into a pre-sized buffer clamps at capacity and
264        // copies from offset 0. Previously-loaded bytes past the new
265        // image are zeroed so a re-load doesn't leak stale content.
266        let mut mem = Memory::with_flash(16 * 1024, 264 * 1024, 2 * 1024 * 1024);
267        mem.load_flash(&[0xAA, 0xBB, 0xCC, 0xDD]);
268        assert_eq!(mem.xip_read32(0), 0xDDCCBBAA);
269        // Past the loaded length: still zero within the mapped window.
270        assert_eq!(mem.xip_read8(4), 0);
271        assert_eq!(mem.xip_read8((2 * 1024 * 1024) - 1), 0);
272        // Re-load with a shorter image: old tail must be zeroed.
273        mem.load_flash(&[0x01]);
274        assert_eq!(mem.xip_read8(0), 0x01);
275        assert_eq!(mem.xip_read8(1), 0);
276        assert_eq!(mem.xip_read8(3), 0);
277    }
278
279    #[test]
280    fn with_sizes_keeps_legacy_dynamic_flash_behavior() {
281        // rp2350_emu uses `with_sizes` and expects `load_flash` to resize
282        // the buffer to the loaded bytes (current behaviour). Changing
283        // this would break the RP2350 XIP tests.
284        let mut mem = Memory::with_sizes(32 * 1024, 520 * 1024);
285        assert_eq!(mem.flash_size(), 0);
286        mem.load_flash(&[0x11, 0x22, 0x33, 0x44]);
287        assert_eq!(mem.flash_size(), 4);
288        assert_eq!(mem.xip_read32(0), 0x44332211);
289    }
290
291    // ------------------------------------------------------------------
292    // Default constructor — covers the RP2350 sizes and the Default impl
293    // ------------------------------------------------------------------
294
295    #[test]
296    fn new_uses_rp2350_default_sizes() {
297        let mem = Memory::new();
298        // ROM reads at the tail must succeed (returns 0, unmapped upper
299        // word reads must fall through to the out-of-range branch).
300        assert_eq!(mem.rom_read8(ROM_SIZE as u32 - 1), 0);
301        // XIP starts empty on the default construction path.
302        assert_eq!(mem.flash_size(), 0);
303    }
304
305    #[test]
306    fn default_matches_new() {
307        // The Default impl just delegates to `new()`. Exercising it here
308        // covers the otherwise-dead `impl Default for Memory`.
309        let a = Memory::default();
310        let b = Memory::new();
311        assert_eq!(a.flash_size(), b.flash_size());
312        // Same SRAM sizing: last byte at SRAM_SIZE-1 reads 0 on both.
313        assert_eq!(a.sram_read8(SRAM_SIZE as u32 - 1), 0);
314        assert_eq!(b.sram_read8(SRAM_SIZE as u32 - 1), 0);
315    }
316
317    // ------------------------------------------------------------------
318    // ROM read paths — in-range round-trip and out-of-range fall-through
319    // ------------------------------------------------------------------
320
321    #[test]
322    fn rom_load_and_read_roundtrip() {
323        let mut mem = Memory::new();
324        // `load_rom` clamps at ROM_SIZE, so oversize input must truncate.
325        let data: Vec<u8> = (0..ROM_SIZE as u32 + 64)
326            .map(|i| (i & 0xFF) as u8)
327            .collect();
328        mem.load_rom(&data);
329        // First few bytes round-trip as-written.
330        assert_eq!(mem.rom_read8(0), 0x00);
331        assert_eq!(mem.rom_read8(1), 0x01);
332        // 16- and 32-bit reads inside the in-range branch.
333        let expected16 = u16::from_le_bytes([data[2], data[3]]);
334        assert_eq!(mem.rom_read16(2), expected16);
335        let expected32 = u32::from_le_bytes([data[4], data[5], data[6], data[7]]);
336        assert_eq!(mem.rom_read32(4), expected32);
337    }
338
339    #[test]
340    fn rom_reads_out_of_range_return_zero() {
341        // Covers the `else { 0 }` branches at memory.rs:70 (rom_read16)
342        // and :79 (rom_read32) plus the `unwrap_or(0)` path in rom_read8.
343        let mut mem = Memory::new();
344        mem.load_rom(&[0xFF; 16]);
345        // read8 past the end: unwrap_or path.
346        assert_eq!(mem.rom_read8(ROM_SIZE as u32), 0);
347        // read16 where off+1 == rom.len(): both len-1 and len trigger.
348        assert_eq!(mem.rom_read16(ROM_SIZE as u32 - 1), 0);
349        assert_eq!(mem.rom_read16(ROM_SIZE as u32), 0);
350        // read32 where off+3 reaches past end: covers the fallback.
351        assert_eq!(mem.rom_read32(ROM_SIZE as u32 - 3), 0);
352        assert_eq!(mem.rom_read32(ROM_SIZE as u32), 0);
353    }
354
355    // ------------------------------------------------------------------
356    // SRAM paths — in-range, writes round-trip, out-of-range no-ops
357    // ------------------------------------------------------------------
358
359    #[test]
360    fn sram_roundtrip_all_widths() {
361        let mut mem = Memory::new();
362        mem.sram_write8(0, 0xAB);
363        assert_eq!(mem.sram_read8(0), 0xAB);
364        mem.sram_write16(4, 0xBEEF);
365        assert_eq!(mem.sram_read16(4), 0xBEEF);
366        mem.sram_write32(8, 0xDEAD_BEEF);
367        assert_eq!(mem.sram_read32(8), 0xDEAD_BEEF);
368    }
369
370    #[test]
371    fn sram_reads_out_of_range_return_zero() {
372        // Covers the `else { 0 }` branches at :99 and :108.
373        let mem = Memory::new();
374        assert_eq!(mem.sram_read8(SRAM_SIZE as u32), 0);
375        assert_eq!(mem.sram_read16(SRAM_SIZE as u32 - 1), 0);
376        assert_eq!(mem.sram_read32(SRAM_SIZE as u32 - 3), 0);
377    }
378
379    #[test]
380    fn sram_writes_out_of_range_are_noops() {
381        // Covers the bounds-guard branches at :122 (write8), :129 (write16),
382        // :138 (write32). None must panic and none must mutate past the tail.
383        let mut mem = Memory::new();
384        // Seed the last valid byte so we can detect a stray write.
385        mem.sram_write8(SRAM_SIZE as u32 - 1, 0x5A);
386        mem.sram_write8(SRAM_SIZE as u32, 0xFF);
387        // write16 where off+1 == len: off=SRAM_SIZE-1, off+1=SRAM_SIZE → skip.
388        mem.sram_write16(SRAM_SIZE as u32 - 1, 0x1234);
389        // write32 where off+3 >= len: off=SRAM_SIZE-3, off+3=SRAM_SIZE → skip.
390        mem.sram_write32(SRAM_SIZE as u32 - 3, 0xDEAD_BEEF);
391        // Last valid byte still the sentinel — no write leaked through.
392        assert_eq!(mem.sram_read8(SRAM_SIZE as u32 - 1), 0x5A);
393        // Reads past end return 0 and do not panic.
394        assert_eq!(mem.sram_read8(SRAM_SIZE as u32 + 16), 0);
395    }
396
397    #[test]
398    fn sram_write_then_read_at_edge_valid() {
399        // Edge of the in-range branch: off = SRAM_SIZE - 4 is valid for
400        // write32 (off+3 = SRAM_SIZE-1, still < len).
401        let mut mem = Memory::new();
402        let edge = SRAM_SIZE as u32 - 4;
403        mem.sram_write32(edge, 0xCAFE_F00D);
404        assert_eq!(mem.sram_read32(edge), 0xCAFE_F00D);
405    }
406
407    // ------------------------------------------------------------------
408    // XIP out-of-range branches — :175 and :184
409    // ------------------------------------------------------------------
410
411    #[test]
412    fn xip_reads_out_of_range_return_zero() {
413        // Fixed-size XIP: out-of-range reads must hit the `else { 0 }`
414        // arms at :175 (xip_read16) and :184 (xip_read32).
415        let mut mem = Memory::with_flash(16 * 1024, 264 * 1024, 16);
416        mem.load_flash(&[0xDE, 0xAD, 0xBE, 0xEF]);
417        // In-range sanity — covers the `if` arm of xip_read16 and xip_read32.
418        assert_eq!(mem.xip_read16(0), 0xADDE);
419        assert_eq!(mem.xip_read16(2), 0xEFBE);
420        assert_eq!(mem.xip_read32(0), 0xEFBEADDE);
421        // Out-of-range read16: off+1 == len → fallback.
422        assert_eq!(mem.xip_read16(15), 0);
423        assert_eq!(mem.xip_read16(16), 0);
424        // Out-of-range read32: off+3 >= len → fallback.
425        assert_eq!(mem.xip_read32(13), 0);
426        assert_eq!(mem.xip_read32(16), 0);
427        // xip_read8 past end: Vec::get().unwrap_or(0).
428        assert_eq!(mem.xip_read8(16), 0);
429    }
430
431    #[test]
432    fn xip_dynamic_mode_out_of_range_reads_zero() {
433        // The `with_sizes` / dynamic XIP path: before any load_flash
434        // call, xip is empty and every read is out of range.
435        let mem = Memory::with_sizes(32 * 1024, 520 * 1024);
436        assert_eq!(mem.xip_read8(0), 0);
437        assert_eq!(mem.xip_read16(0), 0);
438        assert_eq!(mem.xip_read32(0), 0);
439    }
440
441    // ------------------------------------------------------------------
442    // peek / poke dispatchers and into_parts
443    // ------------------------------------------------------------------
444
445    #[test]
446    fn peek_and_poke_cover_all_regions() {
447        // peek8/peek32/poke8/poke32 dispatch on addr[31:28] and only
448        // writes into SRAM are honoured. Exercise every arm.
449        let mut mem = Memory::with_flash(16, 64, 8);
450        // Seed ROM and XIP so their peek arms observe non-zero data.
451        mem.load_rom(&[0x11, 0x22, 0x33, 0x44]);
452        mem.load_flash(&[0xAA, 0xBB, 0xCC, 0xDD]);
453        // ROM peek arm (addr[31:28] == 0x0).
454        assert_eq!(mem.peek8(0x0000_0000), 0x11);
455        assert_eq!(mem.peek32(0x0000_0000), 0x44332211);
456        // XIP peek arm (addr[31:28] == 0x1).
457        assert_eq!(mem.peek8(0x1000_0000), 0xAA);
458        assert_eq!(mem.peek32(0x1000_0000), 0xDDCCBBAA);
459        // SRAM peek arm (addr[31:28] == 0x2). Strip the alias bits.
460        mem.poke32(0x2000_0010, 0xF00D_CAFE);
461        assert_eq!(mem.peek32(0x2000_0010), 0xF00D_CAFE);
462        mem.poke8(0x2000_0020, 0x7E);
463        assert_eq!(mem.peek8(0x2000_0020), 0x7E);
464        // Unmapped region (addr[31:28] == 0xE): peek returns 0 default.
465        assert_eq!(mem.peek8(0xE000_0000), 0);
466        assert_eq!(mem.peek32(0xE000_0000), 0);
467        // Writes into ROM/XIP/unmapped must be silent no-ops — SRAM stays
468        // intact. Read the earlier SRAM byte to prove nothing was lost.
469        mem.poke8(0x0000_0000, 0xFF); // ROM range — no-op
470        mem.poke32(0x1000_0000, 0xFFFF_FFFF); // XIP range — no-op
471        mem.poke8(0xE000_0000, 0xFF); // unmapped — no-op
472        mem.poke32(0xE000_0000, 0xFFFF_FFFF); // unmapped — no-op
473        assert_eq!(mem.peek8(0x2000_0020), 0x7E);
474        assert_eq!(mem.peek32(0x2000_0010), 0xF00D_CAFE);
475    }
476
477    #[test]
478    fn into_parts_yields_owned_buffers() {
479        // `into_parts` is the hand-off path used by the threading
480        // runtime. Verify it surfaces the three independently-sized Vecs.
481        let mut mem = Memory::with_flash(16, 64, 8);
482        mem.load_rom(&[0x77; 4]);
483        mem.sram_write8(3, 0xA5);
484        mem.load_flash(&[0x42; 2]);
485        let (rom, sram, xip) = mem.into_parts();
486        assert_eq!(rom.len(), 16);
487        assert_eq!(rom[..4], [0x77; 4]);
488        assert_eq!(sram.len(), 64);
489        assert_eq!(sram[3], 0xA5);
490        assert_eq!(xip.len(), 8);
491        assert_eq!(xip[..2], [0x42; 2]);
492        // Tail of the pre-sized XIP stays zero after the clamp.
493        assert_eq!(xip[2..], [0u8; 6]);
494    }
495
496    #[test]
497    fn load_rom_clamps_to_rom_len_not_constant() {
498        // Regression: `load_rom` previously clamped against the
499        // RP2350 `ROM_SIZE` constant (32 KB) regardless of the
500        // actual `self.rom` length. On the RP2040 path
501        // (`with_sizes(16 * 1024, ...)`) a 32 KB input would panic
502        // inside `copy_from_slice` because the destination slice
503        // was only 16 KB long. The fix clamps to `self.rom.len()`
504        // — symmetric with `load_flash`.
505        let mut mem = Memory::with_sizes(16 * 1024, 0);
506        let data: Vec<u8> = (0..32 * 1024_u32).map(|i| (i & 0xFF) as u8).collect();
507        // Must not panic.
508        mem.load_rom(&data);
509        // First 16 KB must be the input prefix verbatim.
510        for i in 0..16 * 1024_u32 {
511            assert_eq!(mem.rom_read8(i), (i & 0xFF) as u8);
512        }
513        // Reads past `self.rom.len()` fall through to the
514        // out-of-range branches — they return 0, not the input
515        // tail. Confirms we did not silently grow `rom`.
516        assert_eq!(mem.rom_read8(0x4000), 0);
517        assert_eq!(mem.rom_read32(0x4000), 0);
518    }
519
520    #[test]
521    fn with_sizes_zero_regions_are_usable() {
522        // Corner: zero-sized ROM and SRAM. Every read must fall through
523        // to the out-of-range branch (no panic) and every write must be
524        // a no-op. `load_rom` with empty input also degenerates cleanly.
525        let mut mem = Memory::with_sizes(0, 0);
526        mem.load_rom(&[]);
527        mem.sram_write8(0, 0xFF);
528        mem.sram_write16(0, 0xFFFF);
529        mem.sram_write32(0, 0xFFFF_FFFF);
530        assert_eq!(mem.rom_read8(0), 0);
531        assert_eq!(mem.rom_read16(0), 0);
532        assert_eq!(mem.rom_read32(0), 0);
533        assert_eq!(mem.sram_read8(0), 0);
534        assert_eq!(mem.sram_read16(0), 0);
535        assert_eq!(mem.sram_read32(0), 0);
536    }
537}