1use bincode::{Decode, Encode};
2use jgenesis_common::num::GetBit;
3
4const CODE_CACHE_RAM_LEN: usize = 512;
5
6type CodeCacheRam = [u8; CODE_CACHE_RAM_LEN];
7
8#[derive(Debug, Clone, Encode, Decode)]
9pub struct CodeCache {
10    ram: Box<CodeCacheRam>,
11    cbr: u16,
12    cached_lines: u32,
13}
14
15impl CodeCache {
16    pub fn new() -> Self {
17        Self {
18            ram: vec![0; CODE_CACHE_RAM_LEN].into_boxed_slice().try_into().unwrap(),
19            cbr: 0,
20            cached_lines: 0,
21        }
22    }
23
24    pub fn pc_is_cacheable(&self, address: u16) -> bool {
25        if self.cbr < 0xFE00 {
26            (self.cbr..self.cbr + 512).contains(&address)
27        } else {
28            address >= self.cbr || address < self.cbr.wrapping_add(512)
29        }
30    }
31
32    pub fn get(&self, address: u16) -> Option<u8> {
33        let cache_addr = address.wrapping_sub(self.cbr);
34        let cache_line_bit = (cache_addr >> 4) as u8;
35        self.cached_lines.bit(cache_line_bit).then_some(self.ram[cache_addr as usize])
36    }
37
38    pub fn set(&mut self, address: u16, value: u8) {
39        let cache_addr = address.wrapping_sub(self.cbr);
40        self.ram[cache_addr as usize] = value;
41
42        if address & 0xF == 0xF {
43            // Cache line is set when the last address of a 16-byte block is written
44            self.set_cache_line(cache_addr);
45        }
46    }
47
48    fn set_cache_line(&mut self, cache_addr: u16) {
49        let cache_line = 1 << (cache_addr >> 4);
50        self.cached_lines |= cache_line;
51    }
52
53    pub fn read_ram(&self, address: u16) -> u8 {
54        self.ram[(address & 0x1FF) as usize]
55    }
56
57    pub fn write_ram(&mut self, address: u16, value: u8) {
58        let cache_addr = address & 0x1FF;
59        self.ram[cache_addr as usize] = value;
60
61        if address & 0xF == 0xF {
62            // Cache line is set when the last address of a 16-byte block is written
63            self.set_cache_line(cache_addr);
64        }
65    }
66
67    pub fn update_cbr(&mut self, cbr: u16) {
68        // Changing CBR via a CACHE or LJMP instruction clears all cache lines
69        self.cbr = cbr;
70        self.cached_lines = 0;
71
72        log::trace!("Set CBR to {cbr:04X}");
73    }
74
75    pub fn full_clear(&mut self) {
76        self.cbr = 0;
77        self.cached_lines = 0;
78    }
79
80    pub fn cbr(&self) -> u16 {
81        self.cbr
82    }
83}