Skip to main content

ternaria_dev/
lib.rs

1//! Memory-mapped devices, and the bus that routes addresses to them.
2//!
3//! # Address layout
4//!
5//! Addresses are signed with zero at the centre, so the negative half is used
6//! for devices and the positive half for RAM. No separate I/O instruction space
7//! is needed: a device register is reached by an ordinary load or store.
8//!
9//! Devices occupy [`DEVICE_BASE`] upward, one [`DEVICE_STRIDE`]-tryte slot
10//! each. Both constants are powers of three and divisible by 3, so every device
11//! base is word-aligned.
12//!
13//! ```text
14//!   -19683  console   data, status
15//!   -18954  timer     ticks
16//!   -18225  block     sector, status, control, then the sector window
17//! ```
18//!
19//! Device registers are tryte-granular, so they are reached with `lt` and `st`
20//! rather than `lw` and `sw`.
21
22#![forbid(unsafe_code)]
23#![warn(missing_docs)]
24
25pub mod block;
26pub mod bus;
27pub mod console;
28pub mod timer;
29
30pub use block::BlockDevice;
31pub use bus::Bus;
32pub use console::Console;
33pub use timer::Timer;
34
35use ternaria_arith::Tryte;
36use ternaria_mem::MemoryError;
37
38/// Lowest address used by devices: -3^9.
39pub const DEVICE_BASE: i64 = -19683;
40
41/// Trytes reserved per device: 3^6.
42///
43/// A stride, not a size: device `k` sits at `DEVICE_BASE + k * DEVICE_STRIDE`
44/// and may use less than the whole slot. The console uses two trytes of it.
45pub const DEVICE_STRIDE: i64 = 729;
46
47/// Base address of the console.
48pub const CONSOLE_BASE: i64 = DEVICE_BASE;
49/// Base address of the timer.
50pub const TIMER_BASE: i64 = DEVICE_BASE + DEVICE_STRIDE;
51/// Base address of the block device.
52pub const BLOCK_BASE: i64 = DEVICE_BASE + 2 * DEVICE_STRIDE;
53
54/// Trytes per block-device sector: 3^5.
55///
56/// A size, not a stride: sectors tile the backing image with no gap, so sector
57/// `k` covers bytes `k * SECTOR_SIZE` onward and the window holds exactly this
58/// many trytes. The sector's base within the device is
59/// [`block::WINDOW`].
60///
61/// Small enough that the window and the block device's registers fit inside one
62/// [`DEVICE_STRIDE`] slot.
63pub const SECTOR_SIZE: i64 = 243;
64
65/// A device occupying a contiguous range of addresses.
66///
67/// Offsets are relative to the device's base and measured in trytes.
68pub trait Device {
69    /// A name, used in error messages.
70    fn name(&self) -> &'static str;
71
72    /// Reads one tryte. May have side effects.
73    fn read(&mut self, offset: i64) -> Result<Tryte, MemoryError>;
74
75    /// Writes one tryte.
76    fn write(&mut self, offset: i64, value: Tryte) -> Result<(), MemoryError>;
77}
78
79/// Builds a device error for an address the device does not implement.
80pub(crate) fn unmapped(addr: i64) -> MemoryError {
81    MemoryError::Device {
82        addr,
83        reason: "no register at this offset",
84    }
85}
86
87/// Assembler `.equ` lines naming every device address and register offset.
88///
89/// Prepend this to a guest program so it refers to devices by name instead of
90/// by a literal address. The text is generated from the constants in this
91/// crate, so a device that moves does not leave a stale number behind in the
92/// programs that use it.
93///
94/// ```
95/// # use ternaria_dev::{prelude, CONSOLE_BASE};
96/// let source = format!("{}\n    addi r1, r0, CONSOLE_BASE\n", prelude());
97/// assert!(source.contains(&CONSOLE_BASE.to_string()));
98/// ```
99pub fn prelude() -> String {
100    let entries: [(&str, i64); 15] = [
101        ("CONSOLE_BASE", CONSOLE_BASE),
102        ("CONSOLE_DATA", console::DATA),
103        ("CONSOLE_STATUS", console::STATUS),
104        ("TIMER_BASE", TIMER_BASE),
105        ("TIMER_TICKS", timer::TICKS),
106        ("BLOCK_BASE", BLOCK_BASE),
107        ("BLOCK_SECTOR", block::SECTOR),
108        ("BLOCK_CONTROL", block::CONTROL),
109        ("BLOCK_STATUS", block::STATUS),
110        ("BLOCK_WINDOW", block::WINDOW),
111        ("BLOCK_LOAD", block::CONTROL_LOAD as i64),
112        ("BLOCK_STORE", block::CONTROL_STORE as i64),
113        ("SECTOR_SIZE", SECTOR_SIZE),
114        ("DEVICE_STRIDE", DEVICE_STRIDE),
115        ("TIMER_COMPARE", timer::COMPARE),
116    ];
117    let mut out = String::new();
118    for (name, value) in entries {
119        out.push_str(&format!(".equ {name}, {value}\n"));
120    }
121    out
122}