From 87fffde9c9ab0b21a734a4f1801ecd0ff205531c Mon Sep 17 00:00:00 2001 From: transistor Date: Sat, 27 Nov 2021 09:39:38 -0800 Subject: [PATCH] Added post --- docs/posts/2021-11-making-an-emulator.md | 900 ++++++++++++++++++ docs/posts/images/2021-11-moa-computie-os.png | Bin 0 -> 36680 bytes 2 files changed, 900 insertions(+) create mode 100644 docs/posts/2021-11-making-an-emulator.md create mode 100644 docs/posts/images/2021-11-moa-computie-os.png diff --git a/docs/posts/2021-11-making-an-emulator.md b/docs/posts/2021-11-making-an-emulator.md new file mode 100644 index 0000000..d813352 --- /dev/null +++ b/docs/posts/2021-11-making-an-emulator.md @@ -0,0 +1,900 @@ + +Making a 68000 Emulator in Rust +=============================== + +###### *Written November 2021* + + +A few months ago, I was looking for a project to do while recovering from a bout of illness. I +needed something that was straight-forward enough to work on without getting stuck on tough +decisions or the need to learn a lot before diving in, which was the case with all the other +projects on my plate at the time. My girlfriend suggested writing an +[emulator](https://jabberwocky.ca/projects/moa/), and my first thought was to try emulating the +computer I made last year, [computie](https://jabberwocky.ca/projects/Computie/), since I already +had a fair amount of code for it that I knew well, and the 68000 architecture and assembly language +was still fresh in my mind. Naturally I chose to write it in Rust. + +I've worked on different projects that have some vague similarities to emulators, such as +programming language interpreters and artificial life simulations but I haven't actually tried to +make an emulator before. I'm not aiming to make a fast emulator, and since this is meant to be fun, +I'm not *as* concerned about accuracy (specifically instruction execution time accuracy). I would, +however, like something that's flexible enough to experiment with different hardware designs. +Perhaps I could use this with some of my future hardware designs to test out hardware configurations +and to develop and test the software that runs on them. It would also be nice to emulate some +vintage computers that use the 68000, which each have their own I/O devices that would need +simulating. With that in mind, we should work towards making independent components for each +device, which interact in regular ways and can be combined in different configurations without the +need to modify the components themselves (ie. no tight coupling of components) + +I chose Rust because it's currently my favorite systems language. I've used C and C++ quite a bit +as well, but Rust's advanced type system is much more pleasant to work with, and the compile-time +checks means I can focus less on simple bugs and more on the problem at hand, getting more done in +the same amount of time. I'm assuming here some familiarity with Rust, as well as the basic +principles of how a computer works, but not necessarily that much about the 68000 or emulators. We +will start with some code to simulate the computer memory, creating an abstract way of accessing +data. We'll then implement the NOP (no operation) instruction, the simplest possible instruction, +for the 68000, and expand the implementation from there. Once we've created a way of handling the +passage of time for the CPUs and I/O devices, we'll implement a simple serial port controller which +will act as our primary I/O device. From there we'll make all of our simulated devices, represented +as `struct` objects in Rust, look the same so that we can treat them the same, regardless of what +they represent internally. We'll then be able to package them up into single working system and set +them in motion to run the Computie software from binaries images. + +* [The Computer](#the-computer) +* [The 68000](#the-68000) +* [The 68681 Serial Port Controller](#the-68681-serial-port-controller) +* [The Memory](#the-memory) +* [Simulating The CPU](#simulating-the-cpu) +* [Adding Instructions](#adding-instructions) +* [Abstracting Time](#abstracting-time) +* [Some I/O](#some-i-o) +* [Box It Up](#box-it-up) +* [An Addressable of Addressables (The Data Bus)](#an-addressable-of-addressables--the-data-bus-) +* [A Happy Little System](#a-happy-little-system) +* [Tying It All Together](#tying-it-all-together) +* [Now What](#now-what) + + +The Computer +------------ + +Computie is a single board computer with a Motorola 68010 CPU connected to 1MB of RAM, some flash, +and an MC68681 dual serial port controller, which handles most of the I/O. (The +[68010](https://en.wikipedia.org/wiki/Motorola_68010) is almost identical to the 68000 but with some +minor fixes which won't affect us here. I'll mostly be referring to the common aspects of both +processors). One of the serial connections is used as a TTY to interact with either the unix-like +Computie OS, or the monitor software that's normally stored in the flash chip. It also supports a +CompactFlash card and SLIP connection over the other serial port for internet access, but we wont +cover those here. In order to get a working emulator, we'll focus on just the CPU, memory, and +MC68681 controller. + + +The 68000 +--------- + +The 68000 is 32-bit processor with a 16-bit data bus and 16-bit arithmetic logic unit. It was used +on many early computers including the early Macintosh series, the early Sun Microsystems +workstations, the Amiga, the Atari ST, and the Sega Genesis/Mega Drive, just to name a few. It was +almost chosen for the IBM PC as well, but IBM wanted to use an inexpensive 8-bit data bus in the PC, +and the 68008 (with an 8-bit data bus) wasn't available at the time. The 8088, with it's 8-bit bus, +was available however, and we have been stuck with that decision ever since. + +The 68000 has 8 32-bit general purpose data registers, and 7 32-bit general purpose address +registers plus two stack registers which can be accessed as the 8th address register depending on +whether the CPU is in Supervisor mode or User mode. Internally the address registers use a separate +bus and adder unit from the main arithmetic logic unit, which only operates on the data registers. +This affects which instructions can be used with which registers, and operations on address +registers don't always affect the condition flags (which has caused me many troubles both when +writing the Computie software, and with the implementation of instructions here). + +A 16-bit status register is used for condition codes and for privileged flags like the Supervisor +mode enable flag and the interrupt priority level. Only the lower 8 bits can be modified from User +mode. The conditions flags are set by many of the instructions based on their results, and can be +checked by the conditional jump instructions to branch based on the results of comparisons. + +The program counter register keeps track of the next instruction to be executed. Instructions are +always a multiple of 2-byte words, and the CPU uses [big +endian](https://en.wikipedia.org/wiki/Endianness) byte order, so the most significant byte will +always be in the lower byte address in memory. As an example, the NOP instruction uses the opcode +0x4E71, where 0x4E would be the first byte in memory followed by 0x71. A longer instruction like +ADDI can have instruction words after the opcode (in this case the immediate data to add). For +example, `addil #0x12345678, %d0` which adds the hex number 0x12345678 to data register 0 would +be encoded as the sequence of words [0x0680, 0x1234, 0x5678]. The opcode word, 0x0680, has encoded +in it that it's an ADDI instruction, that the size of the operation is a long word (32-bit), and +that it should use data register 0 as both the number to add to, and the destination where the +result will be stored. (Note: 68000 assembly language instructions move data from the left hand +operand to the right hand operand, unlike Intel x86 assembly language which uses the reverse). + +A vector table is expected at address 0 which contains an array of up to 256 32-bit addresses. The +first address contains the value to be loaded into the Supervisor stack register upon reset, and the +remaining addresses are the value loaded into the program counter when a given exception occurs. +This table cannot be relocated on the 68000, but it can be changed after reset on the 68010. +Computie uses the 68010 for this exact feature, so that the OS can put the vector table in RAM, but +the monitor software doesn't use interrupts. We wont cover interrupts here so this feature isn't +needed for now and we can focus only on emulating the 68000. As for the vector table, we just need +to simulate how the processor starts up on power on or after a reset, in which case the vector table +is always at address 0, and the first two long words are the stack pointer and initial program +counter respectively. + + +The 68681 Serial Port Controller +-------------------------------- + +The MC68681 is a peripheral controller designed specifically for use with the 68000 series. It has +two serial channels, A and B, as well as an onboard 16-bit timer/counter, 8 general purpose output +pins, and 6 general purpose input pins. Internally it has 16 registers which can be read or written +to from the CPU. The behaviour of the 16 registers is sometimes different between reading and +writing, such as the status registers (SRA & SRB) which indicate the current status of the serial +ports when read, and the clock select registers (CSRA & CSRB) which configure the serial port clocks +when writing to the same address as the status registers. It can also generate an interrupt for +one of 8 different internal conditions, such as data ready to read on a given channel, or the timer +reaching zero. It uses it's own clock signal to generate the serial clocks and to count down with, +so it will need to run some code along side the CPU to simulate its internal state. + + +The Memory +---------- + +Now that we have a bit of background on the devices we'll be emulating, lets start making the +emulator. The first thing we'll need in order to emulate our computer is a way of accessing and +addressing memory where instruction data can be read from. We need to eventually have a common way +of reading and writing to either simulated ram, or simulated I/O devices. Since we want to keep +things generic and interchangeable, using Rust enums for the different devices in the system would +be too tightly-coupled. Traits it is, so we'll start with an `Addressable` trait. + +```rust +type Address = u64; // I (wishfully) chose u64 here in case + // I ever emulate a 64-bit system + +pub trait Addressable { + fn len(&self) -> usize; + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error>; + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error>; + ... +} +``` + +I went through a few different iterations of this, especially for the `.read()` method. At first I +returned an iterator over the data starting at the address given, which works well for simulated +memory, but reading from an I/O device could return data that is unique to when it was read. For +example, when reading the next byte of data from a serial port, the data will be removed from the +device's internal FIFO and returned, and since at that point it wont be stored anywhere, we can't +have a reference to it. We need the data (of variable length) to be owned by the caller when the +method returns, and passing in a reference to a mutable array to hold that data is a simple way to +do that. + +We can also add some default methods to the trait which will make it easier to access multi-byte +values. The example here only shows methods to read and write 16 bit numbers in big endian byte +order but there are also similar methods for 32-bit numbers and for little endian numbers. + +```rust +pub trait Addressable { + ... + fn read_beu16(&mut self, addr: Address) -> Result { + let mut data = [0; 2]; + self.read(addr, &mut data)?; + Ok(read_beu16(&data)) + } + + fn write_beu16(&mut self, addr: Address, value: u16) -> Result<(), Error> { + let mut data = [0; 2]; + write_beu16(&mut data, value); + self.write(addr, &data) + } + ... +} + +#[inline(always)] +pub fn read_beu16(data: &[u8]) -> u16 { + (data[0] as u16) << 8 | + (data[1] as u16) +} + +#[inline(always)] +pub fn write_beu16(data: &mut [u8], value: u16) -> &mut [u8] { + data[0] = (value >> 8) as u8; + data[1] = value as u8; + data +} +``` + +Now for some simulated ram that implements our trait. (I'm leaving out the `.new()` methods for +most of the code snippets because they are pretty straight-forward, but you can assume they exist, +and take the their field values as arguments, or set their fields to 0). + +```rust +pub struct MemoryBlock { + pub content: Vec, +} + +impl Addressable for MemoryBlock { + fn len(&self) -> usize { + self.contents.len() + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + for i in 0..data.len() { + data[i] = self.contents[(addr as usize) + i]; + } + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + for i in 0..data.len() { + self.contents[(addr as usize) + i] = data[i]; + } + Ok(()) + } +} +``` + + +Simulating The CPU +------------------ + +With just the Addressable trait, we can start simulating the CPU. Each cycle of the CPU involves +reading in instruction data, decoding it, executing the instruction, modifying the stored state of +the CPU, and finally checking for interrupts or breakpoints before looping again. We don't need all +of this to start though, so first lets make some CPU state so that we at least have a PC (program +counter) to keep track of the instruction data we've read. + +```rust +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum M68kStatus { + Init, + Running, + Stopped, +} + +#[derive(Clone, Debug, PartialEq)] +pub struct M68kState { + pub status: M68kStatus, + + pub pc: u32, // Program Counter + pub sr: u16, // Status Register + pub d_reg: [u32; 8], // Data Registers + pub a_reg: [u32; 7], // Address Registers + pub ssp: u32, // Supervisor Stack Pointer + pub usp: u32, // User Stack Pointer +} + +pub struct M68k { + pub state: M68kState, + ... +} +``` +(I've separated the state into its own struct, separate from the `M68k` struct, in part to make it +cleaner, but mostly to make it easier to test. We can get a complete known state using just +M68kState::new(), and we can clone, modify, and compare states because we've derived `PartialEq`, so +after running a test on a given instruction, we only need one `assert_eq!(cpu.state, +expected_state)` to check if the resulting state is what we expected). + +Next we need to initialize the CPU. When the 68000 is reset, it first reads in the stack pointer +and initial value of the PC register from the beginning of memory where the vector table is located. + +```rust +impl M68k { + ... + pub fn init(&mut self, memory: &mut dyn Addressable) -> Result<(), Error> { + self.state.ssp = memory.read_beu32(0)?; + self.state.pc = memory.read_beu32(4)?; + self.state.status = M68kStatus::Running; + Ok(()) + } + ... +} +``` + +Since the decoding of instructions can be a bit convoluted, and since speed is not our utmost goal, it's +cleaner to decode the instructions fully into some kind of internal representation, and then execute +them based on that representation. For now, lets just add a NOP instruction. + +```rust +#[derive(Clone, Debug)] +pub enum Instruction { + NOP, +} + +impl M68k { + pub fn read_instruction_word(&mut self, memory: &mut dyn Addressable) -> Result { + let ins = memory.read_beu16(self.state.pc as Address)?; + self.state.pc += 2; + Ok(ins) + } + + pub fn decode(&mut self, memory: &mut dyn Addressable) -> Result { + let ins = self.read_instruction_word(memory)?; + + match ins { + 0x4E71 => Ok(Instruction::NOP), + _ => panic!("instruction not yet supported: {:#04X}", ins), + } + } +} +``` + +We can then make a function to execute the decoded instruction + +```rust +impl M68k { + pub fn execute(&mut self, memory: &mut dyn Addressable, instruction: Instruction) -> Result<(), Error> { + match instruction { + Instruction::NOP => { + // Do Nothing + Ok(()) + }, + _ => panic!("Instruction not implemented: {:?}", instruction), + } + } +} +``` + +And finally a function to wrap these stages together into a single step. For debugging purposes +we'll print out what instruction we're about to execute after decoding but before executing. + +```rust +impl M68k { + pub fn step(&mut self, memory: &mut dyn Addressable) -> Result<(), Error> { + match self.state.status { + M68kStatus::Init => self.init(memory), + M68kStatus::Stopped => Err(Error::new("cpu has stopped")), + M68kStatus::Running => { + let addr = self.state.pc; + let instruction = self.decode(memory)?; + println!("{:08x}: {:?}", addr, instruction); + self.execute(memory, instruction)?; + Ok(()) + }, + } + } +} +``` + +At this point we have enough pieces to loop over a series of NOP instructions. Our main function +looks like the following +```rust +const ROM: &[u16] = &[ + 0x0010, 0x0000, // Initial stack address is at 0x00100000 + 0x0000, 0x0008, // Initial PC address is at 0x8, which is the word + // that follows this + + 0x4e71, // 4 NOP instructions + 0x4e71, + 0x4e71, + 0x4e71, + + 0x4e72, 0x2700 // The STOP #0x2700 instruction, which would normally + // stop the CPU but it's unsupported, so it will + // cause a panic!() +]; + +let mut cpu = M68k::new(); +let mut memory = MemoryBlock::from_u16(ROM); +loop { + cpu.step(&mut memory).unwrap(); +} +``` + +Our output should look something like this: +``` +00000008: NOP +0000000a: NOP +0000000c: NOP +0000000e: NOP +thread 'main' panicked at 'instruction not yet supported: 0x4E72', src/main.rs:184:18 +``` + + +Adding Instructions +------------------- + +Since the 68000 has a reasonably orthogonal instruction set, we can break down the opcode word into +sub-components, and build up instructions by separately interpreting those sub-components, rather +than having a match arm for each of the 65536 combinations. There is a really helpful [chart by +GoldenCrystal](http://goldencrystal.free.fr/M68kOpcodes-v2.3.pdf) which shows the full breakdown of +opcodes for the 68000. We can look at the first 4 bits of the instruction word to separate it into +16 broad categories of instruction, and then further break it down from there. The full code can be +seen [here](https://github.com/transistorfet/moa/blob/main/src/cpus/m68k/decode.rs) + +We can extend our `Instruction` type to contain more instructions, including the addressing modes +that the 68000 supports. A `MOVE` instruction for example can move data to or from a data or +address register, or to memory indirectly using an address in an address register (optionally +pre-decrementing or post-incrementing the address), or indirectly using an offset added to an +address register, as well as a few others. Since these different addressing modes are available for +most instructions, we can wrap them up into a `Target` type, and use that as the arguments of +instructions in the `Instruction` type. For example, the instruction `addil #0x12345678, %d0` would +be represented as `Instruction::ADD(Target::Immediate(0x12345678), Target::DirectDReg(0), +Size::Long)`. We will maintain the same operand order as the 68k assembly language so data will +move from the left hand operand (`0x12345678`) to the right hand operand (`%d0`). + +```rust +pub type Register = u8; + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Size { + Byte, + Word, + Long, +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Condition { + NotEqual, + Equal, + ... +} + +#[derive(Copy, Clone, Debug, PartialEq)] +pub enum Target { + Immediate(u32), // A u32 literal + DirectDReg(Register), // Contents of a data register + DirectAReg(Register), // Contents of an address register + IndirectAReg(Register), // Contents of a memory location given by an address register + IndirectARegInc(Register), // Same as IndirectAReg but increment the address register + // by the size of the operation *after* reading memory + IndirectARegDec(Register), // Same as IndirectAReg but decrement the address register + // by the size of the operation *before* reading memory + IndirectARegOffset(Register, i32), // Contents of memory given by an address register plus the + // signed offset (address register will *not* be modified) + IndirectPCOffset(i32), // Same as IndirectARegOffset but using the PC register + IndirectMemory(u32), // Contents of memory location given by literal u32 value +} + +#[derive(Clone, Debug, PartialEq)] +pub enum Instruction { + ADD(Target, Target, Size), // Addition + ADDA(Target, Register, Size), // Adding to an address register + // doesn't affect flags + + Bcc(Condition, i32), // Branch Conditionally + BRA(i32), // Branch to PC + offset + BSR(i32), // Branch to Subroutine + // (Push PC; PC = PC + offset) + + JMP(Target), // Set the PC to the given value + JSR(Target), // Push PC to the stack and then + // set PC to the given value + + LEA(Target, Register), // Load effective address into + // address register + + MOVE(Target, Target, Size), + MOVEA(Target, Register, Size), + NOP, + + RTS, // Return from subroutine (Pop PC) + STOP(u16), // Load word into SR register and + // stop until an interrupt + + SUB(Target, Target, Size), // Subtraction + SUBA(Target, Register, Size), + ... +} +``` +This is just an example of the instruction type definitions. The full code can be found +[here](https://github.com/transistorfet/moa/blob/main/src/cpus/m68k/instructions.rs). Note: it's +possible to express more combinations with these instruction types than there are legal instruction +combinations. Some combinations are illegal on the 68000 but allowed on the 68020 and up, which the +emulator supports using an enum to represent the different 68000 model numbers. Most of the error +checking and version difference checking is done during the decode phase (and an illegal instruction +exception raised if necessary). Some instructions have special behaviours, so they've been given +their own variant in the enum, like ADDA to add a value to an address register which doesn't affect +the condition codes the way the ADD instruction does, or CMPA to compare an address register which +sign extends byte or word operands to long words before comparing them. + + +Abstracting Time +---------------- + +In the case of a real CPU, the clock signal is what drives the CPU forward, but it's not the only +device in the system that is driven by a clock. I/O devices, such as the MC68681 serial port +controller also take an input clock which affects their state over time. In the case of the serial +port controller, it has a timer which needs to count down periodically. We're going to need some +way of running code for different devices based on the clock, so lets use another trait for this. + +```rust +pub type Clock = u64; +pub type ClockElapsed = u64; + +trait Steppable { + fn step(&mut self, memory: &mut dyn Addressable, clock: Clock) -> Result; +} +``` + +Now we can call the `step()` method each cycle and pass it the current clock value. It will +return the number of clock ticks that should elapse before the next call to that device's `step()` +method. + +In the case of Computie, the CPU runs at 10 MHz, but the serial port controller runs on a separate +clock at 3.6864 MHz. In order to handle these different clock speeds, we can arbitrarily decide +that our `Clock` value will be the number of nanoseconds from the start of the simulation, and +`ClockElapsed` will be a difference in nanoseconds from the start of the start of the step. We use +a `u64` here so that we can keep track of simulation time in nanoseconds for approximately 584 which +*should* be enough. Keeping track of the time will allow us to later limit how much time passes +(either speeding up or slowing down the execution relative to real-time). With this, we can get a +somewhat accurate count when simulating the timer in the serial controller chip. That said, we wont +worry about simulating CPU execution times, which varies quite a bit based on each instruction and +it's operands, so can add a lot of complexity. + + +Some I/O +-------- + +It's time to implement the MC68681 serial port controller. Since it's both an Addressable device +and a Steppable device, we'll need to implement both traits. The registers we'll need to support +first are the serial port channel A registers. `REG_TBA_WR` is the transmit write buffer, which +will output the character written to it over serial channel A. We can just print the character +written to this register to the screen for now. We also need to implement the `REG_SRA_RD` +register, which is a status register. Bits 3 and 2 of the status register indicate if the transmit +buffer for channel A is ready and empty. The software for Computie checks the status register +before writing data because the real MC68681 can't transmit fast enough to avoid a buffer overrun, +so as long as we return a value with those bits set, the software will write characters to the +`REG_TBA_WR` register. This example also shows the `REG_RBA_RD` register for reading serial data +in, but setting the `port_a_input` field is not shown for simplicity. The full code has be viewed +[here](https://github.com/transistorfet/moa/blob/main/src/peripherals/mc68681.rs) + +```rust +// Register Addresses (relative to mapped address) +const REG_SRA_RD: Address = 0x03; // Ch A Status Register +const REG_TBA_WR: Address = 0x07; // Ch A Byte to Transmit +const REG_RBA_RD: Address = 0x07; // Ch A Received Byte +const REG_ISR_RD: Address = 0x0B; // Interrupt Status Register + +// Status Register Bits (SRA/SRB) +const SR_TX_EMPTY: u8 = 0x08; +const SR_TX_READY: u8 = 0x04; +const SR_RX_FULL: u8 = 0x02; +const SR_RX_READY: u8 = 0x01; + +// Interrupt Status/Mask Bits (ISR/IVR) +const ISR_TIMER_CHANGE: u8 = 0x08; + +struct MC68681 { + pub port_a_status: u8, + pub port_a_input: u8, + + pub is_timing: bool, + pub timer: u16, + pub int_status: u8, +} + +impl Addressable for MC68681 { + fn len(&self) -> usize { + 0x10 + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + match addr { + REG_SRA_RD => { + data[0] = SR_TX_READY | SR_TX_EMPTY; + }, + REG_RBA_RD => { + data[0] = self.port_a_input; + self.port_a_status &= !SR_RX_READY; + }, + REG_ISR_RD => { + data[0] = self.int_status; + }, + _ => { }, + } + debug!("{}: read from {:0x} of {:0x}", DEV_NAME, addr, data[0]); + Ok(()) + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + debug!("{}: writing {:0x} to {:0x}", DEV_NAME, data[0], addr); + match addr { + REG_TBA_WR => { + // Print the character + println!("{}", data[0] as char); + }, + _ => { }, + } + Ok(()) + } +} + +impl Steppable for MC68681 { + fn step(&mut self, memory: &mut dyn Addressable, clock: Clock) -> Result { + if self.is_timing { + // Count down, wrapping around from 0 to 0xFFFF + self.timer = self.timer.wrapping_sub(1); + + if self.timer == 0 { + // Set the interrupt flag + self.int_status |= ISR_TIMER_CHANGE; + } + } + + // Delay for the number of nanoseconds of our 3.6864 MHz clock + Ok(1_000_000_000 / 3_686_400) + } +} +``` + +This implementation is enough to print the `Welcome to the 68k Monitor!` message that the monitor +software prints at boot, but we can't accept input yet. Since Computie is meant to be connected via +TTY, we could open a new pseudoterminal on the host computer (Linux in this case), and then connect to +that pseudoterminal using `miniterm` like we would with the real Computie. This will also free the +console window for debugging and log messages. + +I originally implemented this using non-blocking input to check for a new character received on the +machine-end of the pseudoterm, and then storing it in a single byte in the MC68681 object, but I +later changed this to use a separate thread for polling, and `mpsc` channels to communicate with the +simulation thread. It's a bit too much code to include here but you can see the full tty +implementation [here](https://github.com/transistorfet/moa/blob/main/src/host/tty.rs) + + +Box It Up +--------- + +We have 3 different devices objects at this point: the CPU (`M68k`), the memory (`MemoryBlock`), and +the serial port controller (`MC68681`). The CPU implements the `Steppable` trait, the memory +implements the `Addressable` trait, and the controller implements both. That last one is a problem +because we can't have two mutable references to both the `Addressable` and `Steppable` trait objects +of the controller at the same time, and rust doesn't yet support trait objects implementing multiple +traits under these conditions. Generics wont work either because we need to store the +`Addressable`s in some kind of list, and items in a list must have the same type, so we need some +other way of getting a reference to one of the trait objects only when we need to use it. If we +weren't so adamant about making this flexible and configurable, we could possibly keep this simpler, +but alas, we are that adamant. So lets introduce another trait, and some reference counted boxes! + +```rust +pub trait Transmutable { + fn as_steppable(&mut self) -> Option<&mut dyn Steppable> { + None + } + + fn as_addressable(&mut self) -> Option<&mut dyn Addressable> { + None + } +} +``` + +The `Transmutable` trait can be implemented for each device object, and it has a method for each of +our traits, which returns the trait object reference we need. The default implementations return +`None` to indicate that the trait is not implemented for that device object. Device objects that +*do* implement a given trait can redefine the `Transmutable` function to return `Some(self)` +instead. That means that any `Transmutable` trait object can be turned into either an `Addressable` +or a `Steppable`, if supported by the underlying device object, and we have a way of checking that +support at runtime. + +We top it all off with `TransmutableBox` which will allow us to have multiple references to any +`Transmutable` objects, so we can pass them around to build our machine configuration. +`wrap_transmutable` is a helper function to box up the device object during startup. The device +objects will be set up once at start up and then not changed until the program terminates, so we're +not too concerned about reference cycles. + +```rust +pub type TransmutableBox = Rc>>; + +pub fn wrap_transmutable(value: T) -> TransmutableBox { + Rc::new(RefCell::new(Box::new(value))) +} +``` + +Using this method does introduce some performance penalties, but they are marginal relative to the +decode and execution times of each instruction and we can still achieve pretty decent instruction +cycle speeds despite the overhead. If speed was a concern, we might try to avoid the `Transmutable` +trait by also eliminating our `Steppable` trait and using a static machine configuration where the +device object types are known at compile time and the top level function for each machine +configuration would drive the simulation forward. This latter style architecture is used by many +other emulators, such as the [ClockSignal (CLK)](https://github.com/TomHarte/CLK) emulator (which +has a very well organized and easy to read codebase if you're looking for another emulator project +to learn from). The downside is that each machine requires more machine-specific top level code to +tie the pieces together. + +If anyone knows of a better way to organize this so that we can get the best of both worlds, speed +and the ability to abstract away all the devices without tight coupling, I'd love to heard about it. +I'm always looking for new ways to design things in Rust. + + +An Addressable of Addressables (The Data Bus) +--------------------------------------------- + +With this new trait, all of our devices look the same regardless of the combinations of traits they +implement. We can store them all in the same list, as well as pass them to other objects to be +turned into their respective trait references when needed. We can now implement a `Bus` to hold our +`Addressable`s so that we can access them all through the same address space + +```rust +pub struct Block { + pub base: Address, + pub length: usize, + pub dev: TransmutableBox, +} + +pub struct Bus { + pub blocks: Vec, +} + +impl Bus { + /// Insert the devices in the correct address order + pub fn insert(&mut self, base: Address, length: usize, dev: TransmutableBox) { + let block = Block { base, length, dev }; + let i = self.blocks.iter().position(|cur| cur.base > block.base).unwrap_or(self.blocks.len()); + self.blocks.insert(i, block); + } + + /// Find the device that's mapped to the given address range, and return + /// the device, as well as the given address minus the device's base address + pub fn get_device_at(&self, addr: Address, count: usize) -> Result<(TransmutableBox, Address), Error> { + for block in &self.blocks { + if addr >= block.base && addr < (block.base + block.length as Address) { + let relative_addr = addr - block.base; + if relative_addr as usize + count <= block.length { + return Ok((block.dev.clone(), relative_addr)); + } else { + return Err(Error::new(&format!("Error reading address {:#010x}", addr))); + } + } + } + return Err(Error::new(&format!("No segment found at {:#010x}", addr))); + } +} + +impl Addressable for Bus { + fn len(&self) -> usize { + let block = &self.blocks[self.blocks.len() - 1]; + (block.base as usize) + block.length + } + + fn read(&mut self, addr: Address, data: &mut [u8]) -> Result<(), Error> { + let (dev, relative_addr) = self.get_device_at(addr, data.len())?; + let result = dev.borrow_mut().as_addressable().unwrap().read(relative_addr, data); + result + } + + fn write(&mut self, addr: Address, data: &[u8]) -> Result<(), Error> { + let (dev, relative_addr) = self.get_device_at(addr, data.len())?; + let result = dev.borrow_mut().as_addressable().unwrap().write(relative_addr, data); + result + } +} +``` + +Our `Bus` can act like an `Addressable`, so anything that's mapped to an address on the bus can be +accessed through the same `&mut Addressable` allowing us to pass it to the CPU's `.step()` method. + + +A Happy Little System +--------------------- + +The last piece we need is a way to call all of our `.step()` methods, but only the top level loop +needs to be able to call them, so we don't need to package them up the same as `Addressable`. We +do, however, need to keep track of the passage of time. We can make a `System` to hold our pieces, +which can also hold our event tracking code, and our `Bus`. + +```rust +pub struct NextStep { + pub next_clock: Clock, + pub device: TransmutableBox, +} + +struct System { + pub clock: Clock, + pub bus: Bus, + pub event_queue: Vec, +} + +impl System { + /// Insert into the queue such that the last device is the next step to execute + fn queue_device(&mut self, device_step: NextStep) { + for i in (0..self.event_queue.len()).rev() { + if self.event_queue[i].next_clock > device_step.next_clock { + self.event_queue.insert(i + 1, device_step); + return; + } + } + self.event_queue.insert(0, device_step); + } + + /// Add a device to the system, and if it's `Steppable` then queue it + pub fn add_device(&mut self, device: TransmutableBox) -> Result<(), Error> { + if device.borrow_mut().as_steppable().is_some() { + self.queue_device(NextStep::new(device)); + } + Ok(()) + } + + /// Add a device to the system while also mapping it to the given address on the bus + pub fn add_addressable_device(&mut self, addr: Address, device: TransmutableBox) -> Result<(), Error> { + let length = device.borrow_mut().as_addressable().unwrap().len(); + self.bus.insert(addr, length, device.clone()); + self.add_device(device) + } + + /// Execute one step function from the queue + pub fn step(&mut self) -> Result<(), Error> { + // Remove the last item in the queue which is the next step to execute + let mut next_device = self.event_queue.pop().unwrap(); + + self.clock = next_device.next_clock; + let memory = self.bus.as_addressable().unwrap(); + + // Execute the step function + let diff = next_device.device.borrow_mut().as_steppable().unwrap().step(memory, self.clock)?; + // Adjust the device's next scheduled step + next_device.next_clock = self.clock + diff; + + // Re-insert into the queue in order of next event + self.queue_device(next_device); + Ok(()) + } + + /// Run step functions until the system clock has changed by the given time in nanoseconds + pub fn run_for(&mut self, elapsed: ClockElapsed) -> Result<(), Error> { + let target = self.clock + elapsed; + + while self.clock < target { + self.step()?; + } + Ok(()) + } +} +``` + +In later iterations, I pass the whole immutable reference to `System` into the step functions, and +put `Bus` inside a `RefCell` which can then be accessed if needed by the `.step()` function for the +device object. I've also added an interrupt controller to `System` which can be used for hardware +interrupts from devices (`MC68681` in the case of Computie). + + +Tying It All Together +--------------------- + +Finally we can tie all our pieces together and set them in motion. + +```rust +fn main() -> Result<(), Error> { + let mut system = System::new(); + + // Create our Flash memory, pre-loaded with our Computie monitor firmware + let rom = MemoryBlock::load("binaries/computie/monitor.bin")?; + system.add_addressable_device(0x00000000, wrap_transmutable(rom))?; + + // Create a RAM segment at the 1MB address + let mut ram = MemoryBlock::new(vec![0; 0x00100000]); + system.add_addressable_device(0x00100000, wrap_transmutable(ram))?; + + let mut serial = MC68681::new(); + // Open a new PTY and launch a terminal emulator that in turn + // launches miniterm and connects to the other end of the PTY + launch_terminal_emulator(serial.port_a.connect(Box::new(SimplePty::open()?))?); + system.add_addressable_device(0x00700000, wrap_transmutable(serial))?; + + let mut cpu = M68k::new(M68kType::MC68010); + system.add_device(wrap_transmutable(cpu))?; + + // Run forever + system.run_for(u64::MAX)?; + Ok(()) +} + +pub fn launch_terminal_emulator(name: String) { + use std::thread; + use std::time::Duration; + use std::process::Command; + + Command::new("x-terminal-emulator").arg("-e").arg(&format!("pyserial-miniterm {}", name)).spawn().unwrap(); + thread::sleep(Duration::from_secs(1)); +} +``` + +We can now boot our monitor firmware and once interrupts are added, we can even boot the OS! + +![alt text](images/2021-11-moa-computie-os.png "The Computie OS booting in Moa") + + +Now What +-------- + +After only a couple weeks I was able to get all of the Computie software running, including the OS, +albeit with a buggy and incomplete 68000 implementation. That didn't seem too hard. What else can +I get running inside this emulator. How about I try emulating the Sega Genesis! How hard can it +be? Well... after a few months it's damn near broken me, but I'm determined to complete it +eventually. I've also taken detours into emulating the original Macintosh (simpler than the Genesis +so easier to debug the 68k implementation with) and the TRS-80 (in order to test the Z80 +implementation I made to eventually use in the Genesis). I'm hoping to write more about those other +efforts, once I get them somewhat working. Until then, happy emulating! + diff --git a/docs/posts/images/2021-11-moa-computie-os.png b/docs/posts/images/2021-11-moa-computie-os.png new file mode 100644 index 0000000000000000000000000000000000000000..4f184d9c15f69604b27427769b5d94b68b0727bf GIT binary patch literal 36680 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 oSAL