Expand description
Simulating and execution for LC-3 assembly.
This module is focused on executing fully assembled code (i.e., ObjectFile
).
This module consists of:
Simulator
: The struct that simulates assembled code.mem
: The module handling memory relating to the registers.device
: The module handling simulator IO, interrupts, and general handling of external devices.debug
: The module handling types of breakpoints for the simulator.frame
: The module handling the frame stack and call frame management.
§Usage
To simulate some code, you need to instantiate a Simulator and load an object file to it:
use lc3_ensemble::sim::Simulator;
let mut simulator = Simulator::new(Default::default());
simulator.load_obj_file(&obj_file);
simulator.run().unwrap();
§Flags
Here, we define simulator
to have the default flags.
We could also configure the simulator by editing the flags. For example,
if we wish to enable real traps, we can edit the flags like so:
let mut simulator = Simulator::new(SimFlags { use_real_traps: true, ..Default::default() });
All of the available flags can be found in SimFlags
.
§Execution
Beyond the basic Simulator::run
(which runs until halting),
there are also:
Simulator::step_in
,Simulator::step_out
,Simulator::step_over
: manual step-by-step simulationSimulator::run_while
,Simulator::run_with_limit
: more advanced programmatic execution
use lc3_ensemble::parse::parse_ast;
use lc3_ensemble::asm::assemble;
use lc3_ensemble::sim::Simulator;
use lc3_ensemble::ast::Reg::R0;
let src = "
.orig x3000
AND R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #1
ADD R0, R0, #1
HALT
.end
";
let ast = parse_ast(src).unwrap();
let obj_file = assemble(ast).unwrap();
let mut sim = Simulator::new(Default::default());
sim.load_obj_file(&obj_file);
// Running step by step:
sim.step_in().unwrap();
assert_eq!(sim.reg_file[R0].get(), 0);
sim.step_in().unwrap();
assert_eq!(sim.reg_file[R0].get(), 1);
sim.step_in().unwrap();
assert_eq!(sim.reg_file[R0].get(), 2);
sim.step_in().unwrap();
assert_eq!(sim.reg_file[R0].get(), 3);
§Querying State
You can query (or set) a variety of different state values from the simulator.
- If you wish to access the PC, it can simply be done through the
sim.pc
field. - If you wish to access the PSR or MCR, the
Simulator::psr
andSimulator::mcr
methods are present to query those values. - If you wish to access the register file, you can access it through the
sim.reg_file
field.
RegFile
holds its values in a Word
, which is a memory cell which keeps track of initialization state.
Accessing can simply be done with Word::get
and Word::set
:
use lc3_ensemble::sim::Simulator;
use lc3_ensemble::ast::Reg::R0;
let mut sim = Simulator::new(Default::default());
sim.reg_file[R0].set(0x1234);
assert_eq!(sim.reg_file[R0].get(), 0x1234);
- If you wish to access the memory, the simulator provides two pairs of memory access:
- Direct access to the memory array (via the
mem
) field, which does not trigger access violations or IO. Simulator::read_mem
andSimulator::write_mem
, which are used for accesses which do trigger access violations and IO.
- Direct access to the memory array (via the
use lc3_ensemble::sim::Simulator;
let mut sim = Simulator::new(Default::default());
// Raw memory access:
sim.mem[0x3000].set(0x5678);
assert_eq!(sim.mem[0x3000].get(), 0x5678);
// Through read/write:
use lc3_ensemble::sim::mem::Word;
assert!(sim.write_mem(0x0000, Word::new_init(0x9ABC), sim.default_mem_ctx()).is_err());
assert!(sim.write_mem(0x3000, Word::new_init(0x9ABC), sim.default_mem_ctx()).is_ok());
assert!(sim.read_mem(0x0000, sim.default_mem_ctx()).is_err());
assert!(sim.read_mem(0x3000, sim.default_mem_ctx()).is_ok());
- Other state can be accessed. Consult the
Simulator
docs for more information.
§Frames
The simulator also keeps track of subroutine frame information, accessible on the frame_stack
field of Simulator
.
If debug_frames
is not enabled in SimFlags
, the only information the Simulator
keeps track of
is the number of subroutine frames deep the simulator is (via FrameStack::len
):
- During a JSR instruction, the frame count increases by 1.
- During a RET instruction, the frame count decreases by 1.
If debug_frames
is enabled in SimFlags
, the frame information is significantly extended.
The simulator then keeps track of several frame values (such as caller and callee address).
These are accessible via the FrameStack::frames
method.
Debug frame information by default includes caller and callee addresses, but can be
configured to also include frame pointer and argument information. See the frame
module for details.
§Debugging with breakpoints
Breakpoints are accessible through the breakpoints
field on Simulator
.
To add a breakpoint
, simply insert a Breakpoint
and
it will break if its condition is met during all execution functions (except Simulator::step_in
).
use lc3_ensemble::parse::parse_ast;
use lc3_ensemble::asm::assemble;
use lc3_ensemble::sim::Simulator;
use lc3_ensemble::sim::debug::Breakpoint;
let src = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #1
ADD R0, R0, #2
ADD R0, R0, #3
HALT
.end
";
let ast = parse_ast(src).unwrap();
let obj_file = assemble(ast).unwrap();
let mut sim = Simulator::new(Default::default());
sim.load_obj_file(&obj_file);
// Without breakpoint
sim.run().unwrap();
assert_eq!(sim.pc, 0x3004);
// With breakpoint
sim.reset();
sim.load_obj_file(&obj_file);
sim.breakpoints.insert(Breakpoint::PC(0x3002));
sim.run().unwrap();
assert_eq!(sim.pc, 0x3002);
§IO, interrupts, and external devices
IO and interrupts are handled by “external devices” (the trait ExternalDevice
).
These can be added by registering the device in the Simulator’s device handler
(DeviceHandler::add_device
of the device_handler
field).
When a load or store to a memory-mapped address (0xFE00-0xFFFF) occurs, the device handler sends the corresponding load/store to the device for it to handle.
The best IO for programmatic uses is device::BufferedKeyboard
and device::BufferedDisplay
,
which exposes the IO to memory buffers that can be modified.
use lc3_ensemble::parse::parse_ast;
use lc3_ensemble::asm::assemble;
use lc3_ensemble::sim::Simulator;
use lc3_ensemble::sim::device::{BufferedKeyboard, BufferedDisplay};
use std::sync::Arc;
let src = "
.orig x3000
LOOP:
GETC
PUTC
ADD R0, R0, #0
BRnp LOOP
HALT
.end
";
let ast = parse_ast(src).unwrap();
let obj_file = assemble(ast).unwrap();
let mut sim = Simulator::new(Default::default());
sim.load_obj_file(&obj_file);
let input = BufferedKeyboard::default();
let output = BufferedDisplay::default();
sim.device_handler.set_keyboard(input.clone());
sim.device_handler.set_display(output.clone());
input.get_buffer().write().unwrap().extend(b"Hello, World!\0");
sim.run().unwrap();
assert_eq!(&*input.get_buffer().read().unwrap(), b"");
assert_eq!(&**output.get_buffer().read().unwrap(), b"Hello, World!\0");
These external devices also support interrupt-based IO.
If the device::BufferedKeyboard
device is enabled, interrupts can be enabled by setting KBSR[14]
.
Once enabled, the keyboard can interrupt the simulator and run its interrupt service routine.
§Strictness (experimental)
This simulator also features uninitialized memory access checks (via the strict
flag).
These strict memory checks verify that unintialized data is not written to the register files, memory, or other areas that do not expect uninitialized data. Uninitialized data here is defined as data that is unknown as it was never fully set and is dependent on the values the machine was initialized with.
The strict
flag can currently detect:
- Loads of uninitialized data into a register (excluding uninitialized reads from
mem[R6 + offset]
). - Stores of uninitialized data into memory (excluding uninitialized stores to
mem[R6 + offset]
and.blkw
’d memory). - Stores of uninitialized data into memory-mapped IO
- Loads and stores through an uninitialized memory address
- Jumping to an uninitialized address (e.g., via
JSRR
orJMP
) - Jumping to a memory location that is uninitialized
- Decoding an instruction from uninitialized data
- Setting the PSR to an uninitialized value
Note that this is considered experimental as false positives can still occur. Also note that the exceptions for loads and stores of uninitialized data are present to prevent typical value manipulation on the stack or in stored memory from triggering a strictness error.
use lc3_ensemble::parse::parse_ast;
use lc3_ensemble::asm::assemble;
use lc3_ensemble::sim::{Simulator, SimErr};
let src = "
.orig x3000
ADD R0, R0, #0
ADD R0, R0, #15 ;; R0 = 15
HALT
.end
";
let ast = parse_ast(src).unwrap();
let obj_file = assemble(ast).unwrap();
let mut sim = Simulator::new(Default::default());
sim.load_obj_file(&obj_file);
sim.flags.strict = true;
// Strictness check detects `R0` was set without first being cleared.
assert!(matches!(sim.run(), Err(SimErr::StrictRegSetUninit)));
assert_eq!(sim.prefetch_pc(), 0x3000);
Modules§
- debug
- Utilities to debug simulation.
- device
- Handlers for external devices connected to the Simulator.
- frame
- The frame stack and call frame management.
- mem
- Memory handling for the LC-3 simulator.
- observer
- Module handles memory access observers, which store which accesses occur at a given memory location.
Structs§
- MemAccess
Ctx - Context behind a memory access.
- PSR
- A wrapper over
u16
in order to faciliate the PSR. - SimFlags
- Configuration flags for
Simulator
. - Simulator
- Executes assembled code.
Enums§
- Internal
Register - A register which stores internal state in
Simulator
. - MMap
Internal Err - Error in
Simulator::mmap_internal
. - SimErr
- Errors that can occur during simulation.
Type Aliases§
- MCR
- A type alias for MCR.