main.rsannotatedmain.rssource159 lines · 3.5 KB · raw

Test runner for running tests assembled for CP/M, specifically ZEXDOC and ZEXALL.

3use env_logger::Env;
4use std::error::Error;
5use std::io::Write;
6use std::path::Path;
7use std::{env, fs, io, process};
8use z80_emu::Z80;
9use z80_emu::debug::DummyZ80Debugger;
10use z80_emu::traits::{BusInterface, InterruptLine};

This is an I/O routine that emulates the CP/M's $05 syscall, which does the following:

If C=2, treat the contents of the E register as an ASCII code and print it If C=9, treat the contents of DE as a memory address, and continuously print ASCII characters starting from that address until a '$' character (36d / 0x24) is encountered

Pure assembly version:

PUSH AF PUSH DE LD A, C CP 2 JR Z, singlechar CP 9 JR Z, multichar

return: POP DE POP AF RET

singlechar: LD A, E OUT (0), A JR return

multichar: LD A, (DE) CP 36 JR Z, return OUT (0), A INC DE JR multichar

45const CPM_IO_ROUTINE: &str = concat!(
46    "F5",   // PUSH AF
47    "D5",   // PUSH DE
48    "79",   // LD A, C
49    "FE02", // CP 2
50    "2807", // JR Z, 7
51    "FE09", // CP 9
52    "2808", // JR Z, 8
53    "D1",   // POP DE
54    "F1",   // POP AF
55    "C9",   // RET
56    "7B",   // LD A, E
57    "D300", // OUT (0), A
58    "18F8", // JR -8
59    "1A",   // LD A, (DE)
60    "FE24", // CP 36
61    "28F3", // JR Z, -13
62    "D300", // OUT (0), A
63    "13",   // INC DE
64    "18F6", // JR -10
65);
67fn hex_to_bytes(s: &str) -> Vec<u8> {
68    let mut bytes = Vec::new();
69    for i in (0..s.len()).step_by(2) {
70        let hex_char = &s[i..i + 2];
71        let byte = u8::from_str_radix(hex_char, 16).unwrap();
72        bytes.push(byte);
73    }
74    bytes
75}
76
77struct FullyWritableBus {
78    memory: Box<[u8; 0x10000]>,
79    io_ports: [u8; 0x100],
80}
81
82impl FullyWritableBus {
83    fn new() -> Self {
84        Self {
85            memory: vec![0; 0x10000].into_boxed_slice().try_into().unwrap(),
86            io_ports: [0; 0x100],
87        }
88    }
89}
90
91impl BusInterface for FullyWritableBus {
92    type DebugView<'a>
93        = DummyZ80Debugger
94    where
95        Self: 'a;
96
97    fn read_memory(&mut self, address: u16) -> u8 {
98        self.memory[address as usize]
99    }
100
101    fn write_memory(&mut self, address: u16, value: u8) {
102        self.memory[address as usize] = value;
103    }
104
105    fn read_io(&mut self, address: u16) -> u8 {
106        self.io_ports[(address & 0xFF) as usize]
107    }
108
109    fn write_io(&mut self, address: u16, value: u8) {
110        self.io_ports[(address & 0xFF) as usize] = value;
111        print!("{}", value as char);
112    }
113
114    fn nmi(&self) -> InterruptLine {
115        InterruptLine::High
116    }
117
118    fn int(&self) -> InterruptLine {
119        InterruptLine::High
120    }
121
122    fn busreq(&self) -> bool {
123        false
124    }
125
126    fn reset(&self) -> bool {
127        false
128    }
129}
130
131fn main() -> Result<(), Box<dyn Error>> {
132    env_logger::Builder::from_env(Env::default().default_filter_or("info")).init();
133
134    let mut args = env::args();
135    if args.len() < 2 {
136        io::stderr().write_all("ARGS: <filename>\n".as_bytes())?;
137        process::exit(1);
138    }
139    args.next();
140
141    let filename = args.next().unwrap();
142
143    let bytes = fs::read(Path::new(&filename))?;
144
145    let mut bus = FullyWritableBus::new();
146    bus.memory[0x100..bytes.len() + 0x100].copy_from_slice(&bytes);
147
148    let io_routine = hex_to_bytes(CPM_IO_ROUTINE);
149    bus.memory[0x05..io_routine.len() + 0x05].copy_from_slice(&io_routine);
150
151    let mut z80 = Z80::new();
152    z80.set_pc(0x100);
153
154    while z80.pc() != 0 {
155        z80.execute_instruction(&mut bus);
156    }
157
158    Ok(())
159}