Krolyxon
Commit 1504706

Branches

Tags

Refs →

bench51cratesbench51-coresrcsimmod.rs

449 lines · 12.8 KB · Rust

1//! Intel 8051 simulator: CPU core, memories and on-chip peripherals.
2
3mod cpu;
4pub mod disasm;
5mod periph;
6
7use std::collections::{HashSet, VecDeque};
8
9use crate::isa;
10use crate::sfr::{self, ACC, B, DPH, DPL, IE, IP, P0, P1, P2, P3, PCON, PSW, SBUF, SP};
11pub use periph::Uart;
12
13pub const CY: u8 = 0x80;
14pub const AC: u8 = 0x40;
15pub const OV: u8 = 0x04;
16pub const PARITY: u8 = 0x01;
17
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub 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
32pub 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
59impl Default for Sim {
60 fn default() -> Self {
61 Self::new()
62 }
63}
64
65impl 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)]
368mod 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)]
433mod 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}