Test runner for running tests assembled for CP/M, specifically ZEXDOC and ZEXALL.
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}