use onerom_config::chip::{CHIP_TYPES, ChipType};
use onerom_config::hw::{Board, HeaderColumn, HeaderRole, HeaderSlot};
use onerom_config::mcu::PinTolerance;
use onerom_gen::ChipSetType;
use onerom_gen::compat::{check_chip_set_on_board, default_cs_config, format_size};
use onerom_gen::socket_pin_offset;
const PAD_W: usize = 9;
const BODY_W: usize = 17;
fn left(s: &str, w: usize) -> String {
let n = s.chars().count();
if n >= w {
s.to_string()
} else {
format!("{s}{}", " ".repeat(w - n))
}
}
fn header_role_label(role: &HeaderRole) -> String {
match role {
HeaderRole::Power5V => "5V".to_string(),
HeaderRole::Gnd => "GND".to_string(),
HeaderRole::Run => "RUN".to_string(),
HeaderRole::Bootsel => "BOOTSEL".to_string(),
HeaderRole::Select(b) => format!("SEL_{}", (b'A' + *b) as char),
HeaderRole::Swclk => "SWCLK".to_string(),
HeaderRole::Swdio => "SWDIO".to_string(),
HeaderRole::X1 => "X1".to_string(),
HeaderRole::X2 => "X2".to_string(),
HeaderRole::Addr(n) => format!("A{n}"),
}
}
#[allow(clippy::wildcard_enum_match_arm)]
fn header_role_gpio(board: &Board, role: &HeaderRole) -> Option<u8> {
match role {
HeaderRole::Select(b) => board.sel_pins().get(*b as usize).copied(),
HeaderRole::X1 => (board.pin_x1() != 255).then(|| board.pin_x1()),
HeaderRole::X2 => (board.pin_x2() != 255).then(|| board.pin_x2()),
HeaderRole::Addr(n) => board.addr_pins().get(*n as usize).copied(),
_ => None,
}
}
fn tolerance_tag(t: PinTolerance) -> &'static str {
match t {
PinTolerance::FiveVolt => "5V",
PinTolerance::ThreeVolt3 => "!!3V3!!",
}
}
fn header_pad_tokens(board: &Board, slot: &HeaderSlot) -> Vec<String> {
match slot {
HeaderSlot::NotPopulated => Vec::new(),
HeaderSlot::NotConnected => vec!["n/c".to_string()],
HeaderSlot::Roles(roles) => {
let mut tokens: Vec<String> = roles.iter().map(header_role_label).collect();
if let Some(gpio) = roles.iter().find_map(|r| header_role_gpio(board, r)) {
tokens.push(format!("GPIO{gpio}"));
if let Some(tol) = board.gpio_tolerance(gpio) {
tokens.push(tolerance_tag(tol).to_string());
}
}
tokens
}
}
}
fn header_pad_lines(tokens: &[String], height: usize) -> Vec<String> {
(0..height)
.map(|i| left(tokens.get(i).map(String::as_str).unwrap_or(""), PAD_W))
.collect()
}
fn hcell(content: &str) -> String {
format!("│ {content} │")
}
fn hgap() -> String {
" ".repeat(PAD_W + 4)
}
const HTOP: &str = "┌───────────┐";
const HDIV: &str = "├───────────┤";
const HBOT: &str = "└───────────┘";
#[allow(clippy::wildcard_enum_match_arm)]
pub fn render_pin_header(board: &Board) -> Option<String> {
let header = board.jumper_header()?;
let mcu = board
.rp_variant()
.map(|v| v.to_string())
.unwrap_or_else(|| board.mcu_family().to_string());
let cols: &[HeaderColumn] = header.columns;
let max_col = cols.iter().map(|c| c.col).max().unwrap_or(0);
let by_col = |n: u8| cols.iter().find(|c| c.col == n);
let rp = board.rp_variant().is_some();
let present = |slot: &HeaderSlot| !matches!(slot, HeaderSlot::NotPopulated);
let row1_present = |n: u8| by_col(n).is_some_and(|c| present(&c.row1));
let row2_present = |n: u8| by_col(n).is_some_and(|c| present(&c.row2));
let row3_present = |n: u8| by_col(n).and_then(|c| c.row3.as_ref()).is_some_and(present);
let any_row3 = (1..=max_col).any(row3_present);
let mut pad_h = 1usize;
for c in cols {
if present(&c.row1) {
pad_h = pad_h.max(header_pad_tokens(board, &c.row1).len());
}
if present(&c.row2) {
pad_h = pad_h.max(header_pad_tokens(board, &c.row2).len());
}
if let Some(x) = &c.row3
&& present(x)
{
pad_h = pad_h.max(header_pad_tokens(board, x).len());
}
}
let row = |f: &dyn Fn(u8) -> String| -> String {
(1..=max_col)
.map(f)
.collect::<Vec<_>>()
.join(" ")
.trim_end()
.to_string()
};
let mut out = String::new();
out.push_str(&format!(
"Pin header · {} · {mcu}\n",
board.description()
));
out.push_str(
"Viewed from above (component side up) — header runs along the board's top edge.\n\n",
);
out.push_str(" ◄ pin 1\n");
out.push_str(" ");
out.push_str(&row(&|n| {
if row1_present(n) {
HTOP.to_string()
} else {
hgap()
}
}));
out.push('\n');
for k in 0..pad_h {
out.push_str(" ");
out.push_str(&row(&|n| {
if row1_present(n) {
let toks = header_pad_tokens(board, &by_col(n).unwrap().row1);
hcell(&header_pad_lines(&toks, pad_h)[k])
} else {
hgap()
}
}));
out.push('\n');
}
out.push_str(" ");
out.push_str(&row(&|n| match (row1_present(n), row2_present(n)) {
(true, true) => HDIV.to_string(),
(true, false) => HBOT.to_string(),
(false, true) => HTOP.to_string(),
(false, false) => hgap(),
}));
out.push('\n');
for k in 0..pad_h {
out.push_str(" ");
out.push_str(&row(&|n| {
if row2_present(n) {
let toks = header_pad_tokens(board, &by_col(n).unwrap().row2);
hcell(&header_pad_lines(&toks, pad_h)[k])
} else {
hgap()
}
}));
out.push('\n');
}
out.push_str(" ");
out.push_str(&row(&|n| {
if row2_present(n) {
HBOT.to_string()
} else {
hgap()
}
}));
out.push('\n');
if any_row3 {
out.push_str(" ");
out.push_str(&row(&|n| {
if row3_present(n) {
HTOP.to_string()
} else {
hgap()
}
}));
out.push('\n');
for k in 0..pad_h {
out.push_str(" ");
out.push_str(&row(&|n| {
if row3_present(n) {
let x = by_col(n).unwrap().row3.as_ref().unwrap();
hcell(&header_pad_lines(&header_pad_tokens(board, x), pad_h)[k])
} else {
hgap()
}
}));
out.push('\n');
}
out.push_str(" ");
out.push_str(&row(&|n| {
if row3_present(n) {
HBOT.to_string()
} else {
hgap()
}
}));
out.push('\n');
}
let roles_present = |pred: &dyn Fn(&HeaderRole) -> bool| -> bool {
cols.iter()
.flat_map(|c| [Some(&c.row1), Some(&c.row2), c.row3.as_ref()])
.flatten()
.any(|slot| match slot {
HeaderSlot::Roles(rs) => rs.iter().any(pred),
_ => false,
})
};
let has_x = roles_present(&|r| matches!(r, HeaderRole::X1 | HeaderRole::X2));
let has_addr = roles_present(&|r| matches!(r, HeaderRole::Addr(_)));
let has_nc = cols
.iter()
.flat_map(|c| [Some(&c.row1), Some(&c.row2), c.row3.as_ref()])
.flatten()
.any(|slot| matches!(slot, HeaderSlot::NotConnected));
out.push('\n');
if rp {
out.push_str(" !!3V3!! = 3.3V-only (ADC pin, keep ≤3.3V) 5V = 5V-tolerant\n");
}
out.push_str(" SEL_A = image-select bit 0 (LSB); each further letter is the next bit\n");
if has_x {
out.push_str(" X1/X2 = jumper X pins\n");
}
if has_addr {
out.push_str(" A<n> = high address line broken out on the header\n");
}
if has_nc {
out.push_str(" n/c = pad fitted but not connected\n");
}
Some(out)
}
fn socket_function(chip: ChipType, pin: u8) -> Option<String> {
let mut funcs: Vec<String> = Vec::new();
for (i, &p) in chip.address_pins().iter().enumerate() {
if p == pin {
funcs.push(format!("A{i}"));
}
}
for (i, &p) in chip.data_pins().iter().enumerate() {
if p == pin {
funcs.push(format!("D{i}"));
}
}
for c in chip.control_lines().iter().filter(|c| c.pin == pin) {
funcs.push(c.name.to_ascii_uppercase());
}
for p in chip.power_pins().iter().filter(|p| p.pin == pin) {
funcs.push(p.name.to_ascii_uppercase());
}
if let Some(pins) = chip.programming_pins() {
for p in pins.iter().filter(|p| p.pin == pin) {
funcs.push(p.name.to_ascii_uppercase());
}
}
(!funcs.is_empty()).then(|| funcs.join("/"))
}
fn socket_gpios(board: &Board, pin: u8) -> Option<String> {
let gpios = board.gpios_for_socket_pin(pin);
if gpios.is_empty() {
None
} else {
Some(
gpios
.iter()
.map(|g| format!("GPIO{g}"))
.collect::<Vec<_>>()
.join("/"),
)
}
}
fn board_power_name(board: &Board, pin: u8) -> Option<String> {
for chip in CHIP_TYPES {
if chip.chip_pins() == board.chip_pins()
&& board.supports_chip_type(*chip)
&& let Some(p) = chip.power_pins().iter().find(|p| p.pin == pin)
{
return Some(p.name.to_ascii_uppercase());
}
}
None
}
pub fn render_rom_socket(board: &Board, chip: Option<ChipType>, show_gpio: bool) -> String {
let bp = board.chip_pins() as i16;
let cp = chip.map(|c| c.chip_pins() as i16).unwrap_or(bp);
let n = bp.max(cp);
let chip_lo = (n - cp) / 2;
let board_lo = (n - bp) / 2;
let offset = chip_lo - board_lo;
let mut fly: Vec<(i16, &'static str)> = Vec::new();
if let Some(c) = chip {
for &ap in c.address_pins() {
let ap = ap as i16;
if (ap + offset < 1 || ap + offset > bp)
&& let Some(x) = ["X1", "X2"].get(fly.len())
{
fly.push((ap, x));
}
}
}
let label = |pos: i16| -> String {
let chip_pin = pos - chip_lo;
let board_pin = pos - board_lo;
let cv = chip.is_some() && (1..=cp).contains(&chip_pin);
let bv = (1..=bp).contains(&board_pin);
let behind = bv
.then(|| {
socket_gpios(board, board_pin as u8)
.or_else(|| board_power_name(board, board_pin as u8))
})
.flatten();
let with_gpio = |primary: String| match (show_gpio, &behind) {
(true, Some(b)) if *b != primary => format!("{primary} ({b})"),
_ => primary,
};
match chip {
None => behind.unwrap_or_else(|| "—".to_string()),
Some(c) => {
let func = cv.then(|| socket_function(c, chip_pin as u8)).flatten();
match (cv, bv) {
(true, true) => with_gpio(func.unwrap_or_else(|| "NC".to_string())),
(false, true) => with_gpio("overhang".to_string()),
(true, false) => match fly.iter().find(|(ap, _)| *ap == chip_pin) {
Some((_, x)) => format!("{} → {x}", func.unwrap_or_default()),
None => match func {
Some(f) => format!("{f} (empty)"),
None => "(empty)".to_string(),
},
},
(false, false) => "—".to_string(),
}
}
}
};
let n = n as usize;
let half = n / 2;
let left: Vec<(usize, String)> = (1..=half).map(|p| (p, label(p as i16))).collect();
let right: Vec<(usize, String)> = ((half + 1)..=n)
.rev()
.map(|p| (p, label(p as i16)))
.collect();
let lw = left
.iter()
.map(|(_, l)| l.chars().count())
.max()
.unwrap_or(0);
let indent = lw + 7;
let mut out = String::new();
let title = match chip {
None => format!(
"ROM socket · {} · GPIO map (no ROM type given)",
board.description()
),
Some(c) => {
let gpio_suffix = if show_gpio { ", with GPIOs" } else { "" };
let geom = if bp > cp {
format!(" (One ROM overhangs the {}-pin socket)", cp)
} else if bp < cp {
format!(
" ({}-pin socket; One ROM at pins {}–{})",
cp,
board_lo + 1,
board_lo + bp
)
} else {
String::new()
};
format!(
"ROM socket · {} · as {}{gpio_suffix}{geom}",
board.description(),
c.name()
)
}
};
out.push_str(&title);
out.push_str("\n\n");
out.push_str(&format!("{}┌{}┐\n", " ".repeat(indent), "─".repeat(BODY_W)));
for i in 0..half {
let (lp, ll) = &left[i];
let (rp, rl) = &right[i];
out.push_str(&format!(
"{ll:>lw$} {lp:>2} ──┤{body}├── {rp:>2} {rl}\n",
body = " ".repeat(BODY_W),
));
}
out.push_str(&format!("{}└{}┘\n", " ".repeat(indent), "─".repeat(BODY_W)));
out.push('\n');
if let Some(c) = chip {
if let Ok(result) =
check_chip_set_on_board(*board, c, ChipSetType::Single, 1, default_cs_config(c))
{
out.push_str(&format!(
" Image size {} (ROM size {}) — the flash One ROM uses to emulate this chip.\n",
format_size(result.slot_size_bytes),
format_size(c.size_bytes() as u32),
));
}
}
match chip {
None => out.push_str(
" Add --chip-type <chip> to show ROM pin functions (A/D/CS/…) instead of GPIOs.\n",
),
Some(_) if bp > cp => out.push_str(
" 'overhang' pins are One ROM pins outside the socket — reroute power to One ROM's \
VCC/5V pin (see COMPATIBILITY.md).\n",
),
Some(_) if bp < cp => out.push_str(
" '(empty)' pins are socket positions One ROM does not reach — the socket's VCC is \
one of them, so power One ROM's own VCC/5V pin instead (shown in the '(VCC)' \
annotation). '→ X1/X2' address lines need a fly-lead to that One ROM header pin \
(see COMPATIBILITY.md).\n",
),
Some(_) => {}
}
out
}
#[allow(clippy::wildcard_enum_match_arm)]
pub fn gpio_header_role(board: &Board, gpio: u8) -> Option<String> {
if gpio == 255 {
return None;
}
if let Some(header) = board.jumper_header() {
let pad = header
.columns
.iter()
.flat_map(|c| [Some(&c.row1), Some(&c.row2), c.row3.as_ref()])
.flatten()
.find_map(|slot| match slot {
HeaderSlot::Roles(roles) => {
let named: Vec<String> = roles
.iter()
.filter(|r| header_role_gpio(board, r) == Some(gpio))
.map(header_role_label)
.collect();
(!named.is_empty()).then(|| named.join("/"))
}
_ => None,
});
if pad.is_some() {
return pad;
}
}
if let Some(bit) = board.sel_pins().iter().position(|&p| p == gpio) {
return Some(header_role_label(&HeaderRole::Select(bit as u8)));
}
if board.pin_x1() == gpio {
return Some(header_role_label(&HeaderRole::X1));
}
if board.pin_x2() == gpio {
return Some(header_role_label(&HeaderRole::X2));
}
None
}
pub fn gpio_rom_function(board: &Board, chip: ChipType, gpio: u8) -> Option<String> {
let socket_pin = board.socket_pin_for_gpio(gpio)?;
let offset = socket_pin_offset(chip.chip_pins(), board.chip_pins())?;
let chip_pin = i16::from(socket_pin) - offset;
if chip_pin < 1 || chip_pin > i16::from(chip.chip_pins()) {
return None;
}
socket_function(chip, chip_pin as u8)
}
pub fn gpio_system_functions(board: &Board, gpio: u8) -> Vec<&'static str> {
let mut functions = Vec::new();
if gpio == 255 {
return functions;
}
if board.pin_status() == gpio {
functions.push("Status LED");
}
if board.pin_neo() == Some(gpio) {
functions.push("RGB LED");
}
if board.usb_vbus_pin() == Some(gpio) {
functions.push("USB VBUS");
}
if board.external_flash_cs_pin() == Some(gpio) {
functions.push("ext flash CS");
}
functions
}
#[cfg(test)]
mod tests {
use super::*;
fn board() -> Board {
Board::try_from_str("fire-24-f").unwrap()
}
#[test]
fn pin_header_flags_adc_select_pins() {
let s = render_pin_header(&board()).expect("fire-24-f has a header");
assert!(s.contains("SEL_A"));
assert!(s.contains("GPIO26"));
assert!(s.contains("!!3V3!!"));
assert!(s.contains("SWDIO"));
assert!(s.contains("GPIO24"));
assert!(s.contains("5V = 5V-tolerant"));
assert!(s.contains("X1"));
assert!(s.contains("GPIO9"));
}
#[test]
fn pin_header_keeps_unpopulated_column_in_place_without_numbers() {
let b = Board::try_from_str("fire-24-usb-b").unwrap();
let s = render_pin_header(&b).expect("fire-24-usb-b has a header");
assert!(!s.contains("n/p"));
assert!(s.contains(" ◄ pin 1\n"));
assert!(s.contains("│ SEL_D"));
let border = s.lines().find(|l| l.contains('┌')).unwrap();
assert!(!border.starts_with(" ┌"));
}
#[test]
fn pin_header_populated_first_column_starts_flush() {
let s = render_pin_header(&board()).expect("fire-24-f has a header");
let border = s.lines().find(|l| l.contains('┌')).unwrap();
assert!(border.starts_with(" ┌"));
assert!(s.contains("│ 5V"));
assert!(s.contains("│ SEL_A"));
}
#[test]
fn pin_header_keeps_not_connected_pads_with_legend() {
let b = Board::try_from_str("fire-24-a").unwrap();
let s = render_pin_header(&b).expect("fire-24-a has a header");
assert!(s.contains("n/c"));
assert!(s.contains("n/c = pad fitted but not connected"));
}
#[test]
fn pin_header_row3_pads_carry_no_pin_marker() {
let s = render_pin_header(&board()).expect("fire-24-f has a header");
assert!(!s.contains("│ X "));
assert!(s.contains("X1"));
assert!(s.contains("X2"));
}
#[test]
fn pin_header_none_when_uncharacterised() {
let ice = Board::try_from_str("ice-24-f").unwrap();
assert!(ice.jumper_header().is_none());
assert!(render_pin_header(&ice).is_none());
}
#[test]
fn socket_gpio_map_has_no_functions() {
let s = render_rom_socket(&board(), None, false);
assert!(s.contains("GPIO16"));
assert!(s.contains("GPIO map"));
assert!(s.contains("VCC"));
assert!(s.contains("GND"));
assert!(!s.contains(" A7 "));
assert!(!s.contains("CS1"));
}
#[test]
fn socket_function_view_matches_2364_pinout() {
let s = render_rom_socket(&board(), Some(ChipType::Chip2364), false);
assert!(s.contains("as 2364"));
assert!(s.contains("A7")); assert!(s.contains("CS1")); assert!(s.contains("A12")); assert!(s.contains("VCC")); assert!(!s.contains("GPIO"));
}
#[test]
fn socket_function_with_gpio_overlays_both() {
let s = render_rom_socket(&board(), Some(ChipType::Chip2364), true);
assert!(s.contains("with GPIOs"));
assert!(s.contains("A7 (GPIO16)"));
assert!(s.contains("CS1 (GPIO10)"));
}
#[test]
fn socket_no_notch_or_footer() {
let s = render_rom_socket(&board(), None, false);
assert!(!s.contains('∪'));
assert!(!s.contains("top→bottom"));
}
#[test]
fn socket_overhang_smaller_chip_on_larger_board() {
let b = Board::try_from_str("fire-28-c").unwrap();
let s = render_rom_socket(&b, Some(ChipType::Chip2364), true);
assert!(s.contains("One ROM overhangs the 24-pin socket"));
assert!(s.contains("overhang"));
let a7 = s.lines().find(|l| l.contains(" A7 ")).unwrap();
assert!(a7.contains(" 3 "));
assert!(s.contains("overhang (GPIO"));
assert!(s.contains("overhang (VCC)"));
}
#[test]
fn socket_flylead_larger_chip_on_smaller_board() {
let b = Board::try_from_str("fire-24-f").unwrap();
let s = render_rom_socket(&b, Some(ChipType::Chip2764), true);
assert!(s.contains("28-pin socket; One ROM at pins 3–26"));
assert!(s.contains("A12 → X1"));
assert!(s.contains("(empty)"));
assert!(s.contains("NC (VCC)"));
assert!(s.contains("power One ROM's own VCC/5V pin"));
}
#[test]
fn socket_2316_shows_three_chip_selects() {
let s = render_rom_socket(&board(), Some(ChipType::Chip2316), false);
assert!(s.contains("CS1"));
assert!(s.contains("CS2"));
assert!(s.contains("CS3"));
}
#[test]
fn pin_header_32pin_flags_rp235xb_adc_and_address_lines() {
let b = Board::try_from_str("fire-32-b").unwrap();
let s = render_pin_header(&b).expect("fire-32-b has a header");
assert!(s.contains("GPIO40"));
assert!(s.contains("!!3V3!!"));
assert!(s.contains("high address line broken out"));
assert!(!s.contains("jumper X pins"));
}
#[test]
fn socket_27c400_is_16bit_with_byte_pin() {
let b = Board::try_from_str("fire-40-b").unwrap();
let s = render_rom_socket(&b, Some(ChipType::Chip27C400), false);
assert!(s.contains("D15"));
assert!(s.contains("BYTE"));
assert!(s.contains("CE"));
assert!(s.contains("OE"));
}
#[test]
fn gpio_header_role_names_only_what_the_gpio_is() {
let b = board();
assert_eq!(gpio_header_role(&b, 26).as_deref(), Some("SEL_A"));
assert_eq!(gpio_header_role(&b, 9).as_deref(), Some("X1"));
assert_eq!(gpio_header_role(&b, 8).as_deref(), Some("X2"));
assert_eq!(gpio_header_role(&b, 25).as_deref(), Some("SEL_C"));
assert_eq!(gpio_header_role(&b, 24).as_deref(), Some("SEL_D"));
let header = render_pin_header(&b).expect("fire-24-f has a header");
assert!(header.contains("SWDIO"), "{header}");
assert_eq!(gpio_header_role(&b, 0), None);
}
#[test]
fn gpio_header_role_degrades_without_a_jumper_header() {
let b = Board::try_from_str("ice-24-d").unwrap();
assert!(b.jumper_header().is_none());
let sel_a = b.sel_pins()[0];
assert_eq!(gpio_header_role(&b, sel_a).as_deref(), Some("SEL_A"));
assert_eq!(b.pin_x1(), 255);
assert_eq!(gpio_header_role(&b, 255), None);
}
#[test]
fn gpio_rom_function_matches_the_socket_diagram() {
let b = board();
assert_eq!(
gpio_rom_function(&b, ChipType::Chip2364, 16).as_deref(),
Some("A7")
);
assert_eq!(
gpio_rom_function(&b, ChipType::Chip2364, 10).as_deref(),
Some("CS1")
);
assert_eq!(gpio_rom_function(&b, ChipType::Chip2364, 29), None);
}
#[test]
fn gpio_rom_function_follows_the_socket_pin_offset() {
let b = Board::try_from_str("fire-28-c").unwrap();
let socket_pin_3_gpio = b.gpios_for_socket_pin(3)[0];
assert_eq!(
gpio_rom_function(&b, ChipType::Chip2364, socket_pin_3_gpio).as_deref(),
Some("A7")
);
let socket_pin_1_gpio = b.gpios_for_socket_pin(1)[0];
assert_eq!(
gpio_rom_function(&b, ChipType::Chip2364, socket_pin_1_gpio),
None
);
}
#[test]
fn gpio_system_functions_name_the_firmwares_system_pins() {
let b = board();
assert_eq!(b.pin_status(), 29);
assert_eq!(b.pin_neo(), Some(29));
assert_eq!(gpio_system_functions(&b, 29), ["Status LED", "RGB LED"]);
assert!(gpio_system_functions(&b, 0).is_empty());
let b32 = Board::try_from_str("fire-32-b").unwrap();
assert_eq!(gpio_system_functions(&b32, 44), ["RGB LED"]);
assert_eq!(gpio_system_functions(&b32, 47), ["ext flash CS"]);
assert_eq!(gpio_system_functions(&b32, 45), ["Status LED"]);
let ice = Board::try_from_str("ice-24-d").unwrap();
assert_eq!(ice.pin_status(), 255);
assert!(gpio_system_functions(&ice, 255).is_empty());
}
#[test]
fn every_gpio_of_every_board_can_be_named() {
use onerom_config::hw::BOARDS;
for board in BOARDS {
let board = &board;
for gpio in 0u8..48 {
let _ = gpio_header_role(board, gpio);
let _ = gpio_system_functions(board, gpio);
for &chip in CHIP_TYPES {
let _ = gpio_rom_function(board, chip, gpio);
}
}
}
}
#[test]
fn all_boards_and_chips_render_without_panic() {
use onerom_config::hw::BOARDS;
for board in BOARDS {
let board = &board;
if let Some(h) = render_pin_header(board) {
assert!(h.contains(board.description()), "header for {board:?}");
}
let gpio_map = render_rom_socket(board, None, false);
assert!(
gpio_map.contains(board.description()),
"gpio map for {board:?}"
);
for &chip in CHIP_TYPES {
for show_gpio in [false, true] {
let s = render_rom_socket(board, Some(chip), show_gpio);
assert!(s.contains(board.description()), "socket {board:?} {chip:?}");
}
}
}
}
#[test]
fn socket_diagrams_are_structurally_aligned() {
use onerom_config::hw::BOARDS;
fn col(line: &str, ch: char) -> Option<usize> {
line.chars().position(|c| c == ch)
}
fn check(s: &str, expected_rows: usize) {
assert!(!s.contains('∪'), "socket must have no notch");
let lines: Vec<&str> = s.lines().collect();
let top = lines.iter().find(|l| l.contains('┌')).expect("top border");
let bot = lines
.iter()
.find(|l| l.contains('└'))
.expect("bottom border");
let pin_rows: Vec<&&str> = lines
.iter()
.filter(|l| l.contains('┤') && l.contains('├'))
.collect();
assert_eq!(pin_rows.len(), expected_rows, "pin row count");
let l_wall = col(top, '┌').unwrap();
let r_wall = col(top, '┐').unwrap();
assert_eq!(col(bot, '└'), Some(l_wall), "bottom-left corner");
assert_eq!(col(bot, '┘'), Some(r_wall), "bottom-right corner");
for row in pin_rows {
assert_eq!(col(row, '┤'), Some(l_wall), "left wall: {row}");
assert_eq!(col(row, '├'), Some(r_wall), "right wall: {row}");
}
}
for board in BOARDS {
let board = &board;
let bp = board.chip_pins() as usize;
check(&render_rom_socket(board, None, false), bp / 2);
for &chip in CHIP_TYPES {
let n = bp.max(chip.chip_pins() as usize);
check(&render_rom_socket(board, Some(chip), true), n / 2);
}
}
}
}