use super::*;
use crate::bus::Chip;
use crate::cpu::Bus;
use crate::cpu::exec::INTERRUPT_VECTOR;
use crate::io::timers::{CTRL_INT, CTRL_WAKE, CTRL_XTRA_OR_RUN};
use crate::modules::Nce1;
const MAIN: u32 = 0x20;
const LOOP: &str = "6FFF";
fn rom_image(code: &[(u32, &str)]) -> Vec<u8> {
let mut nibbles = vec![0u8; Model::Hp48sx.rom_bytes() * 2];
let mut place = |addr: u32, s: &str| {
for (i, c) in s.chars().enumerate() {
nibbles[addr as usize + i] = c.to_digit(16).unwrap() as u8;
}
};
place(0, "6F10"); place(INTERRUPT_VECTOR, LOOP);
for &(a, s) in code {
place(a, s);
}
nibbles.chunks(2).map(|p| p[0] | (p[1] << 4)).collect()
}
fn machine(code: &[(u32, &str)]) -> Machine {
Machine::new(Model::Hp48sx, &rom_image(code)).unwrap()
}
fn with_hdw(main: &str) -> Machine {
let mut m = machine(&[(MAIN, main)]);
m.hw.config(0x100);
m
}
fn cycles_for_ticks(ticks: u64) -> u64 {
ticks * u64::from(Model::Hp48sx.clock_hz()) / TICKS_PER_SECOND + 100
}
#[test]
fn rom_size_checked() {
let err = Machine::new(Model::Hp48sx, &[0; 10]).unwrap_err();
assert_eq!(
err,
Error::RomSize {
expected: 262_144,
actual: 10
}
);
}
#[test]
fn rom_answers_when_nothing_configured() {
let mut m = machine(&[]);
assert_eq!(m.peek(0), 6);
assert_eq!(m.peek(1), 0xF);
assert_eq!(m.hw.read_nibble(2), 1);
m.step().unwrap();
assert_eq!(m.cpu.regs.pc, MAIN);
}
#[test]
fn config_program_maps_ram_over_rom() {
let prog = "3400100805340000F80534000078056FFF";
let mut m = machine(&[(MAIN, prog), (0x70000, "A")]);
assert_eq!(m.peek(0x70000), 0xA);
m.run_cycles(500).unwrap();
assert!(m.hw.mc.is_configured(Chip::Hdw));
assert_eq!(m.hw.mc.window(Chip::Nce2), Some((0x70000, 0xF0000)));
assert_eq!(m.peek(0x70000), 0);
m.hw.write_nibble(0x70000, 9);
m.hw.write_nibble(0x7FFFF, 3);
assert_eq!(m.peek(0x70000), 9);
assert_eq!(m.hw.read_nibble(0x7FFFF), 3);
assert_eq!(m.hw.ram.read(0), 9);
m.hw.write_nibble(0x50, 5);
assert_eq!(m.peek(0x50), 0);
}
#[test]
fn hdw_reaches_io_registers() {
let mut m = with_hdw(LOOP);
m.hw.write_nibble(0x137, 5);
assert_eq!(m.hw.io.timers.t1, 5);
assert_eq!(m.hw.read_nibble(0x137), 5);
assert_eq!(m.peek(0x137), 5);
let mut m = machine(&[]);
m.hw.config(0x200);
m.hw.write_nibble(0x237, 7);
assert_eq!(m.hw.io.timers.t1, 7);
}
#[test]
fn crc_follows_data_reads_not_fetches() {
let mut m = machine(&[(MAIN, "1B04000142"), (MAIN + 10, LOOP), (0x40, "12345")]);
m.hw.config(0x100);
m.step().unwrap(); m.step().unwrap(); assert_eq!(m.hw.io.crc(), 0);
m.step().unwrap(); let crc = m.hw.io.crc();
assert_ne!(crc, 0);
m.step().unwrap(); assert_eq!(m.hw.io.crc(), crc);
m.hw.read_data(0x137);
assert_eq!(m.hw.io.crc(), crc);
m.hw.read_data(0x41);
assert_ne!(m.hw.io.crc(), crc);
}
#[test]
fn timer2_interrupt_vectors() {
let mut m = with_hdw(LOOP);
m.hw.io.timers.t2 = 10;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_INT);
m.run_cycles(cycles_for_ticks(5)).unwrap();
assert!(!m.cpu.regs.in_interrupt);
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(m.cpu.regs.in_interrupt);
assert!((INTERRUPT_VECTOR..INTERRUPT_VECTOR + 4).contains(&m.cpu.regs.pc));
}
#[test]
fn timer_interrupt_ignores_intoff() {
let mut m = with_hdw(LOOP);
m.cpu.regs.interrupts_enabled = false;
m.hw.io.timers.t2 = 3;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_INT);
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(m.cpu.regs.in_interrupt);
}
fn key_setup(enabled: bool, t2_run: bool) -> Machine {
let mut m = with_hdw(LOOP);
m.cpu.regs.interrupts_enabled = enabled;
m.hw.io.timers.t2 = 0x7000_0000;
if t2_run {
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN);
}
m.hw.write_out(0x1FF);
m.run_cycles(cycles_for_ticks(16)).unwrap();
m
}
#[test]
fn key_press_interrupts() {
let mut m = key_setup(true, true);
assert!(!m.cpu.regs.in_interrupt);
m.key_down(Key::Enter).unwrap();
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(m.cpu.regs.in_interrupt);
assert_eq!(m.hw.io.peek(0x19), 0x8, "KDN set by the poll");
}
#[test]
fn key_press_masked_by_intoff() {
let mut m = key_setup(false, true);
m.key_down(Key::Enter).unwrap();
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(!m.cpu.regs.in_interrupt);
m.cpu.regs.interrupts_enabled = true;
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(!m.cpu.regs.in_interrupt);
}
#[test]
fn key_poll_stops_with_timer2() {
let mut m = key_setup(true, false);
m.key_down(Key::Enter).unwrap();
m.run_cycles(cycles_for_ticks(10)).unwrap();
assert!(!m.cpu.regs.in_interrupt);
}
#[test]
fn on_key_is_non_maskable() {
let mut m = key_setup(false, false);
m.key_down(Key::On).unwrap();
m.step().unwrap();
assert!(m.cpu.regs.in_interrupt);
}
#[test]
fn rti_with_on_held_and_pending_enters_once() {
let mut m = machine(&[(INTERRUPT_VECTOR, "0F")]);
m.hw.keyboard.press(Key::On);
m.cpu.interrupt(); m.cpu.interrupt(); assert!(m.cpu.regs.interrupt_pending);
m.step().unwrap(); assert_eq!(m.cpu.regs.pc, INTERRUPT_VECTOR);
assert!(m.cpu.regs.in_interrupt);
assert!(!m.cpu.regs.interrupt_pending);
}
#[test]
fn shutdn_wakes_on_timer_with_wake_bit() {
let mut m = with_hdw("8076FFF");
m.hw.io.timers.t2 = 100;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_WAKE);
m.step().unwrap(); m.step().unwrap(); assert!(m.is_shutdown());
m.run_cycles(cycles_for_ticks(50)).unwrap();
assert!(m.is_shutdown());
assert_eq!(m.cpu.regs.pc, MAIN + 3);
m.run_cycles(cycles_for_ticks(60)).unwrap();
assert!(!m.is_shutdown());
assert!(!m.cpu.regs.in_interrupt);
assert_eq!(m.cpu.regs.pc, MAIN + 3);
}
#[test]
fn shutdn_without_wake_bit_sleeps_on() {
let mut m = with_hdw("8076FFF");
m.hw.io.timers.t2 = 100;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN);
m.run_cycles(cycles_for_ticks(500)).unwrap();
assert!(m.is_shutdown());
}
#[test]
fn idle_cycles_reports_the_sleep_until_the_next_timer_event() {
let mut m = with_hdw("8076FFF");
assert_eq!(m.idle_cycles(), None);
m.hw.io.timers.t2 = 100;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_WAKE);
m.step().unwrap(); m.step().unwrap(); assert!(m.is_shutdown());
let idle = m.idle_cycles().unwrap();
let per_tick = u64::from(Model::Hp48sx.clock_hz()) / TICKS_PER_SECOND;
assert!(idle > 100 * per_tick && idle <= 101 * per_tick, "{idle}");
let before = m.cycles();
m.step().unwrap();
assert_eq!(m.cycles() - before, idle);
m.key_down(Key::On).unwrap();
assert_eq!(m.idle_cycles(), None);
}
#[test]
fn shutdn_with_timer_interrupt_wakes_into_handler() {
let mut m = with_hdw("8076FFF");
m.hw.io.timers.t2 = 100;
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_INT);
m.run_cycles(cycles_for_ticks(200)).unwrap();
assert!(!m.is_shutdown());
assert!(m.cpu.regs.in_interrupt);
}
#[test]
fn hour_long_shutdn_is_fast() {
let mut m = with_hdw("8076FFF");
m.hw.io.timers.t2 = 0x01C2_0000; m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN | CTRL_WAKE);
m.run_cycles(100).unwrap();
assert!(m.is_shutdown());
let start = std::time::Instant::now();
m.run_cycles(3599 * u64::from(Model::Hp48sx.clock_hz()))
.unwrap();
assert!(m.is_shutdown());
m.run_cycles(2 * u64::from(Model::Hp48sx.clock_hz()))
.unwrap();
assert!(!m.is_shutdown());
assert!(start.elapsed() < std::time::Duration::from_secs(1));
}
#[test]
fn shutdn_with_key_held_does_not_stop() {
let mut m = with_hdw("8076FFF");
m.hw.write_out(0x1FF);
m.key_down(Key::Enter).unwrap();
m.step().unwrap();
m.step().unwrap();
assert!(!m.is_shutdown());
}
#[test]
fn reset_keeps_ram() {
let mut m = with_hdw(LOOP);
m.hw.config(0xF0000);
m.hw.config(0x70000);
m.hw.write_nibble(0x70005, 7);
m.run_cycles(100).unwrap();
m.reset();
assert_eq!(m.cpu.regs.pc, 0);
assert!(!m.hw.mc.is_configured(Chip::Hdw));
assert_eq!(m.hw.ram.read(5), 7);
}
#[test]
fn invalid_opcode_halts() {
let mut m = machine(&[(MAIN, "105")]);
m.step().unwrap();
let err = m.run_cycles(100).unwrap_err();
assert!(matches!(err, Halt::InvalidOpcode { pc: MAIN, .. }));
assert!(err.to_string().starts_with("invalid opcode at #00020"));
}
fn lcd_machine() -> Machine {
let mut m = with_hdw(LOOP);
m.hw.config(0xF0000);
m.hw.config(0x70000);
m
}
fn set_io(m: &mut Machine, off: u32, value: u32, nibbles: u32) {
for i in 0..nibbles {
m.hw.write_nibble(0x100 + off + i, ((value >> (4 * i)) & 0xF) as u8);
}
}
#[test]
fn lcd_off_is_blank() {
let mut m = lcd_machine();
for a in 0x70000..0x70000 + 34 * 64 {
m.hw.write_nibble(a, 0xF);
}
set_io(&mut m, 0x20, 0x70000, 5);
assert!(m.lcd().pixels.iter().all(|r| r.iter().all(|&p| !p)));
set_io(&mut m, 0x00, 0x8, 1);
assert!(m.lcd().pixels.iter().all(|r| r.iter().all(|&p| p)));
}
#[test]
fn lcd_held_while_bitmaps_unmapped() {
let dark = |l: &Lcd| l.pixels.iter().all(|r| r.iter().all(|&p| p));
let light = |l: &Lcd| l.pixels.iter().all(|r| r.iter().all(|&p| !p));
let mut m = lcd_machine();
for a in 0x70000..0x70000 + 34 * 64 {
m.hw.write_nibble(a, 0xF);
}
set_io(&mut m, 0x20, 0x70000, 5);
set_io(&mut m, 0x30, 0x70000, 5);
set_io(&mut m, 0x00, 0x8, 1);
assert!(dark(&m.lcd()));
m.hw.unconfig(0x70000);
assert!(light(&m.hw.render_lcd()));
assert!(dark(&m.lcd()));
m.hw.config(0xFC000);
assert!(dark(&m.lcd()) && m.hw.held.is_some());
m.hw.config(0x70000);
assert!(m.hw.held.is_none());
assert!(dark(&m.lcd()));
m.hw.unconfig(0x70000);
set_io(&mut m, 0x00, 0x0, 1);
assert!(light(&m.lcd()));
set_io(&mut m, 0x00, 0x8, 1);
assert!(dark(&m.lcd()));
m.run_cycles(cycles_for_ticks(hardware::FRAME_TICKS))
.unwrap();
assert!(m.hw.held.is_none());
assert!(light(&m.lcd()));
}
fn lcd_machine_at_73f00() -> Machine {
let mut m = lcd_machine();
for a in 0x73F00..0x73F00 + 34 * 64 {
m.hw.write_nibble(a, 0xF);
}
set_io(&mut m, 0x20, 0x73F00, 5);
set_io(&mut m, 0x30, 0x73F00, 5);
set_io(&mut m, 0x00, 0x8, 1);
m
}
#[test]
fn lcd_held_until_every_row_decodes_again() {
let dark = |l: &Lcd| l.pixels.iter().all(|r| r.iter().all(|&p| p));
let mut m = lcd_machine_at_73f00();
assert!(dark(&m.lcd()));
m.hw.unconfig(0x70000);
m.hw.config(0xFC000);
m.hw.config(0x70000);
assert!(m.hw.held.is_some());
assert!(dark(&m.lcd()));
let live = m.hw.render_lcd();
assert!(live.pixels[..7].iter().all(|r| r.iter().all(|&p| p)));
assert!(live.pixels[8..].iter().all(|r| r.iter().all(|&p| !p)));
m.hw.unconfig(0x70000);
m.hw.config(0xF0000);
m.hw.config(0x70000);
assert!(m.hw.held.is_none());
assert!(dark(&m.lcd()));
}
#[test]
fn lcd_held_across_reset_instruction() {
let dark = |l: &Lcd| l.pixels.iter().all(|r| r.iter().all(|&p| p));
let light = |l: &Lcd| l.pixels.iter().all(|r| r.iter().all(|&p| !p));
let mut m = lcd_machine_at_73f00();
Bus::reset(&mut m.hw);
assert!(m.hw.io.display_on());
assert!(light(&m.hw.render_lcd()));
assert!(dark(&m.lcd()));
m.hw.config(0x100);
m.hw.config(0xF0000);
assert!(m.hw.held.is_some());
m.hw.config(0x70000);
assert!(m.hw.held.is_none());
assert!(dark(&m.lcd()));
Bus::reset(&mut m.hw);
m.reset();
assert!(m.hw.held.is_none() && light(&m.lcd()));
}
#[test]
fn lcd_main_and_menu_bitmaps() {
let mut m = lcd_machine();
let main = 0x70000;
let menu = 0x72000;
let line_offset = 4; for r in 0..56u32 {
let a = main + r * 38 + r / 4;
m.hw.write_nibble(a, 1 << (r % 4));
}
for r in 0..8u32 {
m.hw.write_nibble(menu + r * 34, 1);
m.hw.write_nibble(menu + r * 34 + 32, 4);
}
set_io(&mut m, 0x00, 0x8, 1);
set_io(&mut m, 0x20, main, 5);
set_io(&mut m, 0x25, line_offset, 3);
set_io(&mut m, 0x28, 55, 2);
set_io(&mut m, 0x30, menu, 5);
let lcd = m.lcd();
for (y, row) in lcd.pixels.iter().enumerate() {
let lit: Vec<usize> = (0..LCD_WIDTH).filter(|&x| row[x]).collect();
if y < 56 {
assert_eq!(lit, vec![y], "row {y}");
} else {
assert_eq!(lit, vec![0, 130], "row {y}");
}
}
let text = lcd.to_text();
assert_eq!(text.lines().count(), 64);
assert!(text.lines().all(|l| l.len() == 131));
assert!(text.starts_with("#."));
}
#[test]
fn lcd_bit_offset_shifts_left() {
let mut m = lcd_machine();
let main = 0x70000;
for r in 0..64u32 {
m.hw.write_nibble(main + r * 36 + 1, 0x2);
}
set_io(&mut m, 0x00, 0x8 | 5, 1);
set_io(&mut m, 0x20, main, 5);
let lcd = m.lcd();
for row in &lcd.pixels {
let lit: Vec<usize> = (0..LCD_WIDTH).filter(|&x| row[x]).collect();
assert_eq!(lit, vec![0]);
}
}
fn card_machine() -> Machine {
let mut m = with_hdw(LOOP);
for v in [0xF0000, 0x70000, 0xE0000, 0x80000] {
m.hw.config(v);
}
m
}
#[test]
fn card_size_checked() {
let mut m = card_machine();
for n in [0, 512, 1000, 3072, 256 * 1024] {
assert_eq!(
m.insert_card(Port::One, &vec![0; n]),
Err(Error::CardSize {
actual: n,
max: 128 * 1024
})
);
}
assert!(m.hw.card(Port::One).is_none());
assert!(!m.remove_card(Port::Two));
}
#[test]
fn card_reads_writes_and_mirrors() {
let mut m = card_machine();
assert_eq!(m.peek(0x80000), 0, "empty port reads open bus");
let mut image = vec![0u8; 1024];
image[0] = 0x21;
m.insert_card(Port::One, &image).unwrap();
assert_eq!(m.peek(0x80000), 1);
assert_eq!(m.peek(0x80001), 2);
assert_eq!(m.peek(0x80800), 1);
m.hw.write_nibble(0x80003, 0xC);
assert_eq!(m.card_image(Port::One).unwrap()[1], 0xC0);
assert_eq!(m.card_image(Port::Two), None);
assert!(m.remove_card(Port::One));
assert_eq!(m.peek(0x80000), 0);
}
#[test]
fn card_detect_interrupts_and_sets_mp() {
let mut m = card_machine();
m.insert_card(Port::Two, &[0; 1024]).unwrap();
m.step().unwrap();
assert!(!m.cpu.regs.in_interrupt);
assert_eq!(m.peek(0x10F), 0);
m.hw.write_nibble(0x10E, 0xC);
assert_eq!(m.peek(0x10F), 0xA);
m.cpu.regs.interrupts_enabled = false;
assert!(m.remove_card(Port::Two));
m.step().unwrap();
assert!(m.cpu.regs.in_interrupt);
assert_eq!(m.cpu.regs.hst & crate::cpu::regs::HST_MP, 0x8);
assert_eq!(m.peek(0x10E), 0xE);
m.hw.write_nibble(0x10E, 0xC);
m.cpu.regs.hst = 0;
m.step().unwrap();
assert_eq!(m.cpu.regs.hst, 0);
}
#[test]
fn reset_keeps_cards() {
let mut m = card_machine();
m.insert_card(Port::One, &[0x33; 2048]).unwrap();
m.reset();
assert!(m.hw.card(Port::One).is_some());
m.hw.config(0x100);
m.hw.write_nibble(0x10E, 0x8);
assert_eq!(m.peek(0x10F), 0x5);
}
#[test]
fn card_status_bits_pair_by_chip_select() {
let mut m = card_machine();
m.hw.write_nibble(0x10E, 0x8);
m.insert_card(Port::One, &[0; 1024]).unwrap();
assert_eq!(m.peek(0x10F), 0x1 | 0x4);
m.remove_card(Port::One);
m.insert_card(Port::Two, &[0; 1024]).unwrap();
assert_eq!(m.peek(0x10F), 0x2 | 0x8);
m.insert_card(Port::One, &[0; 1024]).unwrap();
assert_eq!(m.peek(0x10F), 0xF);
}
#[test]
fn display_refresh_stalls_the_cpu() {
let mut m = lcd_machine();
m.step().unwrap();
let mut cpu = m.clone();
let table: u64 = (0..1000)
.map(|_| u64::from(cpu.cpu.step(&mut cpu.hw).cycles))
.sum();
let off: u64 = (0..1000).map(|_| u64::from(m.step().unwrap())).sum();
assert!(off.abs_diff(table * 1267 / 1000) <= 1, "{off} vs {table}");
set_io(&mut m, 0x00, 0x8, 1);
let on: u64 = (0..1000).map(|_| u64::from(m.step().unwrap())).sum();
assert!(
on.abs_diff(table * 1267 * 113 / 100_000) <= 1,
"{on} vs {table}"
);
}
#[test]
fn framebuffer_annunciators_and_contrast() {
let mut m = with_hdw(LOOP);
set_io(&mut m, 0x01, 0x1C, 2); set_io(&mut m, 0x0B, 0x85, 2); let fb = m.framebuffer();
assert_eq!(fb.contrast, 0x1C);
assert_eq!(fb.annunciators, Annunciators::default(), "TIMER2 stopped");
m.hw.write_nibble(0x12F, CTRL_XTRA_OR_RUN);
let fb = m.framebuffer();
assert!(fb.annunciators.left_shift && fb.annunciators.alpha);
assert_eq!(fb.annunciator_line(), "leftshift alpha");
assert_eq!(fb.to_text(), m.lcd().to_text());
m.hw.write_nibble(0x10C, 0x0);
assert_eq!(m.framebuffer().annunciator_line(), "-");
}
const ECHO: &str = "1B0110030815C0191115A0808603F194114A196114862EF";
fn frame_cycles(frames: u64) -> u64 {
(frames * 182 * u64::from(Model::Hp48sx.clock_hz())).div_ceil(16 * 9600)
}
fn echo_machine() -> Machine {
let mut m = with_hdw(ECHO);
m.hw.write_nibble(0x10D, 6);
m.run_cycles(200).unwrap();
assert!(m.hw.io.uart.is_on());
m
}
#[test]
fn echo_program_disassembles() {
let m = echo_machine();
let mut pc = MAIN;
let mut text = Vec::new();
for _ in 0..11 {
let d = crate::cpu::decode(|a| m.peek(a), pc);
text.push(crate::cpu::disassemble(&d.instr));
pc += u32::from(d.len);
}
let text = text.join("; ");
assert!(text.contains("?ABIT=0 0 GOYES #0002E"), "{text}");
assert!(text.ends_with("GOTO #0002E"), "{text}");
}
#[test]
fn serial_round_trip_through_cpu_at_line_rate() {
let mut m = echo_machine();
assert_eq!(m.serial_baud(), 9600);
m.serial_push(b"hello");
assert_eq!(m.serial_pending(), 5);
m.run_cycles(frame_cycles(2)).unwrap();
assert_eq!(m.serial_pending(), 3);
assert_eq!(m.serial_drain(), b"h");
m.run_cycles(frame_cycles(4)).unwrap();
assert_eq!(m.serial_pending(), 0);
assert_eq!(m.serial_drain(), b"ello");
assert!(m.serial_drain().is_empty());
}
#[test]
fn serial_loop_back_without_cpu() {
let mut m = with_hdw(LOOP);
m.hw.write_nibble(0x10D, 6);
m.hw.write_nibble(0x110, 0x8);
m.hw.write_nibble(0x112, 0x4); m.serial_push(b"ignored");
m.hw.write_nibble(0x116, 0xA);
m.hw.write_nibble(0x117, 0x5);
m.run_cycles(frame_cycles(1)).unwrap();
assert_eq!(m.hw.read_nibble(0x111), 0x1, "RBF");
assert_eq!(m.hw.read_nibble(0x114), 0xA);
assert_eq!(m.hw.read_nibble(0x115), 0x5);
assert_eq!(m.hw.read_nibble(0x111), 0x0, "the read cleared RBF");
m.run_cycles(frame_cycles(8)).unwrap();
assert!(m.serial_drain().is_empty());
assert_eq!(m.serial_pending(), 0, "wire bytes passed unseen");
assert_eq!(m.hw.read_nibble(0x111), 0x0);
}
#[test]
fn uart_interrupt_vectors_and_ignores_intoff() {
let mut m = with_hdw(LOOP);
m.cpu.regs.interrupts_enabled = false;
m.hw.write_nibble(0x10D, 6);
m.hw.write_nibble(0x110, 0x8 | 0x2); m.serial_push(&[0x42]);
m.run_cycles(frame_cycles(1) / 2).unwrap();
assert!(!m.cpu.regs.in_interrupt);
m.run_cycles(frame_cycles(1) / 2).unwrap();
assert!(m.cpu.regs.in_interrupt);
assert!((INTERRUPT_VECTOR..INTERRUPT_VECTOR + 4).contains(&m.cpu.regs.pc));
}
#[test]
fn shutdn_wakes_on_receive_start() {
let mut m = with_hdw("8076FFF");
m.hw.write_nibble(0x10D, 6);
m.hw.write_nibble(0x110, 0x8 | 0x1); m.step().unwrap(); m.step().unwrap(); assert!(m.is_shutdown());
m.run_cycles(2_000_000).unwrap();
assert!(m.is_shutdown());
let before = m.cycles();
m.serial_push(&[1]);
m.run_cycles(100).unwrap();
assert!(!m.is_shutdown());
assert!(m.cpu.regs.in_interrupt);
assert!(m.cycles() - before < 200, "woke at the start bit");
}
#[test]
fn reset_keeps_the_wire() {
let mut m = echo_machine();
m.serial_push(b"abc");
m.run_cycles(frame_cycles(2)).unwrap();
m.reset();
assert_eq!(m.serial_drain(), b"a");
assert_eq!(m.serial_pending(), 1, "c waits, b was in flight");
assert!(!m.hw.io.uart.is_on());
}
#[test]
fn serial_clear_inbound_drops_the_queue() {
let mut m = machine(&[]);
m.serial_push(b"abc");
assert_eq!(m.serial_pending(), 3);
assert_eq!(m.serial_clear_inbound(), 3);
assert_eq!(m.serial_pending(), 0);
assert_eq!(m.serial_clear_inbound(), 0);
}
fn gx_machine(main: &str) -> Machine {
let mut nibbles = vec![0u8; Model::Hp48gx.rom_bytes() * 2];
let mut place = |addr: u32, s: &str| {
for (i, c) in s.chars().enumerate() {
nibbles[addr as usize + i] = c.to_digit(16).unwrap() as u8;
}
};
place(0, "6F10");
place(INTERRUPT_VECTOR, LOOP);
place(MAIN, main);
place(0x41234, "4");
place(0xC1234, "B");
let image: Vec<u8> = nibbles.chunks(2).map(|p| p[0] | (p[1] << 4)).collect();
Machine::new(Model::Hp48gx, &image).unwrap()
}
fn gx_bring_up(m: &mut Machine, ports_at_c0000: bool) {
let ports: [u32; 4] = if ports_at_c0000 {
[0xC0000, 0xC0000, 0xC0000, 0xC0000]
} else {
[0xFF000, 0x7E000, 0xFF000, 0x7E000]
};
for v in [0x100, 0xC0000, 0x80000, 0xFF000, 0x7F000] {
m.hw.config(v);
}
for v in ports {
m.hw.config(v);
}
assert!(m.hw.mc.all_configured());
}
fn set_da19(m: &mut Machine, on: bool) {
m.hw.write_nibble(0x129, if on { 0x8 } else { 0x0 });
}
#[test]
fn gx_da19_switches_upper_rom() {
let mut m = gx_machine(LOOP);
gx_bring_up(&mut m, false);
assert_eq!(m.peek(0xC1234), 4);
assert_eq!(m.peek(0x41234), 4);
set_da19(&mut m, true);
assert_eq!(m.peek(0xC1234), 0xB);
assert_eq!(m.peek(0x41234), 4);
set_da19(&mut m, false);
assert_eq!(m.peek(0xC1234), 4);
}
#[test]
fn gx_bank_latch_selects_port_2_bank() {
let mut m = gx_machine(LOOP);
gx_bring_up(&mut m, true);
let mut card = vec![0u8; 256 * 1024];
card[0] = 0x01;
card[128 * 1024] = 0x02;
m.insert_card(Port::Two, &card).unwrap();
assert_eq!(m.peek(0xC0000), 0);
m.hw.config(0); m.hw.unconfig(0xC0000); assert_eq!(m.peek(0xC0000), 0, "BEN = 0: ROM (mirrored lower half)");
m.hw.read_data(0x7F042);
m.hw.read_data(0x7F043);
assert_eq!(m.hw.bank_latch(), 0x21);
assert_eq!(m.peek(0xC0000), 2);
m.hw.read_data(0x7F040);
assert_eq!(m.peek(0xC0000), 1);
m.hw.write_nibble(0xC0001, 0x7);
assert_eq!(m.card_image(Port::Two).unwrap()[0], 0x71);
m.hw.read_data(0x7F046);
assert_eq!(m.peek(0xC0000), 2);
set_da19(&mut m, true);
assert_eq!(m.peek(0xC1234), 0xB);
set_da19(&mut m, false);
m.hw.read_data(0x7F000);
assert_eq!(m.hw.bank_latch(), 0);
assert_eq!(m.peek(0xC1234), 4);
m.peek(0x7F044);
m.hw.write_nibble(0x7F044, 0);
assert_eq!(m.hw.bank_latch(), 0);
}
#[test]
fn gx_latch_cleared_by_shutdn_and_reset() {
let mut m = gx_machine("807");
gx_bring_up(&mut m, true);
m.hw.read_data(0x7F05E);
assert_eq!(m.hw.bank_latch(), 0x2F);
m.step().unwrap();
m.step().unwrap();
assert_eq!(m.hw.bank_latch(), 0);
m.hw.read_data(0x7F05E);
m.reset();
assert_eq!(m.hw.bank_latch(), 0);
}
#[test]
fn gx_card_ports_and_status_bits() {
let mut m = gx_machine(LOOP);
gx_bring_up(&mut m, true);
m.hw.write_nibble(0x10E, 0x8);
m.insert_card(Port::One, &[0x21; 1024]).unwrap();
assert_eq!(m.peek(0x10F), 0xA);
assert_eq!(m.peek(0xC0000), 1, "port 1 card on CE2 at #C0000");
m.insert_card(Port::Two, &[0; 1024]).unwrap();
assert_eq!(m.peek(0x10F), 0xF);
m.remove_card(Port::One);
assert_eq!(m.peek(0x10F), 0x5);
assert_eq!(
m.insert_card(Port::One, &vec![0; 256 * 1024]),
Err(Error::CardSize {
actual: 256 * 1024,
max: 128 * 1024
})
);
m.insert_card(Port::Two, &vec![0; 4 * 1024 * 1024]).unwrap();
assert!(m.insert_card(Port::Two, &vec![0; 8 * 1024 * 1024]).is_err());
}
#[test]
fn gx_runs_at_4_mhz() {
let m = gx_machine(LOOP);
assert_eq!(m.model().clock_hz(), 4_000_000);
assert_eq!(m.hw.ram.len(), 2 * 128 * 1024);
}
#[test]
fn hp49g_builds_from_flash_images() {
assert_eq!(
Machine::new(Model::Hp49g, &[0; 16]).unwrap_err(),
Error::RomSize {
expected: 2 * 1024 * 1024,
actual: 16
}
);
let packed = vec![0x21u8; 2 * 1024 * 1024];
let m = Machine::new(Model::Hp49g, &packed).unwrap();
assert_eq!(m.peek(0), 1);
assert_eq!(m.peek(1), 2);
assert_eq!(m.hw.keyboard.layout(), crate::io::Layout::Hp49);
assert_eq!(m.hw.ram.len(), 2 * 256 * 1024);
let unpacked: Vec<u8> = packed.iter().flat_map(|&b| [b & 0xF, b >> 4]).collect();
let u = Machine::new(Model::Hp49g, &unpacked).unwrap();
assert_eq!(u.hw.nce1, m.hw.nce1);
assert_eq!(u.rom_sum, m.rom_sum);
}
#[test]
fn hp49g_flash_banks_and_write_path() {
use crate::modules::Flash;
let mut img = vec![0xFFu8; 2 * 1024 * 1024];
for bank in 0..16 {
img[bank * 0x2_0000] = bank as u8 | 0xF0;
}
let flash = Flash::from_packed(&img).unwrap();
let mut hw = Hardware::new(Model::Hp49g, Nce1::Flash(Box::new(flash)));
assert_eq!(hw.peek(0x00000), 0);
assert_eq!(hw.peek(0x40000), 0);
for v in [0x100, 0xC0000, 0x80000, 0xFF000, 0x3F000] {
hw.config(v);
}
hw.read_nibble(0x3F058);
assert_eq!(hw.peek(0x00000), 2);
assert_eq!(hw.peek(0x40000), 12);
for v in [0xFF000, 0x7E000, 0xC0000, 0x40000] {
hw.config(v);
}
assert!(hw.mc.all_configured());
hw.write_nibble(0x40100, 0x3);
assert_eq!(hw.peek(0x40100), 0x3, "NCE3 RAM");
hw.write_nibble(0x11C, 0x8);
assert_eq!(hw.peek(0x40000), 12);
for (a, v) in [
(0x40000, 0x0),
(0x40001, 0x4),
(0x40200, 0xA),
(0x40201, 0x5),
] {
hw.write_nibble(a, v);
}
assert_eq!(hw.peek(0x40001), 0x8, "status: ready");
for (a, v) in [(0x40000, 0xF), (0x40001, 0xF)] {
hw.write_nibble(a, v);
}
assert_eq!(hw.peek(0x40200), 0xA);
assert_eq!(hw.peek(0x40201), 0x5);
hw.write_nibble(0x11C, 0x0);
assert_eq!(hw.peek(0x40100), 0x3);
let f = hw.nce1.flash().unwrap();
assert!(!f.write_enabled());
assert_eq!(f.bank(12)[0x200], 0xA);
hw.unconfig(0x40000);
assert_eq!(hw.peek(0x40200), 0xA);
}
#[test]
fn hp49g_profile_hooks() {
let mut hw = Hardware::new(Model::Hp49g, Nce1::Rom(Rom::from_nibbles(vec![7; 16])));
for v in [
0x100, 0x80000, 0x80000, 0xFF000, 0x3F000, 0xC0000, 0xC0000, 0xC0000, 0x40000,
] {
hw.config(v);
}
assert!(hw.mc.all_configured());
hw.write_nibble(0x3F05E, 0);
assert_eq!(hw.bank_latch(), 0x2F);
hw.write_nibble(0xC0003, 0x9);
hw.write_nibble(0x40003, 0x6);
assert_eq!(hw.peek(0xC0003), 0x9);
assert_eq!(hw.peek(0x40003), 0x6);
hw.write_nibble(0x11C, 0x8);
assert_eq!(hw.io.lcr(), 0x8);
hw.write_nibble(0x40004, 0x5);
assert_eq!(hw.peek(0x40004), 0x5);
assert_eq!(hw.peek(0x00200), 7);
}
#[test]
fn hp39g_builds_from_packed_and_unpacked_images() {
assert_eq!(
Machine::new(Model::Hp39g, &[0; 16]).unwrap_err(),
Error::RomSize {
expected: 1024 * 1024,
actual: 16
}
);
let mut unpacked = vec![0u8; 2 * 1024 * 1024];
for bank in 0..8 {
unpacked[bank * 0x4_0000] = bank as u8;
}
unpacked[0x100..0x140].fill(5);
let u = Machine::new(Model::Hp39g, &unpacked).unwrap();
let packed: Vec<u8> = unpacked.chunks(2).map(|p| p[0] | (p[1] << 4)).collect();
let p = Machine::new(Model::Hp40g, &packed).unwrap();
assert_eq!(u.hw.nce1, p.hw.nce1);
assert!(u.hw.nce1.nibbles()[0x100..0x140].iter().all(|&n| n == 0));
assert_eq!(u.hw.keyboard.layout(), crate::io::Layout::Hp39);
assert_eq!(u.hw.ram.len(), 2 * 256 * 1024);
assert_eq!(u.model().clock_hz(), 4_000_000);
}
#[test]
fn hp39g_banks_through_the_latch_and_40g_strap() {
let mut nibbles = vec![0u8; 2 * 1024 * 1024];
for bank in 0..8 {
nibbles[bank * 0x4_0000] = bank as u8;
}
for model in [Model::Hp39g, Model::Hp40g] {
let rom = Nce1::BankedRom(Rom::from_nibbles(nibbles.clone()));
let mut hw = Hardware::new(model, rom);
for v in [0x100, 0x80000, 0x80000, 0xFF000, 0x7E000] {
hw.config(v);
}
hw.read_nibble(0x7E012);
assert_eq!(hw.bank_latch(), 9);
assert_eq!(hw.peek(0x40000), 1);
assert_eq!(hw.peek(0x00000), 0);
hw.write_nibble(0x7E00E, 0);
assert_eq!(hw.peek(0x40000), 7);
hw.write_nibble(0x11C, 0x8);
assert_eq!(hw.peek(0x40000), 7);
hw.write_nibble(0x11A, 0x0);
let strap = if model == Model::Hp40g { 0x8 } else { 0 };
assert_eq!(hw.peek(0x11A), strap, "{model:?}");
assert_eq!(hw.read_nibble(0x11A), strap, "{model:?}");
assert_eq!(hw.peek(0x11B), 0);
}
}
#[test]
fn hp40g_state_round_trip_keeps_its_strap_and_keys() {
let mut m = Machine::new(Model::Hp40g, &vec![0u8; Model::Hp40g.rom_bytes()]).unwrap();
m.key_down(crate::io::Key::Aplet).unwrap();
m.key_down(crate::io::Key::Views).unwrap();
let saved = m.save_state();
let mut n = Machine::new(Model::Hp40g, &vec![0u8; Model::Hp40g.rom_bytes()]).unwrap();
n.load_state(&saved).unwrap();
assert!(n.hw.keyboard.is_pressed(crate::io::Key::Views));
assert_eq!(n.save_state(), saved);
let mut g = Machine::new(Model::Hp39g, &vec![0u8; Model::Hp39g.rom_bytes()]).unwrap();
assert_eq!(g.load_state(&saved), Err(Error::StateModelMismatch));
}
#[test]
fn keys_off_the_model_are_refused() {
let mut m = Machine::new(Model::Hp49g, &vec![0u8; Model::Hp49g.rom_bytes()]).unwrap();
assert!(!m.has_key(Key::Prg));
assert_eq!(
m.key_down(Key::Prg),
Err(Error::KeyNotOnModel {
key: "prg",
model: "49g"
})
);
assert!(m.key_up(Key::Prg).is_err());
assert_eq!(m.hw.read_in_lines(), 0, "nothing pressed");
m.key_down(Key::Apps).unwrap();
assert!(m.hw.keyboard.is_pressed(Key::Apps));
let mut sx = machine(&[(MAIN, LOOP)]);
assert_eq!(
sx.key_down(Key::Apps).unwrap_err().to_string(),
"key \"apps\" is not on the 48sx keyboard"
);
sx.key_down(Key::Prg).unwrap();
}
#[test]
fn rti_re_enters_while_the_uart_request_is_held() {
let mut m = machine(&[(MAIN, LOOP), (INTERRUPT_VECTOR, "0F")]);
m.hw.config(0x100);
m.hw.write_nibble(0x10D, 6);
m.hw.write_nibble(0x110, 0x8 | 0x2); m.serial_push(&[0x42]);
m.run_cycles(frame_cycles(1)).unwrap();
for _ in 0..3 {
assert!(m.cpu.regs.in_interrupt);
assert_eq!(m.cpu.regs.pc, INTERRUPT_VECTOR);
m.step().unwrap();
}
assert_eq!(m.hw.read_nibble(0x114), 0x2);
m.step().unwrap(); assert!(!m.cpu.regs.in_interrupt);
assert_eq!(m.cpu.regs.pc, MAIN);
}
fn hp42s() -> Machine {
let packed: Vec<u8> = (0..Model::Hp42s.rom_bytes())
.map(|i| ((2 * i) & 0xF) as u8 | ((((2 * i + 1) & 0xF) as u8) << 4))
.collect();
Machine::new(Model::Hp42s, &packed).unwrap()
}
#[test]
fn hp42s_fixed_lewis_map() {
let mut m = hp42s();
assert_eq!(m.hw.ram.len(), 2 * 8 * 1024);
assert_eq!(m.peek(0x1FFF7), 7);
assert_eq!(m.peek(0x20001), 0);
m.hw.config(0x00100);
assert_eq!(m.peek(0x00105), 5);
m.hw.write_nibble(0x50003, 0xA);
assert_eq!(m.peek(0x50003), 0xA);
assert_eq!(m.peek(0x54003), 0xA);
assert_eq!(m.peek(0x5C003), 0xA);
assert_eq!(m.peek(0x60003), 0);
m.hw.write_nibble(0x40123, 0x5);
assert_eq!(m.peek(0x40123), 0x5);
assert_eq!(m.hw.lewis.ram_nibble(0x123), 0x5);
m.hw.write_nibble(0x00010, 0xF);
assert_eq!(m.peek(0x00010), 0);
}
#[test]
fn hp42s_registers_display_contrast_crc_timers() {
let mut m = hp42s();
assert!(!m.hw.display_on());
m.hw.write_nibble(0x40301, 0x6);
m.hw.write_nibble(0x40303, 0x2 | 0x8);
assert_eq!(m.hw.contrast(), 22);
assert!(m.hw.display_on());
assert_eq!(m.framebuffer().contrast, 22);
for a in 0x40304..0x40308 {
m.hw.write_nibble(a, 0);
}
m.hw.read_data(0x00003);
let mut io = crate::io::IoRegisters::new();
io.crc_update(3);
assert_eq!(m.hw.io.crc(), io.crc());
let crc: u16 = (0..4)
.map(|i| u16::from(m.peek(0x40304 + i)) << (4 * i))
.sum();
assert_eq!(crc, io.crc());
m.hw.read_data(0x40301);
assert_eq!(
m.hw.io.crc(),
io.crc(),
"reads of the block leave the CRC alone"
);
m.hw.write_nibble(0x403F8, 0x4);
m.hw.write_nibble(0x4030F, CTRL_XTRA_OR_RUN | CTRL_WAKE);
m.hw.write_nibble(0x403F7, 0x9);
m.hw.write_nibble(0x4030E, CTRL_INT);
assert_eq!(m.hw.io.timers.t2, 4);
assert!(m.hw.io.timers.t2_running());
assert_eq!(m.hw.io.timers.t1, 9);
assert_eq!(m.peek(0x4030E) & 0x7, CTRL_INT);
assert_eq!(m.peek(0x403F7), 9);
m.hw.write_nibble(0x40308, 0xF);
assert_eq!(m.peek(0x40308), 0);
m.hw.write_nibble(0x40010, 0x3);
m.reset();
assert_eq!(m.peek(0x40301), 0);
assert_eq!(m.peek(0x40010), 0x3);
}
#[test]
fn hp42s_lcd_from_the_two_column_drivers() {
let mut m = hp42s();
m.hw.write_nibble(0x40000, 0x1);
assert_eq!(m.lcd(), Lcd::blank_rows(LCD_HEIGHT_42S));
m.hw.write_nibble(0x40303, 0x8);
m.hw.write_nibble(0x40003, 0x8);
m.hw.write_nibble(0x40000 + 2 * (4 * 64 + 2), 0x4);
m.hw.write_nibble(0x40000 + 2 * (4 * 65 + 1), 0x1);
let lcd = m.lcd();
assert_eq!(lcd.height(), 16);
let lit: Vec<(usize, usize)> = (0..16)
.flat_map(|y| (0..131).map(move |x| (y, x)))
.filter(|&(y, x)| lcd.pixels[y][x])
.collect();
assert_eq!(lit, vec![(0, 0), (2, 130), (8, 65), (15, 0)]);
assert_eq!(lcd.to_text().lines().count(), 16);
}
#[test]
fn hp42s_annunciators_and_keys() {
let mut m = hp42s();
m.hw.write_nibble(0x40303, 0x8);
for (slot, v) in [(0x40220, 0xF), (0x40240, 0xF), (0x40248, 0xF), (0x40218, 0)] {
m.hw.write_nibble(slot, v);
}
let a = m.framebuffer().annunciators;
assert!(a.left_shift && a.g && a.rad);
assert!(!a.updown && !a.battery && !a.busy && !a.transmitting);
assert_eq!(m.framebuffer().annunciator_line(), "leftshift g rad");
m.hw.write_nibble(0x40210, 0xF);
assert_eq!(
m.framebuffer().annunciator_line(),
"leftshift busy transmit updown battery g rad"
);
m.hw.write_nibble(0x40303, 0);
assert_eq!(m.framebuffer().annunciators, Annunciators::default());
use crate::io::Key;
m.key_down(Key::Xeq).unwrap();
m.hw.set_out(0x001);
assert_eq!(m.hw.read_in_lines(), 0x40);
assert!(m.key_down(Key::A).is_err());
}
#[test]
fn hp42s_has_no_serial_port() {
let mut m = hp42s();
assert!(!m.has_serial());
m.serial_push(b"abc");
assert_eq!(m.serial_pending(), 0);
m.run_cycles(10_000).unwrap();
assert!(m.serial_drain().is_empty());
let mut sx = machine(&[]);
assert!(sx.has_serial());
sx.serial_push(b"abc");
assert_eq!(sx.serial_pending(), 3);
}
#[test]
fn hp42s_state_round_trip_keeps_the_lewis_block() {
let mut m = hp42s();
m.hw.write_nibble(0x40007, 0xC);
m.hw.write_nibble(0x40301, 0x6);
let saved = m.save_state();
let mut n = hp42s();
n.load_state(&saved).unwrap();
assert_eq!(n.peek(0x40007), 0xC);
assert_eq!(n.hw.contrast(), 6);
assert_eq!(n.save_state(), saved);
let mut sx = machine(&[]);
assert!(sx.load_state(&saved).is_err());
}