use crate::cpu::ADDR_MASK;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Chip {
Hdw,
Nce2,
Ce1,
Ce2,
Nce3,
}
pub const DAISY_CHAIN: [Chip; 5] = [Chip::Hdw, Chip::Nce2, Chip::Ce1, Chip::Ce2, Chip::Nce3];
pub const PRIORITY: [Chip; 5] = [Chip::Hdw, Chip::Nce2, Chip::Ce2, Chip::Ce1, Chip::Nce3];
const HDW_MASK: u32 = 0xF_FFC0;
const SIZE_MASK: u32 = 0xF_F000;
const ID_ADDR_MASK: u32 = 0xF_FF00;
const HDW_ID: u32 = 0x19;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Select {
Chip {
chip: Chip,
offset: u32,
},
Rom {
addr: u32,
},
}
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq)]
pub(crate) struct ChipState {
pub(crate) size: Option<u32>,
pub(crate) base: Option<u32>,
pub(crate) last: u32,
}
#[derive(Clone, Debug, Default, PartialEq, Eq)]
pub struct MemoryController {
pub(crate) chips: [ChipState; 5],
}
fn index(chip: Chip) -> usize {
match chip {
Chip::Hdw => 0,
Chip::Nce2 => 1,
Chip::Ce1 => 2,
Chip::Ce2 => 3,
Chip::Nce3 => 4,
}
}
impl MemoryController {
pub fn new() -> Self {
Self::default()
}
fn state(&self, chip: Chip) -> &ChipState {
&self.chips[index(chip)]
}
fn state_mut(&mut self, chip: Chip) -> &mut ChipState {
&mut self.chips[index(chip)]
}
pub fn reset(&mut self) {
for s in &mut self.chips {
s.size = None;
s.base = None;
}
}
fn first_unconfigured(&self) -> Option<Chip> {
DAISY_CHAIN.into_iter().find(|&c| !self.is_configured(c))
}
pub fn config(&mut self, value: u32) {
let Some(chip) = self.first_unconfigured() else {
return;
};
let value = value & ADDR_MASK;
let s = self.state_mut(chip);
s.last = value;
if chip == Chip::Hdw {
s.base = Some(value & HDW_MASK);
} else if let Some(mask) = s.size {
s.base = Some(value & mask);
} else {
s.size = Some(value & SIZE_MASK);
}
}
pub fn unconfig(&mut self, addr: u32) {
let addr = addr & ADDR_MASK;
if let Some(chip) = PRIORITY.into_iter().find(|&c| self.contains(c, addr)) {
let s = self.state_mut(chip);
s.size = None;
s.base = None;
}
}
pub fn read_id(&self) -> u32 {
let Some(chip) = self.first_unconfigured() else {
return 0;
};
let s = self.state(chip);
let size_next = s.size.is_none();
let code = match (chip, size_next) {
(Chip::Hdw, _) => return (s.last & HDW_MASK) | HDW_ID,
(Chip::Nce3, true) => 0x01,
(Chip::Nce2, true) => 0x03,
(Chip::Ce1, true) => 0x05,
(Chip::Ce2, true) => 0x07,
(Chip::Nce3, false) => 0xF2,
(Chip::Nce2, false) => 0xF4,
(Chip::Ce1, false) => 0xF6,
(Chip::Ce2, false) => 0xF8,
};
(s.last & ID_ADDR_MASK) | code
}
#[cfg(any(test, feature = "internals"))]
pub fn select(&self, addr: u32) -> Select {
self.select_where(addr, |_| true)
}
pub fn select_where(&self, addr: u32, active: impl Fn(Chip) -> bool) -> Select {
let addr = addr & ADDR_MASK;
for chip in PRIORITY {
if !active(chip) {
continue;
}
if let Some((base, mask)) = self.window(chip)
&& addr & mask == base
{
return Select::Chip {
chip,
offset: addr & !mask & ADDR_MASK,
};
}
}
Select::Rom { addr }
}
fn contains(&self, chip: Chip, addr: u32) -> bool {
self.window(chip)
.is_some_and(|(base, mask)| addr & mask == base)
}
pub fn is_configured(&self, chip: Chip) -> bool {
self.window(chip).is_some()
}
#[cfg(test)]
pub fn all_configured(&self) -> bool {
self.first_unconfigured().is_none()
}
pub fn window(&self, chip: Chip) -> Option<(u32, u32)> {
let s = self.state(chip);
let base = s.base?;
if chip == Chip::Hdw {
Some((base, HDW_MASK))
} else {
s.size.map(|mask| (base, mask))
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn sx_bring_up() -> MemoryController {
let mut mc = MemoryController::new();
for v in [
0x00100, 0xF8000, 0x70000, 0xC0000, 0x80000, 0xC0000, 0xC0000, 0xFF000, 0xD0000,
] {
mc.config(v);
}
mc
}
#[test]
fn sx_bring_up_sequence() {
let mc = sx_bring_up();
assert!(mc.all_configured());
assert_eq!(mc.read_id(), 0);
assert_eq!(
mc.select(0x00105),
Select::Chip {
chip: Chip::Hdw,
offset: 5
}
);
assert_eq!(
mc.select(0x70010),
Select::Chip {
chip: Chip::Nce2,
offset: 0x10
}
);
assert_eq!(mc.select(0x00140), Select::Rom { addr: 0x00140 });
assert!(matches!(
mc.select(0xD0000),
Select::Chip {
chip: Chip::Ce2,
..
}
));
assert_eq!(
mc.select(0x80000),
Select::Chip {
chip: Chip::Ce1,
offset: 0
}
);
assert_eq!(mc.window(Chip::Nce3), Some((0xD0000, 0xFF000)));
}
#[test]
fn config_ignored_when_all_configured() {
let mut mc = sx_bring_up();
let before = mc.clone();
mc.config(0x12345);
assert_eq!(mc, before);
}
#[test]
fn c_eq_id_sequence() {
let mut mc = MemoryController::new();
assert_eq!(mc.read_id(), 0x00019);
mc.config(0x00100);
assert_eq!(mc.read_id(), 0x00003);
mc.config(0xF8000);
assert_eq!(mc.read_id(), 0xF80F4);
mc.reset();
assert!(!mc.is_configured(Chip::Hdw));
assert_eq!(mc.read_id(), 0x00119);
}
#[test]
fn address_snaps_down_to_size() {
let mut mc = MemoryController::new();
mc.config(0x00100);
mc.config(0xF8000);
mc.config(0x7A000);
assert_eq!(mc.window(Chip::Nce2), Some((0x78000, 0xF8000)));
assert_eq!(
mc.select(0x7A001),
Select::Chip {
chip: Chip::Nce2,
offset: 0x2001
}
);
}
#[test]
fn hdw_window_aligned() {
let mut mc = MemoryController::new();
mc.config(0x0012F);
assert_eq!(mc.window(Chip::Hdw), Some((0x00100, 0xFFFC0)));
assert!(mc.is_configured(Chip::Hdw));
assert!(!mc.is_configured(Chip::Nce2));
}
#[test]
fn unconfig_removes_higher_priority_first() {
let mut mc = sx_bring_up();
mc.unconfig(0xD0000);
assert!(!mc.is_configured(Chip::Ce2));
assert!(mc.is_configured(Chip::Nce3));
assert_eq!(
mc.select(0xD0005),
Select::Chip {
chip: Chip::Nce3,
offset: 5
}
);
mc.unconfig(0xD0000);
assert!(!mc.is_configured(Chip::Nce3));
assert_eq!(mc.select(0xD0005), Select::Rom { addr: 0xD0005 });
let before = mc.clone();
mc.unconfig(0xD0000);
assert_eq!(mc, before);
}
#[test]
fn inactive_chip_falls_through() {
let mc = sx_bring_up();
assert_eq!(
mc.select_where(0xD0001, |c| c != Chip::Ce2),
Select::Chip {
chip: Chip::Nce3,
offset: 1
}
);
assert_eq!(
mc.select_where(0xD0001, |c| !matches!(c, Chip::Ce2 | Chip::Nce3)),
Select::Rom { addr: 0xD0001 }
);
}
#[test]
fn reconfig_after_unconfig_goes_to_freed_chip() {
let mut mc = sx_bring_up();
mc.unconfig(0x80000);
assert!(!mc.is_configured(Chip::Ce1));
assert_eq!(mc.read_id(), (0x80000 & 0xFFF00) | 0x05);
}
}