use std::str::FromStr;
use crate::bus::Chip;
use crate::error::Error;
use crate::io::Key;
use super::hardware::Port;
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Model {
Hp48sx,
Hp48gx,
Hp38g,
Hp49g,
Hp39g,
Hp40g,
Hp42s,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum ChipRole {
Empty,
Ram(usize),
Card(Port),
BankedCard(Port),
BankLatch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct HardwareProfile {
pub ce1: ChipRole,
pub ce2: ChipRole,
pub nce3: ChipRole,
pub nce3_shares_a19: bool,
pub latch_writes: bool,
pub nce3_flash_path: bool,
pub shutdn_clears_latch: bool,
pub card_max_bytes: [usize; 2],
pub io_strap: Option<(u8, u8)>,
pub lewis: bool,
}
impl HardwareProfile {
pub fn role(&self, chip: Chip) -> ChipRole {
match chip {
Chip::Ce1 => self.ce1,
Chip::Ce2 => self.ce2,
Chip::Nce3 => self.nce3,
Chip::Hdw | Chip::Nce2 => ChipRole::Empty,
}
}
pub fn port_chip(&self, port: Port) -> Option<Chip> {
[Chip::Ce1, Chip::Ce2, Chip::Nce3].into_iter().find(
|&c| matches!(self.role(c), ChipRole::Card(p) | ChipRole::BankedCard(p) if p == port),
)
}
}
const KB: usize = 1024;
const HP48SX: HardwareProfile = HardwareProfile {
ce1: ChipRole::Card(Port::One),
ce2: ChipRole::Card(Port::Two),
nce3: ChipRole::Empty,
nce3_shares_a19: false,
latch_writes: false,
nce3_flash_path: false,
shutdn_clears_latch: true,
card_max_bytes: [128 * KB, 128 * KB],
io_strap: None,
lewis: false,
};
const HP48GX: HardwareProfile = HardwareProfile {
ce1: ChipRole::BankLatch,
ce2: ChipRole::Card(Port::One),
nce3: ChipRole::BankedCard(Port::Two),
nce3_shares_a19: true,
latch_writes: false,
nce3_flash_path: false,
shutdn_clears_latch: true,
card_max_bytes: [128 * KB, 4096 * KB],
io_strap: None,
lewis: false,
};
const HP38G: HardwareProfile = HardwareProfile {
ce1: ChipRole::BankLatch,
ce2: ChipRole::Empty,
nce3: ChipRole::Empty,
nce3_shares_a19: true,
latch_writes: false,
nce3_flash_path: false,
shutdn_clears_latch: true,
card_max_bytes: [0, 0],
io_strap: None,
lewis: false,
};
const HP49G: HardwareProfile = HardwareProfile {
ce1: ChipRole::BankLatch,
ce2: ChipRole::Ram(2 * 128 * KB),
nce3: ChipRole::Ram(2 * 128 * KB),
nce3_shares_a19: false,
latch_writes: true,
nce3_flash_path: true,
shutdn_clears_latch: false,
card_max_bytes: [0, 0],
io_strap: None,
lewis: false,
};
const HP39G: HardwareProfile = HardwareProfile {
ce1: ChipRole::BankLatch,
ce2: ChipRole::Empty,
nce3: ChipRole::Empty,
nce3_shares_a19: false,
latch_writes: true,
nce3_flash_path: false,
shutdn_clears_latch: false,
card_max_bytes: [0, 0],
io_strap: None,
lewis: false,
};
const HP40G: HardwareProfile = HardwareProfile {
io_strap: Some((0x1A, 0x8)),
..HP39G
};
const HP42S: HardwareProfile = HardwareProfile {
ce1: ChipRole::Empty,
ce2: ChipRole::Empty,
nce3: ChipRole::Empty,
nce3_shares_a19: false,
latch_writes: false,
nce3_flash_path: false,
shutdn_clears_latch: false,
card_max_bytes: [0, 0],
io_strap: None,
lewis: true,
};
impl Model {
pub const ALL: [Model; 7] = [
Model::Hp48sx,
Model::Hp48gx,
Model::Hp38g,
Model::Hp49g,
Model::Hp39g,
Model::Hp40g,
Model::Hp42s,
];
pub fn name(self) -> &'static str {
match self {
Model::Hp48sx => "48sx",
Model::Hp48gx => "48gx",
Model::Hp38g => "38g",
Model::Hp49g => "49g",
Model::Hp39g => "39g",
Model::Hp40g => "40g",
Model::Hp42s => "42s",
}
}
pub(crate) fn hardware(self) -> &'static HardwareProfile {
match self {
Model::Hp48sx => &HP48SX,
Model::Hp48gx => &HP48GX,
Model::Hp38g => &HP38G,
Model::Hp49g => &HP49G,
Model::Hp39g => &HP39G,
Model::Hp40g => &HP40G,
Model::Hp42s => &HP42S,
}
}
pub fn contrast_range(self) -> std::ops::RangeInclusive<u8> {
match self {
Model::Hp48sx => 3..=19,
Model::Hp42s => 15..=31,
_ => 9..=24,
}
}
pub fn default_contrast(self) -> u8 {
match self {
Model::Hp48sx => 11,
Model::Hp39g | Model::Hp40g => 12,
Model::Hp42s => 22,
Model::Hp48gx | Model::Hp38g | Model::Hp49g => 14,
}
}
pub fn rom_bytes(self) -> usize {
match self {
Model::Hp42s => 64 * KB,
Model::Hp48sx => 256 * KB,
Model::Hp48gx | Model::Hp38g => 512 * KB,
Model::Hp49g => 2048 * KB,
Model::Hp39g | Model::Hp40g => 1024 * KB,
}
}
pub fn accepts_rom_len(self, len: usize) -> bool {
len == self.rom_bytes()
|| (matches!(self, Model::Hp49g | Model::Hp39g | Model::Hp40g)
&& len == 2 * self.rom_bytes())
}
pub fn ram_nibbles(self) -> usize {
match self {
Model::Hp42s => 2 * 8 * KB,
Model::Hp48sx => 2 * 32 * KB,
Model::Hp48gx => 2 * 128 * KB,
Model::Hp38g => 2 * 32 * KB,
Model::Hp49g | Model::Hp39g | Model::Hp40g => 2 * 256 * KB,
}
}
pub fn clock_hz(self) -> u32 {
match self {
Model::Hp42s => 1_000_000,
Model::Hp48sx => 2_000_000,
_ => 4_000_000,
}
}
pub(crate) fn cycle_table(self) -> crate::cpu::CycleTable {
match self {
Model::Hp48sx | Model::Hp42s => crate::cpu::CycleTable::Sasm,
_ => crate::cpu::CycleTable::MetaKernel,
}
}
pub fn cycle_scale_permille(self) -> u32 {
match self {
Model::Hp42s => 1000,
Model::Hp48sx => 1267,
Model::Hp48gx | Model::Hp38g => 1335,
Model::Hp49g | Model::Hp39g | Model::Hp40g => 1205,
}
}
pub(crate) fn keyboard_layout(self) -> crate::io::Layout {
match self {
Model::Hp42s => crate::io::Layout::Hp42,
Model::Hp48sx | Model::Hp48gx => crate::io::Layout::Hp48,
Model::Hp38g => crate::io::Layout::Hp38,
Model::Hp49g => crate::io::Layout::Hp49,
Model::Hp39g | Model::Hp40g => crate::io::Layout::Hp39,
}
}
pub fn has_serial(self) -> bool {
!self.hardware().lewis
}
pub fn card_max_bytes(self, port: Port) -> usize {
self.hardware().card_max_bytes[port.index()]
}
pub fn has_key(self, k: Key) -> bool {
k.position(self.keyboard_layout()).is_some()
}
pub fn keys(self) -> impl Iterator<Item = Key> {
Key::on_layout(self.keyboard_layout())
}
}
impl FromStr for Model {
type Err = Error;
fn from_str(name: &str) -> Result<Self, Error> {
Model::ALL
.into_iter()
.find(|m| m.name().eq_ignore_ascii_case(name))
.ok_or_else(|| Error::UnknownModel {
name: name.to_string(),
})
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn models_by_name() {
assert_eq!("48SX".parse(), Ok(Model::Hp48sx));
assert_eq!("49g".parse(), Ok(Model::Hp49g));
assert_eq!("42S".parse(), Ok(Model::Hp42s));
for m in Model::ALL {
assert_eq!(m.name().parse(), Ok(m));
}
let e = "41c".parse::<Model>().unwrap_err();
assert_eq!(
e.to_string(),
"unknown model \"41c\"; expected one of 48sx, 48gx, 38g, 49g, 39g, 40g, 42s"
);
}
#[test]
fn port_chips_follow_the_profile() {
let sx = Model::Hp48sx.hardware();
assert_eq!(sx.port_chip(Port::One), Some(Chip::Ce1));
assert_eq!(sx.port_chip(Port::Two), Some(Chip::Ce2));
let gx = Model::Hp48gx.hardware();
assert_eq!(gx.port_chip(Port::One), Some(Chip::Ce2));
assert_eq!(gx.port_chip(Port::Two), Some(Chip::Nce3));
assert_eq!(Model::Hp49g.hardware().port_chip(Port::One), None);
}
#[test]
fn model_facts() {
assert_eq!(Model::Hp48gx.rom_bytes(), 524_288);
assert_eq!(Model::Hp48gx.clock_hz(), 4_000_000);
assert_eq!(Model::Hp48gx.card_max_bytes(Port::Two), 4 * 1024 * 1024);
assert_eq!(Model::Hp48sx.card_max_bytes(Port::Two), 128 * 1024);
for m in Model::ALL {
assert!(m.contrast_range().end() < &32);
assert!(m.contrast_range().contains(&m.default_contrast()));
}
assert_eq!(Model::Hp39g.rom_bytes(), 1024 * 1024);
assert!(Model::Hp40g.accepts_rom_len(2 * 1024 * 1024));
assert!(Model::Hp49g.accepts_rom_len(4 * 1024 * 1024));
assert!(!Model::Hp48gx.accepts_rom_len(1024 * 1024));
assert_eq!(Model::Hp40g.hardware().io_strap, Some((0x1A, 0x8)));
assert_eq!(Model::Hp39g.hardware().io_strap, None);
assert_eq!(
Model::Hp39g.keyboard_layout(),
Model::Hp40g.keyboard_layout()
);
assert_eq!(Model::Hp42s.rom_bytes(), 65_536);
assert_eq!(Model::Hp42s.clock_hz(), 1_000_000);
assert_eq!(Model::Hp42s.card_max_bytes(Port::One), 0);
assert!(Model::Hp42s.hardware().lewis);
assert!(!Model::Hp42s.has_serial() && Model::Hp40g.has_serial());
assert!(Model::ALL.iter().filter(|m| m.hardware().lewis).count() == 1);
let names: std::collections::HashSet<_> = Model::ALL.iter().map(|m| m.name()).collect();
assert_eq!(names.len(), Model::ALL.len());
}
}