use crate::bus::{Chip, MemoryController, Select};
use crate::cpu::Bus;
use crate::io::registers::{
CARD_CE2_PRESENT, CARD_CE2_WRITE, CARD_OTHER_PRESENT, CARD_OTHER_WRITE,
};
use crate::io::{IoRegisters, Keyboard, LewisIo};
use crate::modules::{Nce1, Ram};
use super::Lcd;
use super::model::{ChipRole, HardwareProfile, Model};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
pub enum Port {
One,
Two,
}
impl Port {
pub const ALL: [Port; 2] = [Port::One, Port::Two];
pub fn from_number(n: u8) -> Option<Port> {
match n {
1 => Some(Port::One),
2 => Some(Port::Two),
_ => None,
}
}
pub fn number(self) -> u8 {
match self {
Port::One => 1,
Port::Two => 2,
}
}
pub(crate) fn index(self) -> usize {
match self {
Port::One => 0,
Port::Two => 1,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Card {
pub ram: Ram,
pub writable: bool,
}
pub(crate) const KEY_IN_MASK: u16 = 0x1FF;
const LATCH_BANK_MASK: u8 = 0x1F;
pub(crate) const LATCH_BEN: u8 = 0x20;
const BANK_NIBBLES: u32 = 0x4_0000;
const LOWER_ROM_MASK: u32 = 0x7_FFFF;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct Hardware {
pub(crate) profile: HardwareProfile,
pub mc: MemoryController,
pub nce1: Nce1,
pub ram: Ram,
pub(crate) chip_ram: [Ram; 3],
pub(crate) latch: u8,
pub io: IoRegisters,
pub keyboard: Keyboard,
pub(crate) cards: [Option<Card>; 2],
pub(crate) out: u16,
pub lewis: LewisIo,
pub(crate) held: Option<HeldFrame>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub(crate) struct HeldFrame {
pub(crate) lcd: Lcd,
decode: Vec<(Select, Select)>,
pub(crate) ticks: u64,
}
pub(crate) const FRAME_TICKS: u64 = 128;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
enum LewisSel {
Rom(u32),
Io(u32),
Ram(u32),
Open,
}
const LEWIS_IO_BASE: u32 = 0x4_0000;
const LEWIS_RAM_BASE: u32 = 0x5_0000;
const LEWIS_RAM_WINDOW: u32 = 0x1_0000;
fn latch_bits(offset: u32) -> u8 {
((offset >> 1) & 0x3F) as u8
}
fn chip_ram_index(chip: Chip) -> Option<usize> {
match chip {
Chip::Ce1 => Some(0),
Chip::Ce2 => Some(1),
Chip::Nce3 => Some(2),
Chip::Hdw | Chip::Nce2 => None,
}
}
impl Hardware {
pub fn new(model: Model, nce1: Nce1) -> Self {
let profile = *model.hardware();
let ram_for = |chip| match profile.role(chip) {
ChipRole::Ram(n) => Ram::new(n),
_ => Ram::new(0),
};
Self {
profile,
mc: MemoryController::new(),
nce1,
ram: Ram::new(model.ram_nibbles()),
chip_ram: [ram_for(Chip::Ce1), ram_for(Chip::Ce2), ram_for(Chip::Nce3)],
latch: 0,
io: IoRegisters::new(),
keyboard: Keyboard::with_layout(model.keyboard_layout()),
cards: [None, None],
out: 0,
lewis: LewisIo::new(),
held: None,
}
}
fn display_decode(&self) -> Vec<(Select, Select)> {
Lcd::row_spans(&self.io)
.map(|(first, last)| (self.select(first), self.select(last)))
.collect()
}
fn remap(&mut self, change: impl FnOnce(&mut MemoryController)) {
let watch = self.held.is_none() && !self.profile.lewis && self.io.display_on();
let before = watch.then(|| (self.mc.clone(), self.display_decode()));
change(&mut self.mc);
if let Some((mut mc, decode)) = before
&& self.display_decode() != decode
{
std::mem::swap(&mut self.mc, &mut mc);
let lcd = self.render_lcd();
self.mc = mc;
self.held = Some(HeldFrame {
lcd,
decode,
ticks: 0,
});
}
}
pub(crate) fn render_lcd(&self) -> Lcd {
Lcd::render(&self.io, |a| self.peek(a))
}
fn lewis_select(&self, addr: u32) -> LewisSel {
let addr = addr & crate::cpu::ADDR_MASK;
let rom_len = self.nce1.nibbles().len() as u32;
if addr < rom_len {
LewisSel::Rom(addr)
} else if (LEWIS_IO_BASE..LEWIS_IO_BASE + crate::io::lewis::SIZE as u32).contains(&addr) {
LewisSel::Io(addr - LEWIS_IO_BASE)
} else if (LEWIS_RAM_BASE..LEWIS_RAM_BASE + LEWIS_RAM_WINDOW).contains(&addr) {
LewisSel::Ram(addr - LEWIS_RAM_BASE)
} else {
LewisSel::Open
}
}
fn lewis_read(&self, sel: LewisSel) -> u8 {
match sel {
LewisSel::Rom(a) => self.nce1.read(a, 0),
LewisSel::Io(off) => self.lewis.peek(&self.io, off),
LewisSel::Ram(off) => self.ram.read(off),
LewisSel::Open => OPEN_BUS,
}
}
pub fn display_on(&self) -> bool {
if self.profile.lewis {
self.lewis.display_on()
} else {
self.io.display_on()
}
}
pub fn contrast(&self) -> u8 {
if self.profile.lewis {
self.lewis.contrast()
} else {
self.io.contrast()
}
}
pub fn profile(&self) -> &HardwareProfile {
&self.profile
}
#[cfg(any(test, feature = "internals"))]
pub fn bank_latch(&self) -> u8 {
self.latch
}
pub(crate) fn clear_latch(&mut self) {
self.latch = 0;
}
pub fn card(&self, port: Port) -> Option<&Card> {
self.cards[port.index()].as_ref()
}
pub(crate) fn set_card(&mut self, port: Port, card: Option<Card>) {
self.cards[port.index()] = card;
let status = self.card_pins();
self.io.set_card_status(status);
}
pub(crate) fn card_pins(&self) -> u8 {
let mut s = 0;
for port in Port::ALL {
let (Some(c), Some(chip)) = (self.card(port), self.profile.port_chip(port)) else {
continue;
};
let (present, write) = if chip == Chip::Ce2 {
(CARD_CE2_PRESENT, CARD_CE2_WRITE)
} else {
(CARD_OTHER_PRESENT, CARD_OTHER_WRITE)
};
s |= present;
if c.writable {
s |= write;
}
}
s
}
#[cfg(feature = "internals")]
pub fn out(&self) -> u16 {
self.out
}
pub(crate) fn set_out(&mut self, out: u16) {
self.out = out;
}
pub fn read_in_lines(&self) -> u16 {
self.keyboard.read_in(self.out)
}
fn chip_active(&self, chip: Chip) -> bool {
chip != Chip::Nce3
|| !self.profile.nce3_shares_a19
|| (!self.io.da19() && self.latch & LATCH_BEN != 0)
}
pub(crate) fn select(&self, addr: u32) -> Select {
self.mc.select_where(addr, |c| self.chip_active(c))
}
pub fn peek(&self, addr: u32) -> u8 {
if self.profile.lewis {
return self.lewis_read(self.lewis_select(addr));
}
match self.select(addr) {
Select::Chip {
chip: Chip::Hdw,
offset,
} => self.io.peek(offset) | self.strap(offset),
sel => self.read_memory(sel),
}
}
fn strap(&self, offset: u32) -> u8 {
match self.profile.io_strap {
Some((reg, mask)) if offset & 0x3F == u32::from(reg) => mask,
_ => 0,
}
}
fn read_selected(&mut self, sel: Select) -> u8 {
match sel {
Select::Chip {
chip: Chip::Hdw,
offset,
} => self.io.read(offset) | self.strap(offset),
Select::Chip { chip, offset } if self.profile.role(chip) == ChipRole::BankLatch => {
self.latch = latch_bits(offset);
OPEN_BUS
}
sel => self.read_memory(sel),
}
}
fn read_memory(&self, sel: Select) -> u8 {
match sel {
Select::Chip {
chip: Chip::Nce2,
offset,
} => self.ram.read(offset),
Select::Chip {
chip: Chip::Nce3,
offset,
} if self.nce3_to_nce1() => self
.nce1
.nce3_read(offset, self.latch)
.unwrap_or_else(|| self.read_role(Chip::Nce3, offset)),
Select::Chip { chip, offset } => self.read_role(chip, offset),
Select::Rom { addr } => self.nce1.read(self.rom_addr(addr), self.latch),
}
}
fn nce3_to_nce1(&self) -> bool {
self.profile.nce3_flash_path && self.io.lcr() & 0x8 != 0
}
fn read_role(&self, chip: Chip, offset: u32) -> u8 {
match self.profile.role(chip) {
ChipRole::Ram(_) => {
chip_ram_index(chip).map_or(OPEN_BUS, |i| self.chip_ram[i].read(offset))
}
ChipRole::Card(p) => self.card(p).map_or(OPEN_BUS, |c| c.ram.read(offset)),
ChipRole::BankedCard(p) => self
.card(p)
.map_or(OPEN_BUS, |c| c.ram.read(self.banked_offset(offset))),
ChipRole::BankLatch | ChipRole::Empty => OPEN_BUS,
}
}
fn banked_offset(&self, offset: u32) -> u32 {
u32::from(self.latch & LATCH_BANK_MASK) * BANK_NIBBLES + (offset % BANK_NIBBLES)
}
fn rom_addr(&self, addr: u32) -> u32 {
if self.profile.nce3_shares_a19 && !self.io.da19() {
addr & LOWER_ROM_MASK
} else {
addr
}
}
}
const OPEN_BUS: u8 = 0;
impl Bus for Hardware {
fn read_nibble(&mut self, addr: u32) -> u8 {
if self.profile.lewis {
return self.lewis_read(self.lewis_select(addr));
}
let sel = self.select(addr);
self.read_selected(sel)
}
fn read_data(&mut self, addr: u32) -> u8 {
if self.profile.lewis {
let sel = self.lewis_select(addr);
let v = self.lewis_read(sel);
if !matches!(sel, LewisSel::Io(_)) {
self.io.crc_update(v);
}
return v;
}
let sel = self.select(addr);
let is_hdw = matches!(
sel,
Select::Chip {
chip: Chip::Hdw,
..
}
);
let v = self.read_selected(sel);
if !is_hdw {
self.io.crc_update(v);
}
v
}
fn write_nibble(&mut self, addr: u32, nibble: u8) {
if self.profile.lewis {
match self.lewis_select(addr) {
LewisSel::Io(off) => self.lewis.write(&mut self.io, off, nibble),
LewisSel::Ram(off) => self.ram.write(off, nibble),
LewisSel::Rom(_) | LewisSel::Open => {}
}
return;
}
let sel = self.select(addr);
let Select::Chip { chip, offset } = sel else {
return;
};
if chip == Chip::Nce3
&& self.nce3_to_nce1()
&& self.nce1.nce3_write(offset, self.latch, nibble)
{
return;
}
match chip {
Chip::Hdw => {
self.io.write(offset, nibble);
if offset & 0x3F == crate::io::registers::LCR as u32 {
let on = self.io.lcr() & 0x8 != 0;
self.nce1.set_write_enabled(on);
}
}
Chip::Nce2 => self.ram.write(offset, nibble),
_ => match self.profile.role(chip) {
ChipRole::Ram(_) => {
if let Some(i) = chip_ram_index(chip) {
self.chip_ram[i].write(offset, nibble);
}
}
ChipRole::Card(p) => {
if let Some(c) = self.cards[p.index()].as_mut().filter(|c| c.writable) {
c.ram.write(offset, nibble);
}
}
ChipRole::BankedCard(p) => {
let off = self.banked_offset(offset);
if let Some(c) = self.cards[p.index()].as_mut().filter(|c| c.writable) {
c.ram.write(off, nibble);
}
}
ChipRole::BankLatch => {
if self.profile.latch_writes {
self.latch = latch_bits(offset);
}
}
ChipRole::Empty => {}
},
}
}
fn read_in(&mut self) -> u16 {
let v = self.read_in_lines();
self.io.set_key_down(v & KEY_IN_MASK != 0);
v
}
fn write_out(&mut self, out: u16) {
self.out = out;
}
fn config(&mut self, addr: u32) {
self.mc.config(addr);
if self
.held
.as_ref()
.is_some_and(|h| h.decode == self.display_decode())
{
self.held = None;
}
}
fn unconfig(&mut self, addr: u32) {
self.remap(|mc| mc.unconfig(addr));
}
fn read_id(&mut self) -> u32 {
self.mc.read_id()
}
fn reset(&mut self) {
self.remap(MemoryController::reset);
}
fn interrupt_pending(&mut self) -> bool {
self.read_in_lines() != 0
}
}