Created factory for system creation.

Systems are now defined in JSON rather than hard-coded
This commit is contained in:
Jan-Peter von Hunnius 2025-09-29 12:41:16 +02:00
parent c039e9069a
commit 6f3295dc5e
31 changed files with 1610 additions and 1709 deletions

View file

@ -4,12 +4,12 @@ use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component, RunnableComponent};
use crate::pin::{Pin, PinValue};
use crate::components::common::intel_400x::{
Intel400xClockHandling, Intel400xDataBus, Intel400xAddressHandling,
Intel400xControlPins, Intel400xResetHandling, Intel400xTimingState,
TimingState, MemoryState, RamState, utils
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins,
Intel400xDataBus, Intel400xResetHandling, Intel400xTimingState, RamState,
TimingState,
};
use crate::pin::{Pin, PinValue};
/// Intel 4002 - 320-bit RAM (80 nibbles × 4 bits) with integrated output ports
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
@ -28,39 +28,38 @@ use crate::components::common::intel_400x::{
/// - Bank selection and status character handling follows Intel MCS-4 architecture
pub struct Intel4002 {
base: BaseComponent,
variant: RamVariant, // RAM variant (4002-1 or 4002-2)
memory: [u8; 80], // 80 nibbles of RAM (320 bits total) - 4 banks × 20 nibbles
last_address: u8, // Last accessed memory address
access_time: Duration, // RAM access latency (500ns typical)
variant: RamVariant, // RAM variant (4002-1 or 4002-2)
memory: [u8; 80], // 80 nibbles of RAM (320 bits total) - 4 banks × 20 nibbles
last_address: u8, // Last accessed memory address
access_time: Duration, // RAM access latency (500ns typical)
address_latch_time: Option<Instant>, // Timestamp when address was latched
output_ports: [u8; 4], // 4 output ports (4 bits each) - TODO: Make [[u8; 4]; 4] for 4-bit ports
input_latch: u8, // Input data latch for I/O operations
status_characters: [u8; 4], // 4 separate status character latches (4 bits each)
bank_select: u8, // RAM bank selection (2 bits)
output_ports: [u8; 4], // 4 output ports (4 bits each) - TODO: Make [[u8; 4]; 4] for 4-bit ports
input_latch: u8, // Input data latch for I/O operations
status_characters: [u8; 4], // 4 separate status character latches (4 bits each)
bank_select: u8, // RAM bank selection (2 bits)
// Clock edge detection (same as 4001)
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
// Two-phase addressing for 8-bit address (same as 4001)
address_high_nibble: Option<u8>, // High nibble of 8-bit address
address_low_nibble: Option<u8>, // Low nibble of 8-bit address
full_address_ready: bool, // Whether complete address is assembled
// RAM operation state machine
ram_state: RamState, // Current state of RAM operation
ram_state: RamState, // Current state of RAM operation
// Data latching for RAM operations
data_latch: Option<u8>, // Latched data for write operations
data_latch: Option<u8>, // Latched data for write operations
// Instruction cycle tracking
instruction_phase: bool, // Whether we're in instruction phase
current_instruction: u8, // Current instruction being processed
instruction_phase: bool, // Whether we're in instruction phase
current_instruction: u8, // Current instruction being processed
}
/// Intel 4002 RAM variants
/// - 4002-1: Standard RAM variant
/// - 4002-2: RAM variant with different configuration
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum RamVariant {
Type1, // 4002-1
Type2, // 4002-2
Type1, // 4002-1
Type2, // 4002-2
}
/// Intel 4002 timing constants (based on datasheet specifications)
@ -68,10 +67,10 @@ pub enum RamVariant {
struct TimingConstants;
impl TimingConstants {
const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // T_ADDRESS_SETUP
const DATA_VALID: Duration = Duration::from_nanos(200); // T_DATA_VALID
const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // T_ADDRESS_SETUP
const DATA_VALID: Duration = Duration::from_nanos(200); // T_DATA_VALID
const OUTPUT_DISABLE: Duration = Duration::from_nanos(150); // T_OUTPUT_DISABLE
const RAM_ACCESS: Duration = Duration::from_nanos(500); // RAM access time
const RAM_ACCESS: Duration = Duration::from_nanos(500); // RAM access time
}
impl Intel400xClockHandling for Intel4002 {
@ -163,7 +162,7 @@ impl Intel4002 {
Self::new_with_variant_and_access_time(name, RamVariant::Type1, 500) // Default 500ns access time
}
/// Create a new Intel 4002 RAM with custom access time (for testing)
/// Create a new Intel 4002 RAM with custom access time (for testing)
/// Parameters: name - Component identifier, access_time_ns - Memory access time in nanoseconds
/// Returns: New Intel4002 instance with configurable access timing
pub fn new_with_access_time(name: String, access_time_ns: u64) -> Self {
@ -173,7 +172,11 @@ impl Intel4002 {
/// Create a new Intel 4002 RAM with specified variant and access time
/// Parameters: name - Component identifier, variant - RAM variant (Type1 or Type2), access_time_ns - Memory access time in nanoseconds
/// Returns: New Intel4002 instance with configurable variant and access timing
pub fn new_with_variant_and_access_time(name: String, variant: RamVariant, access_time_ns: u64) -> Self {
pub fn new_with_variant_and_access_time(
name: String,
variant: RamVariant,
access_time_ns: u64,
) -> Self {
// Intel 4002 pinout (based on MCS-4 architecture):
// - 4 data pins (D0-D3) for multiplexed address/data
// - 4 output port pins (O0-O3)
@ -186,28 +189,28 @@ impl Intel4002 {
// - P0: RAM chip select (must be HIGH for RAM access)
// - RESET: Clears internal state
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"D0", "D1", "D2", "D3", // Data/Address pins
"O0", "O1", "O2", "O3", // Output port pins
"SYNC", // Sync signal
"CM", // ROM Chip Select
"P0", // RAM Chip Select
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
"SYNC", // Sync signal
"CM", // ROM Chip Select
"P0", // RAM Chip Select
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
Intel4002 {
Intel4002 {
base: BaseComponent::new(name, pins),
variant,
memory: [0u8; 80], // 80 nibbles = 4 banks × 20 nibbles each
memory: [0u8; 80], // 80 nibbles = 4 banks × 20 nibbles each
last_address: 0,
access_time: Duration::from_nanos(access_time_ns),
address_latch_time: None,
output_ports: [0u8; 4],
input_latch: 0,
status_characters: [0u8; 4], // 4 separate status character latches
status_characters: [0u8; 4], // 4 separate status character latches
bank_select: 0,
prev_phi1: PinValue::Low,
prev_phi2: PinValue::Low,
@ -312,7 +315,8 @@ impl Intel4002 {
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(format!("{}_DATA", self.base.name())), PinValue::HighZ);
pin_guard
.set_driver(Some(format!("{}_DATA", self.base.name())), PinValue::HighZ);
}
}
}
@ -324,13 +328,15 @@ impl Intel4002 {
for port in 0..4 {
if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some(format!("{}_OUTPUT", self.base.name())), PinValue::HighZ);
pin_guard.set_driver(
Some(format!("{}_OUTPUT", self.base.name())),
PinValue::HighZ,
);
}
}
}
}
/// Read the two-phase clock pins from CPU
/// Returns: (PHI1_value, PHI2_value)
/// Hardware: Intel 4004 provides two-phase clock for synchronization
@ -358,11 +364,6 @@ impl Intel4002 {
(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
@ -436,10 +437,10 @@ impl Intel4002 {
fn handle_reset(&mut self) {
if Intel400xResetHandling::handle_reset(self, "RESET") {
// Hardware reset - clear all registers specific to Intel4002
self.memory = [0u8; 80]; // Clear 80 nibbles
self.memory = [0u8; 80]; // Clear 80 nibbles
self.output_ports = [0u8; 4];
self.input_latch = 0;
self.status_characters = [0u8; 4]; // Clear 4 status character latches
self.status_characters = [0u8; 4]; // Clear 4 status character latches
self.bank_select = 0;
// Reset all state machines using common functionality
@ -464,7 +465,7 @@ impl Intel4002 {
fn assemble_full_address(&mut self) {
if let (Some(high), Some(low)) = (self.address_high_nibble, self.address_low_nibble) {
// Assemble 8-bit address: (high << 4) | low
self.last_address = ((high as u8) << 4) | (low as u8);
self.last_address = (high << 4) | low;
self.full_address_ready = true;
self.address_latch_time = Some(Instant::now());
@ -482,18 +483,18 @@ impl Intel4002 {
// The 4002 uses the high nibble of the address for bank selection
// and the low nibble for within-bank addressing
// Each bank has 20 nibbles (0-19), not 16
let bank = (address_low >> 4) & 0x03; // 2-bit bank from high nibble
let ram_address = (bank * 20) + (address_low & 0x0F); // 20 nibbles per bank
let bank = (address_low >> 4) & 0x03; // 2-bit bank from high nibble
let ram_address = (bank * 20) + (address_low & 0x0F); // 20 nibbles per bank
// Status characters are separate latches, not part of RAM
if self.is_status_character_instruction(instruction) {
// Status characters are separate from RAM - return special address
return (bank, 0xFF); // Special marker for status character access
return (bank, 0xFF); // Special marker for status character access
}
// Output ports are also separate from RAM (addresses 0x14-0x17)
if address_low >= 0x14 && address_low <= 0x17 {
return (bank, 0xFF); // Special marker for output port access
return (bank, 0xFF); // Special marker for output port access
}
(bank, ram_address)
@ -833,9 +834,9 @@ impl Intel4002 {
/// Handle instruction cycle synchronization
fn handle_instruction_cycle(&mut self) {
let (sync, _cm, p0, _) = self.read_control_pins();
let (sync, _cm, _p0, _) = self.read_control_pins();
if sync && self.is_phi1_rising_edge() {
if sync && self.is_phi1_rising_edge(self.prev_phi1) {
// Start of new instruction cycle
self.instruction_phase = true;
self.current_instruction = self.read_data_bus();
@ -1071,7 +1072,7 @@ impl Intel4002 {
/// Clear all RAM to zero
pub fn clear_ram(&mut self) {
self.memory = [0u8; 80]; // Clear 80 nibbles
self.memory = [0u8; 80]; // Clear 80 nibbles
}
/// Get all RAM data for a specific bank
@ -1079,7 +1080,7 @@ impl Intel4002 {
/// Returns: Vector of 20 nibbles for the bank (4 banks × 20 nibbles = 80 total)
pub fn get_ram_bank(&self, bank: u8) -> Vec<u8> {
let bank = (bank & 0x03) as usize;
let start = bank * 20; // 20 nibbles per bank
let start = bank * 20; // 20 nibbles per bank
let end = start + 20;
if end <= self.memory.len() {
@ -1094,9 +1095,16 @@ impl Intel4002 {
/// Debug function to log state transitions for troubleshooting
/// Parameters: test_name - Name of the test for context
pub fn debug_state_transitions(&self, test_name: &str) {
println!("{} - State: {:?}, Bank: {}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}",
test_name, self.ram_state, self.bank_select, self.address_high_nibble,
self.address_low_nibble, self.last_address, self.full_address_ready);
println!(
"{} - State: {:?}, Bank: {}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}",
test_name,
self.ram_state,
self.bank_select,
self.address_high_nibble,
self.address_low_nibble,
self.last_address,
self.full_address_ready
);
}
/// Setup helper for instruction testing
@ -1104,12 +1112,6 @@ impl Intel4002 {
self.current_instruction = instruction;
self.instruction_phase = true;
}
/// Tri-state all outputs (comprehensive tri-state)
fn tri_state_all_outputs(&self) {
self.tri_state_data_bus();
self.tri_state_output_ports();
}
}
// Custom formatter for debugging
@ -1182,7 +1184,7 @@ mod tests {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Write test data to different banks
ram.write_ram(0, 0x01).unwrap(); // Bank 0, address 0
ram.write_ram(0, 0x01).unwrap(); // Bank 0, address 0
ram.write_ram(20, 0x02).unwrap(); // Bank 1, address 0
ram.write_ram(40, 0x03).unwrap(); // Bank 2, address 0
@ -1272,9 +1274,15 @@ mod tests {
}
// High nibble
phi1_pin.lock().unwrap().set_driver(Some("TEST".into()), PinValue::High);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::High);
ram.update(); // rising edge -> latch high nibble
phi1_pin.lock().unwrap().set_driver(Some("TEST".into()), PinValue::Low);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::Low);
ram.update(); // falling edge
// Should have transitioned to AddressPhase
@ -1294,9 +1302,15 @@ mod tests {
}
// Low nibble
phi1_pin.lock().unwrap().set_driver(Some("TEST".into()), PinValue::High);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::High);
ram.update(); // rising edge -> latch low nibble
phi1_pin.lock().unwrap().set_driver(Some("TEST".into()), PinValue::Low);
phi1_pin
.lock()
.unwrap()
.set_driver(Some("TEST".into()), PinValue::Low);
ram.update(); // falling edge
// Should have assembled full address and transitioned to WaitLatency
@ -1446,13 +1460,13 @@ mod tests {
assert_eq!(ram.read_ram(4).unwrap(), 0x05);
// Test overflow
let large_data = vec![0u8; 81]; // More than 80 nibbles
let large_data = vec![0u8; 81]; // More than 80 nibbles
assert!(ram.initialize_ram(&large_data).is_err());
}
#[test]
fn test_ram_state_transitions() {
let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
let ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Initially should be in idle state
assert_eq!(ram.ram_state, RamState::Idle);
@ -1500,8 +1514,8 @@ mod tests {
// SRC instruction sets up output port addressing
ram.setup_instruction_test(0x25); // SRC with specific address
// Verify output port addressing is set up correctly
// This would require more complex setup in a real implementation
// Verify output port addressing is set up correctly
// This would require more complex setup in a real implementation
}
#[test]