| 1 | //! Intel 8051 simulator: CPU core, memories and on-chip peripherals. |
| 2 | |
| 3 | mod cpu; |
| 4 | pub mod disasm; |
| 5 | mod periph; |
| 6 | |
| 7 | use std::collections::{HashSet, VecDeque}; |
| 8 | |
| 9 | use crate::isa; |
| 10 | use crate::sfr::{self, ACC, B, DPH, DPL, IE, IP, P0, P1, P2, P3, PCON, PSW, SBUF, SP}; |
| 11 | pub use periph::Uart; |
| 12 | |
| 13 | pub const CY: u8 = 0x80; |
| 14 | pub const AC: u8 = 0x40; |
| 15 | pub const OV: u8 = 0x04; |
| 16 | pub const PARITY: u8 = 0x01; |
| 17 | |
| 18 | #[derive(Debug, Clone, PartialEq, Eq)] |
| 19 | pub enum Stop { |
| 20 | /// Ran the requested number of instructions / cycle budget. |
| 21 | Budget, |
| 22 | Breakpoint(u16), |
| 23 | /// A step-over / step-out target was reached. |
| 24 | Target, |
| 25 | ReservedOpcode { |
| 26 | pc: u16, |
| 27 | opcode: u8, |
| 28 | }, |
| 29 | PowerDown, |
| 30 | } |
| 31 | |
| 32 | pub struct Sim { |
| 33 | pub code: Box<[u8; 0x10000]>, |
| 34 | /// Internal RAM. Indirect addressing reaches all 256 bytes (8052-style |
| 35 | /// upper 128); direct addresses 80h-FFh go to the SFRs instead. |
| 36 | pub iram: [u8; 256], |
| 37 | /// SFR space 80h-FFh, indexed by `addr - 0x80`. |
| 38 | pub sfr: [u8; 128], |
| 39 | pub xram: Box<[u8; 0x10000]>, |
| 40 | pub pc: u16, |
| 41 | /// Machine cycles since reset. |
| 42 | pub cycles: u64, |
| 43 | /// Levels driven onto the port pins from outside (1 = released/high). |
| 44 | /// A pin reads as `latch & external`. |
| 45 | pub pins: [u8; 4], |
| 46 | pub uart: Uart, |
| 47 | pub idle: bool, |
| 48 | pub power_down: bool, |
| 49 | /// Priority levels of interrupts currently in service (innermost last). |
| 50 | pub in_service: Vec<u8>, |
| 51 | /// Vector of the last interrupt taken, for the UI log. |
| 52 | pub last_irq: Option<u16>, |
| 53 | /// RETI or a write to IE/IP delays interrupt acceptance by one instruction. |
| 54 | irq_hold: bool, |
| 55 | /// Pin levels of P3 in the previous cycle, for edge detection. |
| 56 | prev_p3: u8, |
| 57 | } |
| 58 | |
| 59 | impl Default for Sim { |
| 60 | fn default() -> Self { |
| 61 | Self::new() |
| 62 | } |
| 63 | } |
| 64 | |
| 65 | impl Sim { |
| 66 | pub fn new() -> Self { |
| 67 | let mut s = Self { |
| 68 | code: Box::new([0; 0x10000]), |
| 69 | iram: [0; 256], |
| 70 | sfr: [0; 128], |
| 71 | xram: Box::new([0; 0x10000]), |
| 72 | pc: 0, |
| 73 | cycles: 0, |
| 74 | pins: [0xFF; 4], |
| 75 | uart: Uart::default(), |
| 76 | idle: false, |
| 77 | power_down: false, |
| 78 | in_service: Vec::new(), |
| 79 | last_irq: None, |
| 80 | irq_hold: false, |
| 81 | prev_p3: 0xFF, |
| 82 | }; |
| 83 | s.reset(); |
| 84 | s |
| 85 | } |
| 86 | |
| 87 | /// Hardware reset: SFRs to their reset values, PC to 0. Code and XRAM |
| 88 | /// are kept; IRAM is cleared so runs are reproducible. |
| 89 | pub fn reset(&mut self) { |
| 90 | self.iram = [0; 256]; |
| 91 | self.sfr = [0; 128]; |
| 92 | for p in [P0, P1, P2, P3] { |
| 93 | self.set_sfr(p, 0xFF); |
| 94 | } |
| 95 | self.set_sfr(SP, 0x07); |
| 96 | self.pc = 0; |
| 97 | self.cycles = 0; |
| 98 | self.uart.reset(); |
| 99 | self.idle = false; |
| 100 | self.power_down = false; |
| 101 | self.in_service.clear(); |
| 102 | self.last_irq = None; |
| 103 | self.irq_hold = false; |
| 104 | self.prev_p3 = self.port_pins(3); |
| 105 | } |
| 106 | |
| 107 | pub fn load_code(&mut self, bytes: impl Iterator<Item = (u16, u8)>) { |
| 108 | self.code.fill(0); |
| 109 | for (a, b) in bytes { |
| 110 | self.code[a as usize] = b; |
| 111 | } |
| 112 | } |
| 113 | |
| 114 | // ---- Raw register access ---- |
| 115 | |
| 116 | pub fn sfr(&self, addr: u8) -> u8 { |
| 117 | self.sfr[(addr & 0x7F) as usize] |
| 118 | } |
| 119 | |
| 120 | pub fn set_sfr(&mut self, addr: u8, v: u8) { |
| 121 | self.sfr[(addr & 0x7F) as usize] = v; |
| 122 | } |
| 123 | |
| 124 | pub fn acc(&self) -> u8 { |
| 125 | self.sfr(ACC) |
| 126 | } |
| 127 | |
| 128 | pub fn set_acc(&mut self, v: u8) { |
| 129 | self.set_sfr(ACC, v); |
| 130 | } |
| 131 | |
| 132 | pub fn b(&self) -> u8 { |
| 133 | self.sfr(B) |
| 134 | } |
| 135 | |
| 136 | pub fn psw(&self) -> u8 { |
| 137 | self.sfr(PSW) |
| 138 | } |
| 139 | |
| 140 | pub fn sp(&self) -> u8 { |
| 141 | self.sfr(SP) |
| 142 | } |
| 143 | |
| 144 | pub fn dptr(&self) -> u16 { |
| 145 | u16::from_be_bytes([self.sfr(DPH), self.sfr(DPL)]) |
| 146 | } |
| 147 | |
| 148 | pub fn set_dptr(&mut self, v: u16) { |
| 149 | let [h, l] = v.to_be_bytes(); |
| 150 | self.set_sfr(DPH, h); |
| 151 | self.set_sfr(DPL, l); |
| 152 | } |
| 153 | |
| 154 | pub fn flag(&self, mask: u8) -> bool { |
| 155 | self.psw() & mask != 0 |
| 156 | } |
| 157 | |
| 158 | pub fn set_flag(&mut self, mask: u8, on: bool) { |
| 159 | let p = self.psw(); |
| 160 | self.set_sfr(PSW, if on { p | mask } else { p & !mask }); |
| 161 | } |
| 162 | |
| 163 | pub fn bank(&self) -> u8 { |
| 164 | (self.psw() >> 3) & 3 |
| 165 | } |
| 166 | |
| 167 | /// IRAM address of register Rn in the active bank. |
| 168 | pub fn reg_addr(&self, n: u8) -> u8 { |
| 169 | self.bank() * 8 + n |
| 170 | } |
| 171 | |
| 172 | pub fn reg(&self, n: u8) -> u8 { |
| 173 | self.iram[self.reg_addr(n) as usize] |
| 174 | } |
| 175 | |
| 176 | /// Pin levels of port `n`: latch AND externally driven level. |
| 177 | pub fn port_pins(&self, n: usize) -> u8 { |
| 178 | self.sfr(P0 + 0x10 * n as u8) & self.pins[n] |
| 179 | } |
| 180 | |
| 181 | // ---- Addressing modes ---- |
| 182 | |
| 183 | /// Reads a direct address. Ports read their pins, except for |
| 184 | /// read-modify-write instructions (`rmw`) which read the latch. |
| 185 | pub fn read_direct(&self, addr: u8, rmw: bool) -> u8 { |
| 186 | match addr { |
| 187 | 0x00..=0x7F => self.iram[addr as usize], |
| 188 | P0 | P1 | P2 | P3 if !rmw => self.port_pins(((addr - P0) >> 4) as usize), |
| 189 | SBUF => self.uart.sbuf_rx, |
| 190 | _ => self.sfr(addr), |
| 191 | } |
| 192 | } |
| 193 | |
| 194 | pub fn write_direct(&mut self, addr: u8, v: u8) { |
| 195 | match addr { |
| 196 | 0x00..=0x7F => self.iram[addr as usize] = v, |
| 197 | SBUF => self.uart_transmit(v), |
| 198 | IE | IP => { |
| 199 | self.set_sfr(addr, v); |
| 200 | self.irq_hold = true; |
| 201 | } |
| 202 | PCON => { |
| 203 | // IDL (bit 0) and PD (bit 1) take effect immediately. |
| 204 | self.set_sfr(addr, v); |
| 205 | self.power_down |= v & 0x02 != 0; |
| 206 | self.idle |= v & 0x01 != 0; |
| 207 | } |
| 208 | _ => self.set_sfr(addr, v), |
| 209 | } |
| 210 | } |
| 211 | |
| 212 | pub fn read_bit(&self, bit: u8, rmw: bool) -> bool { |
| 213 | let (byte, idx) = sfr::bit_location(bit); |
| 214 | self.read_direct(byte, rmw) & (1 << idx) != 0 |
| 215 | } |
| 216 | |
| 217 | pub fn write_bit(&mut self, bit: u8, on: bool) { |
| 218 | let (byte, idx) = sfr::bit_location(bit); |
| 219 | let v = self.read_direct(byte, true); |
| 220 | self.write_direct(byte, if on { v | (1 << idx) } else { v & !(1 << idx) }); |
| 221 | } |
| 222 | |
| 223 | pub fn push(&mut self, v: u8) { |
| 224 | let sp = self.sp().wrapping_add(1); |
| 225 | self.set_sfr(SP, sp); |
| 226 | self.iram[sp as usize] = v; |
| 227 | } |
| 228 | |
| 229 | pub fn pop(&mut self) -> u8 { |
| 230 | let sp = self.sp(); |
| 231 | self.set_sfr(SP, sp.wrapping_sub(1)); |
| 232 | self.iram[sp as usize] |
| 233 | } |
| 234 | |
| 235 | pub fn push_pc(&mut self) { |
| 236 | let [h, l] = self.pc.to_be_bytes(); |
| 237 | self.push(l); |
| 238 | self.push(h); |
| 239 | } |
| 240 | |
| 241 | pub fn pop_pc(&mut self) { |
| 242 | let h = self.pop(); |
| 243 | let l = self.pop(); |
| 244 | self.pc = u16::from_be_bytes([h, l]); |
| 245 | } |
| 246 | |
| 247 | // ---- Execution control ---- |
| 248 | |
| 249 | /// Executes one instruction (or one idle cycle), then services interrupts. |
| 250 | pub fn step(&mut self) -> Result<(), Stop> { |
| 251 | if self.power_down { |
| 252 | return Err(Stop::PowerDown); |
| 253 | } |
| 254 | if self.idle { |
| 255 | self.tick(1); |
| 256 | } else { |
| 257 | let pc = self.pc; |
| 258 | let opcode = self.code[pc as usize]; |
| 259 | let Some(info) = isa::info(opcode) else { |
| 260 | return Err(Stop::ReservedOpcode { pc, opcode }); |
| 261 | }; |
| 262 | self.pc = pc.wrapping_add(1); |
| 263 | self.execute(opcode); |
| 264 | let p = self.acc().count_ones() % 2 == 1; |
| 265 | self.set_flag(PARITY, p); |
| 266 | self.tick(info.cycles as u32); |
| 267 | } |
| 268 | self.service_interrupts(); |
| 269 | Ok(()) |
| 270 | } |
| 271 | |
| 272 | fn current_call_len(&self) -> Option<u16> { |
| 273 | let op = self.code[self.pc as usize]; |
| 274 | match op { |
| 275 | 0x12 => Some(3), |
| 276 | _ if op & 0x1F == 0x11 => Some(2), |
| 277 | _ => None, |
| 278 | } |
| 279 | } |
| 280 | |
| 281 | /// Runs up to `max_steps` instructions, stopping at breakpoints. |
| 282 | /// `resume` ignores a breakpoint at the starting PC, so execution can |
| 283 | /// continue from the breakpoint it last stopped at. |
| 284 | pub fn run(&mut self, max_steps: u32, breakpoints: &HashSet<u16>, resume: bool) -> Stop { |
| 285 | for i in 0..max_steps { |
| 286 | if (i > 0 || !resume) && !self.idle && breakpoints.contains(&self.pc) { |
| 287 | return Stop::Breakpoint(self.pc); |
| 288 | } |
| 289 | if let Err(s) = self.step() { |
| 290 | return s; |
| 291 | } |
| 292 | } |
| 293 | Stop::Budget |
| 294 | } |
| 295 | |
| 296 | /// Like `step`, but executes a whole ACALL/LCALL subroutine. |
| 297 | pub fn step_over(&mut self, max_steps: u32, breakpoints: &HashSet<u16>) -> Stop { |
| 298 | match self.current_call_len() { |
| 299 | Some(len) => { |
| 300 | let target = self.pc.wrapping_add(len); |
| 301 | let sp = self.sp(); |
| 302 | self.run_until(max_steps, breakpoints, |s| s.pc == target && s.sp() == sp) |
| 303 | } |
| 304 | None => match self.step() { |
| 305 | Ok(()) => Stop::Target, |
| 306 | Err(s) => s, |
| 307 | }, |
| 308 | } |
| 309 | } |
| 310 | |
| 311 | /// Runs until the current subroutine (or interrupt handler) returns. |
| 312 | pub fn step_out(&mut self, max_steps: u32, breakpoints: &HashSet<u16>) -> Stop { |
| 313 | let sp = self.sp(); |
| 314 | let mut returning = false; |
| 315 | self.run_until_with(max_steps, breakpoints, |s, before| { |
| 316 | if before { |
| 317 | let op = s.code[s.pc as usize]; |
| 318 | returning = (op == 0x22 || op == 0x32) && s.sp() <= sp && !s.idle; |
| 319 | false |
| 320 | } else { |
| 321 | returning |
| 322 | } |
| 323 | }) |
| 324 | } |
| 325 | |
| 326 | fn run_until( |
| 327 | &mut self, |
| 328 | max_steps: u32, |
| 329 | bps: &HashSet<u16>, |
| 330 | mut done: impl FnMut(&Sim) -> bool, |
| 331 | ) -> Stop { |
| 332 | self.run_until_with(max_steps, bps, |s, before| !before && done(s)) |
| 333 | } |
| 334 | |
| 335 | /// `done(sim, before)` is called before (`true`) and after (`false`) each step. |
| 336 | fn run_until_with( |
| 337 | &mut self, |
| 338 | max_steps: u32, |
| 339 | bps: &HashSet<u16>, |
| 340 | mut done: impl FnMut(&Sim, bool) -> bool, |
| 341 | ) -> Stop { |
| 342 | for i in 0..max_steps { |
| 343 | if i > 0 && !self.idle && bps.contains(&self.pc) { |
| 344 | return Stop::Breakpoint(self.pc); |
| 345 | } |
| 346 | done(self, true); |
| 347 | if let Err(s) = self.step() { |
| 348 | return s; |
| 349 | } |
| 350 | if done(self, false) { |
| 351 | return Stop::Target; |
| 352 | } |
| 353 | } |
| 354 | Stop::Budget |
| 355 | } |
| 356 | |
| 357 | /// Queue bytes for the UART receiver, as if typed on a serial terminal. |
| 358 | pub fn uart_send(&mut self, bytes: &[u8]) { |
| 359 | self.uart.rx_queue.extend(bytes.iter().copied()); |
| 360 | } |
| 361 | |
| 362 | pub fn uart_rx_pending(&self) -> &VecDeque<u8> { |
| 363 | &self.uart.rx_queue |
| 364 | } |
| 365 | } |
| 366 | |
| 367 | #[cfg(test)] |
| 368 | mod example_tests { |
| 369 | use super::Sim; |
| 370 | use crate::asm::assemble; |
| 371 | |
| 372 | fn load(src: &str) -> Sim { |
| 373 | let out = assemble(src); |
| 374 | assert!(out.ok(), "{:?}", out.diagnostics); |
| 375 | let mut s = Sim::new(); |
| 376 | s.load_code(out.bytes()); |
| 377 | s |
| 378 | } |
| 379 | |
| 380 | fn steps(s: &mut Sim, n: usize) { |
| 381 | for _ in 0..n { |
| 382 | s.step().unwrap(); |
| 383 | } |
| 384 | } |
| 385 | |
| 386 | #[test] |
| 387 | fn serial_hello_greets_and_echoes() { |
| 388 | let mut s = load(include_str!("../../examples/serial_hello.asm")); |
| 389 | steps(&mut s, 60_000); |
| 390 | assert_eq!( |
| 391 | String::from_utf8_lossy(&s.uart.tx_log), |
| 392 | "Hello from the 8051!\r\nType something: " |
| 393 | ); |
| 394 | s.uart_send(b"hi\r"); |
| 395 | steps(&mut s, 20_000); |
| 396 | assert!(String::from_utf8_lossy(&s.uart.tx_log).ends_with("hi\r\n")); |
| 397 | } |
| 398 | |
| 399 | #[test] |
| 400 | fn timer_irq_ticks() { |
| 401 | let mut s = load(include_str!("../../examples/timer_irq.asm")); |
| 402 | steps(&mut s, 5_000); |
| 403 | // ~10000 cycles at one interrupt per 1000 cycles. |
| 404 | assert!((3..=10).contains(&s.reg(7)), "R7 = {}", s.reg(7)); |
| 405 | } |
| 406 | |
| 407 | #[test] |
| 408 | fn button_counts_edges_while_idle() { |
| 409 | let mut s = load(include_str!("../../examples/button_int0.asm")); |
| 410 | steps(&mut s, 20); |
| 411 | assert!(s.idle); |
| 412 | for _ in 0..3 { |
| 413 | s.pins[3] &= !0x04; |
| 414 | steps(&mut s, 10); |
| 415 | s.pins[3] |= 0x04; |
| 416 | steps(&mut s, 10); |
| 417 | } |
| 418 | assert_eq!(s.sfr(crate::sfr::P1), 3); |
| 419 | } |
| 420 | |
| 421 | #[test] |
| 422 | fn bcd_and_lookup() { |
| 423 | let mut s = load(include_str!("../../examples/bcd_add.asm")); |
| 424 | steps(&mut s, 20); |
| 425 | assert_eq!((s.iram[0x31], s.iram[0x30]), (0x69, 0x12)); |
| 426 | let mut s = load(include_str!("../../examples/lookup_movc.asm")); |
| 427 | steps(&mut s, 3 + 4 * 5 + 3); // setup, digits 0-3, then up to MOV P2,A |
| 428 | assert_eq!(s.sfr(crate::sfr::P2), 0x66); // digit 4 |
| 429 | } |
| 430 | } |
| 431 | |
| 432 | #[cfg(test)] |
| 433 | mod run_tests { |
| 434 | use super::{Sim, Stop}; |
| 435 | use crate::asm::assemble; |
| 436 | use std::collections::HashSet; |
| 437 | |
| 438 | #[test] |
| 439 | fn breakpoint_at_chunk_boundary_is_hit() { |
| 440 | let out = assemble("l: NOP\nNOP\nSJMP l"); |
| 441 | let mut s = Sim::new(); |
| 442 | s.load_code(out.bytes()); |
| 443 | let bps = HashSet::from([1u16]); |
| 444 | assert_eq!(s.run(1, &bps, true), Stop::Budget); // stops at PC 1 by budget |
| 445 | assert_eq!(s.run(10, &bps, false), Stop::Breakpoint(1)); // next chunk must not skip it |
| 446 | assert_eq!(s.run(10, &bps, true), Stop::Breakpoint(1)); // resuming loops back to it |
| 447 | assert_eq!(s.pc, 1); |
| 448 | } |
| 449 | } |