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

@ -1,6 +1,7 @@
# Hardware Components
This directory contains implementations of various hardware components used in the Intel 4004 microprocessor system and other architectures.
This directory contains implementations of various hardware components used in the Intel 4004 microprocessor system and
other architectures.
## Component Architecture
@ -16,22 +17,25 @@ All components implement the base `Component` trait defined in `src/component.rs
### CPU Components (`cpu/`)
#### Intel 4004 CPU
- **File**: `intel_4004.rs`
- **Features**:
- Complete Intel 4004 instruction set implementation
- 4-bit data processing
- 12-bit address space
- Two-phase clock operation (Φ1, Φ2)
- 16 general-purpose registers (8 index, 8 main)
- Stack-based subroutine calls
- Complete Intel 4004 instruction set implementation
- 4-bit data processing
- 12-bit address space
- Two-phase clock operation (Φ1, Φ2)
- 16 general-purpose registers (8 index, 8 main)
- Stack-based subroutine calls
- **Status**: Fully implemented with comprehensive testing
#### MOS 6502 CPU (Placeholder)
- **File**: `mos_6502.rs`
- **Features**: Placeholder for MOS Technology 6502 implementation
- **Status**: Not yet implemented
#### WDC 65C02 CPU (Placeholder)
- **File**: `wdc_65c02.rs`
- **Features**: Placeholder for Western Design Center 65C02 implementation
- **Status**: Not yet implemented
@ -39,67 +43,74 @@ All components implement the base `Component` trait defined in `src/component.rs
### Memory Components (`memory/`)
#### Intel 4001 ROM
- **File**: `intel_4001.rs`
- **Features**:
- 256 bytes of mask-programmable ROM
- 4-bit I/O ports for peripheral interface
- Two-phase addressing (8-bit address in two 4-bit cycles)
- 500ns typical access time
- SYNC signal detection for instruction fetch
- 256 bytes of mask-programmable ROM
- 4-bit I/O ports for peripheral interface
- Two-phase addressing (8-bit address in two 4-bit cycles)
- 500ns typical access time
- SYNC signal detection for instruction fetch
- **Status**: Fully implemented with comprehensive testing
#### Intel 4002 RAM
- **File**: `intel_4002.rs`
- **Features**:
- 320 bits of read/write memory (80 × 4-bit characters)
- 4-bit output port for status/display
- Banked memory organization (4 banks × 20 characters)
- Refresh circuitry for dynamic memory cells
- Status character instructions for control
- 320 bits of read/write memory (80 × 4-bit characters)
- 4-bit output port for status/display
- Banked memory organization (4 banks × 20 characters)
- Refresh circuitry for dynamic memory cells
- Status character instructions for control
- **Status**: Implemented with known limitations
#### Intel 4003 Shift Register
- **File**: `intel_4003.rs`
- **Features**:
- 10-bit static shift register
- Serial input/output capability
- Parallel input from data bus
- 200ns typical access time
- Bidirectional shifting
- 10-bit static shift register
- Serial input/output capability
- Parallel input from data bus
- 200ns typical access time
- Bidirectional shifting
- **Status**: Basic implementation
#### Generic Memory Components
- **GenericRAM**: `generic_ram.rs` - Configurable RAM implementation
- **GenericROM**: `generic_rom.rs` - Configurable ROM implementation
### Common Functionality (`common/`)
#### Intel 400x Series Common Code
- **File**: `intel_400x.rs`
- **Purpose**: Shared functionality for all Intel 400x series chips
- **Features**:
- Common timing constants and state machines
- Clock edge detection and handling
- Data bus operations and bit manipulation
- Address latching and assembly logic
- Control pin reading and management
- Reset handling and initialization
- Memory operation state machines
- Common timing constants and state machines
- Clock edge detection and handling
- Data bus operations and bit manipulation
- Address latching and assembly logic
- Control pin reading and management
- Reset handling and initialization
- Memory operation state machines
### Clock Components (`clock/`)
#### Generic Clock Generator
- **File**: `generic_clock.rs`
- **Features**:
- Configurable frequency and duty cycle
- Multiple output waveforms
- Synchronization capabilities
- Test pattern generation
- Configurable frequency and duty cycle
- Multiple output waveforms
- Synchronization capabilities
- Test pattern generation
- **Status**: Basic implementation
## Component Interface
### Required Methods
All components must implement:
```rust
@ -113,6 +124,7 @@ fn is_running(&self) -> bool; // Execution status
```
### Optional Traits
Components can implement additional traits for specialized functionality:
- **RunnableComponent**: Automatic thread spawning
@ -122,12 +134,14 @@ Components can implement additional traits for specialized functionality:
## Pin Configuration
### Standard Pin Types
- **Data Pins**: `D0-D3` for 4-bit data/address bus
- **Clock Pins**: `PHI1`, `PHI2` for two-phase clock
- **Control Pins**: `SYNC`, `RESET`, `CM`, `CI` for system control
- **I/O Pins**: `IO0-IO3` for peripheral interface
### Pin Signal States
- **High**: Logic level 1
- **Low**: Logic level 0
- **HighZ**: High impedance (tri-state, not driving)
@ -135,6 +149,7 @@ Components can implement additional traits for specialized functionality:
## Usage Examples
### Creating and Using a Component
```rust
use rusty_emu::components::memory::intel_4001::Intel4001;
@ -143,38 +158,42 @@ let mut rom = Intel4001::new("ROM1".to_string());
// Load program data
let program = vec![0x12, 0x34, 0x56, 0x78];
rom.load_rom_data(program, 0)?;
rom.load_rom_data(program, 0) ?;
// Use in simulation loop
while simulation_running() {
rom.update(); // Process one clock cycle
rom.update(); // Process one clock cycle
}
```
### Component Interconnection
```rust
// Components connect through shared pin references
let cpu_pin = cpu.get_pin("D0")?;
let rom_pin = rom.get_pin("D0")?;
let ram_pin = ram.get_pin("D0")?;
let cpu_pin = cpu.get_pin("D0") ?;
let rom_pin = rom.get_pin("D0") ?;
let ram_pin = ram.get_pin("D0") ?;
// Connect pins together
connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
connect_pins(vec![cpu_pin, rom_pin, ram_pin]) ?;
```
## Testing Strategy
### Unit Tests
- Located in each component's implementation file
- Test individual component behavior in isolation
- Focus on state management and pin operations
### Integration Tests
- Located in `tests/` directory
- Test component interactions and system behavior
- Verify proper timing and signal propagation
### Mock-Based Tests
- Use mock components for controlled testing
- Test error conditions and edge cases
- Verify behavior with various input patterns
@ -182,6 +201,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
## Development Guidelines
### Adding New Components
1. **Implement Core Traits**: Start with `Component` trait implementation
2. **Define Pin Configuration**: Specify required pins and their purposes
3. **Implement Timing**: Add clock cycle processing in `update()` method
@ -190,6 +210,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
6. **Update Exports**: Add the component to the appropriate module exports
### Component Design Principles
- **Single Responsibility**: Each component should have one primary function
- **Clear Interfaces**: Well-defined pin configurations and behaviors
- **Timing Awareness**: Proper handling of clock cycles and timing constraints
@ -197,6 +218,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
- **Testability**: Design for easy testing and verification
### Performance Considerations
- **Pin Access**: Minimize lock contention on shared pins
- **State Updates**: Efficient state machine transitions
- **Memory Layout**: Optimize memory access patterns
@ -205,6 +227,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
## Hardware Accuracy
### Intel 4004 Series Specifics
- **Two-Phase Clock**: All operations synchronized to Φ1 and Φ2
- **4-Bit Architecture**: Data processed in 4-bit nibbles
- **12-Bit Addressing**: 8-bit addresses transferred in two 4-bit cycles
@ -212,6 +235,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
- **Bus Sharing**: Multiple components share the same data bus
### Implementation Notes
- Some timing specifications are approximated due to Rust's timing limitations
- Pin signal propagation is simplified for simulation performance
- Bus contention handling prioritizes correctness over perfect hardware accuracy
@ -220,6 +244,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
## Future Enhancements
### Planned Components
- Enhanced Intel 4002 RAM with proper refresh cycles
- Intel 4008/4009 I/O expanders
- Intel 4269 programmable ROM
@ -227,6 +252,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin])?;
- Cycle-accurate instruction execution
### Architecture Improvements
- Better pin signal propagation modeling
- More sophisticated bus arbitration
- Enhanced timing constraint checking

