use alloc::collections::BTreeSet;
use alloc::vec;
use alloc::vec::Vec;
use onerom_config::chip::ChipType;
use onerom_config::hw::Board;
use super::addr_layout::{AddrLayout, LayoutError};
use super::alg_preference::{CsAlgPreference, cs_alg_preference};
use super::gpio_window::fits_pio_window;
use super::slot_context::SlotContext;
use crate::image::{ChipSetType, CsConfig, CsLogic};
use super::multi_cs_config::{ControlLineKind, control_line_logic};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum SelectRole {
Cs1,
Cs2,
Cs3,
Ce,
Oe,
X1,
X2,
HalfSelect,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct SelectLine {
pub role: SelectRole,
pub gpio: u8,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct AlgCs2Config {
pub base_qualifier_pin: u8,
pub num_qualifier_pins: u8,
pub qualifier_inactive_pattern: u8,
}
fn control_line_kind_to_select_role(kind: ControlLineKind) -> SelectRole {
match kind {
ControlLineKind::Ce => SelectRole::Ce,
ControlLineKind::Oe => SelectRole::Oe,
ControlLineKind::Cs1 => SelectRole::Cs1,
ControlLineKind::Cs2 => SelectRole::Cs2,
ControlLineKind::Cs3 => SelectRole::Cs3,
}
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CsDataLayout {
pub gpio_base: u8,
pub base_data_pin: u8,
pub num_data_pins: u8,
pub data_pin_gpios: Vec<u8>,
pub base_cs_pin: u8,
pub num_cs_pins: u8,
pub cs_ignore_index: Option<u8>,
pub select_lines: Vec<SelectLine>,
pub commoned_lines: Vec<SelectLine>,
pub alg_cs2: Option<AlgCs2Config>,
}
fn select_phys_pins(
board: Board,
chip_type: ChipType,
cs_config: &CsConfig,
) -> Result<Vec<(SelectRole, u8)>, LayoutError> {
let lines = chip_type.control_lines();
let mut pins = vec![];
let active = |l: CsLogic| matches!(l, CsLogic::ActiveLow | CsLogic::ActiveHigh);
for line in lines {
match line.name {
"ce" => {
let logic = control_line_logic("ce", cs_config);
if active(logic) {
pins.push((SelectRole::Ce, line.pin));
}
}
"oe" => {
let logic = control_line_logic("oe", cs_config);
if active(logic) {
pins.push((SelectRole::Oe, line.pin));
}
}
"cs1" => {
let logic = control_line_logic("cs1", cs_config);
if active(logic) {
pins.push((SelectRole::Cs1, line.pin));
}
}
"cs2" => {
let logic = control_line_logic("cs2", cs_config);
if active(logic) {
pins.push((SelectRole::Cs2, line.pin));
}
}
"cs3" => {
let logic = control_line_logic("cs3", cs_config);
if active(logic) {
pins.push((SelectRole::Cs3, line.pin));
}
}
_ => {}
}
}
if pins.is_empty() {
return Err(LayoutError::NoSelectLine { board, chip_type });
}
Ok(pins)
}
fn gpios_for_pin(
board: Board,
chip_type: ChipType,
phys_pin: u8,
pin_offset: i16,
) -> Result<&'static [u8], LayoutError> {
let socket_pin = phys_pin as i16 + pin_offset;
if socket_pin < 1 || socket_pin > board.chip_pins() as i16 {
return Err(LayoutError::UnmappedPin {
board,
chip_type,
phys_pin,
});
}
let gpios = board.gpios_for_socket_pin(socket_pin as u8);
if gpios.is_empty() {
return Err(LayoutError::UnmappedPin {
board,
chip_type,
phys_pin,
});
}
Ok(gpios)
}
pub fn derive_cs_data_layout(
ctx: &SlotContext,
addr_layout: Option<&AddrLayout>,
) -> Result<CsDataLayout, LayoutError> {
let board = ctx.board;
let set_type = ctx.set_type;
let chip_types = &ctx.chip_types;
let cs_config = &ctx.cs_config;
let multi_cs_config = ctx.multi_cs_config.as_ref();
let pin_offset = ctx.pin_offset;
let chip0 = chip_types[0];
let addr_pin_gpios: Option<&[u8]> = addr_layout.map(|a| a.addr_pin_gpios.as_slice());
let excess_addr_pin_gpios: &[u8] = addr_layout
.map(|a| a.excess_addr_pin_gpios.as_slice())
.unwrap_or(&[]);
let qual_indices = chip0.deselect_when_address_all_high();
if qual_indices.is_some() && addr_pin_gpios.is_none() {
return Err(LayoutError::MissingAddrPinGpios {
board,
chip_type: chip0,
});
}
if !excess_addr_pin_gpios.is_empty() {
match cs_config.cs1_logic() {
Some(CsLogic::ActiveLow) | Some(CsLogic::ActiveHigh) => {}
_ => {
return Err(LayoutError::RomTooLargeNoCsConfig {
board,
chip_type: chip0,
});
}
}
}
let qual_gpios: Option<Vec<u8>> = qual_indices.map(|indices| {
let gpios = addr_pin_gpios.expect("pre-checked above");
indices.iter().map(|&i| gpios[i as usize]).collect()
});
let mut data_candidates: Vec<&'static [u8]> = Vec::with_capacity(chip0.data_pins().len());
for &phys_pin in chip0.data_pins() {
data_candidates.push(gpios_for_pin(board, chip0, phys_pin, pin_offset)?);
}
let num_data_pins = chip0.data_pins().len() as u8;
let mut select_roles: Vec<SelectRole> = Vec::new();
let mut select_candidates: Vec<&'static [u8]> = Vec::new();
let mut commoned_candidates: Vec<&'static [u8]> = Vec::new();
let mut commoned_roles: Vec<SelectRole> = Vec::new();
let mut ignored_gpios: BTreeSet<u8> = BTreeSet::new();
match set_type {
ChipSetType::Single | ChipSetType::Banked => {
for (role, phys_pin) in select_phys_pins(board, chip0, cs_config)? {
select_roles.push(role);
select_candidates.push(gpios_for_pin(board, chip0, phys_pin, pin_offset)?);
}
}
ChipSetType::Multi => {
let mcc = multi_cs_config
.expect("derive_cs_data_layout called for Multi set without MultiChipCsConfig");
let per_chip_line = chip0
.control_lines()
.iter()
.find(|l| l.name == mcc.per_chip_select.name())
.ok_or(LayoutError::NoSelectLine {
board,
chip_type: chip0,
})?;
select_roles.push(control_line_kind_to_select_role(mcc.per_chip_select));
select_candidates.push(gpios_for_pin(board, chip0, per_chip_line.pin, pin_offset)?);
for &commoned_kind in &mcc.commoned_lines {
let commoned_line = chip0
.control_lines()
.iter()
.find(|l| l.name == commoned_kind.name())
.ok_or(LayoutError::NoSelectLine {
board,
chip_type: chip0,
})?;
commoned_roles.push(control_line_kind_to_select_role(commoned_kind));
commoned_candidates.push(gpios_for_pin(
board,
chip0,
commoned_line.pin,
pin_offset,
)?);
}
for &ignored_kind in &mcc.ignored_lines {
let ignored_line = chip0
.control_lines()
.iter()
.find(|l| l.name == ignored_kind.name())
.ok_or(LayoutError::NoSelectLine {
board,
chip_type: chip0,
})?;
for &gpio in gpios_for_pin(board, chip0, ignored_line.pin, pin_offset)? {
ignored_gpios.insert(gpio);
}
}
if chip_types.len() >= 2 {
let x1 = board.gpios_for_x_pin(1);
if x1.is_empty() {
return Err(LayoutError::MissingXPin { board, x_pin: 1 });
}
select_roles.push(SelectRole::X1);
select_candidates.push(x1);
}
if chip_types.len() >= 3 {
let x2 = board.gpios_for_x_pin(2);
if x2.is_empty() {
return Err(LayoutError::MissingXPin { board, x_pin: 2 });
}
select_roles.push(SelectRole::X2);
select_candidates.push(x2);
}
}
}
let select_start = data_candidates.len();
let select_end = select_start + select_candidates.len();
let mut all_candidates = data_candidates;
all_candidates.extend_from_slice(&select_candidates);
all_candidates.extend_from_slice(&commoned_candidates);
let two_option_slots: Vec<usize> = all_candidates
.iter()
.enumerate()
.filter(|(_, opts)| opts.len() > 1)
.map(|(i, _)| i)
.collect();
let num_combos: u32 = 1 << two_option_slots.len();
let mut best: Option<(CsDataLayout, (CsAlgPreference, u32))> = None;
let mut last_noncontig_select: Option<Vec<u8>> = None;
let mut interior_ignored: Option<u8> = None;
for combo in 0..num_combos {
let resolved: Vec<u8> = all_candidates
.iter()
.enumerate()
.map(|(i, opts)| {
let choice = two_option_slots
.iter()
.position(|&s| s == i)
.map(|bit| ((combo >> bit) & 1) as usize)
.unwrap_or(0);
opts[choice]
})
.collect();
let data_gpios: BTreeSet<u8> = resolved[..select_start].iter().copied().collect();
let select_resolved = &resolved[select_start..select_end];
let commoned_resolved = &resolved[select_end..];
let mut select_gpios: BTreeSet<u8> = select_resolved.iter().copied().collect();
for &gpio in excess_addr_pin_gpios {
select_gpios.insert(gpio);
}
for &gpio in commoned_resolved {
select_gpios.insert(gpio);
}
let data_min = *data_gpios.iter().next().unwrap();
let data_max = *data_gpios.iter().last().unwrap();
if data_max - data_min + 1 != data_gpios.len() as u8 {
continue;
}
let sel_min = *select_gpios.iter().next().unwrap();
let sel_max = *select_gpios.iter().last().unwrap();
let sel_span = sel_max - sel_min + 1;
let sel_len = select_gpios.len() as u8;
let cs_ignore_index = if sel_span == sel_len {
None
} else if sel_span == sel_len + 1
&& matches!(set_type, ChipSetType::Single | ChipSetType::Banked)
{
let gap = (sel_min..=sel_max)
.position(|g| !select_gpios.contains(&g))
.expect("span - len == 1 implies exactly one gap") as u8;
Some(gap)
} else {
if interior_ignored.is_none() {
interior_ignored = (sel_min..=sel_max)
.find(|g| !select_gpios.contains(g) && ignored_gpios.contains(g));
}
last_noncontig_select = Some(select_gpios.into_iter().collect());
continue;
};
let num_cs_pins = sel_span;
let mut all_min = data_min.min(sel_min);
let mut all_max = data_max.max(sel_max);
if let Some(ref qg) = qual_gpios {
let q_min = *qg.iter().min().unwrap();
let q_max = *qg.iter().max().unwrap();
all_min = all_min.min(q_min);
all_max = all_max.max(q_max);
}
let gpio_base: u8 = if all_min < 16 { 0 } else { 16 };
let base_data_pin = data_min - gpio_base;
let base_cs_pin = sel_min - gpio_base;
let window_span = all_max - gpio_base + 1;
if !fits_pio_window(gpio_base, window_span) {
continue;
}
let alg_cs2 = qual_gpios.as_ref().map(|qg| {
let q_min = *qg.iter().min().unwrap();
let q_max = *qg.iter().max().unwrap();
let base_qualifier_pin = q_min - gpio_base;
let num_qualifier_pins = q_max - q_min + 1;
let qualifier_inactive_pattern =
qg.iter().fold(0u8, |acc, &g| acc | (1 << (g - q_min)));
AlgCs2Config {
base_qualifier_pin,
num_qualifier_pins,
qualifier_inactive_pattern,
}
});
let alg_pref = cs_alg_preference(cs_ignore_index, alg_cs2.as_ref());
let score = (alg_pref, (all_max - all_min + 1) as u32);
if best.as_ref().is_none_or(|(_, s)| score < *s) {
let mut select_lines: Vec<SelectLine> = select_roles
.iter()
.zip(select_resolved.iter())
.map(|(&role, &gpio)| SelectLine { role, gpio })
.collect();
for &gpio in excess_addr_pin_gpios {
select_lines.push(SelectLine {
role: SelectRole::HalfSelect,
gpio,
});
}
let commoned_lines: Vec<SelectLine> = commoned_roles
.iter()
.zip(commoned_resolved.iter())
.map(|(&role, &gpio)| SelectLine { role, gpio })
.collect();
best = Some((
CsDataLayout {
gpio_base,
base_data_pin,
num_data_pins,
data_pin_gpios: resolved[..select_start].to_vec(),
base_cs_pin,
num_cs_pins,
cs_ignore_index,
select_lines,
commoned_lines,
alg_cs2,
},
score,
));
}
}
best.map(|(layout, _)| layout).ok_or({
if let Some(gpio) = interior_ignored {
LayoutError::InteriorIgnoredLine {
board,
chip_type: chip0,
gpio,
}
} else if let Some(gpios) = last_noncontig_select {
LayoutError::NonContiguousSelect {
board,
chip_type: chip0,
gpios,
}
} else {
LayoutError::NoValidLayout { board }
}
})
}
#[cfg(test)]
mod tests {
use super::*;
use onerom_metadata::BitModes;
use super::super::multi_cs_config::MultiChipCsConfig;
use super::super::slot_context::SlotContext;
fn ctx_single(board: Board, chip_type: ChipType, cs_config: CsConfig) -> SlotContext {
SlotContext {
board,
set_type: ChipSetType::Single,
chip_types: alloc::vec![chip_type],
cs_config,
bit_mode: BitModes::BitMode8,
pin_offset: 0,
force_16_bit: false,
multi_cs_config: None,
}
}
fn ctx_multi(
board: Board,
chip_types: Vec<ChipType>,
cs_config: CsConfig,
mcc: MultiChipCsConfig,
) -> SlotContext {
SlotContext {
board,
set_type: ChipSetType::Multi,
chip_types,
cs_config,
bit_mode: BitModes::BitMode8,
pin_offset: 0,
force_16_bit: false,
multi_cs_config: Some(mcc),
}
}
#[test]
fn fire24a_2364_single() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let ctx = ctx_single(Board::Fire24A, ChipType::Chip2364, cs_config);
let layout = derive_cs_data_layout(&ctx, None).expect("layout derivation should succeed");
assert_eq!(layout.gpio_base, 0);
assert_eq!(layout.base_data_pin, 16);
assert_eq!(layout.num_data_pins, 8);
assert_eq!(layout.base_cs_pin, 13);
assert_eq!(layout.num_cs_pins, 1);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(layout.alg_cs2, None);
assert_eq!(
layout.select_lines,
vec![SelectLine {
role: SelectRole::Cs1,
gpio: 13
}]
);
assert_eq!(layout.data_pin_gpios, vec![16, 17, 18, 19, 20, 21, 22, 23]);
}
#[test]
fn fire24a_2316_single_cs2_cs3_ignored() {
let cs_config = CsConfig::new(
Some(CsLogic::ActiveLow),
Some(CsLogic::Ignore),
Some(CsLogic::Ignore),
);
let ctx = ctx_single(Board::Fire24A, ChipType::Chip2316, cs_config);
let layout = derive_cs_data_layout(&ctx, None).expect("layout derivation should succeed");
assert_eq!(layout.gpio_base, 0);
assert_eq!(layout.base_cs_pin, 13);
assert_eq!(layout.num_cs_pins, 1);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(layout.alg_cs2, None);
assert_eq!(
layout.select_lines,
vec![SelectLine {
role: SelectRole::Cs1,
gpio: 13
}]
);
}
#[test]
fn fire24a_2316_single_cs2_active() {
let cs_config = CsConfig::new(
Some(CsLogic::ActiveLow),
Some(CsLogic::ActiveLow),
Some(CsLogic::Ignore),
);
let ctx = ctx_single(Board::Fire24A, ChipType::Chip2316, cs_config);
let layout =
derive_cs_data_layout(&ctx, None).expect("layout derivation should succeed (AlgCs1)");
assert_eq!(layout.gpio_base, 0);
assert_eq!(layout.base_cs_pin, 13);
assert_eq!(layout.num_cs_pins, 3);
assert_eq!(layout.cs_ignore_index, Some(1));
assert_eq!(layout.alg_cs2, None);
assert_eq!(
layout.select_lines,
vec![
SelectLine {
role: SelectRole::Cs1,
gpio: 13
},
SelectLine {
role: SelectRole::Cs2,
gpio: 15
},
]
);
}
#[test]
fn missing_addr_pin_gpios_for_alg_cs2_chip_errors() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let ctx = ctx_single(Board::Fire28A, ChipType::Chip23QL384, cs_config);
let result = derive_cs_data_layout(&ctx, None);
assert!(matches!(
result,
Err(LayoutError::MissingAddrPinGpios { .. })
));
}
#[test]
fn fire28a_23ql384_single_alg_cs2_populated() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let ctx = ctx_single(Board::Fire28A, ChipType::Chip23QL384, cs_config);
let addr_layout = super::super::addr_layout::derive_addr_layout(&ctx)
.expect("addr layout derivation should succeed");
let layout = derive_cs_data_layout(&ctx, Some(&addr_layout))
.expect("layout derivation should succeed");
let cs2 = layout.alg_cs2.expect("23QL384 must have alg_cs2");
assert_eq!(cs2.num_qualifier_pins, 2);
assert_eq!(cs2.qualifier_inactive_pattern, 0b11);
assert!(cs2.base_qualifier_pin < 32);
}
#[test]
fn fire24a_2364_multi_2chip_cs1_primary() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24A,
alloc::vec![ChipType::Chip2364, ChipType::Chip2364],
cs_config,
mcc,
);
let result = derive_cs_data_layout(&ctx, None);
assert!(
matches!(result, Err(LayoutError::NonContiguousSelect { .. })),
"Fire24A Multi 2364: CS1 (GPIO 13) and X1 (GPIO 9) are not contiguous"
);
}
#[test]
fn fire24a_2732_multi_2chip_ce_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::ActiveLow), Some(CsLogic::Ignore));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Ce,
commoned_lines: alloc::vec![ControlLineKind::Oe],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24A,
alloc::vec![ChipType::Chip2732, ChipType::Chip2732],
cs_config,
mcc,
);
let result = derive_cs_data_layout(&ctx, None);
assert!(
matches!(result, Err(LayoutError::NonContiguousSelect { .. })),
"Fire24A Multi 2732 CE-primary: CE (GPIO 15) and X1 (GPIO 9) are not contiguous"
);
}
#[test]
fn fire24a_2732_multi_2chip_oe_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::Ignore), Some(CsLogic::ActiveLow));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Oe,
commoned_lines: alloc::vec![ControlLineKind::Ce],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24A,
alloc::vec![ChipType::Chip2732, ChipType::Chip2732],
cs_config,
mcc,
);
let result = derive_cs_data_layout(&ctx, None);
assert!(
matches!(result, Err(LayoutError::NonContiguousSelect { .. })),
"Fire24A Multi 2732 OE-primary: OE (GPIO 13) and X1 (GPIO 9) are not contiguous"
);
}
#[test]
fn fire24e_2364_multi_2chip_cs1_primary() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24E,
alloc::vec![ChipType::Chip2364, ChipType::Chip2364],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire24E Multi 2364 2-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Cs1,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire24e_2364_multi_3chip_cs1_primary() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24E,
alloc::vec![ChipType::Chip2364, ChipType::Chip2364, ChipType::Chip2364],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire24E Multi 2364 3-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Cs1,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
assert_eq!(
layout.select_lines[2],
SelectLine {
role: SelectRole::X2,
gpio: 8
}
);
}
#[test]
fn fire24e_2732_multi_2chip_oe_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::Ignore), Some(CsLogic::ActiveLow));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Oe,
commoned_lines: alloc::vec![ControlLineKind::Ce],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24E,
alloc::vec![ChipType::Chip2732, ChipType::Chip2732],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire24E Multi 2732 OE-primary layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Oe,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire24e_2732_multi_2chip_ce_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::ActiveLow), Some(CsLogic::Ignore));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Ce,
commoned_lines: alloc::vec![ControlLineKind::Oe],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire24E,
alloc::vec![ChipType::Chip2732, ChipType::Chip2732],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None).expect(
"Fire24E Multi 2732 CE-primary: OE (GPIO 10) fills gap between CE (11) and X1 (9)",
);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Ce,
gpio: 11
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire28c_23128_multi_2chip_cs1_primary() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![ControlLineKind::Cs2, ControlLineKind::Cs3],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire28C,
alloc::vec![ChipType::Chip23128, ChipType::Chip23128],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire28C Multi 23128 2-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Cs1,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire28c_23128_multi_3chip_cs1_primary() {
let cs_config = CsConfig::new(Some(CsLogic::ActiveLow), None, None);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![ControlLineKind::Cs2, ControlLineKind::Cs3],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire28C,
alloc::vec![
ChipType::Chip23128,
ChipType::Chip23128,
ChipType::Chip23128
],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire28C Multi 23128 3-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Cs1,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
assert_eq!(
layout.select_lines[2],
SelectLine {
role: SelectRole::X2,
gpio: 8
}
);
}
#[test]
fn fire28c_27128_multi_2chip_ce_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::ActiveLow), Some(CsLogic::Ignore));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Ce,
commoned_lines: alloc::vec![ControlLineKind::Oe],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire28C,
alloc::vec![ChipType::Chip27128, ChipType::Chip27128],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire28C Multi 27128 CE-primary 2-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Ce,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire28c_27128_multi_3chip_ce_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::ActiveLow), Some(CsLogic::Ignore));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Ce,
commoned_lines: alloc::vec![ControlLineKind::Oe],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire28C,
alloc::vec![
ChipType::Chip27128,
ChipType::Chip27128,
ChipType::Chip27128
],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire28C Multi 27128 CE-primary 3-chip layout derivation should succeed");
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Ce,
gpio: 10
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
assert_eq!(
layout.select_lines[2],
SelectLine {
role: SelectRole::X2,
gpio: 8
}
);
}
#[test]
fn fire28c_27128_multi_2chip_oe_primary() {
let cs_config = CsConfig::new_with_ce_oe(Some(CsLogic::Ignore), Some(CsLogic::ActiveLow));
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Oe,
commoned_lines: alloc::vec![ControlLineKind::Ce],
ignored_lines: alloc::vec![],
};
let ctx = ctx_multi(
Board::Fire28C,
alloc::vec![ChipType::Chip27128, ChipType::Chip27128],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None).expect(
"Fire28C Multi 27128 OE-primary: CE (GPIO 10) fills gap between OE (11) and X1 (9)",
);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines[0],
SelectLine {
role: SelectRole::Oe,
gpio: 11
}
);
assert_eq!(
layout.select_lines[1],
SelectLine {
role: SelectRole::X1,
gpio: 9
}
);
}
#[test]
fn fire24f_2316_multi_2chip_cs2_cs3_ignored() {
let cs_config = CsConfig::new(
Some(CsLogic::ActiveLow),
Some(CsLogic::Ignore),
Some(CsLogic::Ignore),
);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![ControlLineKind::Cs2, ControlLineKind::Cs3],
};
let ctx = ctx_multi(
Board::Fire24F,
alloc::vec![ChipType::Chip2316, ChipType::Chip2316],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire24F Multi 2316 2-chip layout derivation should succeed");
assert_eq!(layout.gpio_base, 0);
assert_eq!(layout.base_cs_pin, 9);
assert_eq!(layout.num_cs_pins, 2);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines,
vec![
SelectLine {
role: SelectRole::Cs1,
gpio: 10
},
SelectLine {
role: SelectRole::X1,
gpio: 9
},
]
);
}
#[test]
fn fire24f_2316_multi_3chip_cs2_cs3_ignored() {
let cs_config = CsConfig::new(
Some(CsLogic::ActiveLow),
Some(CsLogic::Ignore),
Some(CsLogic::Ignore),
);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs1,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![ControlLineKind::Cs2, ControlLineKind::Cs3],
};
let ctx = ctx_multi(
Board::Fire24F,
alloc::vec![ChipType::Chip2316, ChipType::Chip2316, ChipType::Chip2316],
cs_config,
mcc,
);
let layout = derive_cs_data_layout(&ctx, None)
.expect("Fire24F Multi 2316 3-chip layout derivation should succeed");
assert_eq!(layout.gpio_base, 0);
assert_eq!(layout.base_cs_pin, 8);
assert_eq!(layout.num_cs_pins, 3);
assert_eq!(layout.cs_ignore_index, None);
assert_eq!(
layout.select_lines,
vec![
SelectLine {
role: SelectRole::Cs1,
gpio: 10
},
SelectLine {
role: SelectRole::X1,
gpio: 9
},
SelectLine {
role: SelectRole::X2,
gpio: 8
},
]
);
}
#[test]
fn fire24f_2316_multi_interior_ignored_errors() {
let cs_config = CsConfig::new(
Some(CsLogic::Ignore),
Some(CsLogic::Ignore),
Some(CsLogic::ActiveLow),
);
let mcc = MultiChipCsConfig {
per_chip_select: ControlLineKind::Cs3,
commoned_lines: alloc::vec![],
ignored_lines: alloc::vec![ControlLineKind::Cs1, ControlLineKind::Cs2],
};
let ctx = ctx_multi(
Board::Fire24F,
alloc::vec![ChipType::Chip2316, ChipType::Chip2316, ChipType::Chip2316],
cs_config,
mcc,
);
let result = derive_cs_data_layout(&ctx, None);
assert!(
matches!(
result,
Err(LayoutError::InteriorIgnoredLine { gpio: 10, .. })
),
"interior ignored CS1@10 must produce InteriorIgnoredLine, got {result:?}"
);
}
}