Rust code formatter

Added threaded RunnableComponent trait
Accurate, threaded implementation of the 4001 ROM
This commit is contained in:
Jan-Peter von Hunnius 2025-09-27 19:23:31 +02:00
parent 01117af411
commit cff459d2b5
22 changed files with 488 additions and 197 deletions

View file

@ -1,9 +1,8 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::component::{BaseComponent, Component, RunnableComponent};
use crate::pin::{Pin, PinValue};
/// Intel 4001 - 256-byte ROM with integrated I/O
@ -17,12 +16,19 @@ pub struct Intel4001 {
output_latch: u8,
input_latch: u8,
io_mode: IoMode,
// Clock edge detection
prev_phi1: PinValue,
prev_phi2: PinValue,
// Access latency modeling
address_latched: bool,
address_latch_time: Option<Instant>,
data_available: bool,
}
#[derive(Debug, Clone, Copy, PartialEq)]
enum IoMode {
Input, // I/O pins as inputs
Output, // I/O pins as outputs
Input, // I/O pins as inputs
Output, // I/O pins as outputs
Bidirectional, // I/O pins bidirectional
}
@ -31,18 +37,26 @@ impl Intel4001 {
// Intel 4001 has 256 bytes of ROM
let rom_size = 256;
// Intel 4001 pinout:
// - 10 address pins (A0-A9, but only A0-A7 used for 256-byte addressing)
// Intel 4001 pinout (per datasheet):
// - 4 data pins (D0-D3) for multiplexed address/data
// - 4 I/O pins (IO0-IO3)
// - Control pins: SYNC, CM-ROM, CM-RAM, RESET
// - Control pins: SYNC, RESET, CM, CI
// - Clock pins: Φ1, Φ2 (two-phase clock from 4004 CPU)
//
// Control pin behavior:
// - SYNC: Marks start of instruction cycle
// - RESET: Clears internal state
// - CM: Chip select for ROM vs RAM
// - CI: Distinguishes I/O (LOW) vs ROM access (HIGH) when CM-ROM is HIGH
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"IO0", "IO1", "IO2", "IO3", // I/O pins
"SYNC", // Sync signal
"CM_ROM", // ROM Chip Select
"CM_RAM", // RAM Chip Select (for system configuration)
"RESET", // Reset
"D0", "D1", "D2", "D3", // Data/Address pins
"IO0", "IO1", "IO2", "IO3", // I/O pins
"SYNC", // Sync signal
"CM", // CM-ROM: ROM/RAM Chip Select
"CI", // CM-RAM: RAM Chip Select (I/O vs ROM access)
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
@ -57,6 +71,11 @@ impl Intel4001 {
output_latch: 0,
input_latch: 0,
io_mode: IoMode::Input,
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
address_latched: false,
address_latch_time: None,
data_available: false,
}
}
@ -64,7 +83,9 @@ impl Intel4001 {
if offset + data.len() > self.memory.len() {
return Err(format!(
"Data exceeds ROM capacity: offset {} + data length {} > ROM size {}",
offset, data.len(), self.memory.len()
offset,
data.len(),
self.memory.len()
));
}
@ -149,26 +170,87 @@ impl Intel4001 {
}
fn tri_state_data_bus(&self) {
// CRITICAL: Tri-state data bus to avoid contention with other chips
// The 4001 must be high-Z whenever not actively driving valid data
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
pin_guard
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
}
}
}
}
fn should_drive_data_bus(&self) -> bool {
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
// ROM drives data bus ONLY when all conditions are met:
sync && cm_rom && cm_ram && self.data_available
}
fn tri_state_io_pins(&self) {
for i in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
pin_guard
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
}
}
}
}
fn read_clock_pins(&self) -> (PinValue, PinValue) {
let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read()
} else {
PinValue::Low
}
} else {
PinValue::Low
};
let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read()
} else {
PinValue::Low
}
} else {
PinValue::Low
};
(phi1, phi2)
}
fn is_phi1_rising_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::High && self.prev_phi1 == PinValue::Low
}
fn is_phi1_falling_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
}
fn is_phi2_rising_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
}
fn is_phi2_falling_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
}
fn update_clock_states(&mut self) {
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
}
fn read_control_pins(&self) -> (bool, bool, bool, bool) {
// SYNC: Marks the start of an instruction cycle
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
@ -179,7 +261,8 @@ impl Intel4001 {
false
};
let cm_rom = if let Ok(pin) = self.base.get_pin("CM_ROM") {
// CM-ROM: Chip select for ROM vs RAM (HIGH = ROM access)
let cm_rom = if let Ok(pin) = self.base.get_pin("CM") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
@ -189,7 +272,9 @@ impl Intel4001 {
false
};
let cm_ram = if let Ok(pin) = self.base.get_pin("CM_RAM") {
// CM-RAM: Distinguishes I/O vs ROM access when CM-ROM is HIGH
// HIGH = ROM access, LOW = I/O access
let cm_ram = if let Ok(pin) = self.base.get_pin("CI") {
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
@ -213,12 +298,18 @@ impl Intel4001 {
}
fn handle_reset(&mut self) {
if self.read_control_pins().3 { // RESET is high
let (_, _, _, reset) = self.read_control_pins();
if reset {
// RESET is high - clear internal state
self.output_latch = 0;
self.input_latch = 0;
self.io_mode = IoMode::Input;
self.tri_state_data_bus();
self.tri_state_io_pins();
// Reset access latency state
self.address_latched = false;
self.address_latch_time = None;
self.data_available = false;
}
}
@ -247,25 +338,41 @@ impl Intel4001 {
fn handle_memory_operation(&mut self) -> bool {
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
// Memory operations occur when SYNC is high and CM-ROM is high
if sync && cm_rom {
if cm_ram {
// Second cycle of memory read - output data
let address = self.last_address;
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
self.write_data_bus(data);
return true;
}
} else {
// First cycle of memory operation - latch address
let address_low = self.read_data_bus();
self.last_address = address_low as u16;
// Tri-state during address phase
self.tri_state_data_bus();
// TRI-STATE RULE: ROM only drives data bus when ALL of these are true:
// - SYNC=1 (instruction cycle active)
// - CM-ROM=1 (ROM chip selected)
// - CM-RAM=1 (data phase, not address phase)
// - data_available=true (access latency has elapsed)
//
// ALL other cases MUST be high-Z to avoid bus contention with CPU/other chips
if self.should_drive_data_bus() {
// EXACT CONDITIONS: SYNC=1, CM-ROM=1, CM-RAM=1, and data available
// This is the ONLY case where ROM drives the data bus
let address = self.last_address;
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
self.write_data_bus(data);
return true; // Successfully drove the bus
}
}
// ALL other cases: tri-state the data bus
self.tri_state_data_bus();
// Handle address latching when in address phase (but still tri-state)
if sync && cm_rom && !cm_ram {
// First cycle: CPU outputs address, ROM latches it
let address_low = self.read_data_bus();
self.last_address = address_low as u16;
self.address_latched = true;
self.address_latch_time = Some(Instant::now());
self.data_available = false;
} else if !self.address_latched {
// Reset data available flag when not in active operation
self.data_available = false;
}
false
}
}
@ -284,11 +391,28 @@ impl Component for Intel4001 {
}
fn update(&mut self) {
// Respect access timing
if self.last_access.elapsed() < self.access_time {
// Only update on PHI1 rising edge (synchronous with CPU clock)
if !self.is_phi1_rising_edge() {
return;
}
// Update clock states for next edge detection
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
// Check if we have a latched address and need to make data available
if self.address_latched {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= self.access_time {
// Access latency has elapsed, data is now available
self.data_available = true;
self.address_latched = false;
self.address_latch_time = None;
}
}
}
self.handle_reset();
// Handle I/O operations (higher priority than memory operations)
@ -297,23 +421,28 @@ impl Component for Intel4001 {
// Handle memory operations
let memory_accessed = self.handle_memory_operation();
// If no memory operation occurred, ensure data bus is tri-stated when not active
if !memory_accessed {
let (sync, cm_rom, _, _) = self.read_control_pins();
if !sync || !cm_rom {
self.tri_state_data_bus();
}
// Additional safety: ensure data bus is tri-stated when ROM is not actively driving
// This handles any edge cases not covered in handle_memory_operation
if !memory_accessed && !self.should_drive_data_bus() {
self.tri_state_data_bus();
}
self.last_access = Instant::now();
}
fn run(&mut self) {
// Time-slice model: run in a loop calling update() each cycle
self.base.set_running(true);
// Initialize clock states
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(10));
// Small delay to prevent busy waiting when no clock is present
std::thread::sleep(std::time::Duration::from_micros(1));
}
}
@ -323,6 +452,11 @@ impl Component for Intel4001 {
// Tri-state all outputs when stopped
self.tri_state_data_bus();
self.tri_state_io_pins();
// Reset access latency state
self.address_latched = false;
self.address_latch_time = None;
self.data_available = false;
}
fn is_running(&self) -> bool {
@ -330,6 +464,8 @@ impl Component for Intel4001 {
}
}
impl RunnableComponent for Intel4001 {}
// Intel 4001 specific methods
impl Intel4001 {
pub fn get_rom_size(&self) -> usize {
@ -435,4 +571,4 @@ mod tests {
assert_eq!(IoMode::Output.to_string(), "Output");
assert_eq!(IoMode::Bidirectional.to_string(), "Bidirectional");
}
}
}