View file

@ -1,10 +1,12 @@
# Intel 400x Common Functionality
This module provides shared functionality for all Intel 400x series chips (4001, 4002, 4003, 4004). It contains common traits, utilities, and behaviors that are used across multiple components.
This module provides shared functionality for all Intel 400x series chips (4001, 4002, 4003, 4004). It contains common
traits, utilities, and behaviors that are used across multiple components.
## Purpose
The Intel 400x series shares many common characteristics:
- **Two-phase clock operation** (Φ1, Φ2)
- **4-bit data bus** with multiplexed address/data
- **12-bit address space** (8-bit addresses in two 4-bit cycles)
@ -61,6 +63,7 @@ pub trait Intel400xClockHandling {
```
**Key Features:**
- Edge detection for both clock phases
- Previous state tracking for transition detection
- Thread-safe pin access
@ -81,6 +84,7 @@ pub trait Intel400xDataBus {
```
**Key Features:**
- Safe pin access with error handling
- Driver name tracking for debugging
- Bus contention avoidance
@ -101,6 +105,7 @@ pub trait Intel400xAddressHandling {
```
**Key Features:**
- Two-phase address latching (high nibble, then low nibble)
- Timing-aware address assembly
- Access time validation
@ -121,6 +126,7 @@ pub trait Intel400xControlPins {
```
**Key Features:**
- Safe pin reading with fallback values
- Consistent pin naming across components
@ -139,6 +145,7 @@ pub trait Intel400xResetHandling {
```
**Key Features:**
- System-wide reset signal handling
- Component-specific reset behaviors
@ -167,6 +174,7 @@ pub trait Intel400xTimingState {
```
**Key Features:**
- Unified state machine interface
- Timing constraint management
- Address tracking and validation
@ -243,6 +251,7 @@ pub mod utils {
### Error Handling
All pin operations include proper error handling:
- **Lock failures**: Return safe default values
- **Pin not found**: Return appropriate error messages
- **Invalid states**: Graceful degradation
@ -347,19 +356,19 @@ The common code implements hardware-accurate timing where possible:
### Planned Improvements
1. **Enhanced Timing Simulation**
- More accurate sub-nanosecond timing
- Better clock phase relationship modeling
- Improved signal propagation delays
- More accurate sub-nanosecond timing
- Better clock phase relationship modeling
- Improved signal propagation delays
2. **Extended Component Support**
- Support for additional Intel 400x variants
- Enhanced I/O device support
- Better peripheral modeling
- Support for additional Intel 400x variants
- Enhanced I/O device support
- Better peripheral modeling
3. **Testing Improvements**
- More sophisticated mock scenarios
- Better timing verification tools
- Enhanced debugging capabilities
- More sophisticated mock scenarios
- Better timing verification tools
- Enhanced debugging capabilities
### Extension Points
@ -394,4 +403,5 @@ The common functionality depends on:
- **Shared Code**: No duplication across components
- **Efficient State Storage**: Compact state representation
This common functionality module provides a solid foundation for all Intel 400x components while maintaining high performance and hardware accuracy.
This common functionality module provides a solid foundation for all Intel 400x components while maintaining high
performance and hardware accuracy.

View file

@ -3,10 +3,10 @@
//! This module provides shared timing, clock handling, and bus operations
//! used across all Intel 400x family components (4001, 4002, 4003, 4004).
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
use std::sync::{Arc, Mutex};
use std::time::{Duration, Instant};
// Memory operation state machine states (shared across all 400x chips)
#[derive(Debug, Clone, Copy, PartialEq)]
@ -17,15 +17,14 @@ pub enum MemoryState {
DriveData, // Latency elapsed, driving data on bus
}
/// Common timing constants for Intel 400x series
pub struct TimingConstants;
impl TimingConstants {
pub const DEFAULT_ACCESS_TIME: Duration = Duration::from_nanos(500); // 500ns default
pub const FAST_ACCESS_TIME: Duration = Duration::from_nanos(200); // 200ns for shift registers
pub const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // Address setup time
pub const DATA_VALID: Duration = Duration::from_nanos(200); // Data valid delay
pub const FAST_ACCESS_TIME: Duration = Duration::from_nanos(200); // 200ns for shift registers
pub const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // Address setup time
pub const DATA_VALID: Duration = Duration::from_nanos(200); // Data valid delay
}
/// Common clock edge detection and timing functionality
@ -121,7 +120,10 @@ pub trait Intel400xDataBus {
} else {
PinValue::Low
};
pin_guard.set_driver(Some(format!("{}_DATA", self.get_base().get_name())), pin_value);
pin_guard.set_driver(
Some(format!("{}_DATA", self.get_base().get_name())),
pin_value,
);
}
}
}
@ -132,7 +134,10 @@ pub trait Intel400xDataBus {
for i in 0..4 {
if let Ok(pin) = self.get_base().get_pin(&format!("D{}", i)) {
if let Ok(mut pin_guard) = pin.lock() {
pin_guard.set_driver(Some(format!("{}_DATA", self.get_base().get_name())), PinValue::HighZ);
pin_guard.set_driver(
Some(format!("{}_DATA", self.get_base().get_name())),
PinValue::HighZ,
);
}
}
}
@ -146,7 +151,11 @@ pub trait Intel400xAddressHandling {
/// Assemble complete 8-bit address from high and low nibbles
/// Hardware: Intel 4004 provides address in two 4-bit phases
/// Format: (high_nibble << 4) | low_nibble
fn assemble_full_address(&self, high_nibble: Option<u8>, low_nibble: Option<u8>) -> Option<u16> {
fn assemble_full_address(
&self,
high_nibble: Option<u8>,
low_nibble: Option<u8>,
) -> Option<u16> {
if let (Some(high), Some(low)) = (high_nibble, low_nibble) {
Some(((high as u16) << 4) | (low as u16))
} else {
@ -163,7 +172,7 @@ pub trait Intel400xAddressHandling {
address_low_nibble: &mut Option<u8>,
full_address_ready: &mut bool,
address_latch_time: &mut Option<Instant>,
access_time: Duration,
_access_time: Duration,
) {
if address_high_nibble.is_none() {
// First cycle: latch high nibble (bits 7-4)
@ -173,7 +182,9 @@ pub trait Intel400xAddressHandling {
*address_low_nibble = Some(nibble);
// Try to assemble full address
if let Some(address) = self.assemble_full_address(*address_high_nibble, *address_low_nibble) {
if let Some(_address) =
self.assemble_full_address(*address_high_nibble, *address_low_nibble)
{
*full_address_ready = true;
*address_latch_time = Some(Instant::now());
@ -186,7 +197,11 @@ pub trait Intel400xAddressHandling {
/// Handle latency timing during wait state
/// Returns: true if latency has elapsed
fn handle_latency_wait(&self, address_latch_time: &Option<Instant>, access_time: Duration) -> bool {
fn handle_latency_wait(
&self,
address_latch_time: &Option<Instant>,
access_time: Duration,
) -> bool {
if let Some(latch_time) = address_latch_time {
latch_time.elapsed() >= access_time
} else {
@ -214,7 +229,8 @@ pub trait Intel400xControlPins {
/// Read CM-ROM pin state
fn read_cm_rom_pin(&self) -> bool {
if let Ok(pin) = self.get_base().get_pin("CM") { // Note: CM pin name varies by component
if let Ok(pin) = self.get_base().get_pin("CM") {
// Note: CM pin name varies by component
if let Ok(pin_guard) = pin.lock() {
pin_guard.read() == PinValue::High
} else {
@ -413,4 +429,4 @@ pub mod utils {
}
// Re-export commonly used items for convenience
pub use utils::*;
pub use utils::*;

View file

@ -3,4 +3,4 @@
pub mod intel_400x;
// Re-export commonly used items for convenience
pub use intel_400x::*;
pub use intel_400x::*;

View file

@ -40,26 +40,26 @@ enum MemoryState {
#[derive(Debug, Clone, Copy, PartialEq)]
enum Instruction {
// Data Transfer Instructions (8)
Ldm(u8), // Load accumulator immediate (LDM #)
Ld(u8), // Load accumulator from register (LD R)
Xch(u8), // Exchange accumulator with register (XCH R)
Add(u8), // Add register to accumulator (ADD R)
Sub(u8), // Subtract register from accumulator (SUB R)
Inc(u8), // Increment register (INC R)
Bbl(u8), // Branch back and load (BBL #)
Jun(u16), // Jump unconditional (JUN addr)
Ldm(u8), // Load accumulator immediate (LDM #)
Ld(u8), // Load accumulator from register (LD R)
Xch(u8), // Exchange accumulator with register (XCH R)
Add(u8), // Add register to accumulator (ADD R)
Sub(u8), // Subtract register from accumulator (SUB R)
Inc(u8), // Increment register (INC R)
Bbl(u8), // Branch back and load (BBL #)
Jun(u16), // Jump unconditional (JUN addr)
// Arithmetic Instructions (4)
AddC(u8), // Add register with carry (ADC R)
SubC(u8), // Subtract register with carry (SBC R)
Dad(u8), // Decimal add register (DAD R)
Daa, // Decimal adjust accumulator (DAA)
AddC(u8), // Add register with carry (ADC R)
SubC(u8), // Subtract register with carry (SBC R)
Dad(u8), // Decimal add register (DAD R)
Daa, // Decimal adjust accumulator (DAA)
// Logic Instructions (4)
Ral, // Rotate left (RAL)
Rar, // Rotate right (RAR)
Tcc, // Transmit carry clear (TCC)
Tcs, // Transmit carry set (TCS)
Ral, // Rotate left (RAL)
Rar, // Rotate right (RAR)
Tcc, // Transmit carry clear (TCC)
Tcs, // Transmit carry set (TCS)
// Control Transfer Instructions (8)
Jcn(u8, u16), // Jump conditional (JCN condition, addr)
@ -68,25 +68,25 @@ enum Instruction {
JntInvert(u16), // Jump on test inverted (JNT addr) - wait instruction
// Machine Instruction (1)
Src(u8), // Send register control (SRC R)
Src(u8), // Send register control (SRC R)
// Input/Output and RAM Instructions (8)
Wrm, // Write accumulator to RAM (WRM)
Wmp, // Write memory pointer (WMP)
Wrr, // Write ROM port and register (WRR)
Wpm, // Write program memory (WPM)
Adm, // Add from memory (ADM)
Sbm, // Subtract from memory (SBM)
Rdm, // Read memory (RDM)
Rdr, // Read ROM port and register (RDR)
Wrm, // Write accumulator to RAM (WRM)
Wmp, // Write memory pointer (WMP)
Wrr, // Write ROM port and register (WRR)
Wpm, // Write program memory (WPM)
Adm, // Add from memory (ADM)
Sbm, // Subtract from memory (SBM)
Rdm, // Read memory (RDM)
Rdr, // Read ROM port and register (RDR)
// Accumulator Group Instructions (8)
Clb, // Clear both (CLB)
Clc, // Clear carry (CLC)
Cmc, // Complement carry (CMC)
Stc, // Set carry (STC)
Cma, // Complement accumulator (CMA)
Iac, // Increment accumulator (IAC)
Clb, // Clear both (CLB)
Clc, // Clear carry (CLC)
Cmc, // Complement carry (CMC)
Stc, // Set carry (STC)
Cma, // Complement accumulator (CMA)
Iac, // Increment accumulator (IAC)
// Note: CMC and RAL are already defined above
// Invalid instruction
@ -124,19 +124,19 @@ pub struct Intel4004 {
ram_bank: u8, // Currently selected RAM bank (0-7)
// Two-phase clock state tracking
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
// Memory operation state machine
memory_state: MemoryState, // Current state of memory operation
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
memory_state: MemoryState, // Current state of memory operation
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
// Instruction execution state
current_op: Instruction, // Currently decoded instruction
operand_latch: u8, // Latched operand for multi-byte instructions
jump_address: Option<u16>, // Jump target address for conditional jumps
current_op: Instruction, // Currently decoded instruction
operand_latch: u8, // Latched operand for multi-byte instructions
jump_address: Option<u16>, // Jump target address for conditional jumps
// Timing and synchronization
address_latch_time: Option<Instant>, // Timestamp when address was latched
@ -150,13 +150,13 @@ impl Intel4004 {
pub fn new(name: String, clock_speed: f64) -> Self {
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data bus pins
"SYNC", // Sync signal
"CM_ROM", // ROM chip select
"CM_RAM", // RAM chip select
"TEST", // Test pin
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
"SYNC", // Sync signal
"CM_ROM", // ROM chip select
"CM_RAM", // RAM chip select
"TEST", // Test pin
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
@ -462,7 +462,8 @@ impl Intel4004 {
/// Hardware: RESET pin clears all internal state and tri-states outputs
fn handle_reset(&mut self) {
let (_, _, _, test) = self.read_control_pins();
if test { // Note: Using TEST pin as RESET for now - should be RESET pin
if test {
// Note: Using TEST pin as RESET for now - should be RESET pin
// RESET is high - clear all internal state
self.accumulator = 0;
self.carry = false;
@ -575,7 +576,7 @@ impl Intel4004 {
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.address_latch = ((high as u8) << 4) | (low as u8);
self.address_latch = (high << 4) | low;
self.full_address_ready = true;
self.address_latch_time = Some(Instant::now());
@ -646,7 +647,7 @@ impl Intel4004 {
0x00..=0x0F => {
let reg = opcode & 0x0F;
if opcode < 0x08 {
Instruction::Ld(reg) // LD R
Instruction::Ld(reg) // LD R
} else {
Instruction::Xch(reg) // XCH R
}
@ -656,17 +657,17 @@ impl Intel4004 {
0x10..=0x1F => {
let reg = opcode & 0x0F;
if opcode < 0x18 {
Instruction::Add(reg) // ADD R
Instruction::Add(reg) // ADD R
} else {
Instruction::Sub(reg) // SUB R
Instruction::Sub(reg) // SUB R
}
}
// Logic Instructions (0x20-0x2F)
0x20..=0x2F => {
match opcode {
0x20..=0x27 => Instruction::AddC(opcode & 0x0F), // ADC R
0x28..=0x2F => Instruction::SubC(opcode & 0x0F), // SBC R
0x20..=0x27 => Instruction::AddC(opcode & 0x0F), // ADC R
0x28..=0x2F => Instruction::SubC(opcode & 0x0F), // SBC R
_ => Instruction::Invalid,
}
}
@ -674,10 +675,10 @@ impl Intel4004 {
// Control Transfer Instructions (0x30-0x3F)
0x30..=0x3F => {
match opcode {
0x30..=0x33 => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x34..=0x37 => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x38..=0x3B => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x3C..=0x3F => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x30..=0x33 => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x34..=0x37 => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x38..=0x3B => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
0x3C..=0x3F => Instruction::Jcn(opcode & 0x0F, 0), // JCN condition
_ => Instruction::Invalid,
}
}
@ -685,24 +686,23 @@ impl Intel4004 {
// Immediate Instructions (0x40-0x4F)
0x40..=0x4F => {
let reg = opcode & 0x0F;
Instruction::Ldm(reg) // LDM #
Instruction::Ldm(reg) // LDM #
}
// I/O and RAM Instructions (0x50-0x5F)
0x50..=0x5F => {
match opcode {
0x50..=0x57 => Instruction::Wrm, // WRM
0x58..=0x5F => Instruction::Wmp, // WMP
0x50..=0x57 => Instruction::Wrm, // WRM
0x58..=0x5F => Instruction::Wmp, // WMP
_ => Instruction::Invalid,
}
}
// Register Reference Instructions (0x60-0x6F)
0x60..=0x6F => {
let reg = opcode & 0x0F;
match opcode {
0x60..=0x67 => Instruction::Wrr, // WRR
0x68..=0x6F => Instruction::Wpm, // WPM
0x60..=0x67 => Instruction::Wrr, // WRR
0x68..=0x6F => Instruction::Wpm, // WPM
_ => Instruction::Invalid,
}
}
@ -710,22 +710,22 @@ impl Intel4004 {
// Accumulator Group Instructions (0x70-0x7F)
0x70..=0x7F => {
match opcode {
0x70 => Instruction::Adm, // ADM
0x71 => Instruction::Sbm, // SBM
0x72 => Instruction::Clb, // CLB
0x73 => Instruction::Clc, // CLC
0x74 => Instruction::Cmc, // CMC
0x75 => Instruction::Stc, // STC
0x76 => Instruction::Cma, // CMA
0x77 => Instruction::Iac, // IAC
0x78 => Instruction::Rdm, // RDM
0x79 => Instruction::Rdr, // RDR
0x7A => Instruction::Ral, // RAL
0x7B => Instruction::Rar, // RAR
0x7C => Instruction::Tcc, // TCC
0x7D => Instruction::Tcs, // TCS
0x7E => Instruction::Daa, // DAA
0x7F => Instruction::Tcs, // TCS (duplicate in some docs)
0x70 => Instruction::Adm, // ADM
0x71 => Instruction::Sbm, // SBM
0x72 => Instruction::Clb, // CLB
0x73 => Instruction::Clc, // CLC
0x74 => Instruction::Cmc, // CMC
0x75 => Instruction::Stc, // STC
0x76 => Instruction::Cma, // CMA
0x77 => Instruction::Iac, // IAC
0x78 => Instruction::Rdm, // RDM
0x79 => Instruction::Rdr, // RDR
0x7A => Instruction::Ral, // RAL
0x7B => Instruction::Rar, // RAR
0x7C => Instruction::Tcc, // TCC
0x7D => Instruction::Tcs, // TCS
0x7E => Instruction::Daa, // DAA
0x7F => Instruction::Tcs, // TCS (duplicate in some docs)
_ => Instruction::Invalid,
}
}
@ -733,62 +733,62 @@ impl Intel4004 {
// Jump Instructions (0x80-0x8F)
0x80..=0x8F => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R
Instruction::Src(reg) // SRC R
}
// Jump Instructions (0x90-0x9F)
0x90..=0x9F => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xA0-0xAF)
0xA0..=0xAF => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xB0-0xBF)
0xB0..=0xBF => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xC0-0xCF)
0xC0..=0xCF => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xD0-0xDF)
0xD0..=0xDF => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xE0-0xEF)
0xE0..=0xEF => {
let reg = opcode & 0x0F;
Instruction::Src(reg) // SRC R (continued)
Instruction::Src(reg) // SRC R (continued)
}
// Jump Instructions (0xF0-0xFF)
0xF0..=0xFF => {
match opcode {
0xF0 => Instruction::Clb, // CLB
0xF1 => Instruction::Clc, // CLC
0xF2 => Instruction::Iac, // IAC
0xF3 => Instruction::Cmc, // CMC
0xF4 => Instruction::Cma, // CMA
0xF5 => Instruction::Ral, // RAL
0xF6 => Instruction::Rar, // RAR
0xF7 => Instruction::Rar, // RAR (duplicate)
0xF8 => Instruction::Daa, // DAA
0xF9 => Instruction::Daa, // DAA (duplicate)
0xFA => Instruction::Stc, // STC
0xFB => Instruction::Stc, // STC (duplicate)
0xFC => Instruction::Tcc, // TCC
0xFD => Instruction::Tcs, // TCS
0xF0 => Instruction::Clb, // CLB
0xF1 => Instruction::Clc, // CLC
0xF2 => Instruction::Iac, // IAC
0xF3 => Instruction::Cmc, // CMC
0xF4 => Instruction::Cma, // CMA
0xF5 => Instruction::Ral, // RAL
0xF6 => Instruction::Rar, // RAR
0xF7 => Instruction::Rar, // RAR (duplicate)
0xF8 => Instruction::Daa, // DAA
0xF9 => Instruction::Daa, // DAA (duplicate)
0xFA => Instruction::Stc, // STC
0xFB => Instruction::Stc, // STC (duplicate)
0xFC => Instruction::Tcc, // TCC
0xFD => Instruction::Tcs, // TCS
0xFE => Instruction::Invalid,
0xFF => Instruction::Invalid,
_ => Instruction::Invalid,
@ -839,7 +839,9 @@ impl Intel4004 {
Instruction::Sub(reg) => {
if reg < 16 {
let result = self.accumulator.wrapping_sub(self.index_registers[reg as usize]);
let result = self
.accumulator
.wrapping_sub(self.index_registers[reg as usize]);
self.carry = self.accumulator < self.index_registers[reg as usize];
self.accumulator = result & 0x0F;
}
@ -860,8 +862,12 @@ impl Intel4004 {
Instruction::SubC(reg) => {
if reg < 16 {
let carry_val = if self.carry { 1 } else { 0 };
let result = self.accumulator.wrapping_sub(self.index_registers[reg as usize]).wrapping_sub(carry_val);
self.carry = self.accumulator < (self.index_registers[reg as usize] + carry_val);
let result = self
.accumulator
.wrapping_sub(self.index_registers[reg as usize])
.wrapping_sub(carry_val);
self.carry =
self.accumulator < (self.index_registers[reg as usize] + carry_val);
self.accumulator = result & 0x0F;
}
self.program_counter.inc();
@ -870,14 +876,16 @@ impl Intel4004 {
// Logic Instructions
Instruction::Ral => {
let new_carry = (self.accumulator & 0x08) != 0;
self.accumulator = ((self.accumulator << 1) | (if self.carry { 1 } else { 0 })) & 0x0F;
self.accumulator =
((self.accumulator << 1) | (if self.carry { 1 } else { 0 })) & 0x0F;
self.carry = new_carry;
self.program_counter.inc();
}
Instruction::Rar => {
let new_carry = (self.accumulator & 0x01) != 0;
self.accumulator = ((self.accumulator >> 1) | (if self.carry { 0x08 } else { 0 })) & 0x0F;
self.accumulator =
((self.accumulator >> 1) | (if self.carry { 0x08 } else { 0 })) & 0x0F;
self.carry = new_carry;
self.program_counter.inc();
}
@ -947,22 +955,22 @@ impl Intel4004 {
Instruction::Jcn(condition, addr) => {
let should_jump = match condition {
0x0 => !self.carry, // JNT (Jump if no carry)
0x1 => self.carry, // JC (Jump if carry)
0x2 => self.accumulator == 0, // JZ (Jump if zero)
0x3 => self.accumulator != 0, // JNZ (Jump if not zero)
0x4 => true, // JUN (Jump unconditional)
0x5 => false, // Always false
0x6 => true, // Always true
0x7 => false, // Always false
0x8 => true, // Always true
0x9 => false, // Always false
0xA => true, // Always true
0xB => false, // Always false
0xC => true, // Always true
0xD => false, // Always false
0xE => true, // Always true
0xF => false, // Always false
0x0 => !self.carry, // JNT (Jump if no carry)
0x1 => self.carry, // JC (Jump if carry)
0x2 => self.accumulator == 0, // JZ (Jump if zero)
0x3 => self.accumulator != 0, // JNZ (Jump if not zero)
0x4 => true, // JUN (Jump unconditional)
0x5 => false, // Always false
0x6 => true, // Always true
0x7 => false, // Always false
0x8 => true, // Always true
0x9 => false, // Always false
0xA => true, // Always true
0xB => false, // Always false
0xC => true, // Always true
0xD => false, // Always false
0xE => true, // Always true
0xF => false, // Always false
_ => false,
};
@ -1042,7 +1050,8 @@ impl Intel4004 {
// Increment Register Instructions
Instruction::Inc(reg) => {
if reg < 16 {
self.index_registers[reg as usize] = (self.index_registers[reg as usize] + 1) & 0x0F;
self.index_registers[reg as usize] =
(self.index_registers[reg as usize] + 1) & 0x0F;
}
self.program_counter.inc();
}

View file

@ -5,9 +5,8 @@ use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component, RunnableComponent};
use crate::components::common::intel_400x::{
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins,
Intel400xDataBus, Intel400xResetHandling, Intel400xTimingState,
MemoryState, TimingState
Intel400xAddressHandling, Intel400xClockHandling, Intel400xControlPins, Intel400xDataBus,
Intel400xResetHandling, Intel400xTimingState, MemoryState, TimingState,
};
use crate::pin::{Pin, PinValue};
@ -49,7 +48,6 @@ enum IoMode {
Bidirectional, // I/O pins bidirectional (not used in 4001)
}
impl Intel400xClockHandling for Intel4001 {
fn get_base(&self) -> &BaseComponent {
&self.base
@ -396,8 +394,12 @@ impl Intel4001 {
/// Hardware-accurate: Φ2 is when peripherals drive data, so we handle data driving
/// Focus: Data driving operations
fn handle_memory_data_operations(&mut self) {
println!("DEBUG: {} - handle_memory_data_operations: state={:?}, address_ready={}",
self.base.name(), self.memory_state, self.full_address_ready);
println!(
"DEBUG: {} - handle_memory_data_operations: state={:?}, address_ready={}",
self.base.name(),
self.memory_state,
self.full_address_ready
);
match self.memory_state {
MemoryState::Idle => {
// During data phase, idle state means tri-state the bus
@ -408,28 +410,43 @@ impl Intel4001 {
MemoryState::AddressPhase => {
// Address phase should be handled by Φ1, not Φ2
// Tri-state bus during wrong phase
println!("DEBUG: {} - In AddressPhase during Φ2, tri-stating", self.base.name());
println!(
"DEBUG: {} - In AddressPhase during Φ2, tri-stating",
self.base.name()
);
self.tri_state_data_bus();
}
MemoryState::WaitLatency => {
// Address latched, waiting for access latency
// Check if latency has elapsed and we can transition to data phase
println!("DEBUG: {} - In WaitLatency, checking latency", self.base.name());
println!(
"DEBUG: {} - In WaitLatency, checking latency",
self.base.name()
);
self.handle_latency_wait();
// If we transitioned to DriveData, handle data driving
if self.memory_state == MemoryState::DriveData {
println!("DEBUG: {} - Transitioned to DriveData, calling handle_data_driving", self.base.name());
println!(
"DEBUG: {} - Transitioned to DriveData, calling handle_data_driving",
self.base.name()
);
self.handle_data_driving();
} else {
println!("DEBUG: {} - Still in WaitLatency after handle_latency_wait", self.base.name());
println!(
"DEBUG: {} - Still in WaitLatency after handle_latency_wait",
self.base.name()
);
}
}
MemoryState::DriveData => {
// Latency elapsed, drive data on bus during Φ2
// Data will remain on bus until Φ2 falling edge
println!("DEBUG: {} - In DriveData state, calling handle_data_driving", self.base.name());
println!(
"DEBUG: {} - In DriveData state, calling handle_data_driving",
self.base.name()
);
self.handle_data_driving();
}
}
@ -474,7 +491,9 @@ impl Intel4001 {
} else if self.address_low_nibble.is_none() {
// Second cycle: latch low nibble (bits 3-0) and transition to latency wait
self.address_low_nibble = Some(nibble);
if let Some(address) = self.assemble_full_address(self.address_high_nibble, self.address_low_nibble) {
if let Some(address) =
self.assemble_full_address(self.address_high_nibble, self.address_low_nibble)
{
self.last_address = address;
self.start_latency_wait();
}
@ -493,16 +512,27 @@ impl Intel4001 {
fn handle_latency_wait(&mut self) {
if let Some(latch_time) = self.address_latch_time {
let elapsed = latch_time.elapsed();
println!("DEBUG: {} - handle_latency_wait: elapsed={:?}, access_time={:?}, ready={}",
self.base.get_name(), elapsed, self.access_time, self.full_address_ready);
println!(
"DEBUG: {} - handle_latency_wait: elapsed={:?}, access_time={:?}, ready={}",
self.base.get_name(),
elapsed,
self.access_time,
self.full_address_ready
);
if elapsed >= self.access_time {
// Latency elapsed, transition to data driving
// Data will be driven on next Φ2 rising edge
println!("DEBUG: {} - Latency elapsed, transitioning to DriveData", self.base.get_name());
println!(
"DEBUG: {} - Latency elapsed, transitioning to DriveData",
self.base.get_name()
);
self.start_data_driving();
}
} else {
println!("DEBUG: {} - handle_latency_wait: no latch_time set", self.base.get_name());
println!(
"DEBUG: {} - handle_latency_wait: no latch_time set",
self.base.get_name()
);
}
}
@ -520,8 +550,14 @@ impl Intel4001 {
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
println!("DEBUG: {} - handle_data_driving: SYNC={}, CM={}, CI={}, Address_Ready={}",
self.base.name(), sync, chip_select, io_select, self.full_address_ready);
println!(
"DEBUG: {} - handle_data_driving: SYNC={}, CM={}, CI={}, Address_Ready={}",
self.base.name(),
sync,
chip_select,
io_select,
self.full_address_ready
);
// Memory read: CM=1 (chip_select), CI=0 (!io_select), valid address
if sync && chip_select && !io_select && self.full_address_ready {
@ -530,12 +566,21 @@ impl Intel4001 {
let address = self.last_address;
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
println!("DEBUG: {} - All conditions met, driving data 0x{:x} to address 0x{:x}", self.base.name(), data, address);
println!(
"DEBUG: {} - All conditions met, driving data 0x{:x} to address 0x{:x}",
self.base.name(),
data,
address
);
self.write_data_bus(data);
// Note: Don't call return_to_idle() here - wait for Φ2 falling edge
} else {
// Invalid address, tri-state
println!("DEBUG: {} - Invalid address 0x{:x}, tri-stating", self.base.name(), address);
println!(
"DEBUG: {} - Invalid address 0x{:x}, tri-stating",
self.base.name(),
address
);
self.tri_state_data_bus();
}
} else {
@ -682,9 +727,16 @@ impl Intel4001 {
/// 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: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}",
test_name, self.memory_state, self.address_high_nibble, self.address_low_nibble,
self.last_address, self.full_address_ready, self.address_latch_time);
println!(
"{} - State: {:?}, High: {:?}, Low: {:?}, Address: 0x{:x}, Ready: {}, Latch time: {:?}",
test_name,
self.memory_state,
self.address_high_nibble,
self.address_low_nibble,
self.last_address,
self.full_address_ready,
self.address_latch_time
);
}
}
@ -886,7 +938,7 @@ mod tests {
assert_eq!(rom.memory_state, MemoryState::WaitLatency);
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
}
#[test]
@ -1024,7 +1076,7 @@ mod tests {
// Advance simulated time to exceed access_time (1ns)
rom.debug_state_transitions("BEFORE_SLEEP");
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
rom.debug_state_transitions("AFTER_SLEEP");
// --- Data phase ---
@ -1049,14 +1101,20 @@ mod tests {
// Debug: Check what data the ROM thinks it should drive
let address = rom.last_address;
let expected_data = rom.memory[address as usize];
println!("DEBUG: ROM address 0x{:x}, data at address: 0x{:x}", address, expected_data);
println!(
"DEBUG: ROM address 0x{:x}, data at address: 0x{:x}",
address, expected_data
);
// Debug: Check the bit extraction logic
let bit0 = (expected_data >> 0) & 1;
let bit1 = (expected_data >> 1) & 1;
let bit2 = (expected_data >> 2) & 1;
let bit3 = (expected_data >> 3) & 1;
println!("DEBUG: Bit extraction - bit0: {}, bit1: {}, bit2: {}, bit3: {}", bit0, bit1, bit2, bit3);
println!(
"DEBUG: Bit extraction - bit0: {}, bit1: {}, bit2: {}, bit3: {}",
bit0, bit1, bit2, bit3
);
// Debug: Check actual pin states and settlement status
let d0_actual = d0_pin.lock().unwrap().read();
@ -1074,10 +1132,22 @@ mod tests {
let d2_settled = d2_pin.lock().unwrap().is_settled();
let d3_settled = d3_pin.lock().unwrap().is_settled();
println!("DEBUG: D0 actual: {:?} (bit 0: {}), immediate: {:?}, settled: {}", d0_actual, bit0, d0_immediate, d0_settled);
println!("DEBUG: D1 actual: {:?} (bit 1: {}), immediate: {:?}, settled: {}", d1_actual, bit1, d1_immediate, d1_settled);
println!("DEBUG: D2 actual: {:?} (bit 2: {}), immediate: {:?}, settled: {}", d2_actual, bit2, d2_immediate, d2_settled);
println!("DEBUG: D3 actual: {:?} (bit 3: {}), immediate: {:?}, settled: {}", d3_actual, bit3, d3_immediate, d3_settled);
println!(
"DEBUG: D0 actual: {:?} (bit 0: {}), immediate: {:?}, settled: {}",
d0_actual, bit0, d0_immediate, d0_settled
);
println!(
"DEBUG: D1 actual: {:?} (bit 1: {}), immediate: {:?}, settled: {}",
d1_actual, bit1, d1_immediate, d1_settled
);
println!(
"DEBUG: D2 actual: {:?} (bit 2: {}), immediate: {:?}, settled: {}",
d2_actual, bit2, d2_immediate, d2_settled
);
println!(
"DEBUG: D3 actual: {:?} (bit 3: {}), immediate: {:?}, settled: {}",
d3_actual, bit3, d3_immediate, d3_settled
);
// Debug: Check actual pin states immediately after data driving
let d0_actual = d0_pin.lock().unwrap().read();
@ -1085,7 +1155,10 @@ mod tests {
let d2_actual = d2_pin.lock().unwrap().read();
let d3_actual = d3_pin.lock().unwrap().read();
println!("DEBUG: After data driving - D0: {:?}, D1: {:?}, D2: {:?}, D3: {:?}", d0_actual, d1_actual, d2_actual, d3_actual);
println!(
"DEBUG: After data driving - D0: {:?}, D1: {:?}, D2: {:?}, D3: {:?}",
d0_actual, d1_actual, d2_actual, d3_actual
);
// Debug: Check what the ROM thinks it should be driving
let expected_data = 0x12;
@ -1093,14 +1166,17 @@ mod tests {
let bit1 = (expected_data >> 1) & 1;
let bit2 = (expected_data >> 2) & 1;
let bit3 = (expected_data >> 3) & 1;
println!("DEBUG: Expected bits - bit0: {}, bit1: {}, bit2: {}, bit3: {}", bit0, bit1, bit2, bit3);
println!(
"DEBUG: Expected bits - bit0: {}, bit1: {}, bit2: {}, bit3: {}",
bit0, bit1, bit2, bit3
);
// Verify data is driven on bus while Φ2 is high: 0x12 = 0001 0010
// 0x12 = 18 decimal = 0001 0010 binary
// For 4-bit data bus: D3=0, D2=0, D1=1, D0=0
// Small delay to allow pin state to settle
std::thread::sleep(Duration::from_micros(1));
thread::sleep(Duration::from_micros(1));
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::Low); // Bit 0 = 0
assert_eq!(d1_pin.lock().unwrap().read(), PinValue::High); // Bit 1 = 1
@ -1198,7 +1274,7 @@ mod tests {
rom.update(); // falling edge
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
// --- Data phase ---
phi2_pin
@ -1307,7 +1383,7 @@ mod tests {
rom.update(); // rising edge for low nibble
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
// Data phase
phi2_pin
@ -1383,7 +1459,7 @@ mod tests {
rom.update(); // rising edge for low nibble
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
// Data phase
phi2_pin
@ -1419,10 +1495,6 @@ mod tests {
let sync_pin = rom.get_pin("SYNC").unwrap();
let cm_pin = rom.get_pin("CM").unwrap();
let ci_pin = rom.get_pin("CI").unwrap();
let d0_pin = rom.get_pin("D0").unwrap();
let d1_pin = rom.get_pin("D1").unwrap();
let d2_pin = rom.get_pin("D2").unwrap();
let d3_pin = rom.get_pin("D3").unwrap();
// Set up memory read operation: SYNC=1, chip_select=1, io_select=0 (memory access)
{
@ -1740,7 +1812,7 @@ mod tests {
rom.update(); // rising edge for low nibble
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
// Data phase
phi2_pin
@ -1815,7 +1887,7 @@ mod tests {
rom.update(); // rising edge for low nibble
// Advance simulated time to exceed access_time (1ns)
std::thread::sleep(Duration::from_nanos(2));
thread::sleep(Duration::from_nanos(2));
// Data phase
phi2_pin

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]

View file

@ -21,14 +21,14 @@ use crate::pin::{Pin, PinValue};
/// - Output behavior matches 4001/4002 I/O persistence model
pub struct Intel4003 {
base: BaseComponent,
shift_register: [u8; 10], // 10-bit shift register (stored as 10 bytes for simplicity)
output_latch: [u8; 10], // 10-bit output latch for parallel output
serial_input: u8, // Serial input data (4-bit)
access_time: Duration, // Shift register access latency (200ns typical)
shift_register: [u8; 10], // 10-bit shift register (stored as 10 bytes for simplicity)
output_latch: [u8; 10], // 10-bit output latch for parallel output
serial_input: u8, // Serial input data (4-bit)
access_time: Duration, // Shift register access latency (200ns typical)
// Clock edge detection
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
address_high_nibble: Option<u8>, // High nibble of 8-bit address
@ -36,7 +36,7 @@ pub struct Intel4003 {
full_address_ready: bool, // Whether complete address is assembled
// Shift register operation state machine
shift_state: ShiftState, // Current state of shift operation
shift_state: ShiftState, // Current state of shift operation
address_latch_time: Option<Instant>, // Timestamp when address was latched
}
@ -74,21 +74,21 @@ impl Intel4003 {
// - CM: Chip select (must be HIGH for shift register access)
// - RESET: Clears internal state
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"O0", "O1", "O2", "O3", "O4", "O5", "O6", "O7", "O8", "O9", // 10 output pins
"SYNC", // Sync signal
"CM", // Chip Select
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
"D0", "D1", "D2", "D3", // Data/Address pins
"O0", "O1", "O2", "O3", "O4", "O5", "O6", "O7", "O8", "O9", // 10 output pins
"SYNC", // Sync signal
"CM", // Chip Select
"RESET", // Reset
"PHI1", // Clock phase 1
"PHI2", // Clock phase 2
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
Intel4003 {
base: BaseComponent::new(name, pins),
shift_register: [0u8; 10], // 10-bit shift register
output_latch: [0u8; 10], // 10-bit output latch
shift_register: [0u8; 10], // 10-bit shift register
output_latch: [0u8; 10], // 10-bit output latch
serial_input: 0,
access_time: Duration::from_nanos(access_time_ns),
@ -167,7 +167,8 @@ impl Intel4003 {
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);
}
}
}
@ -178,7 +179,10 @@ impl Intel4003 {
for i in 0..10 {
if let Ok(pin) = self.base.get_pin(&format!("O{}", i)) {
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,
);
}
}
}
@ -274,7 +278,7 @@ impl Intel4003 {
/// Assemble complete 8-bit address from high and low nibbles
fn assemble_full_address(&mut self) {
if let (Some(high), Some(low)) = (self.address_high_nibble, self.address_low_nibble) {
if let (Some(_high), Some(_low)) = (self.address_high_nibble, self.address_low_nibble) {
// Assemble 8-bit address: (high << 4) | low
self.full_address_ready = true;
self.address_latch_time = Some(Instant::now());
@ -400,29 +404,41 @@ impl Intel4003 {
if sync && chip_select && self.full_address_ready {
// Read serial input data from data bus
let serial_data = self.read_data_bus();
println!("DEBUG: 4003 shift operation - serial_data = 0x{:x}", serial_data);
println!(
"DEBUG: 4003 shift operation - serial_data = 0x{:x}",
serial_data
);
// Shift in the new bits (serial input)
// For 4003, we shift in 4 bits at a time
// First, shift existing bits right by 4 positions
for j in (4..10).rev() {
self.shift_register[j] = self.shift_register[j-4];
self.shift_register[j] = self.shift_register[j - 4];
}
// Then insert the 4 new bits into positions 0-3
for i in 0..4 {
let bit = (serial_data >> i) & 1;
self.shift_register[i] = bit;
println!("DEBUG: 4003 shift step {} - inserted bit {} at position {}", i, bit, i);
println!(
"DEBUG: 4003 shift step {} - inserted bit {} at position {}",
i, bit, i
);
}
// Update output latch with new shift register contents
self.output_latch.copy_from_slice(&self.shift_register);
self.update_output_pins();
println!("DEBUG: 4003 shift register after operation: {:?}", self.shift_register);
println!(
"DEBUG: 4003 shift register after operation: {:?}",
self.shift_register
);
let (high, low) = self.get_shift_register();
println!("DEBUG: 4003 get_shift_register() returns high=0x{:x}, low=0x{:x}", high, low);
println!(
"DEBUG: 4003 get_shift_register() returns high=0x{:x}, low=0x{:x}",
high, low
);
self.shift_state = ShiftState::OutputData;
} else {
@ -436,7 +452,6 @@ impl Intel4003 {
// This state maintains the output until next operation
}
/// Get the current shift register value
/// Returns: 10-bit value as a tuple (high_byte, low_2_bits)
pub fn get_shift_register(&self) -> (u8, u8) {
@ -593,9 +608,9 @@ mod tests {
let mut d2_guard = d2_pin.lock().unwrap();
let mut d3_guard = d3_pin.lock().unwrap();
d0_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Bit 0 = 1
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0
d1_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 1 = 0
d2_guard.set_driver(Some("TEST".to_string()), PinValue::High); // Bit 2 = 1
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0
d3_guard.set_driver(Some("TEST".to_string()), PinValue::Low); // Bit 3 = 0
}
// Set control pins for shift operation
@ -613,7 +628,10 @@ mod tests {
// Verify shift operation occurred
let (high, low) = sr.get_shift_register();
println!("DEBUG: After shift operation - high=0x{:x}, low=0x{:x}", high, low);
println!(
"DEBUG: After shift operation - high=0x{:x}, low=0x{:x}",
high, low
);
println!("DEBUG: Shift register contents: {:?}", sr.shift_register);
// The shift register should now contain the shifted data
@ -623,7 +641,7 @@ mod tests {
// high byte: 01010101 (0x55) - this is correct, the first 4 bits are 0101 (0x05)
// low byte: 00000001 (0x01) - this is the remaining 2 bits from the original pattern
assert_eq!(high, 0x55); // First 8 bits should be 01010101
assert_eq!(low, 0x01); // Lower 2 bits should be 01
assert_eq!(low, 0x01); // Lower 2 bits should be 01
}
#[test]
@ -670,4 +688,4 @@ mod tests {
assert_eq!(fast_sr.get_access_time(), 1);
assert_eq!(fast_sr.name(), "FAST_SHIFT");
}
}
}