lc3_ensemble/sim.rs
1//! Simulating and execution for LC-3 assembly.
2//!
3//! This module is focused on executing fully assembled code (i.e., [`ObjectFile`]).
4//!
5//! This module consists of:
6//! - [`Simulator`]: The struct that simulates assembled code.
7//! - [`mem`]: The module handling memory relating to the registers.
8//! - [`device`]: The module handling simulator IO, interrupts, and general handling of external devices.
9//! - [`debug`]: The module handling types of breakpoints for the simulator.
10//! - [`frame`]: The module handling the frame stack and call frame management.
11//!
12//! # Usage
13//!
14//! To simulate some code, you need to instantiate a Simulator and load an object file to it:
15//!
16//! ```no_run
17//! use lc3_ensemble::sim::Simulator;
18//!
19//! # let obj_file = panic!("don't actually make an object file");
20//! let mut simulator = Simulator::new(Default::default());
21//! simulator.load_obj_file(&obj_file);
22//! simulator.run().unwrap();
23//! ```
24//!
25//! ## Flags
26//!
27//! Here, we define `simulator` to have the default flags.
28//! We could also configure the simulator by editing the flags. For example,
29//! if we wish to enable real traps, we can edit the flags like so:
30//!
31//! ```no_run
32//! # use lc3_ensemble::sim::{Simulator, SimFlags};
33//! let mut simulator = Simulator::new(SimFlags { use_real_traps: true, ..Default::default() });
34//! ```
35//!
36//! All of the available flags can be found in [`SimFlags`].
37//!
38//! ## Execution
39//!
40//! Beyond the basic [`Simulator::run`] (which runs until halting),
41//! there are also:
42//! - [`Simulator::step_in`], [`Simulator::step_out`], [`Simulator::step_over`]: manual step-by-step simulation
43//! - [`Simulator::run_while`], [`Simulator::run_with_limit`]: more advanced programmatic execution
44//!
45//! ```
46//! use lc3_ensemble::parse::parse_ast;
47//! use lc3_ensemble::asm::assemble;
48//! use lc3_ensemble::sim::Simulator;
49//! use lc3_ensemble::ast::Reg::R0;
50//!
51//! let src = "
52//! .orig x3000
53//! AND R0, R0, #0
54//! ADD R0, R0, #1
55//! ADD R0, R0, #1
56//! ADD R0, R0, #1
57//! HALT
58//! .end
59//! ";
60//! let ast = parse_ast(src).unwrap();
61//! let obj_file = assemble(ast).unwrap();
62//!
63//! let mut sim = Simulator::new(Default::default());
64//! sim.load_obj_file(&obj_file);
65//!
66//! // Running step by step:
67//! sim.step_in().unwrap();
68//! assert_eq!(sim.reg_file[R0].get(), 0);
69//! sim.step_in().unwrap();
70//! assert_eq!(sim.reg_file[R0].get(), 1);
71//! sim.step_in().unwrap();
72//! assert_eq!(sim.reg_file[R0].get(), 2);
73//! sim.step_in().unwrap();
74//! assert_eq!(sim.reg_file[R0].get(), 3);
75//! ```
76//!
77//! ## Querying State
78//!
79//! You can query (or set) a variety of different state values from the simulator.
80//!
81//! - If you wish to access the PC, it can simply be done through the `sim.pc` field.
82//! - If you wish to access the PSR or MCR, the [`Simulator::psr`] and [`Simulator::mcr`] methods are present to query those values.
83//! - If you wish to access the register file, you can access it through the `sim.reg_file` field.
84//!
85//! [`RegFile`] holds its values in a [`Word`], which is a memory cell which keeps track of initialization state.
86//! Accessing can simply be done with [`Word::get`] and [`Word::set`]:
87//! ```
88//! use lc3_ensemble::sim::Simulator;
89//! use lc3_ensemble::ast::Reg::R0;
90//!
91//! let mut sim = Simulator::new(Default::default());
92//!
93//! sim.reg_file[R0].set(0x1234);
94//! assert_eq!(sim.reg_file[R0].get(), 0x1234);
95//! ```
96//!
97//! - If you wish to access the memory, the simulator provides two pairs of memory access:
98//! - Direct access to the memory array (via the `mem`) field, which does not trigger access violations or IO.
99//! - [`Simulator::read_mem`] and [`Simulator::write_mem`], which are used for accesses which do trigger access violations and IO.
100//! ```
101//! use lc3_ensemble::sim::Simulator;
102//!
103//! let mut sim = Simulator::new(Default::default());
104//!
105//! // Raw memory access:
106//! sim.mem[0x3000].set(0x5678);
107//! assert_eq!(sim.mem[0x3000].get(), 0x5678);
108//!
109//! // Through read/write:
110//! use lc3_ensemble::sim::mem::Word;
111//!
112//! assert!(sim.write_mem(0x0000, Word::new_init(0x9ABC), sim.default_mem_ctx()).is_err());
113//! assert!(sim.write_mem(0x3000, Word::new_init(0x9ABC), sim.default_mem_ctx()).is_ok());
114//! assert!(sim.read_mem(0x0000, sim.default_mem_ctx()).is_err());
115//! assert!(sim.read_mem(0x3000, sim.default_mem_ctx()).is_ok());
116//! ```
117//!
118//! - Other state can be accessed. Consult the [`Simulator`] docs for more information.
119//!
120//! ### Frames
121//!
122//! The simulator also keeps track of subroutine frame information, accessible on the `frame_stack` field of [`Simulator`].
123//!
124//! **If `debug_frames` is not enabled in [`SimFlags`]**, the only information the [`Simulator`] keeps track of
125//! is the number of subroutine frames deep the simulator is (via [`FrameStack::len`]):
126//! - During a JSR instruction, the frame count increases by 1.
127//! - During a RET instruction, the frame count decreases by 1.
128//!
129//! **If `debug_frames` is enabled in [`SimFlags`]**, the frame information is significantly extended.
130//! The simulator then keeps track of several frame values (such as caller and callee address).
131//! These are accessible via the [`FrameStack::frames`] method.
132//!
133//! Debug frame information by default includes caller and callee addresses, but can be
134//! configured to also include frame pointer and argument information. See the [`frame`]
135//! module for details.
136//!
137//! ## Debugging with breakpoints
138//!
139//! Breakpoints are accessible through the `breakpoints` field on [`Simulator`].
140//!
141//! To add a `breakpoint`, simply insert a [`Breakpoint`] and
142//! it will break if its condition is met during all execution functions (except [`Simulator::step_in`]).
143//!
144//! ```
145//! use lc3_ensemble::parse::parse_ast;
146//! use lc3_ensemble::asm::assemble;
147//! use lc3_ensemble::sim::Simulator;
148//! use lc3_ensemble::sim::debug::Breakpoint;
149//!
150//! let src = "
151//! .orig x3000
152//! ADD R0, R0, #0
153//! ADD R0, R0, #1
154//! ADD R0, R0, #2
155//! ADD R0, R0, #3
156//! HALT
157//! .end
158//! ";
159//! let ast = parse_ast(src).unwrap();
160//! let obj_file = assemble(ast).unwrap();
161//!
162//! let mut sim = Simulator::new(Default::default());
163//! sim.load_obj_file(&obj_file);
164//!
165//! // Without breakpoint
166//! sim.run().unwrap();
167//! assert_eq!(sim.pc, 0x3004);
168//!
169//! // With breakpoint
170//! sim.reset();
171//! sim.load_obj_file(&obj_file);
172//! sim.breakpoints.insert(Breakpoint::PC(0x3002));
173//! sim.run().unwrap();
174//! assert_eq!(sim.pc, 0x3002);
175//! ```
176//!
177//! ## IO, interrupts, and external devices
178//!
179//! IO and interrupts are handled by "external devices" (the trait [`ExternalDevice`]).
180//!
181//! These can be added by registering the device in the Simulator's device handler
182//! ([`DeviceHandler::add_device`] of the `device_handler` field).
183//!
184//! When a load or store to a memory-mapped address (0xFE00-0xFFFF) occurs,
185//! the device handler sends the corresponding load/store to the device for it to handle.
186//!
187//! The best IO for programmatic uses is [`device::BufferedKeyboard`] and [`device::BufferedDisplay`],
188//! which exposes the IO to memory buffers that can be modified.
189//!
190//! ```
191//! use lc3_ensemble::parse::parse_ast;
192//! use lc3_ensemble::asm::assemble;
193//! use lc3_ensemble::sim::Simulator;
194//! use lc3_ensemble::sim::device::{BufferedKeyboard, BufferedDisplay};
195//! use std::sync::Arc;
196//!
197//! let src = "
198//! .orig x3000
199//! LOOP:
200//! GETC
201//! PUTC
202//! ADD R0, R0, #0
203//! BRnp LOOP
204//! HALT
205//! .end
206//! ";
207//! let ast = parse_ast(src).unwrap();
208//! let obj_file = assemble(ast).unwrap();
209//!
210//! let mut sim = Simulator::new(Default::default());
211//! sim.load_obj_file(&obj_file);
212//!
213//! let input = BufferedKeyboard::default();
214//! let output = BufferedDisplay::default();
215//! sim.device_handler.set_keyboard(input.clone());
216//! sim.device_handler.set_display(output.clone());
217//!
218//! input.get_buffer().write().unwrap().extend(b"Hello, World!\0");
219//! sim.run().unwrap();
220//!
221//! assert_eq!(&*input.get_buffer().read().unwrap(), b"");
222//! assert_eq!(&**output.get_buffer().read().unwrap(), b"Hello, World!\0");
223//! ```
224//!
225//! These external devices also support interrupt-based IO.
226//!
227//! If the [`device::BufferedKeyboard`] device is enabled, interrupts can be enabled by setting `KBSR[14]`.
228//! Once enabled, the keyboard can interrupt the simulator and run its interrupt service routine.
229//!
230//! ## Strictness (experimental)
231//!
232//! This simulator also features uninitialized memory access checks (via the `strict` flag).
233//!
234//! These strict memory checks verify that unintialized data is not written to the register files, memory,
235//! or other areas that do not expect uninitialized data. Uninitialized data here is defined as
236//! data that is unknown as it was never fully set and is dependent on the values the machine was initialized with.
237//!
238//! The `strict` flag can currently detect:
239//! - Loads of uninitialized data into a register (excluding uninitialized reads from `mem[R6 + offset]`).
240//! - Stores of uninitialized data into memory (excluding uninitialized stores to `mem[R6 + offset]` and `.blkw`'d memory).
241//! - Stores of uninitialized data into memory-mapped IO
242//! - Loads and stores through an uninitialized memory address
243//! - Jumping to an uninitialized address (e.g., via `JSRR` or `JMP`)
244//! - Jumping to a memory location that is uninitialized
245//! - Decoding an instruction from uninitialized data
246//! - Setting the PSR to an uninitialized value
247//!
248//! Note that this is considered *experimental* as false positives can still occur.
249//! Also note that the exceptions for loads and stores of uninitialized data
250//! are present to prevent typical value manipulation on the stack or in stored memory
251//! from triggering a strictness error.
252//!
253//! ```
254//! use lc3_ensemble::parse::parse_ast;
255//! use lc3_ensemble::asm::assemble;
256//! use lc3_ensemble::sim::{Simulator, SimErr};
257//!
258//! let src = "
259//! .orig x3000
260//! ADD R0, R0, #0
261//! ADD R0, R0, #15 ;; R0 = 15
262//! HALT
263//! .end
264//! ";
265//! let ast = parse_ast(src).unwrap();
266//! let obj_file = assemble(ast).unwrap();
267//!
268//! let mut sim = Simulator::new(Default::default());
269//! sim.load_obj_file(&obj_file);
270//! sim.flags.strict = true;
271//!
272//! // Strictness check detects `R0` was set without first being cleared.
273//! assert!(matches!(sim.run(), Err(SimErr::StrictRegSetUninit)));
274//! assert_eq!(sim.prefetch_pc(), 0x3000);
275//! ```
276//!
277//! [`VecDeque`]: std::collections::VecDeque
278//! [`Breakpoint`]: self::debug::Breakpoint
279pub mod mem;
280pub mod debug;
281pub mod frame;
282pub mod device;
283mod observer;
284
285use std::collections::HashSet;
286use std::sync::atomic::AtomicBool;
287use std::sync::Arc;
288
289use crate::asm::ObjectFile;
290use crate::ast::Reg::{R6, R7};
291use crate::ast::sim::SimInstr;
292use crate::ast::ImmOrReg;
293use debug::Breakpoint;
294use device::{DeviceHandler, ExternalDevice, ExternalInterrupt};
295
296use self::frame::{FrameStack, FrameType};
297use self::mem::{MemArray, RegFile, Word, MachineInitStrategy};
298
299/// Errors that can occur during simulation.
300#[derive(Debug)]
301pub enum SimErr {
302 /// Word was decoded, but the opcode was invalid.
303 IllegalOpcode,
304 /// Word was decoded, and the opcode is recognized,
305 /// but the instruction's format is invalid.
306 InvalidInstrFormat,
307 /// A privileged instruction was called in user mode.
308 PrivilegeViolation,
309 /// A supervisor region was accessed in user mode.
310 AccessViolation,
311 /// Object file contained unresolved external symbols.
312 UnresolvedExternal(String),
313 /// Interrupt raised.
314 Interrupt(ExternalInterrupt),
315 /// A register was loaded with a partially uninitialized value.
316 ///
317 /// This will ignore loads from the stack (R6), because it is convention to push registers
318 /// (including uninitialized registers).
319 /// This also ignores loads from allocated (`.blkw`) memory in case the program writer
320 /// uses those as register stores.
321 // IDEA: So currently, the way this is implemented is that LDR Rx, R6, OFF is accepted regardless of initialization.
322 // We could make this stricter by keeping track of how much is allocated on the stack.
323 StrictRegSetUninit,
324 /// Memory was loaded with a partially uninitialized value.
325 ///
326 /// This will ignore loads from the stack (R6), because it is convention to push registers
327 /// (including uninitialized registers).
328 /// This also ignores loads from allocated (`.blkw`) memory in case the program writer
329 /// uses those as register stores.
330 // IDEA: See StrictRegSetUninit.
331 StrictMemSetUninit,
332 /// Data was stored into MMIO with a partially uninitialized value.
333 StrictIOSetUninit,
334 /// Address to jump to is coming from an uninitialized value.
335 StrictJmpAddrUninit,
336 /// Address to jump to (which is a subroutine or trap call) is coming from an uninitialized value.
337 StrictSRAddrUninit,
338 /// Address to read from memory is coming from an uninitialized value.
339 StrictMemAddrUninit,
340 /// PC is pointing to an uninitialized value.
341 StrictPCCurrUninit,
342 /// PC was set to an address that has an uninitialized value and will read from it next cycle.
343 StrictPCNextUninit,
344 /// The PSR was loaded with a partially uninitialized value (by RTI).
345 StrictPSRSetUninit,
346}
347impl std::fmt::Display for SimErr {
348 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
349 match self {
350 SimErr::IllegalOpcode => f.write_str("simulator executed illegal opcode"),
351 SimErr::InvalidInstrFormat => f.write_str("simulator executed invalid instruction"),
352 SimErr::PrivilegeViolation => f.write_str("privilege violation"),
353 SimErr::AccessViolation => f.write_str("access violation"),
354 SimErr::UnresolvedExternal(s) => write!(f, "unresolved external label {s} in object file"),
355 SimErr::Interrupt(e) => write!(f, "unhandled interrupt: {e}"),
356 SimErr::StrictRegSetUninit => f.write_str("register was set to uninitialized value (strict mode)"),
357 SimErr::StrictMemSetUninit => f.write_str("tried to write an uninitialized value to memory (strict mode)"),
358 SimErr::StrictIOSetUninit => f.write_str("tried to write an uninitialized value to memory-mapped IO (strict mode)"),
359 SimErr::StrictJmpAddrUninit => f.write_str("PC address was set to uninitialized address (strict mode)"),
360 SimErr::StrictSRAddrUninit => f.write_str("Subroutine starts at uninitialized address (strict mode)"),
361 SimErr::StrictMemAddrUninit => f.write_str("tried to access memory with an uninitialized address (strict mode)"),
362 SimErr::StrictPCCurrUninit => f.write_str("PC is pointing to uninitialized value (strict mode)"),
363 SimErr::StrictPCNextUninit => f.write_str("PC will point to uninitialized value when this instruction executes (strict mode)"),
364 SimErr::StrictPSRSetUninit => f.write_str("tried to set the PSR to an uninitialized value (strict mode)"),
365 }
366 }
367}
368impl std::error::Error for SimErr {}
369
370/// Anything that can cause a step to abruptly fail to finish.
371enum StepBreak {
372 /// A virtual halt was executed.
373 Halt,
374 /// A simulation error occurred.
375 Err(SimErr),
376}
377impl From<SimErr> for StepBreak {
378 fn from(value: SimErr) -> Self {
379 Self::Err(value)
380 }
381}
382
383macro_rules! int_vect {
384 ($Type:ident, {$($name:ident = $value:literal), +}) => {
385 enum $Type {
386 $($name = $value),+
387 }
388 impl TryFrom<u16> for $Type {
389 type Error = ();
390
391 fn try_from(value: u16) -> Result<Self, Self::Error> {
392 match value {
393 $($value => Ok(Self::$name)),+,
394 _ => Err(())
395 }
396 }
397 }
398 }
399}
400int_vect!(RealIntVect, {
401 Halt = 0x25,
402 PrivilegeViolation = 0x100,
403 IllegalOpcode = 0x101,
404 AccessViolation = 0x102
405});
406
407
408/// The OS object file with symbols.
409///
410/// Do not rely on this existing.
411#[doc(hidden)]
412#[allow(non_snake_case)]
413pub fn _os_obj_file() -> &'static ObjectFile {
414 // This is public because LC3Tools UI needs it;
415 // however, I don't think there's any particular other reason
416 // that a developer would need this, so it's #[doc(hidden)].
417 use crate::parse::parse_ast;
418 use crate::asm::assemble_debug;
419 use std::sync::OnceLock;
420
421 static OS_OBJ_FILE: OnceLock<ObjectFile> = OnceLock::new();
422
423 OS_OBJ_FILE.get_or_init(|| {
424 let os_file = include_str!("os.asm");
425 let ast = parse_ast(os_file).unwrap();
426 assemble_debug(ast, os_file).unwrap()
427 })
428}
429
430/// Reason for why execution paused if it wasn't due to an error.
431#[derive(Clone, Copy, PartialEq, Eq, Hash, Debug, Default)]
432enum PauseCondition {
433 /// Program reached a halt.
434 Halt,
435 /// Program set MCR to off.
436 MCROff,
437 /// Program hit a breakpoint.
438 Breakpoint,
439 /// Program hit a tripwire condition.
440 Tripwire,
441 /// Program hit an error and did not pause successfully.
442 #[default]
443 Unsuccessful
444}
445
446/// Configuration flags for [`Simulator`].
447///
448/// These can be modified after the `Simulator` is created with [`Simulator::new`]
449/// and their effects should still apply.
450///
451/// Read the field descriptions for more details.
452#[derive(Debug, PartialEq, Eq, Clone, Copy)]
453pub struct SimFlags {
454 /// Whether strict mode is enabled.
455 ///
456 /// Strict mode adds additional integrity checks to the simulator,
457 /// such as verifying initialization state is normal for provided data.
458 ///
459 /// By default, this flag is `false`.
460 pub strict: bool,
461
462 /// Whether to use emulated version of certain traps.
463 ///
464 /// Certain traps and exceptions have two separate implementations within `Simulator`, namely:
465 /// - `HALT` or `TRAP x25`
466 /// - Privilege mode exception
467 /// - Illegal opcode exception
468 /// - Access violation exception
469 ///
470 /// This flag allows us to configure between the two implementations:
471 /// - **virtual** (`false`): On execution of one of these interrupts, the simulator breaks
472 /// and prints its own error.
473 /// - **real** (`true`): On execution of one of these interrupts, the simulator delegates
474 /// the error to the machine's OS and continues through the OS.
475 ///
476 /// Activating real traps is useful for maintaining integrity to the LC-3 ISA, whereas
477 /// virtual HALT preserves the state of the machine prior to calling the interrupt routines
478 /// and can provide slightly more helpful error messages.
479 ///
480 /// By default, this flag is `false`.
481 pub use_real_traps: bool,
482
483 /// The creation strategy for uninitialized Words.
484 ///
485 /// This is used to initialize the `mem` and `reg_file` fields.
486 ///
487 /// By default, this flag is [`MachineInitStrategy::default`].
488 pub machine_init: MachineInitStrategy,
489
490 /// Whether to store debugging information about call frames.
491 ///
492 /// This flag only goes into effect after a `Simulator::new` or `Simulator::reset` call.
493 ///
494 /// By default, this flag is `false`.
495 pub debug_frames: bool,
496
497 /// If true, privilege checks are ignored and the simulator runs as though
498 /// the executor has supervisor level privilege.
499 ///
500 /// By default, this flag is `false`.
501 pub ignore_privilege: bool
502}
503
504#[allow(clippy::derivable_impls)]
505impl Default for SimFlags {
506 fn default() -> Self {
507 Self {
508 strict: false,
509 use_real_traps: false,
510 machine_init: Default::default(),
511 debug_frames: false,
512 ignore_privilege: false
513 }
514 }
515}
516
517const USER_START: u16 = 0x3000;
518const IO_START: u16 = 0xFE00;
519const PSR_ADDR: u16 = 0xFFFC;
520const MCR_ADDR: u16 = 0xFFFE;
521
522/// Context behind a memory access.
523///
524/// This struct is used by [`Simulator::read_mem`] and [`Simulator::write_mem`] to perform checks against memory accesses.
525/// A default memory access context for the given simulator can be constructed with [`Simulator::default_mem_ctx`].
526#[derive(Clone, Copy, Debug)]
527pub struct MemAccessCtx {
528 /// Whether this access is privileged (false = user, true = supervisor).
529 pub privileged: bool,
530
531 /// Whether writes to memory should follow strict rules
532 /// (no writing partially or fully uninitialized data).
533 ///
534 /// This does not affect [`Simulator::read_mem`].
535 pub strict: bool,
536
537 /// Whether a read to memory-mapped IO should cause side effects.
538 ///
539 /// This can be set to false to observe the value of IO.
540 ///
541 /// This does not affect [`Simulator::write_mem`].
542 pub io_effects: bool
543}
544impl MemAccessCtx {
545 /// Allows any access and allows access to (effectless) IO.
546 ///
547 /// Useful for reading state.
548 pub fn omnipotent() -> Self {
549 MemAccessCtx { privileged: true, strict: false, io_effects: false }
550 }
551}
552/// Executes assembled code.
553#[derive(Debug)]
554pub struct Simulator {
555 // ------------------ SIMULATION STATE ------------------
556 // Calling [`Simulator::reset`] resets these values.
557
558 /// The simulator's memory.
559 ///
560 /// Note that this is held in the heap, as it is too large for the stack.
561 pub mem: MemArray,
562
563 /// The simulator's register file.
564 pub reg_file: RegFile,
565
566 /// The program counter.
567 pub pc: u16,
568
569 /// The processor status register. See [`PSR`] for more details.
570 psr: PSR,
571
572 /// Saved stack pointer (the one currently not in use)
573 saved_sp: Word,
574
575 /// The frame stack.
576 pub frame_stack: FrameStack,
577
578 /// Allocated blocks in object file.
579 ///
580 /// This field keeps track of "allocated" blocks
581 /// (memory written to by instructions or directives like .blkw)
582 /// in the current object file.
583 ///
584 /// Loading and storing uninitialized data in an allocated block
585 /// does not cause strictness errors because we're assuming
586 /// the programmer is using those as data stores.
587 ///
588 /// This is technically a bit lax, because it lets them write
589 /// into instructions but oops.
590 alloca: Box<[(u16, u16)]>,
591
592 /// The number of instructions successfully run since this `Simulator` was initialized.
593 ///
594 /// This can be set to 0 to reset the counter.
595 pub instructions_run: u64,
596
597 /// Indicates whether the PC has been incremented in the fetch stage yet.
598 ///
599 /// This is just for error handling purposes. It's used to compute
600 /// the PC of the instruction that caused an error. See [`Simulator::prefetch_pc`].
601 prefetch: bool,
602
603 /// Indicates the reason why the last execution (via [`Simulator::run_while`] and adjacent)
604 /// had paused.
605 pause_condition: PauseCondition,
606
607 /// Tracks changes in simulator state.
608 pub observer: observer::ChangeObserver,
609
610 /// Indicates whether the OS has been loaded.
611 os_loaded: bool,
612
613 // ------------------ CONFIG/DEBUG STATE ------------------
614 // Calling [`Simulator::reset`] does not reset these values.
615
616 /// Machine control.
617 /// If unset, the program stops.
618 ///
619 /// This is publicly accessible via a reference through [`Simulator::mcr`].
620 mcr: MCR,
621
622 /// Configuration settings for the simulator.
623 ///
624 /// These are preserved between resets.
625 ///
626 /// See [`SimFlags`] for more details on what configuration
627 /// settings are available.
628 pub flags: SimFlags,
629
630 /// Breakpoints for the simulator.
631 pub breakpoints: HashSet<Breakpoint>,
632
633 /// All external devices connected to the system (IO and interrupting devices).
634 pub device_handler: DeviceHandler
635
636}
637impl Simulator where Simulator: Send + Sync {}
638
639impl Simulator {
640 /// Creates a new simulator with the provided initializers
641 /// and with the OS loaded, but without a loaded object file.
642 ///
643 /// This also allows providing an MCR atomic which is used by the Simulator.
644 fn new_with_mcr(flags: SimFlags, mcr: MCR) -> Self {
645 let mut filler = flags.machine_init.generator();
646
647 let mut sim = Self {
648 mem: MemArray::new(&mut filler),
649 reg_file: RegFile::new(&mut filler),
650 pc: 0x3000,
651 psr: PSR::new(),
652 saved_sp: Word::new_init(0x3000),
653 frame_stack: FrameStack::new(flags.debug_frames),
654 alloca: Box::new([]),
655 instructions_run: 0,
656 prefetch: false,
657 pause_condition: Default::default(),
658 observer: Default::default(),
659 os_loaded: false,
660
661 mcr,
662 flags,
663 breakpoints: Default::default(),
664 device_handler: Default::default()
665 };
666
667 sim.mem.as_slice_mut()[IO_START as usize..].fill(Word::new_init(0)); // clear IO section
668 sim.load_os();
669 sim
670 }
671
672 /// Creates a new simulator with the provided initializers
673 /// and with the OS loaded, but without a loaded object file.
674 pub fn new(flags: SimFlags) -> Self {
675 Self::new_with_mcr(flags, Arc::default())
676 }
677
678 /// Loads and initializes the operating system.
679 ///
680 /// Note that this is done automatically with [`Simulator::new`].
681 ///
682 /// This will initialize kernel space and create trap handlers,
683 /// however it will not load working IO. This can cause IO
684 /// traps such as `GETC` and `PUTC` to hang. The only trap that
685 /// is assured to function without IO is `HALT`.
686 ///
687 /// To initialize the IO, use [`Simulator::open_io`].
688 fn load_os(&mut self) {
689 if !self.os_loaded {
690 self.load_obj_file(_os_obj_file())
691 .unwrap_or_else(|_| unreachable!("OS object file should not have externals"));
692 self.os_loaded = true;
693 }
694 }
695
696 /// Resets the simulator.
697 ///
698 /// This resets the state of the `Simulator` back to before any execution calls,
699 /// while preserving configuration and debug state.
700 ///
701 /// Note that this function preserves:
702 /// - Flags
703 /// - Breakpoints
704 /// - External interrupts
705 /// - MCR reference (i.e., anything with access to the Simulator's MCR can still control it)
706 /// - IO (however, note that it does not reset IO state, which must be manually reset)
707 ///
708 /// This also does not reload object files. Any object file data has to be reloaded into the Simulator.
709 pub fn reset(&mut self) {
710 let mcr = Arc::clone(&self.mcr);
711 let flags = self.flags;
712 let breakpoints = std::mem::take(&mut self.breakpoints);
713 let dev_handler = std::mem::take(&mut self.device_handler);
714
715 *self = Simulator::new_with_mcr(flags, mcr);
716 self.breakpoints = breakpoints;
717 self.device_handler = dev_handler;
718 self.device_handler.io_reset();
719 }
720
721 /// Fallibly reads the word at the provided index, erroring if not possible.
722 ///
723 /// This accepts a [`MemAccessCtx`], that describes the parameters of the memory access.
724 /// The simulator provides a default [`MemAccessCtx`] under [`Simulator::default_mem_ctx`].
725 ///
726 /// The flags are used as follows:
727 /// - `privileged`: if false, this access errors if the address is a memory location outside of the user range.
728 /// - `strict`: not used for `read`
729 ///
730 /// Note that this method is used for simulating a read to memory-mapped IO.
731 /// If you would like to query the memory's state, consider using `index` on [`MemArray`].
732 pub fn read_mem(&mut self, addr: u16, ctx: MemAccessCtx) -> Result<Word, SimErr> {
733 use std::sync::atomic::Ordering;
734
735 if !ctx.privileged && !(USER_START..IO_START).contains(&addr) { return Err(SimErr::AccessViolation) };
736
737 // Apply read to IO and write to mem array:
738 match addr {
739 // Supervisor range
740 0..USER_START => { /* Non-IO read */ },
741 // User range
742 USER_START..IO_START => { /* Non-IO read */ },
743 // IO range
744 PSR_ADDR => self.mem[addr].set(self.psr.get()),
745 MCR_ADDR => self.mem[addr].set(u16::from(self.mcr.load(Ordering::Relaxed)) << 15),
746 IO_START.. => {
747 if let Some(data) = self.device_handler.io_read(addr, ctx.io_effects) {
748 self.mem[addr].set(data);
749 }
750 }
751 }
752
753 // Load from mem array:
754 Ok(self.mem[addr])
755 }
756
757 /// Fallibly writes the word at the provided index, erroring if not possible.
758 ///
759 /// This accepts a [`MemAccessCtx`], that describes the parameters of the memory access.
760 /// The simulator provides a default [`MemAccessCtx`] under [`Simulator::default_mem_ctx`].
761 ///
762 /// The flags are used as follows:
763 /// - `privileged`: if false, this access errors if the address is a memory location outside of the user range.
764 /// - `strict`: If true, all accesses that would cause a memory location to be set with uninitialized data causes an error.
765 ///
766 /// Note that this method is used for simulating a write to memory-mapped IO.
767 /// If you would like to edit the memory's state, consider using `index_mut` on [`MemArray`].
768 pub fn write_mem(&mut self, addr: u16, data: Word, ctx: MemAccessCtx) -> Result<(), SimErr> {
769 use std::sync::atomic::Ordering;
770
771 if !ctx.privileged && !(USER_START..IO_START).contains(&addr) { return Err(SimErr::AccessViolation) };
772
773 // Apply write to IO:
774 let success = match addr {
775 // Supervisor range (non-IO write)
776 0..USER_START => true,
777 // User range (non-IO write)
778 USER_START..IO_START => true,
779 // IO range
780 PSR_ADDR => {
781 let io_data = data.get_if_init(ctx.strict, SimErr::StrictIOSetUninit)?;
782 self.psr.set(io_data);
783 true
784 },
785 MCR_ADDR => {
786 let io_data = data.get_if_init(ctx.strict, SimErr::StrictIOSetUninit)?;
787 self.mcr.store((io_data as i16) < 0, Ordering::Relaxed);
788 true
789 },
790 IO_START.. => {
791 let io_data = data.get_if_init(ctx.strict, SimErr::StrictIOSetUninit)?;
792 self.device_handler.io_write(addr, io_data)
793 }
794 };
795
796 // Duplicate write in mem array:
797 if success {
798 if self.mem[addr] != data {
799 self.observer.set_mem_changed(addr);
800 }
801 self.mem[addr]
802 .set_if_init(data, ctx.strict, SimErr::StrictMemSetUninit)?;
803 }
804
805 Ok(())
806 }
807
808 /// Loads an object file into this simulator.
809 pub fn load_obj_file(&mut self, obj: &ObjectFile) -> Result<(), SimErr> {
810 use std::cmp::Ordering;
811
812 // Reject any object files with external symbols.
813 if let Some(ext) = obj.get_external_symbol() {
814 return Err(SimErr::UnresolvedExternal(ext.to_string()));
815 }
816
817 let mut alloca = vec![];
818
819 for (start, words) in obj.block_iter() {
820 self.mem.copy_obj_block(start, words);
821
822 // add this block to alloca
823 let len = words.len() as u16;
824 let end = start.wrapping_add(len);
825
826 match start.cmp(&end) {
827 Ordering::Less => alloca.push((start, len)),
828 Ordering::Equal => {},
829 Ordering::Greater => {
830 // push (start..) and (0..end) as blocks
831 alloca.push((start, start.wrapping_neg()));
832 if end != 0 { alloca.push((0, end)) };
833 },
834 }
835 }
836
837 alloca.sort_by_key(|&(start, _)| start);
838 self.alloca = alloca.into_boxed_slice();
839
840 Ok(())
841 }
842
843 /// Sets the condition codes using the provided result.
844 fn set_cc(&mut self, result: u16) {
845 match (result as i16).cmp(&0) {
846 std::cmp::Ordering::Less => self.psr.set_cc(0b100),
847 std::cmp::Ordering::Equal => self.psr.set_cc(0b010),
848 std::cmp::Ordering::Greater => self.psr.set_cc(0b001),
849 }
850 }
851
852 /// Gets a reference to the PSR.
853 pub fn psr(&self) -> &PSR {
854 // This is not mutable because editing the PSR can cause crashes to occur if
855 // privilege is tampered with during an interrupt.
856 &self.psr
857 }
858
859 /// Gets a reference to the MCR.
860 pub fn mcr(&self) -> &MCR {
861 // The mcr field is not exposed because that allows someone to swap the MCR
862 // with another AtomicBool, which would cause the simulator's MCR
863 // to be inconsistent with any other component's
864 &self.mcr
865 }
866
867 /// Sets the PC to the given address, raising any errors that occur.
868 ///
869 /// The `st_check_mem` parameter indicates whether the data at the PC should be verified in strict mode.
870 /// This should be enabled when it is absolutely known that the PC will read from the provided address
871 /// on the next cycle.
872 ///
873 /// This should be true when this function is used for instructions like `BR` and `JSR`
874 /// and should be false when this function is used to increment PC during fetch.
875 fn set_pc(&mut self, addr_word: Word, st_check_mem: bool) -> Result<(), SimErr> {
876 let addr = addr_word.get_if_init(self.flags.strict, SimErr::StrictJmpAddrUninit)?;
877 if self.flags.strict && st_check_mem {
878 // Check next memory value is initialized:
879 if !self.read_mem(addr, self.default_mem_ctx())?.is_init() {
880 return Err(SimErr::StrictPCNextUninit);
881 }
882 }
883 self.pc = addr;
884 Ok(())
885 }
886 /// Adds an offset to the PC.
887 ///
888 /// See [`Simulator::set_pc`] for details about `st_check_mem`.
889 fn offset_pc(&mut self, offset: i16, st_check_mem: bool) -> Result<(), SimErr> {
890 self.set_pc(Word::from(self.pc.wrapping_add_signed(offset)), st_check_mem)
891 }
892 /// Gets the value of the prefetch PC.
893 ///
894 /// This function is useful as it returns the location of the currently
895 /// executing instruction in memory.
896 pub fn prefetch_pc(&self) -> u16 {
897 self.pc - (!self.prefetch) as u16
898 }
899
900 /// Checks whether the address points to a memory location that was allocated
901 /// in the currently loaded object file.
902 fn in_alloca(&self, addr: u16) -> bool {
903 let first_post = self.alloca.partition_point(|&(start, _)| start <= addr);
904 if first_post == 0 { return false };
905
906 // This is the last block where start <= addr.
907 let (start, len) = self.alloca[first_post - 1];
908
909 // We must also check that addr < end.
910 // If start + len is None, that means end is greater than all possible lengths.
911 match start.checked_add(len) {
912 Some(e) => addr < e,
913 None => true
914 }
915 }
916
917 /// Indicates whether the last execution of the simulator hit a breakpoint.
918 pub fn hit_breakpoint(&self) -> bool {
919 matches!(self.pause_condition, PauseCondition::Breakpoint)
920 }
921
922 /// Indicates whether the last execution of the simulator resulted in a HALT successfully occurring.
923 ///
924 /// This is defined as:
925 /// - `HALT` being executed while virtual HALTs are enabled
926 /// - `MCR` being set to `x0000` during the execution of the program.
927 pub fn hit_halt(&self) -> bool {
928 matches!(self.pause_condition, PauseCondition::Halt | PauseCondition::MCROff)
929 }
930
931 /// Computes the default memory access context,
932 /// which are the default flags to use (see [`Simulator::read_mem`] and [`Simulator::write_mem`]).
933 pub fn default_mem_ctx(&self) -> MemAccessCtx {
934 MemAccessCtx {
935 privileged: self.psr.privileged() || self.flags.ignore_privilege,
936 strict: self.flags.strict,
937 io_effects: true
938 }
939 }
940
941 /// Calls a subroutine.
942 ///
943 /// This does all the steps for calling a subroutine, namely:
944 /// - Setting the PC to the subroutine's start address
945 /// - Setting R7 to the original PC (return address)
946 /// - Adding information to the frame stack
947 pub fn call_subroutine(&mut self, addr: u16) -> Result<(), SimErr> {
948 self.reg_file[R7].set(self.pc);
949 self.frame_stack.push_frame(self.prefetch_pc(), addr, FrameType::Subroutine, &self.reg_file, &self.mem);
950 self.set_pc(Word::new_init(addr), true)
951 }
952
953 /// Calls a trap or interrupt, adding information to the frame stack
954 /// and setting the PC to the start of the trap/interrupt handler.
955 ///
956 /// `0x00-0xFF` represents a trap,
957 /// `0x100-0x1FF` represents an interrupt.
958 fn call_interrupt(&mut self, vect: u16, ft: FrameType) -> Result<(), SimErr> {
959 let addr = self.read_mem(vect, self.default_mem_ctx())?
960 .get_if_init(self.flags.strict, SimErr::StrictSRAddrUninit)?;
961
962 self.frame_stack.push_frame(self.prefetch_pc(), vect, ft, &self.reg_file, &self.mem);
963 self.set_pc(Word::new_init(addr), true)
964 }
965 /// Interrupt, trap, and exception handler.
966 ///
967 /// If priority is none, this will unconditionally initialize the trap or exception handler.
968 /// If priority is not none, this will run the interrupt handler only if the interrupt's priority
969 /// is greater than the PSR's priority.
970 ///
971 /// The address provided is the address into the jump table (either the trap or interrupt vector ones).
972 /// This function will always jump to `mem[vect]` at the end of this function.
973 fn handle_interrupt(&mut self, vect: u16, priority: Option<u8>) -> Result<(), StepBreak> {
974 if priority.is_some_and(|prio| prio <= self.psr.priority()) { return Ok(()) };
975
976 // Virtual traps.
977 // See the flag for documentation.
978 // Virtual HALT
979 if !self.flags.use_real_traps {
980 if let Ok(intv) = RealIntVect::try_from(vect) {
981 if !self.prefetch {
982 // decrement PC so that if play is pressed again, it goes back here
983 self.offset_pc(-1, false)?;
984 self.prefetch = true;
985 }
986 let break_value = match intv {
987 RealIntVect::Halt => StepBreak::Halt,
988 RealIntVect::PrivilegeViolation => StepBreak::Err(SimErr::PrivilegeViolation),
989 RealIntVect::IllegalOpcode => StepBreak::Err(SimErr::IllegalOpcode),
990 RealIntVect::AccessViolation => StepBreak::Err(SimErr::AccessViolation),
991 };
992 return Err(break_value);
993 }
994 };
995
996 if !self.psr.privileged() {
997 std::mem::swap(&mut self.saved_sp, &mut self.reg_file[R6]);
998 }
999
1000 // Push PSR, PC to supervisor stack
1001 let old_psr = self.psr.get();
1002 let old_pc = self.pc;
1003
1004 self.psr.set_privileged(true);
1005 let mctx = self.default_mem_ctx();
1006
1007 // push PSR and PC to stack
1008 let sp = self.reg_file[R6]
1009 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?;
1010
1011 self.reg_file[R6] -= 2u16;
1012 self.write_mem(sp.wrapping_sub(1), Word::new_init(old_psr), mctx)?;
1013 self.write_mem(sp.wrapping_sub(2), Word::new_init(old_pc), mctx)?;
1014
1015 // set PSR to z
1016 self.psr.set_cc_z();
1017
1018 // set interrupt priority
1019 if let Some(prio) = priority {
1020 self.psr.set_priority(prio);
1021 }
1022
1023 let ft = match priority.is_some() {
1024 true => FrameType::Interrupt,
1025 false => FrameType::Trap,
1026 };
1027
1028 self.call_interrupt(vect, ft)
1029 .map_err(Into::into)
1030 }
1031
1032 /// Runs until the tripwire condition returns false (or any of the typical breaks occur).
1033 ///
1034 /// The typical break conditions are:
1035 /// - `HALT` is executed
1036 /// - the MCR is set to false
1037 /// - A breakpoint matches
1038 pub fn run_while(&mut self, mut tripwire: impl FnMut(&mut Simulator) -> bool) -> Result<(), SimErr> {
1039 use std::sync::atomic::Ordering;
1040
1041 self.observer.clear();
1042 std::mem::take(&mut self.pause_condition);
1043 self.mcr.store(true, Ordering::Relaxed);
1044
1045 // event loop
1046 // run until:
1047 // 1. the MCR is set to false
1048 // 2. the tripwire condition returns false
1049 // 3. any of the breakpoints are hit
1050 let result = loop {
1051 // MCR turned off:
1052 if !self.mcr.load(Ordering::Relaxed) {
1053 break Ok(PauseCondition::MCROff);
1054 }
1055 // Tripwire turned off:
1056 if !tripwire(self) {
1057 break Ok(PauseCondition::Tripwire);
1058 }
1059
1060 // Run a step:
1061 match self.step() {
1062 Ok(_) => {},
1063 Err(StepBreak::Halt) => break Ok(PauseCondition::Halt),
1064 Err(StepBreak::Err(e)) => break Err(e)
1065 }
1066
1067 // After executing, check that any breakpoints were hit.
1068 if self.breakpoints.iter().any(|bp| bp.check(self)) {
1069 break Ok(PauseCondition::Breakpoint);
1070 }
1071 };
1072
1073 self.mcr.store(false, Ordering::Relaxed);
1074 self.pause_condition = result?;
1075 Ok(())
1076 }
1077
1078 /// Execute the program.
1079 ///
1080 /// This blocks until the program ends.
1081 /// If you would like to limit the maximum number of steps to execute, consider [`Simulator::run_with_limit`].
1082 pub fn run(&mut self) -> Result<(), SimErr> {
1083 self.run_while(|_| true)
1084 }
1085
1086 /// Execute the program with a limit on how many steps to execute.
1087 ///
1088 /// This blocks until the program ends or until the number of steps to execute has been hit.
1089 pub fn run_with_limit(&mut self, max_steps: u64) -> Result<(), SimErr> {
1090 let i = self.instructions_run;
1091 self.run_while(|sim| sim.instructions_run.wrapping_sub(i) < max_steps)
1092 }
1093
1094 /// Simulate one step, executing one instruction.
1095 ///
1096 /// Unlike [`Simulator::step`], this function does not handle the `use_real_traps` flag.
1097 /// Both of these functions are not meant for general stepping use. That should be done
1098 /// with [`Simulator::step_in`].
1099 fn _step_inner(&mut self) -> Result<(), StepBreak> {
1100 self.prefetch = true;
1101
1102 if let Some(int) = self.device_handler.poll_interrupt() {
1103 match int.kind {
1104 // If priority passes, handle interrupt then skip FETCH:
1105 device::InterruptKind::Vectored { vect, priority } if priority > self.psr().priority() => {
1106 return self.handle_interrupt(0x100 + u16::from(vect), Some(priority));
1107 },
1108 // If priority does not pass, move to FETCH:
1109 device::InterruptKind::Vectored { .. } => Ok(()),
1110
1111 // External interrupt.
1112 device::InterruptKind::External(int) => Err(StepBreak::Err(SimErr::Interrupt(int))),
1113 }?;
1114 }
1115
1116 let word = self.read_mem(self.pc, self.default_mem_ctx())?
1117 .get_if_init(self.flags.strict, SimErr::StrictPCCurrUninit)?;
1118
1119 let instr = SimInstr::decode(word)?;
1120
1121 self.offset_pc(1, false)?;
1122 self.prefetch = false;
1123
1124 match instr {
1125 SimInstr::BR(cc, off) => {
1126 if cc & self.psr.cc() != 0 {
1127 self.offset_pc(off.get(), true)?;
1128 }
1129 },
1130 SimInstr::ADD(dr, sr1, sr2) => {
1131 let val1 = self.reg_file[sr1];
1132 let val2 = match sr2 {
1133 ImmOrReg::Imm(i2) => Word::from(i2.get()),
1134 ImmOrReg::Reg(r2) => self.reg_file[r2],
1135 };
1136
1137 let result = val1 + val2;
1138 self.reg_file[dr].set_if_init(result, self.flags.strict, SimErr::StrictRegSetUninit)?;
1139 self.set_cc(result.get());
1140 },
1141 SimInstr::LD(dr, off) => {
1142 let ea = self.pc.wrapping_add_signed(off.get());
1143 let write_strict = self.flags.strict && !self.in_alloca(ea);
1144
1145 let val = self.read_mem(ea, self.default_mem_ctx())?;
1146 self.reg_file[dr].set_if_init(val, write_strict, SimErr::StrictRegSetUninit)?;
1147 self.set_cc(val.get());
1148 },
1149 SimInstr::ST(sr, off) => {
1150 let ea = self.pc.wrapping_add_signed(off.get());
1151 let write_ctx = MemAccessCtx {
1152 strict: self.flags.strict && !self.in_alloca(ea),
1153 ..self.default_mem_ctx()
1154 };
1155
1156 let val = self.reg_file[sr];
1157 self.write_mem(ea, val, write_ctx)?;
1158 },
1159 SimInstr::JSR(op) => {
1160 // Note: JSRR R7 jumps to address at R7, then sets PC to R7.
1161 // Refer to: https://github.com/gt-cs2110/lc3tools/commit/fa9a23f62106eeee9fef7d2a278ba989356c9ee2
1162
1163 let addr = match op {
1164 ImmOrReg::Imm(off) => Word::from(self.pc.wrapping_add_signed(off.get())),
1165 ImmOrReg::Reg(br) => self.reg_file[br],
1166 }.get_if_init(self.flags.strict, SimErr::StrictSRAddrUninit)?;
1167
1168 self.call_subroutine(addr)?;
1169 },
1170 SimInstr::AND(dr, sr1, sr2) => {
1171 let val1 = self.reg_file[sr1];
1172 let val2 = match sr2 {
1173 ImmOrReg::Imm(i2) => Word::from(i2.get()),
1174 ImmOrReg::Reg(r2) => self.reg_file[r2],
1175 };
1176
1177 let result = val1 & val2;
1178 self.reg_file[dr].set_if_init(result, self.flags.strict, SimErr::StrictRegSetUninit)?;
1179 self.set_cc(result.get());
1180 },
1181 SimInstr::LDR(dr, br, off) => {
1182 let ea = self.reg_file[br]
1183 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?
1184 .wrapping_add_signed(off.get());
1185 let write_strict = self.flags.strict && br != R6 && !self.in_alloca(ea);
1186
1187 let val = self.read_mem(ea, self.default_mem_ctx())?;
1188 self.reg_file[dr].set_if_init(val, write_strict, SimErr::StrictRegSetUninit)?;
1189 self.set_cc(val.get());
1190 },
1191 SimInstr::STR(sr, br, off) => {
1192 let ea = self.reg_file[br]
1193 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?
1194 .wrapping_add_signed(off.get());
1195 let write_ctx = MemAccessCtx {
1196 strict: self.flags.strict && br != R6 && !self.in_alloca(ea),
1197 ..self.default_mem_ctx()
1198 };
1199
1200 let val = self.reg_file[sr];
1201 self.write_mem(ea, val, write_ctx)?;
1202 },
1203 SimInstr::RTI => {
1204 if self.psr.privileged() || self.flags.ignore_privilege {
1205 let mctx = self.default_mem_ctx();
1206
1207 // Pop PC and PSR from the stack
1208 let sp = self.reg_file[R6]
1209 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?;
1210
1211 let pc = self.read_mem(sp, mctx)?
1212 .get_if_init(self.flags.strict, SimErr::StrictJmpAddrUninit)?;
1213 let psr = self.read_mem(sp.wrapping_add(1), mctx)?
1214 .get_if_init(self.flags.strict, SimErr::StrictPSRSetUninit)?;
1215 self.reg_file[R6] += 2u16;
1216
1217 self.set_pc(Word::new_init(pc), true)?;
1218 self.psr = PSR(psr);
1219
1220 if !self.psr.privileged() {
1221 std::mem::swap(&mut self.saved_sp, &mut self.reg_file[R6]);
1222 }
1223
1224 self.frame_stack.pop_frame();
1225 } else {
1226 return Err(SimErr::PrivilegeViolation.into());
1227 }
1228 },
1229 SimInstr::NOT(dr, sr) => {
1230 let val = self.reg_file[sr];
1231
1232 let result = !val;
1233 self.reg_file[dr].set_if_init(result, self.flags.strict, SimErr::StrictRegSetUninit)?;
1234 self.set_cc(result.get());
1235 },
1236 SimInstr::LDI(dr, off) => {
1237 let shifted_pc = self.pc.wrapping_add_signed(off.get());
1238 let ea = self.read_mem(shifted_pc, self.default_mem_ctx())?
1239 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?;
1240 let write_strict = self.flags.strict && !self.in_alloca(ea);
1241
1242 let val = self.read_mem(ea, self.default_mem_ctx())?;
1243 self.reg_file[dr].set_if_init(val, write_strict, SimErr::StrictRegSetUninit)?;
1244 self.set_cc(val.get());
1245 },
1246 SimInstr::STI(sr, off) => {
1247 let shifted_pc = self.pc.wrapping_add_signed(off.get());
1248 let ea = self.read_mem(shifted_pc, self.default_mem_ctx())?
1249 .get_if_init(self.flags.strict, SimErr::StrictMemAddrUninit)?;
1250 let write_ctx = MemAccessCtx {
1251 strict: self.flags.strict && !self.in_alloca(ea),
1252 ..self.default_mem_ctx()
1253 };
1254
1255 let val = self.reg_file[sr];
1256 self.write_mem(ea, val, write_ctx)?;
1257 },
1258 SimInstr::JMP(br) => {
1259 let addr = self.reg_file[br];
1260 self.set_pc(addr, true)?;
1261
1262 // if this is RET,
1263 // we must also handle frame information:
1264 if br.reg_no() == 7 {
1265 self.frame_stack.pop_frame();
1266 }
1267 },
1268 SimInstr::LEA(dr, off) => {
1269 let ea = self.pc.wrapping_add_signed(off.get());
1270 self.reg_file[dr].set(ea);
1271 },
1272 SimInstr::TRAP(vect) => {
1273 self.handle_interrupt(vect.get(), None)?;
1274 },
1275 }
1276
1277 self.instructions_run = self.instructions_run.wrapping_add(1);
1278 Ok(())
1279 }
1280
1281 /// Simulate one step, executing one instruction.
1282 ///
1283 /// This function properly handles the `use_real_traps` flag.
1284 ///
1285 /// This function is a library function and should be used when one step is needed.
1286 /// The difference between this function and [`Simulator::step_in`] is that this
1287 /// function can return [`StepBreak::Halt`] as an error,
1288 /// whereas `step_in` will ignore that error.
1289 fn step(&mut self) -> Result<(), StepBreak> {
1290 match self._step_inner() {
1291 // Virtual traps don't need to go through handle_interrupt logic
1292 s if !self.flags.use_real_traps => s,
1293 // Real traps!
1294 Err(StepBreak::Halt) => self.handle_interrupt(RealIntVect::Halt as u16, None),
1295 Err(StepBreak::Err(SimErr::PrivilegeViolation)) => self.handle_interrupt(RealIntVect::PrivilegeViolation as u16, None),
1296 Err(StepBreak::Err(SimErr::IllegalOpcode)) => self.handle_interrupt(RealIntVect::IllegalOpcode as u16, None),
1297 Err(StepBreak::Err(SimErr::InvalidInstrFormat)) => self.handle_interrupt(RealIntVect::IllegalOpcode as u16, None),
1298 Err(StepBreak::Err(SimErr::AccessViolation)) => self.handle_interrupt(RealIntVect::AccessViolation as u16, None),
1299 s => s
1300 }
1301 }
1302 /// Simulate one step, executing one instruction.
1303 pub fn step_in(&mut self) -> Result<(), SimErr> {
1304 self.observer.clear();
1305 match self.step() {
1306 Ok(()) => Ok(()),
1307 Err(StepBreak::Halt) => Ok(()),
1308 Err(StepBreak::Err(e)) => Err(e)
1309 }
1310 }
1311
1312 /// Simulate one step, executing one instruction and running through entire subroutines as a single step.
1313 pub fn step_over(&mut self) -> Result<(), SimErr> {
1314 let curr_frame = self.frame_stack.len();
1315 let mut first = Some(()); // is Some if this is the first instruction executed in this call
1316
1317 // this function should do at least one step before checking its condition
1318 // condition: run until we have landed back in the same frame
1319 self.run_while(|sim| first.take().is_some() || curr_frame < sim.frame_stack.len())
1320 }
1321
1322 /// Run through the simulator's execution until the subroutine is exited.
1323 pub fn step_out(&mut self) -> Result<(), SimErr> {
1324 let curr_frame = self.frame_stack.len();
1325 let mut first = Some(()); // is Some if this is the first instruction executed in this call
1326
1327 // this function should do at least one step before checking its condition
1328 // condition: run until we've landed in a smaller frame
1329 if curr_frame != 0 {
1330 self.run_while(|sim| first.take().is_some() || curr_frame <= sim.frame_stack.len())?;
1331 }
1332
1333 Ok(())
1334 }
1335}
1336impl Default for Simulator {
1337 fn default() -> Self {
1338 Self::new(Default::default())
1339 }
1340}
1341
1342/// A wrapper over `u16` in order to faciliate the PSR.
1343///
1344/// The word is encoded as the following:
1345/// - `PSR[15..16]`: Privilege mode (0 = supervisor, 1 = user)
1346/// - `PSR[8..11]`: Interrupt priority
1347/// - `PSR[0..3]`: Condition codes
1348///
1349/// ```text
1350/// privilege
1351/// | interrupt priority
1352/// | | condition codes
1353/// | | |
1354/// V V V
1355/// 0x8002: 1000 0000 0000 0010
1356/// ~ ~~~ ~~~
1357/// ```
1358///
1359/// Each of these are exposed as the [`PSR::privileged`], [`PSR::priority`], and [`PSR::cc`] values.
1360#[allow(clippy::upper_case_acronyms)]
1361#[repr(transparent)]
1362pub struct PSR(u16);
1363
1364impl PSR {
1365 /// Creates a PSR with a default value (user mode, `z` condition code).
1366 pub fn new() -> Self {
1367 PSR(0x8002)
1368 }
1369
1370 /// Checks whether the simulator is in privileged mode.
1371 /// - `true` = supervisor mode
1372 /// - `false` = user mode
1373 pub fn privileged(&self) -> bool {
1374 (self.0 >> 15) == 0
1375 }
1376 /// Checks the current interrupt priority of the simulator.
1377 pub fn priority(&self) -> u8 {
1378 ((self.0 >> 8) & 0b111) as u8
1379 }
1380 /// Checks the condition code of the simulator.
1381 pub fn cc(&self) -> u8 {
1382 (self.0 & 0b111) as u8
1383 }
1384 /// Checks the condition code of the simulator is `n`.
1385 pub fn is_n(&self) -> bool {
1386 self.cc() & 0b100 != 0
1387 }
1388 /// Checks the condition code of the simulator is `z`.
1389 pub fn is_z(&self) -> bool {
1390 self.cc() & 0b010 != 0
1391 }
1392 /// Checks the condition code of the simulator is `p`.
1393 pub fn is_p(&self) -> bool {
1394 self.cc() & 0b001 != 0
1395 }
1396
1397 /// Gets the bit-representation of the PSR.
1398 pub fn get(&self) -> u16 {
1399 self.0
1400 }
1401 /// Sets the PSR to the provided data value.
1402 pub fn set(&mut self, data: u16) {
1403 const MASK: u16 = 0b1000_0111_0000_0111;
1404
1405 self.0 = data & MASK;
1406 self.set_cc((data & 0b111) as u8);
1407 }
1408 /// Sets whether the simulator is in privileged mode.
1409 pub fn set_privileged(&mut self, privl: bool) {
1410 self.0 &= 0x7FFF;
1411 self.0 |= u16::from(!privl) << 15;
1412 }
1413 /// Sets the current interrupt priority of the simulator.
1414 pub fn set_priority(&mut self, prio: u8) {
1415 self.0 &= 0xF8FF;
1416 self.0 |= u16::from(prio & 0b111) << 8;
1417 }
1418 /// Sets the condition code of the simulator.
1419 pub fn set_cc(&mut self, mut cc: u8) {
1420 self.0 &= 0xFFF8;
1421
1422 // Guard from invalid CC.
1423 cc &= 0b111;
1424 if cc.count_ones() != 1 { cc = 0b010 };
1425 self.0 |= u16::from(cc);
1426 }
1427 /// Sets the condition code of the simulator to `n`.
1428 pub fn set_cc_n(&mut self) {
1429 self.set_cc(0b100)
1430 }
1431 /// Sets the condition code of the simulator to `z`.
1432 pub fn set_cc_z(&mut self) {
1433 self.set_cc(0b010)
1434 }
1435 /// Sets the condition code of the simulator to `p`.
1436 pub fn set_cc_p(&mut self) {
1437 self.set_cc(0b001)
1438 }
1439}
1440impl Default for PSR {
1441 fn default() -> Self {
1442 Self::new()
1443 }
1444}
1445impl std::fmt::Debug for PSR {
1446 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1447 use std::fmt::Write;
1448 struct CC(u8);
1449
1450 impl std::fmt::Debug for CC {
1451 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
1452 if self.0 & 0b100 != 0 { f.write_char('N')?; };
1453 if self.0 & 0b010 != 0 { f.write_char('Z')?; };
1454 if self.0 & 0b001 != 0 { f.write_char('P')?; };
1455 Ok(())
1456 }
1457 }
1458
1459 f.debug_struct("PSR")
1460 .field("privileged", &self.privileged())
1461 .field("priority", &self.priority())
1462 .field("cc", &CC(self.cc()))
1463 .finish()
1464 }
1465}
1466
1467/// A type alias for MCR.
1468pub type MCR = Arc<AtomicBool>;