This library provides a collections of types and methods for emulating the Machester Baby the first program stored computer.
The Manchester "Baby" was the first computer to store both its program code and data in a common random-accessible memory, it is for this reason the Baby is considdered the first machine to run true software, providing a familiar (abeit, primitive) programming environment to anyone familiar with modern assembly programming, this library can be included in a variety of softwareand platforms allowing emulation functionality of this historic machine.
Command line:
cargo add baby-emulator
Cargo.toml:
baby-emulator = "0.1.2"
The core of this library is core::BabyModel
, this struct has
fields representing all of the Baby's internal registers and
32 word memory, you can initialise this struct with an array of
[i32; 32]
, this array can contain the program code instructions
starting at position 0.
```rust use babyemulator::core::BabyModel; use babyemulator::errors::{BabyError, BabyErrors};
fn main() { let model = BabyModel::newexampleprogram(); let mut lastmodel = BabyModel::new(); let mut result = model.execute(); while let Ok(newmodel) = result { lastmodel = newmodel.clone(); result = newmodel.execute(); } match result { Err(BabyErrors::Stop()) => println!("{}", last_model.accumulator), _ => println!("Something went wrong. ") } } ```
The Baby accepts a 16 bit instructions, of which the upper 3
bits denotes one of 7 instructions, each instruction has a helper
in core::instructions::BabyInstructions
enum:
| Binary | Instruction Enum | Description | |----------|--------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------| | 000 | Jump | Jump to the instruction at the address obtained from the specified memory address S[a] (absolute unconditional jump) | | 100 | RelativeJump | Jump to the instruction at the program counter plus (+) the relative value obtained from the specified memory address S[a] (relative unconditional jump) | | 010 | Negate | Take the number from the specified memory address S, negate it, and load it into the accumulator | | 110 | Store | Store the number in the accumulator to the specified memory address S | | 001\|101 | Subtract | Subtract the number at the specified memory address S from the value in accumulator, and store the result in the accumulator | | 011 | SkipNextIfNegative | Skip next instruction if the accumulator contains a negative value | | 111 | Stop | Stop |
The remaining lower bits is the the operand, the operand is
a memory address for all but the SkipIfNegative
and Stop
instructions which do not use operands.
You can generate a program by making a vec of tuples containing
a BabyInstructions
enum and u16
operand, and then passing this off
to core::instructions::BabyInstructions::to_numbers
function,
this will return a [i32; 32]
array which can be used to initialise
a BabyModel
ready for execution:
```rust use babyemulator::core::{BabyModel, instructions::BabyInstruction}; use babyemulator::errors::{BabyError, BabyErrors};
let instrs = vec![ (BabyInstruction::Negate, 5), (BabyInstruction::Subtract, 5), (BabyInstruction::Store, 6), (BabyInstruction::Negate, 6), (BabyInstruction::Stop, 0) ]; let mut mainstore = BabyInstruction::tonumbers(instrs); main_store[5] = 5; // Initialise with data.
let model = BabyModel::newwithprogram(main_store); ```
Once your model has been initalised, the one method you will need to
use is BabyModel::execute
, this method will look at the current
instruction, and will perform it, in the process modifying all the
values held within the model.
If no issue is found then BabyModel::execute
will return an Ok
with a new model containing all the updated values in the registers
and memories, and with the BabyModel
.instruction
set to the next
instruction ready to be executed and BodyModel
.instruction_address
set to the memory address of that instruction.
If an issue is found then BabyModel::execute
will return an Err
containing an instance of errors::BabyErrors
enum with which
error it is, and containing an inner derivative of errors::BabyError
that holds the metadata on that error.
An error can simply be that a Stop
command has been hit, and
there is nothing else more to execute, so the calling code should
handle that.
To carry on from the above example: ```rust // We will store the state of the model when the last instruction is executed for debug purposes let mut last_model = BabyModel::new();
// We now just keep calling model.execute() until it's not Ok
let mut result = model.execute();
while let Ok(newmodel) = result {
lastmodel = newmodel.clone();
result = newmodel.execute();
}
// Print out the result match result { // If the program ran sucessfully, it would have encountered a stop Err(BabyErrors::Stop()) => println!("{}", lastmodel.accumulator), _ => println!("Something went wrong. ") } ```