mod hardware;
mod lcd;
mod model;
#[cfg(feature = "profile")]
pub mod profile;
use std::fmt;
pub use hardware::Port;
pub(crate) use hardware::{Card, Hardware};
pub use lcd::{Annunciators, Framebuffer, LCD_HEIGHT, LCD_HEIGHT_42S, LCD_WIDTH, Lcd};
pub use model::Model;
use crate::cpu::regs::HST_MP;
use crate::cpu::{ADDR_MASK, Bus as _, Cpu, Event};
use crate::error::Error;
use crate::io::{IoRegisters, Key};
use crate::modules::{Flash, Nce1, Ram, Rom};
use hardware::{FRAME_TICKS, KEY_IN_MASK};
const TICKS_PER_SECOND: u64 = crate::io::timers::TICKS_PER_SECOND as u64;
pub(crate) const SCAN_TICKS: u32 = 8;
const MAX_SKIP_TICKS: u64 = 1 << 28;
const STEP_SKIP_TICKS: u64 = TICKS_PER_SECOND;
const STALL_PERCENT: u64 = 13;
pub(crate) const TIME_FRACTION: u64 = 1000 * 100;
const IO_WINDOW: std::ops::Range<usize> = 0x100..0x140;
pub const CARD_MIN_BYTES: usize = 1024;
pub const CARD_MAX_BYTES: usize = 4096 * 1024;
pub const NEW_CARD_BYTES: usize = 128 * 1024;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Halt {
InvalidOpcode {
pc: u32,
nibbles: [u8; 8],
len: u8,
},
}
impl fmt::Display for Halt {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Halt::InvalidOpcode { pc, nibbles, len } => {
write!(f, "invalid opcode at #{pc:05X}: ")?;
for n in nibbles.iter().take(usize::from(*len)) {
write!(f, "{n:X}")?;
}
Ok(())
}
}
}
}
#[derive(Clone, Debug)]
pub struct Machine {
#[cfg(feature = "internals")]
pub cpu: Cpu,
#[cfg(not(feature = "internals"))]
pub(crate) cpu: Cpu,
#[cfg(feature = "internals")]
pub hw: Hardware,
#[cfg(not(feature = "internals"))]
pub(crate) hw: Hardware,
pub(crate) model: Model,
pub(crate) shutdown: bool,
pub(crate) cycles: u64,
pub(crate) tick_acc: u64,
pub(crate) stall_acc: u64,
pub(crate) scan_acc: u32,
pub(crate) key_level: bool,
pub(crate) on_edge: bool,
pub(crate) timer_irq: bool,
pub(crate) key_irq: bool,
pub(crate) card_irq: bool,
pub(crate) uart_irq: bool,
pub(crate) rom_sum: u64,
#[cfg(feature = "profile")]
pub profile: profile::Profile,
}
impl Machine {
pub fn new(model: Model, rom_packed: &[u8]) -> Result<Self, Error> {
if model == Model::Hp49g {
let flash = if rom_packed.len() == 2 * model.rom_bytes() {
Flash::from_unpacked(rom_packed)?
} else {
Flash::from_packed(rom_packed)?
};
let nce1 = Nce1::Flash(Box::new(flash));
return Ok(Self::with_hardware(model, Hardware::new(model, nce1)));
}
if matches!(model, Model::Hp39g | Model::Hp40g) {
return Self::new_aplet_49(model, rom_packed);
}
let expected = model.rom_bytes();
if rom_packed.len() != expected {
return Err(Error::RomSize {
expected,
actual: rom_packed.len(),
});
}
let nce1 = Nce1::Rom(Rom::from_packed(rom_packed));
Ok(Self::with_hardware(model, Hardware::new(model, nce1)))
}
fn new_aplet_49(model: Model, image: &[u8]) -> Result<Self, Error> {
let mut nibbles: Vec<u8> = if image.len() == 2 * model.rom_bytes() {
image.to_vec()
} else if image.len() == model.rom_bytes() {
image.iter().flat_map(|&b| [b & 0xF, b >> 4]).collect()
} else {
return Err(Error::RomSize {
expected: model.rom_bytes(),
actual: image.len(),
});
};
nibbles[IO_WINDOW].fill(0);
let nce1 = Nce1::BankedRom(Rom::from_nibbles(nibbles));
Ok(Self::with_hardware(model, Hardware::new(model, nce1)))
}
pub(crate) fn with_hardware(model: Model, hw: Hardware) -> Self {
let rom_sum = crate::state::rom_checksum(hw.nce1.nibbles());
let mut m = Self {
cpu: Cpu::new(),
hw,
model,
shutdown: false,
cycles: 0,
tick_acc: 0,
stall_acc: 0,
scan_acc: 0,
key_level: false,
on_edge: false,
timer_irq: false,
key_irq: false,
card_irq: false,
uart_irq: false,
rom_sum,
#[cfg(feature = "profile")]
profile: profile::Profile::default(),
};
m.cpu.timing = model.cycle_table();
m.reset();
m
}
pub fn reset(&mut self) {
self.cpu.reset();
self.hw.mc.reset();
self.hw.held = None;
self.hw.clear_latch();
self.hw.nce1.reset();
let old_uart = std::mem::take(&mut self.hw.io.uart);
self.hw.io = IoRegisters::new();
self.hw.io.uart.keep_wire(old_uart);
self.hw.lewis.reset();
let pins = self.hw.card_pins();
self.hw.io.set_card_status(pins);
self.hw.set_out(self.cpu.regs.out);
self.shutdown = false;
self.scan_acc = 0;
self.key_level = false;
self.on_edge = false;
self.timer_irq = false;
self.key_irq = false;
self.card_irq = false;
self.uart_irq = false;
self.stall_acc = 0;
}
pub fn model(&self) -> Model {
self.model
}
pub fn cycles(&self) -> u64 {
self.cycles
}
pub fn is_shutdown(&self) -> bool {
self.shutdown
}
pub fn step(&mut self) -> Result<u32, Halt> {
let budget = self.ticks_to_cycles(STEP_SKIP_TICKS);
self.step_within(budget)
}
pub fn step_for(&mut self, budget: u64) -> Result<u32, Halt> {
self.step_within(budget.max(1))
}
pub fn run_cycles(&mut self, n: u64) -> Result<(), Halt> {
let end = self.cycles.saturating_add(n);
while self.cycles < end {
self.step_within(end - self.cycles)?;
}
Ok(())
}
pub fn has_key(&self, k: Key) -> bool {
self.model.has_key(k)
}
pub fn key_down(&mut self, k: Key) -> Result<(), Error> {
self.check_key(k)?;
if k == Key::On && !self.hw.keyboard.on_pressed() {
self.on_edge = true;
}
self.hw.keyboard.press(k);
Ok(())
}
pub fn key_up(&mut self, k: Key) -> Result<(), Error> {
self.check_key(k)?;
self.hw.keyboard.release(k);
Ok(())
}
pub fn key_is_down(&self, k: Key) -> bool {
self.hw.keyboard.is_pressed(k)
}
pub fn release_all_keys(&mut self) {
self.hw.keyboard.release_all();
}
fn check_key(&self, k: Key) -> Result<(), Error> {
if self.has_key(k) {
Ok(())
} else {
Err(Error::KeyNotOnModel {
key: k.name(),
model: self.model.name(),
})
}
}
pub fn peek(&self, addr: u32) -> u8 {
self.hw.peek(addr)
}
pub fn poke(&mut self, addr: u32, value: u8) {
self.hw.write_nibble(addr & ADDR_MASK, value & 0xF);
}
pub fn pc(&self) -> u32 {
self.cpu.regs.pc
}
pub fn contrast(&self) -> u8 {
self.hw.contrast()
}
pub fn display_on(&self) -> bool {
self.hw.display_on()
}
pub fn lcd(&self) -> Lcd {
if self.hw.profile().lewis {
Lcd::render_lewis(&self.hw.lewis)
} else {
match &self.hw.held {
Some(h) if self.hw.io.display_on() => h.lcd.clone(),
_ => self.hw.render_lcd(),
}
}
}
pub fn framebuffer(&self) -> Framebuffer {
let annunciators = if self.hw.profile().lewis {
Annunciators::from_lewis(&self.hw.lewis)
} else if self.hw.io.timers.t2_running() {
Annunciators::from_bits(self.hw.io.annunciators())
} else {
Annunciators::default()
};
Framebuffer {
pixels: self.lcd(),
annunciators,
contrast: self.hw.contrast(),
}
}
pub fn insert_card(&mut self, port: Port, packed: &[u8]) -> Result<(), Error> {
let n = packed.len();
let max = self.model.card_max_bytes(port);
if max == 0 {
return Err(Error::NoSuchPort {
port: port.number(),
});
}
if !n.is_power_of_two() || !(CARD_MIN_BYTES..=max).contains(&n) {
return Err(Error::CardSize { actual: n, max });
}
let mut ram = Ram::new(n * 2);
for (dst, &b) in ram.as_mut_slice().chunks_mut(2).zip(packed) {
dst[0] = b & 0xF;
dst[1] = b >> 4;
}
self.hw.set_card(
port,
Some(hardware::Card {
ram,
writable: true,
}),
);
Ok(())
}
pub fn remove_card(&mut self, port: Port) -> bool {
let had = self.hw.card(port).is_some();
if had {
self.hw.set_card(port, None);
}
had
}
pub fn rom_nibbles(&self) -> &[u8] {
self.hw.nce1.nibbles()
}
pub fn rom_generation(&self) -> u64 {
self.hw.nce1.generation()
}
pub fn card_image(&self, port: Port) -> Option<Vec<u8>> {
self.hw.card(port).map(|c| {
c.ram
.as_slice()
.chunks(2)
.map(|p| p[0] | (p.get(1).copied().unwrap_or(0) << 4))
.collect()
})
}
pub fn has_serial(&self) -> bool {
self.model.has_serial()
}
pub fn serial_push(&mut self, bytes: &[u8]) {
if self.has_serial() {
self.hw.io.uart.push(bytes);
}
}
pub fn serial_drain(&mut self) -> Vec<u8> {
self.hw.io.uart.drain()
}
pub fn serial_pending(&self) -> usize {
self.hw.io.uart.pending()
}
pub fn serial_clear_inbound(&mut self) -> usize {
self.hw.io.uart.clear_inbound()
}
pub fn serial_baud(&self) -> u32 {
self.hw.io.uart.baud()
}
fn step_within(&mut self, budget: u64) -> Result<u32, Halt> {
if self.shutdown {
return Ok(self.sleep(budget));
}
#[cfg(feature = "profile")]
let (p_before, int_before, sel) = (
self.cpu.regs.p,
self.cpu.regs.in_interrupt,
self.hw.select(self.cpu.regs.pc),
);
let s = self.cpu.step(&mut self.hw);
#[cfg(feature = "profile")]
self.profile.record(&s, p_before, int_before, sel);
let mut halt = None;
match s.event {
Some(Event::InvalidOpcode { pc, nibbles, len }) => {
halt = Some(Halt::InvalidOpcode { pc, nibbles, len });
}
Some(Event::Shutdown) => {
if !self.wake_condition() {
self.shutdown = true;
}
if self.hw.profile().shutdn_clears_latch {
self.hw.clear_latch();
}
}
Some(Event::Rti)
if (self.hw.keyboard.on_pressed() || self.hw.io.uart.irq_level)
&& !self.cpu.regs.in_interrupt =>
{
self.cpu.interrupt();
}
Some(Event::Rti) | None => {}
}
let table = u64::from(s.cycles);
let percent = if self.hw.display_on() && !self.hw.profile().lewis {
100 + STALL_PERCENT
} else {
100
};
self.stall_acc += table * u64::from(self.model.cycle_scale_permille()) * percent;
let cycles = self.stall_acc / TIME_FRACTION;
self.stall_acc %= TIME_FRACTION;
#[cfg(feature = "profile")]
{
self.profile.stall_cycles += cycles - table;
}
self.advance(cycles);
match halt {
Some(h) => Err(h),
None => Ok(u32::try_from(cycles).unwrap_or(u32::MAX)),
}
}
fn wake_condition(&self) -> bool {
self.hw.io.timers.wake()
|| self.hw.read_in_lines() != 0
|| self.hw.keyboard.on_pressed()
|| self.on_edge
|| self.timer_irq
|| self.key_irq
|| self.card_irq
|| self.uart_irq
|| self.hw.io.module_pulled()
}
fn sleep(&mut self, budget: u64) -> u32 {
if self.wake_condition() {
self.shutdown = false;
self.deliver_interrupts();
return 0;
}
let cycles = self.cycles_until_event().min(budget).max(1);
self.advance(cycles);
u32::try_from(cycles).unwrap_or(u32::MAX)
}
fn cycles_until_event(&self) -> u64 {
let mut ticks = u64::from(self.hw.io.timers.ticks_until_event()).min(MAX_SKIP_TICKS);
if self.hw.keyboard.read_in(KEY_IN_MASK) != 0 {
ticks = ticks.min(u64::from(SCAN_TICKS));
}
let mut cycles = self.ticks_to_cycles(ticks.max(1));
if let Some(c) = self.hw.io.uart.cycles_until_event(self.model.clock_hz()) {
cycles = cycles.min(c);
}
cycles.max(1)
}
pub fn idle_cycles(&self) -> Option<u64> {
if self.shutdown && !self.wake_condition() {
Some(self.cycles_until_event())
} else {
None
}
}
fn ticks_to_cycles(&self, ticks: u64) -> u64 {
let hz = u64::from(self.model.clock_hz());
(ticks * hz)
.saturating_sub(self.tick_acc)
.div_ceil(TICKS_PER_SECOND)
}
fn advance(&mut self, c: u64) {
let hz = u64::from(self.model.clock_hz());
self.cycles = self.cycles.saturating_add(c);
self.tick_acc += c * TICKS_PER_SECOND;
let mut ticks = self.tick_acc / hz;
self.tick_acc %= hz;
let elapsed = ticks;
self.hw.io.uart.advance(c, self.model.clock_hz());
while ticks > 0 {
let t = u32::try_from(ticks).unwrap_or(u32::MAX);
self.hw.io.tick(t);
ticks -= u64::from(t);
}
if let Some(h) = &mut self.hw.held {
h.ticks += elapsed;
if h.ticks >= FRAME_TICKS {
self.hw.held = None;
}
}
self.poll_interrupts(elapsed);
}
fn poll_interrupts(&mut self, ticks: u64) {
if self.hw.io.timers.take_interrupt() {
self.timer_irq = true;
}
if self.hw.io.take_card_interrupt() {
self.card_irq = true;
}
if self.hw.io.uart.take_interrupt() {
self.uart_irq = true;
}
if self.hw.io.module_pulled() {
self.cpu.regs.hst |= HST_MP;
}
let acc = u64::from(self.scan_acc) + ticks;
if acc >= u64::from(SCAN_TICKS) {
self.scan_acc = (acc % u64::from(SCAN_TICKS)) as u32;
if self.hw.io.timers.t2_running() {
let level = self.hw.read_in_lines() & KEY_IN_MASK != 0;
self.hw.io.set_key_down(level);
if level && !self.key_level && self.cpu.regs.interrupts_enabled {
self.key_irq = true;
}
self.key_level = level;
}
} else {
self.scan_acc = acc as u32;
}
if !self.shutdown {
self.deliver_interrupts();
}
}
fn deliver_interrupts(&mut self) {
let any = self.timer_irq || self.on_edge || self.key_irq || self.card_irq || self.uart_irq;
self.uart_irq = false;
self.timer_irq = false;
self.on_edge = false;
self.key_irq = false;
self.card_irq = false;
if any {
self.cpu.interrupt();
}
}
}
#[cfg(test)]
mod tests;