lemurs_8080/
lib.rs

1//! # Intel CPU Emulation
2//!
3//! This emulates early Intel 8-bit microprocessors (currently, only the 8080 is supported).
4//! It packages a fixed chip core with a "board" that can be user-defined (A very basic board
5//! is included and published in the crate, but in most cases, you will want to supply your own,
6//! for instance to emulate the non-CPU features of a historical arcade game).
7//!
8//! Typically, you will implement the `lemurs-8080::Harness` trait on a type of your choice and
9//! then create a `Machine` instance that uses a value of that type. You can use any type that
10//! dereferences to a `mut Harness` of some kind, so you can give a machine sole or shared
11//! ownership of its Harness or just attach it to a reference.
12//!
13//! You can then call the `.execute()` method on your `Machine` to execute one instruction or use
14//! your `Machine` as an iterator. By keeping access to the contents of your Harness, you can
15//! examine the contents produced by the code and use them with another resource, such as printing
16//! text to a console or copying a pixel raster to a window.
17//!
18//! By default, the package is built with the `"std"` feature under the expectation that you will
19//! use it in other Rust projects. You can also use this library in C++ projects, by using
20//! `--no-default-features` to deactivate `"std"`, and add the `"cpp"` (or `"_cpp"`) feature. (`"cpp"`
21//! includes a C++-based global allocator and panic handler from the `cruppers` crate; `"_cpp"` just
22//! turns on the C++ bridge code and requires you to supply your own memory and panic management.)
23//!
24//! The package assumes that you will just use the core opaquely, but the `"open"` feature exposes
25//! several debug features so that you can examine what is happening with the execution directly.
26
27#![no_std]
28#![feature(generic_arg_infer)]
29#![feature(error_in_core)]
30
31#[macro_use]
32extern crate disclose;
33
34#[cfg(feature="std")]
35mod foundation {
36    extern crate std;
37    pub use std::{any, array, borrow, boxed, convert, fmt, num, ops, rc, result, slice, string, sync, vec};
38}
39#[cfg(not(feature="std"))]
40mod foundation {
41    extern crate alloc;
42    pub use alloc::{boxed, rc, string, vec};
43    pub use core::{array, borrow, convert, fmt, num, result, ops, slice, any};
44}
45
46#[cfg_attr(feature="open", disclose)]
47mod prelude {
48    pub use super::{foundation::*, Harness, Machine, chip::State};
49    pub(crate) use super::{raw, bits::{u8, u16}};
50    pub use self::{boxed::Box, num::Wrapping, string::String, borrow::{Borrow, BorrowMut}, ops::{Deref, DerefMut, Index, IndexMut}};
51}
52use self::{prelude::*, rc::Rc};
53
54mod chip;
55
56/// The cpp mod contains FFI exports to create and access Machine objects in C++.
57#[cfg(feature="_cpp")]
58mod cpp;
59
60#[allow(non_camel_case_types)]
61mod raw {
62    pub type u8 = core::primitive::u8;
63    pub type u16 = core::primitive::u16;
64}
65
66#[allow(non_camel_case_types)]
67mod bits {
68    use super::*;
69    pub type u8 = crate::num::Wrapping<raw::u8>;
70    pub type u16 = crate::num::Wrapping<raw::u16>;
71}
72
73#[cfg(any(feature="open", doc))]
74pub use crate::chip::{State, access::*, opcode::{self as op, Op::{self, *}, Flag::*, Test::*}};
75
76/// The Harness trait is the core of using this package; the `Machine` struct will use a
77/// type of your choosing that the chip can use to read 8-bit or 16-bit values from 16-bit
78/// addresses and write 8-bit or 16-bit values to 16-bit addresses, as well as read and write
79/// 8-bit values on 8-bit port numbers.
80pub trait Harness {
81    /// This is the most critical element; you can't build a usable machine without being
82    /// able to supply opcodes to the core via this operation.
83    ///
84    /// This method returns the 8-bit value associated with the supplied 16-bit memory
85    /// address. It should generally be consistent with any writes made to the same address.
86    fn read(&self, from: u16) -> u8;
87
88    /// This is a convenience method; it takes care of reading a 16-bit word in little-endian
89    /// format from the specified address (less-signficant byte) and the subsequent address
90    /// (more-significant byte).
91    ///
92    /// You don't have to supply this method; it defaults to simply calling the `read` method
93    /// on two consecutive addresses and concatenating them together. You can implement this
94    /// to provide an optimized read operation, such as if your memory values are stored in a
95    /// byte slice and you can just read adjacent indices.
96    fn read_word(&self, from: u16) -> u16 {
97        Wrapping(raw::u16::from_le_bytes([self.read(from).0, self.read(from + Wrapping(1)).0]))
98    }
99
100    /// This method takes care of writing a byte to the specified address. It defaults to
101    /// silently discarding the supplied byte, for emulating read-only memory, but you will
102    /// usually want to supply your own implementation to record at least some variables.
103    fn write(&mut self, to: u16, value: u8) { let _ = (value, to); }
104
105    /// This is a convenience method; it takes care of writing a 16-bit word in little-endian
106    /// format to the specified address (less-signficant byte) and the subsequent address
107    /// (more-significant byte).
108    ///
109    /// You don't have to supply this method; it defaults to simply calling the `write` method
110    /// on two consecutive addresses from the two bytes in the argument. You can implement this
111    /// to provide an optimized write operation, such as if your memory values are stored in a
112    /// byte slice and you can just write adjacent indices.
113    fn write_word(&mut self, to: u16, value: u16) {
114        for (index, byte) in value.0.to_le_bytes().into_iter().enumerate() {
115            self.write(to + Wrapping(index as raw::u16), Wrapping(byte))
116        }
117    }
118
119    /// This method handles input operations. The CPU core can request/accept inputs on any
120    /// 8-bit port number. What values are supplied via what ports is entirely application-specific.
121	fn input(&mut self, port: raw::u8) -> u8;
122
123    /// This method handles output operations. The CPU core can publish/transmit outputs on any
124    /// 8-bit port number. What values are carried via what ports is entirely application-specific.
125    fn output(&mut self, port: raw::u8, value: u8);
126
127
128    /// This method reports to the Harness after every operation executed by the CPU, detailing the
129    /// operation executed and providing access to the current state of the CPU's internal registers
130    /// and flags.
131    #[cfg(any(feature="open", doc))]
132    fn did_execute(&mut self, client: &chip::State, did: chip::opcode::Op) -> Result<Option<chip::opcode::Op>, String> { let _ = (client, did); Ok( None ) }
133
134    /// You don't usually need to implement this method; it enables downcasting in cases where a
135    /// Machine stores a `dyn Harness` trait object.
136    fn as_any(&self) -> Option<&dyn any::Any> { None }
137}
138
139#[cfg(feature="std")]
140use crate::sync::{Arc, Mutex};
141use core::{borrow::BorrowMut, cell::RefCell, marker::PhantomData, ops::{Deref, DerefMut}};
142
143type Shared<H, C> = Rc<RefCell<(C, PhantomData<H>)>>;
144
145impl<H: Harness + ?Sized, C: BorrowMut<H>> Harness for Shared<H, C> {
146	fn read(&self, address: u16) -> u8 { self.deref().borrow().0.borrow().read(address) }
147	fn read_word(&self, address: u16) -> u16 { self.deref().borrow().0.borrow().read_word(address) }
148	fn write(&mut self, address: u16, value: u8) { (**self).borrow_mut().0.borrow_mut().write(address, value) }
149	fn write_word(&mut self, address: u16, value: u16) { (**self).borrow_mut().0.borrow_mut().write_word(address, value) }
150	fn input(&mut self, port: raw::u8) -> u8 { (**self).borrow_mut().0.borrow_mut().input(port) }
151	fn output(&mut self, port: raw::u8, value: u8) { (**self).borrow_mut().0.borrow_mut().output(port, value) }
152	#[cfg(feature="cfg")]
153	fn did_execute(&mut self, client: &chip::State, did: chip::opcode::Op) -> Result<Option<chip::opcode::Op>, string::String> {
154		(**self).borrow_mut().0.borrow_mut().did_execute(client, did)
155	}
156}
157
158#[cfg(feature="std")]
159type Synced<H, C> = Arc<Mutex<(C, PhantomData<H>)>>;
160
161#[cfg(feature="std")]
162impl<H: Harness + ?Sized, C: BorrowMut<H>> Harness for Synced<H, C> {
163	fn read(&self, address: u16) -> u8 { self.deref().lock().unwrap().0.borrow().read(address) }
164	fn read_word(&self, address: u16) -> u16 { self.deref().lock().unwrap().0.borrow().read_word(address) }
165	fn write(&mut self, address: u16, value: u8) { (**self).lock().unwrap().0.borrow_mut().write(address, value) }
166	fn write_word(&mut self, address: u16, value: u16) { (**self).lock().unwrap().0.borrow_mut().write_word(address, value) }
167	fn input(&mut self, port: raw::u8) -> u8 { (**self).lock().unwrap().0.borrow_mut().input(port) }
168	fn output(&mut self, port: raw::u8, value: u8) { (**self).lock().unwrap().0.borrow_mut().output(port, value) }
169	#[cfg(feature="cfg")]
170	fn did_execute(&mut self, client: &chip::State, did: chip::opcode::Op) -> Result<Option<chip::opcode::Op>, string::String> {
171		(**self).lock().unwrap().0.borrow_mut().did_execute(client, did)
172	}
173}
174
175/// SimpleBoard is a minimal Harness designed to make it easy to start using the crate;
176/// it just stores a full 16k RAM space and byte arrays to store the input and output port values.
177/// You can address the RAM space by indexing the SimpleBoard directly.
178#[repr(C)]
179pub struct SimpleBoard {
180	ram: [u8; 65536],
181	pub port_out: [u8; 256],
182	pub port_in: [u8; 256]
183}
184
185mod simple;
186
187/// This is the main outward-facing type for actually executing instructions. It also
188/// accepts interrupt requests, including RST instructions. It can be used as an Iterator
189/// to do processing in between operations. It also forwards the contained Harness object
190/// out to receive method requests.
191pub struct Machine<H: Harness + ?Sized, C: BorrowMut<H>> {
192    chip: chip::State,
193    board: C,
194    _grammar: PhantomData<H>,
195}
196
197impl<H: Harness + ?Sized, C: BorrowMut<H>> Machine<H, C> {
198	pub fn new(board: C) -> Self {
199		Self { board, chip: chip::State::new(), _grammar: PhantomData::default() }
200	}
201
202	fn split_mut(&mut self) -> (&mut chip::State, &mut H) { (&mut self.chip, self.board.borrow_mut() )}
203}
204
205/// Machines based on a Shared (`Rc<RefCell<H>>`) or Synced (`Arc<Mutex<H>>`) model can 
206/// provide a new Machine using the same shared Harness but a new State, crudely emulating 
207/// multiprocessing.
208impl<H: Harness + ?Sized, C: BorrowMut<H>> Machine<Shared<H, C>, Shared<H, C>> {
209    pub fn fork(&self) -> Self { Self::new(self.board.clone()) }
210}
211
212#[cfg(feature="std")]
213impl<H: Harness + ?Sized, C: BorrowMut<H>> Machine<Synced<H, C>, Synced<H, C>> {
214    pub fn fork(&self) -> Self { Self::new(self.board.clone()) }
215}
216
217impl<H: Harness + ?Sized, C: BorrowMut<H>> Deref for Machine<H, C> {
218	type Target = H;
219	fn deref(&self) -> &Self::Target { self.board.borrow() }
220}
221
222impl<H: Harness + ?Sized, C: BorrowMut<H>> DerefMut for Machine<H, C> {
223	fn deref_mut(&mut self) -> &mut Self::Target { self.board.borrow_mut() }
224}
225
226pub struct Install<H: Harness + ?Sized>(PhantomData<H>);
227
228impl<H: Harness + ?Sized> Install<H> {
229    /// This associated function generates a new Machine using the provided Harness reference.
230    /// It can accept any value that can be dereferenced to a mut Harness, whether dynamic or
231    /// generic, making it fairly easy to supply a `&mut H`, a `Box<H>` or `Box<dyn Harness>`.
232    pub fn new<C: BorrowMut<H>>(board: C) -> Machine<H, C> {
233        Machine::new( board )
234    }
235
236    /// This associated function takes a standard single Harness access point (which could 
237    /// be an embedded value) and generates a new Machine that uses a shared copy of that 
238    /// access point, using Rc<RefCell<H>> to gate access (this means it is not thread-safe).
239    pub fn new_shared<C: BorrowMut<H>>(board: C) -> Machine<Shared<H, C>, Shared<H, C>> {
240    	Machine::new(Rc::new(RefCell::new( (board, PhantomData::default()) )))
241    }
242
243    /// This associated function takes a standard single Harness access point (which could 
244    /// be an embedded value) and generates a new Machine that uses a shared copy of that 
245    /// access point, using Arc<Mutex<H>> to gate access (Use this one if you want to 
246    /// execute multiple CPUs on separate threads).
247    #[cfg(feature="std")]
248    pub fn new_synced<C: BorrowMut<H>>(board: C) -> Machine<Synced<H, C>, Synced<H, C>> {
249    	Machine::new(Arc::new(Mutex::new( (board, PhantomData::default()) )))
250    }
251}
252
253#[allow(dead_code)]
254impl<H: Harness + ?Sized, C: BorrowMut<H>> Machine<H, C> {
255    fn as_ref(&self) -> &chip::State { &self.chip }
256    fn as_mut(&mut self) -> &mut chip::State { &mut self.chip }
257}
258
259#[cfg(feature="open")]
260impl<H: Harness + ?Sized, C: BorrowMut<H>> AsRef<chip::State> for Machine<H, C> {
261    fn as_ref(&self) -> &chip::State { self.as_ref() }
262}
263
264#[cfg(feature="open")]
265impl<H: Harness + ?Sized, C: BorrowMut<H>> AsMut<chip::State> for Machine<H, C> {
266    fn as_mut(&mut self) -> &mut chip::State { self.as_mut() }
267}