feat(cpu): add A1/A2/A3 address cycle timing and fix pin propagation

This commit is contained in:
Jan-Peter von Hunnius 2026-02-24 18:58:14 +01:00
parent 3cdf99630c
commit 20884b57c3
17 changed files with 4165 additions and 270 deletions

5
.idea/.gitignore generated vendored Normal file
View file

@ -0,0 +1,5 @@
# Default ignored files
/shelf/
/workspace.xml
# Editor-based HTTP Client requests
/httpRequests/

8
.idea/modules.xml generated Normal file
View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/.idea/rusty_emu.iml" filepath="$PROJECT_DIR$/.idea/rusty_emu.iml" />
</modules>
</component>
</project>

View file

@ -0,0 +1,18 @@
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Terminal Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="buildProfileId" value="dev" />
<option name="command" value="run --package rusty_emu --bin rusty_emu" />
<option name="workingDirectory" value="file://$PROJECT_DIR$" />
<envs />
<option name="emulateTerminal" value="false" />
<option name="channel" value="DEFAULT" />
<option name="requiredFeatures" value="true" />
<option name="allFeatures" value="true" />
<option name="withSudo" value="false" />
<option name="buildTarget" value="REMOTE" />
<option name="backtrace" value="SHORT" />
<option name="isRedirectInput" value="false" />
<option name="redirectInputPath" value="" />
<method v="2" />
</configuration>
</component>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="Console Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --package rusty_emu --bin rusty_emu" />
<option name="env">
<map />
</option>
<option name="is_redirect_input" value="false" />
<option name="is_redirect_input_value" value="" />
<option name="redirect_input_path" value="" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<option name="with_sudo" value="false" />
<option name="cargoArguments" value="-- --console --system basic" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="Console Debug" run_configuration_type="CargoCommandRunConfiguration" />
</method>
</configuration>
</component>

View file

@ -0,0 +1,19 @@
<?xml version="1.0" encoding="UTF-8"?>
<component name="ProjectRunConfigurationManager">
<configuration default="false" name="GUI Debug" type="CargoCommandRunConfiguration" factoryName="Cargo Command">
<option name="channel" value="DEFAULT" />
<option name="command" value="run --package rusty_emu --bin rusty_emu" />
<option name="env">
<map />
</option>
<option name="is_redirect_input" value="false" />
<option name="is_redirect_input_value" value="" />
<option name="redirect_input_path" value="" />
<option name="workingDirectory" value="$PROJECT_DIR$" />
<option name="with_sudo" value="false" />
<option name="cargoArguments" value="-- --gui --system basic" />
<method v="2">
<option name="RunConfigurationTask" enabled="true" run_configuration_name="GUI Debug" run_configuration_type="CargoCommandRunConfiguration" />
</method>
</configuration>
</component>

12
.idea/rusty_emu.iml generated Normal file
View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="EMPTY_MODULE" version="4">
<component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/tests" isTestSource="true" />
<excludeFolder url="file://$MODULE_DIR$/target" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
</component>
</module>

6
.idea/vcs.xml generated Normal file
View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
</component>
</project>

11
.kilocode/mcp.json Normal file
View file

@ -0,0 +1,11 @@
{
"mcpServers": {
"jetbrains": {
"command": "npx",
"args": [
"-y",
"@jetbrains/mcp-proxy"
]
}
}
}

3880
Cargo.lock generated Normal file

File diff suppressed because it is too large Load diff

View file

@ -21,7 +21,7 @@
},
"CLOCK": {
"single": {
"component_type": "generic_clock",
"component_type": "two_phase_clock",
"name": "CLOCK_750KHZ",
"properties": {
"frequency": 750000
@ -51,7 +51,7 @@
"connection_type": "clock",
"source": {
"component": "CLOCK_750KHZ",
"pin": "CLK"
"pin": "PHI1"
},
"targets": [
{
@ -72,7 +72,7 @@
"connection_type": "clock",
"source": {
"component": "CLOCK_750KHZ",
"pin": "CLK"
"pin": "PHI2"
},
"targets": [
{
@ -178,7 +178,7 @@
"connection_type": "control",
"source": {
"component": "CPU_4004",
"pin": "CM_ROM"
"pin": "CM"
},
"targets": [
{
@ -191,7 +191,7 @@
"connection_type": "control",
"source": {
"component": "CPU_4004",
"pin": "CM_RAM"
"pin": "CM"
},
"targets": [
{

View file

@ -46,19 +46,19 @@ impl TimingConstants {
/// This is the standard access time for ROM and RAM operations
/// based on the original Intel 4004 timing specifications.
pub const DEFAULT_ACCESS_TIME: Duration = Duration::from_nanos(500); // 500ns default
/// Fast access time for shift register operations (200ns)
///
/// The Intel 4003 shift register has faster access times compared to
/// ROM and RAM components due to its simpler design.
pub const FAST_ACCESS_TIME: Duration = Duration::from_nanos(200); // 200ns for shift registers
/// Address setup time before clock edge (100ns)
///
/// Minimum time that address must be stable before the clock edge
/// to ensure proper latching by the component.
pub const ADDRESS_SETUP: Duration = Duration::from_nanos(100); // Address setup time
/// Data valid delay after clock edge (200ns)
///
/// Time from clock edge until data is guaranteed to be valid on the bus
@ -352,19 +352,19 @@ pub enum Intel400xComponentType {
/// The 4001 is a 256-byte ROM chip that stores program instructions.
/// It responds to address transmission and provides read-only data access.
Intel4001,
/// Intel 4002 RAM (Random Access Memory) chip
///
/// The 4002 is a 320-bit RAM chip organized as four registers of 20 bits each.
/// It supports both read and write operations and includes I/O ports.
Intel4002,
/// Intel 4003 Shift Register chip
///
/// The 4003 is a 10-bit static shift register used for I/O expansion.
/// It operates differently from memory chips and ignores address transmission.
Intel4003,
/// Intel 4004 CPU (Central Processing Unit) chip
///
/// The 4004 is the 4-bit microprocessor that controls the system.
@ -383,25 +383,24 @@ pub enum Intel400xComponentType {
#[derive(Debug, Clone, Copy, PartialEq)]
pub enum Intel400xUnifiedState {
// Common states (shared across all components)
/// Idle state - component is waiting for operation
///
/// In this state, the component is not actively processing any operation
/// and is waiting for a chip select or other control signal.
Idle,
/// Address phase - latching address from CPU
///
/// The component is receiving address information in two 4-bit phases
/// (high nibble first, then low nibble) from the CPU.
AddressPhase,
/// Wait latency - waiting for access time to elapse
///
/// After receiving the complete address, the component waits for the
/// required access time before proceeding with the operation.
WaitLatency,
/// Drive data - transferring data on the bus
///
/// The component is actively driving data onto the bus (for read operations)
@ -409,21 +408,20 @@ pub enum Intel400xUnifiedState {
DriveData,
// Component-specific states
// Intel 4002 (RAM) specific states
/// Read data state - RAM is reading from memory
///
/// The RAM component is reading data from the specified address and
/// preparing to drive it onto the data bus.
ReadData,
/// Write data state - RAM is writing to memory
///
/// The RAM component is accepting data from the bus and writing it
/// to the specified memory location.
WriteData,
/// Output port state - RAM is driving I/O ports
///
/// The RAM component is outputting data to its I/O ports rather than
@ -431,13 +429,12 @@ pub enum Intel400xUnifiedState {
OutputPort,
// Intel 4003 (Shift Register) specific states
/// Shift data state - shifting register contents
///
/// The 4003 shift register is shifting its contents serially,
/// either in or out depending on the operation.
ShiftData,
/// Load data state - loading parallel data
///
/// The 4003 shift register is loading data in parallel format
@ -445,13 +442,12 @@ pub enum Intel400xUnifiedState {
LoadData,
// Intel 4004 (CPU) specific states
/// Fetch instruction state - CPU is fetching instruction
///
/// The CPU is fetching the next instruction from memory and
/// preparing to decode and execute it.
FetchInstruction,
/// Execute instruction state - CPU is executing instruction
///
/// The CPU is executing the currently fetched instruction and
@ -474,79 +470,79 @@ pub enum Intel400xEvent {
/// Generated when the first phase of the two-phase clock transitions
/// from low to high. Used for timing control in various operations.
ClockPhi1Rising,
/// Φ1 (Phase 1) clock falling edge
///
/// Generated when the first phase of the two-phase clock transitions
/// from high to low. Often triggers address latching operations.
ClockPhi1Falling,
/// Φ2 (Phase 2) clock rising edge
///
/// Generated when the second phase of the two-phase clock transitions
/// from low to high. Used for data transfer operations.
ClockPhi2Rising,
/// Φ2 (Phase 2) clock falling edge
///
/// Generated when the second phase of the two-phase clock transitions
/// from high to low. Often triggers state transitions.
ClockPhi2Falling,
/// SYNC signal becomes active (high)
///
/// The SYNC signal indicates the start of a new instruction cycle.
/// Components use this to synchronize their operations with the CPU.
SyncActive,
/// SYNC signal becomes inactive (low)
///
/// The SYNC signal indicates the end of an instruction cycle.
/// Components may use this to reset their internal state.
SyncInactive,
/// Chip select becomes active
///
/// Generated when a component is selected for an operation.
/// This typically initiates a transition from Idle to AddressPhase.
ChipSelect,
/// Chip select becomes inactive
///
/// Generated when a component is deselected.
/// This typically forces a transition back to the Idle state.
ChipDeselect,
/// RESET signal becomes active (high)
///
/// Generated when the system reset signal is asserted.
/// All components transition to their initial state.
ResetActive,
/// RESET signal becomes inactive (low)
///
/// Generated when the system reset signal is released.
/// Components begin normal operation from their reset state.
ResetInactive,
/// Address latching complete
///
/// Generated when both high and low address nibbles have been latched.
/// Triggers transition from AddressPhase to WaitLatency.
AddressComplete,
/// Access latency has elapsed
///
/// Generated when the required access time has passed after address latching.
/// Triggers transition from WaitLatency to operation-specific states.
LatencyElapsed,
/// Data is ready for transfer
///
/// Generated when data is available for reading or writing.
/// Used in component-specific data transfer operations.
DataReady,
/// Operation is complete
///
/// Generated when the current operation has finished successfully.
@ -779,22 +775,22 @@ pub trait Intel400xComponentBehavior {
pub struct Intel400xStateMachine {
/// The type of component this state machine is managing
component_type: Intel400xComponentType,
/// The current state of the state machine
pub(crate) current_state: Intel400xUnifiedState,
/// The previous state before the last transition
previous_state: Intel400xUnifiedState,
/// The high nibble (bits 7-4) of the currently latching address
address_high_nibble: Option<u8>,
/// The low nibble (bits 3-0) of the currently latching address
address_low_nibble: Option<u8>,
/// The time when the address was completely latched
address_latch_time: Option<Instant>,
/// The access time to wait after address latching before data access
access_time: Duration,
}
@ -1514,7 +1510,7 @@ mod tests {
// First need to transition to AddressPhase state
process_event_direct(&mut sm, Intel400xEvent::ChipSelect);
assert_eq!(sm.get_state(), Intel400xUnifiedState::AddressPhase);
// Now transition to WaitLatency state
process_event_direct(&mut sm, Intel400xEvent::AddressComplete);
assert_eq!(sm.get_state(), Intel400xUnifiedState::WaitLatency);

View file

@ -20,9 +20,19 @@ enum InstructionPhase {
#[derive(Debug, Clone, Copy, PartialEq)]
enum MemoryState {
Idle, // No memory operation in progress
AddressPhase, // Currently latching address nibbles
WaitLatency, // Address latched, waiting for access time
DriveData, // Latency elapsed, driving data on bus
AddressPhase, // Currently driving address nibbles to bus
WaitLatency, // Address sent, waiting for ROM to respond
DriveData, // Data ready, reading from bus
}
/// Address cycle counter for Intel 4004 timing
/// The Intel 4004 sends address in multiple cycles (A1, A2, A3)
#[derive(Debug, Clone, Copy, PartialEq)]
enum AddressCycle {
None, // No address cycle in progress
A1, // First address cycle - driving high nibble
A2, // Second address cycle - driving low nibble
A3, // Third address cycle - reading data from ROM
}
/// Intel 4004 instruction set enumeration
@ -126,8 +136,9 @@ pub struct Intel4004 {
// 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
address_cycle: AddressCycle, // Current address cycle (A1, A2, A3)
address_high_nibble: Option<u8>, // High nibble of 8-bit address (for ROM, this is PC[7:4])
address_low_nibble: Option<u8>, // Low nibble of 8-bit address (for ROM, this is PC[3:0])
full_address_ready: bool, // Whether complete address is assembled
// Instruction execution state
@ -186,6 +197,7 @@ impl Intel4004 {
// Memory operation state machine
memory_state: MemoryState::Idle,
address_cycle: AddressCycle::None,
address_high_nibble: None,
address_low_nibble: None,
full_address_ready: false,
@ -230,6 +242,7 @@ impl Intel4004 {
// Reset memory operation state
self.memory_state = MemoryState::Idle;
self.address_cycle = AddressCycle::None;
self.address_latch_time = None;
self.address_high_nibble = None;
self.address_low_nibble = None;
@ -379,24 +392,27 @@ impl Intel4004 {
/// Handle Φ1 rising edge - Address and control phase
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
///
/// Intel 4004 Address Timing:
/// - A1 cycle: CPU drives PC high nibble (bits 7-4) onto data bus
/// - A2 cycle: CPU drives PC low nibble (bits 3-0) onto data bus
/// - A3 cycle: CPU reads data from ROM (instruction byte)
fn handle_phi1_rising(&mut self) {
// Check for memory operation start on Φ1 rising edge with SYNC high
let (sync, cm, _) = self.read_control_pins();
if sync
&& cm
&& self.memory_state == MemoryState::Idle
&& self.instruction_phase == InstructionPhase::Fetch
&& self.address_cycle == AddressCycle::None
{
// Start memory address phase on Φ1 rising edge
println!(
"DEBUG: [CPU] Starting memory operation - SYNC: {}, CM: {}, Phase: {:?}",
sync, cm, self.instruction_phase
);
self.start_memory_address_phase();
}
// Handle memory address phase operations during Φ1
self.handle_memory_address_operations();
// Drive address nibbles onto the bus during A1 and A2 cycles
self.handle_address_driving();
}
/// Handle Φ1 falling edge - End of address phase
@ -412,6 +428,9 @@ impl Intel4004 {
/// Handle Φ2 falling edge - End of data phase
fn handle_phi2_falling(&mut self) {
// Advance address cycle state machine at end of each clock cycle
self.advance_address_cycle();
// Handle memory cleanup operations when Φ2 falls
self.handle_memory_cleanup_operations();
}
@ -444,6 +463,7 @@ impl Intel4004 {
// Reset memory operation state
self.memory_state = MemoryState::Idle;
self.address_cycle = AddressCycle::None;
self.address_latch_time = None;
self.address_high_nibble = None;
self.address_low_nibble = None;
@ -467,37 +487,88 @@ impl Intel4004 {
}
}
/// Start memory address phase
/// Start memory address phase - begin A1 cycle
fn start_memory_address_phase(&mut self) {
self.memory_state = MemoryState::AddressPhase;
self.address_cycle = AddressCycle::A1; // Start with A1 cycle
self.full_address_ready = false;
}
/// Handle memory address-related operations during Φ1
fn handle_memory_address_operations(&mut self) {
/// Handle driving address nibbles onto the data bus during A1/A2 cycles
/// This is the CPU ACTIVELY DRIVING the bus (not reading from it)
///
/// IMPORTANT: Each address cycle (A1, A2) spans a full PHI1+PHI2 clock period
/// - A1: Drive high nibble during PHI1, hold during PHI2, transition to A2 on next PHI1
/// - A2: Drive low nibble during PHI1, hold during PHI2, transition to A3 on next PHI1
/// - A3: Tri-state bus, ROM drives data
fn handle_address_driving(&mut self) {
match self.memory_state {
MemoryState::AddressPhase => {
let pc = self.program_counter.value();
match self.address_cycle {
AddressCycle::A1 => {
// A1 cycle: Drive PC high nibble (bits 7-4) onto data bus
let pc_high = ((pc >> 4) & 0x0F) as u8;
self.write_data_bus(pc_high);
// NOTE: We do NOT transition to A2 here!
// The transition happens in handle_phi2_falling after this cycle completes
}
AddressCycle::A2 => {
// A2 cycle: Drive PC low nibble (bits 3-0) onto data bus
let pc_low = (pc & 0x0F) as u8;
self.write_data_bus(pc_low);
// NOTE: We do NOT transition to A3 here!
// The transition happens in handle_phi2_falling after this cycle completes
}
AddressCycle::A3 | AddressCycle::None => {
// A3 or None: Should not be in AddressPhase, tri-state bus
self.tri_state_data_bus();
}
}
}
MemoryState::Idle => {
// In idle state, ensure bus is tri-stated
self.tri_state_data_bus();
}
MemoryState::AddressPhase => {
// Currently latching address nibbles during Φ1
self.handle_address_latching();
}
MemoryState::WaitLatency => {
// Address latched, waiting for access latency
// Address sent, waiting for ROM to respond
// Tri-state the bus so ROM can drive it
self.tri_state_data_bus();
self.handle_latency_wait();
}
MemoryState::DriveData => {
// Data phase should be handled by Φ2, not Φ1
// Data phase - ROM should be driving the bus, CPU reads
self.tri_state_data_bus();
}
}
}
/// Advance address cycle state machine at end of clock cycle (PHI2 falling)
/// This ensures each address phase lasts a complete clock cycle
fn advance_address_cycle(&mut self) {
if self.memory_state == MemoryState::AddressPhase {
match self.address_cycle {
AddressCycle::A1 => {
// A1 complete, advance to A2
self.address_cycle = AddressCycle::A2;
}
AddressCycle::A2 => {
// A2 complete, advance to A3 (wait for ROM data)
self.address_cycle = AddressCycle::A3;
self.memory_state = MemoryState::WaitLatency;
self.address_latch_time = Some(Instant::now());
}
_ => {}
}
}
}
/// Handle memory data-related operations during Φ2
fn handle_memory_data_operations(&mut self) {
match self.memory_state {
@ -537,41 +608,6 @@ impl Intel4004 {
}
}
/// Handle address nibble latching during address phase
fn handle_address_latching(&mut self) {
let nibble = self.read_data_bus();
if self.address_high_nibble.is_none() {
// First cycle: latch high nibble (bits 7-4)
self.address_high_nibble = Some(nibble);
} 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);
self.assemble_full_address();
self.start_latency_wait();
}
}
/// 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) {
// Assemble 8-bit address: (high << 4) | low
self.address_latch = (high << 4) | low;
self.full_address_ready = true;
self.address_latch_time = Some(Instant::now());
// Clear nibble storage for next address
self.address_high_nibble = None;
self.address_low_nibble = None;
}
}
/// Transition to latency wait state
fn start_latency_wait(&mut self) {
self.memory_state = MemoryState::WaitLatency;
self.address_latch_time = Some(Instant::now());
}
/// Handle latency timing during wait state
fn handle_latency_wait(&mut self) {
if let Some(latch_time) = self.address_latch_time {
@ -596,12 +632,6 @@ impl Intel4004 {
// All conditions met: drive data on bus
let data = self.data_latch;
self.write_data_bus(data);
if self.cycle_count % 1000 == 0 {
// Log every 1000 cycles
println!("DEBUG: [{}] CPU State | PC: 0x{:03X} | Cycles: {} | ACC: 0x{:X} | SYNC: {} | CM: {} | Ready: {}",
self.base.name(), self.program_counter.value(), self.cycle_count, self.accumulator, sync, cm, self.full_address_ready);
}
} else {
// Conditions not met, tri-state
self.tri_state_data_bus();
@ -611,6 +641,7 @@ impl Intel4004 {
/// Reset memory state machine to idle
fn return_to_idle(&mut self) {
self.memory_state = MemoryState::Idle;
self.address_cycle = AddressCycle::None;
self.address_latch_time = None;
self.address_high_nibble = None;
self.address_low_nibble = None;
@ -997,67 +1028,44 @@ impl Intel4004 {
// I/O and RAM Instructions
Instruction::Wrm => {
// Write accumulator to RAM at current RAM address
// This would interface with RAM chips - for now, just log
println!(
"DEBUG: [CPU] WRM - Write ACC 0x{:X} to RAM address 0x{:02X}",
self.accumulator, self.address_latch
);
// This would interface with RAM chips
self.program_counter.inc();
}
Instruction::Wmp => {
// Write memory pointer - set RAM address from accumulator
self.address_latch = self.accumulator;
println!(
"DEBUG: [CPU] WMP - Set RAM address to 0x{:02X}",
self.address_latch
);
self.program_counter.inc();
}
Instruction::Wrr => {
// Write ROM port and register - handled by memory interface
println!("DEBUG: [CPU] WRR - Write to ROM port");
self.program_counter.inc();
}
Instruction::Wpm => {
// Write program memory - handled by memory interface
println!("DEBUG: [CPU] WPM - Write to program memory");
self.program_counter.inc();
}
Instruction::Adm => {
// Add from memory - add RAM data to accumulator
// This would read from RAM and add to accumulator
println!(
"DEBUG: [CPU] ADM - Add from RAM address 0x{:02X}",
self.address_latch
);
self.program_counter.inc();
}
Instruction::Sbm => {
// Subtract from memory - subtract RAM data from accumulator
println!(
"DEBUG: [CPU] SBM - Subtract from RAM address 0x{:02X}",
self.address_latch
);
self.program_counter.inc();
}
Instruction::Rdm => {
// Read memory - read RAM data to accumulator
println!(
"DEBUG: [CPU] RDM - Read from RAM address 0x{:02X}",
self.address_latch
);
self.program_counter.inc();
}
Instruction::Rdr => {
// Read ROM port and register - handled by memory interface
println!("DEBUG: [CPU] RDR - Read from ROM port");
self.program_counter.inc();
}
@ -1195,14 +1203,7 @@ impl Intel4004 {
}
if self.instruction_phase == InstructionPhase::Execute {
let old_pc = self.program_counter.value();
self.execute_instruction();
let new_pc = self.program_counter.value();
println!(
"DEBUG: [TEST] Single Execute | PC: 0x{:03X} -> 0x{:03X} | ACC: 0x{:X}",
old_pc, new_pc, self.accumulator
);
}
}
@ -1219,12 +1220,6 @@ impl Intel4004 {
self.index_registers[i] = byte & 0x0F;
}
}
println!(
"DEBUG: [{}] Loaded test program: {:02X?}",
self.base.name(),
program
);
}
}
@ -1440,58 +1435,21 @@ impl Component for Intel4004 {
self.prev_phi2 = phi2;
if phi1_rising {
// Reduced debug output - only show occasional cycles
// Use atomic counter instead of static mutable for thread safety
use std::sync::atomic::{AtomicU32, Ordering};
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
if count % 1000 == 0 {
let (sync, cm, _) = self.read_control_pins();
println!("DEBUG: [CPU] Φ1 Rising Edge - PC: 0x{:03X}, ACC: 0x{:X}, Phase: {:?}, SYNC: {}, CM: {} (cycle {})",
self.program_counter.value(), self.accumulator, self.instruction_phase, sync, cm, count);
}
self.handle_phi1_rising();
}
if phi1_falling {
// Φ1 Falling Edge: End of address phase
// Reduced debug output - only show occasional cycles
// Use atomic counter instead of static mutable for thread safety
use std::sync::atomic::{AtomicU32, Ordering};
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
if count % 1000 == 0 {
println!("DEBUG: [CPU] Φ1 Falling Edge (cycle {})", count);
}
self.handle_phi1_falling();
}
if phi2_rising {
// Φ2 Rising Edge: Data phase (peripherals drive bus) - handle data operations
// Reduced debug output - only show occasional cycles
// Use atomic counter instead of static mutable for thread safety
use std::sync::atomic::{AtomicU32, Ordering};
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
if count % 1000 == 0 {
println!(
"DEBUG: [CPU] Φ2 Rising Edge - Memory State: {:?} (cycle {})",
self.memory_state, count
);
}
self.handle_phi2_rising();
}
if phi2_falling {
// Φ2 Falling Edge: End of data phase - tri-state bus and return to idle
// Reduced debug output - only show occasional cycles
// Use atomic counter instead of static mutable for thread safety
use std::sync::atomic::{AtomicU32, Ordering};
static CYCLE_COUNT: AtomicU32 = AtomicU32::new(0);
let count = CYCLE_COUNT.fetch_add(1, Ordering::Relaxed);
if count % 1000 == 0 {
println!("DEBUG: [CPU] Φ2 Falling Edge (cycle {})", count);
}
self.handle_phi2_falling();
}
@ -1503,18 +1461,10 @@ impl Component for Intel4004 {
{
self.set_sync(true);
self.set_cm(true);
println!("DEBUG: [CPU] Setting signals - SYNC: HIGH, CM: HIGH");
} else {
self.set_sync(false);
println!("DEBUG: [CPU] Setting SYNC: LOW");
}
println!(
"DEBUG: [CPU] Instruction phase: {:?}, PC: 0x{:03X}, State: {:?}",
self.instruction_phase,
self.program_counter.value(),
self.memory_state
);
match self.instruction_phase {
InstructionPhase::Fetch => {
// Check if we're waiting for an operand (two-instruction format)
@ -1531,14 +1481,10 @@ impl Component for Intel4004 {
Instruction::JunHigh(addr_high) => {
let addr = ((addr_high as u16) << 4) | (operand as u16);
self.current_op = Instruction::Jun(addr);
println!("DEBUG: [CPU] Fetched JUN operand 0x{:02X} -> complete address 0x{:03X}",
operand, addr);
}
Instruction::JmsHigh(addr_high) => {
let addr = ((addr_high as u16) << 4) | (operand as u16);
self.current_op = Instruction::Jms(addr);
println!("DEBUG: [CPU] Fetched JMS operand 0x{:02X} -> complete address 0x{:03X}",
operand, addr);
}
_ => {}
}
@ -1549,13 +1495,8 @@ impl Component for Intel4004 {
} else {
// Normal instruction fetch
let (sync, cm, _) = self.read_control_pins();
println!(
"DEBUG: [CPU] Normal fetch - SYNC: {}, CM: {}, State: {:?}",
sync, cm, self.memory_state
);
if sync && cm {
// Memory access - fetch instruction
println!("DEBUG: [CPU] Fetching instruction from memory - PC: 0x{:03X}, State: {:?}", self.program_counter.value(), self.memory_state);
if self.memory_state == MemoryState::DriveData {
let instruction = self.read_data_bus();
self.current_instruction = instruction;
@ -1567,17 +1508,12 @@ impl Component for Intel4004 {
// Two-instruction format - wait for operand
self.current_op = decoded_op;
// Don't advance PC yet - wait for operand
println!("DEBUG: [CPU] Fetched two-instruction opcode 0x{:02X} from PC 0x{:03X}",
instruction, self.program_counter.value());
}
_ => {
// Single instruction - execute immediately
self.current_op = decoded_op;
self.instruction_phase = InstructionPhase::Execute;
self.program_counter.inc();
println!("DEBUG: [CPU] Fetched single instruction 0x{:02X} from PC 0x{:03X} | ACC: 0x{:X}",
instruction, self.program_counter.value(), self.accumulator);
}
}
}
@ -1594,19 +1530,7 @@ impl Component for Intel4004 {
}
_ => {
// Execute the current instruction
let old_pc = self.program_counter.value();
self.execute_instruction();
let new_pc = self.program_counter.value();
// Debug: Show instruction execution details (reduced frequency)
static mut EXEC_COUNT: u32 = 0;
unsafe {
EXEC_COUNT += 1;
if EXEC_COUNT % 10 == 0 {
println!("DEBUG: [CPU] Executed {:?} | PC: 0x{:03X} -> 0x{:03X} | ACC: 0x{:X} | RAM_Ready: {}",
self.current_op, old_pc, new_pc, self.accumulator, self.full_address_ready);
}
}
self.instruction_phase = InstructionPhase::Fetch;
}

View file

@ -391,11 +391,12 @@ impl Intel4001 {
/// Hardware: Φ1 high = CPU drives bus with address/control information
/// Memory operations start when SYNC goes high during Φ1 rising edge
/// Focus: Address latching, reset, I/O operations
///
/// Intel 4004 Address Timing (CPU drives address during PHI1):
/// - A1 cycle: CPU drives PC high nibble on PHI1 rising
/// - A2 cycle: CPU drives PC low nibble on PHI1 rising
/// - ROM latches each nibble when it sees them on the bus during AddressPhase
fn handle_phi1_rising(&mut self) {
println!(
"DEBUG: [ROM] Φ1 rising edge detected, current state: {:?}",
self.state_machine.get_state()
);
// Handle system reset first (highest priority)
self.handle_reset("RESET");
@ -407,17 +408,23 @@ impl Intel4001 {
let sync = self.read_sync_pin();
let chip_select = self.read_cm_rom_pin();
let io_select = self.read_ci_pin();
println!(
"DEBUG: [ROM] Φ1 rising - SYNC: {}, CM: {}, CI: {}",
sync, chip_select, io_select
);
if sync && chip_select && !io_select {
// Start memory address phase on Φ1 rising edge (ROM access)
// Only start a new memory operation if we're currently idle
if self.state_machine.get_state() == Intel400xUnifiedState::Idle {
self.process_chip_select();
println!("DEBUG: [ROM] Starting memory operation");
}
// CRITICAL: Latch address nibbles during PHI1 when CPU is driving them
// The CPU drives address nibbles on PHI1 rising, so we must latch here
if self.state_machine.get_state() == Intel400xUnifiedState::AddressPhase {
let nibble = self.read_data_bus() & 0x0F;
if self.state_machine.handle_address_nibble(nibble) {
// Address is complete (both nibbles latched), transition to latency wait
self.process_address_complete();
}
}
}
@ -440,10 +447,6 @@ impl Intel4001 {
/// Hardware: Φ2 high = Peripherals drive bus with data
/// Focus: Data driving operations
fn handle_phi2_rising(&mut self) {
println!(
"DEBUG: [ROM] Φ2 rising edge detected, current state: {:?}",
self.state_machine.get_state()
);
// Execute current state logic
self.execute_state_machine();
}
@ -453,10 +456,6 @@ impl Intel4001 {
/// Data is held on bus during Φ2 high, tri-stated when Φ2 falls
/// Focus: Clean up and return to idle state
fn handle_phi2_falling(&mut self) {
println!(
"DEBUG: [ROM] Φ2 falling edge detected, current state: {:?}",
self.state_machine.get_state()
);
match self.state_machine.get_state() {
Intel400xUnifiedState::DriveData => {
// End of data phase - tri-state bus and return to idle
@ -629,10 +628,6 @@ impl Intel4001 {
/// Focus: Address latching and setup operations
#[allow(dead_code)] // This method is kept for compatibility but not currently used
fn handle_memory_address_operations(&mut self) {
println!(
"DEBUG: handle_memory_address_operations called, current state: {:?}",
self.state_machine.get_state()
);
match self.state_machine.get_state() {
Intel400xUnifiedState::Idle => {
// In idle state, ensure bus is tri-stated
@ -706,10 +701,6 @@ impl Intel4001 {
/// Handle address nibble latching during address phase
/// Hardware: CPU provides 8-bit address as two 4-bit nibbles
fn handle_address_latching(&mut self) {
println!(
"DEBUG: handle_address_latching called, current state: {:?}",
self.state_machine.current_state
);
// Use common address latching functionality
let nibble = self.read_data_bus() & 0x0F; // Only 4 bits are valid for address nibbles
@ -723,15 +714,10 @@ impl Intel4001 {
/// Hardware: ROM needs 500ns to access data after address is latched
#[allow(dead_code)] // This method is kept for compatibility with legacy code
fn handle_latency_wait(&mut self) {
println!(
"DEBUG: handle_latency_wait called, state: {:?}",
self.state_machine.current_state
);
// This is now handled by state machine
if self.state_machine.is_latency_elapsed() {
// Latency elapsed, transition to data driving
// Data will be driven on next Φ2 rising edge
println!("DEBUG: Latency elapsed, transitioning to DriveData state");
self.process_latency_elapsed();
}
}
@ -748,23 +734,14 @@ impl Intel4001 {
let address = self.state_machine.get_full_address().unwrap_or(0) as u16;
if (address as usize) < self.memory.len() {
let data = self.memory[address as usize];
println!(
"DEBUG: [ROM] Driving data 0x{:02X} from address 0x{:02X} onto bus",
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: [ROM] Invalid address 0x{:02X}, tri-stating bus",
address
);
self.tri_state_data_bus();
}
} else {
// Address not ready, tri-state
println!("DEBUG: [ROM] Address not ready, tri-stating bus");
self.tri_state_data_bus();
}
}
@ -849,9 +826,7 @@ impl Component for Intel4001 {
/// Main update cycle - handles clock edge detection and operation dispatch
/// Hardware: Responds to Φ1 and Φ2 clock edges from CPU
fn update(&mut self) {
println!("DEBUG: Update called");
if !self.is_running() {
println!("DEBUG: Component not running, returning");
return;
}
// Handle both rising and falling edges for proper two-phase operation
@ -869,7 +844,6 @@ impl Component for Intel4001 {
let mut events = Vec::new();
if phi1_rising {
println!("DEBUG: phi1_rising detected");
events.push(Intel400xEvent::ClockPhi1Rising);
}

View file

@ -70,6 +70,7 @@ pub struct Pin {
last_update: Instant,
settlement_time: Duration,
connected_pins: Vec<Arc<Mutex<Pin>>>,
is_propagating: bool,
}
impl Pin {
pub fn new(name: String) -> Self {
@ -80,6 +81,7 @@ impl Pin {
last_update: Instant::now(),
settlement_time: Duration::from_nanos(10), // 10ns settlement time
connected_pins: Vec::new(),
is_propagating: false,
}
}
@ -162,27 +164,45 @@ impl Pin {
&self.connected_pins
}
pub fn propagate(&self) {
for connected_pin in &self.connected_pins {
if let Ok(mut pin) = connected_pin.lock() {
// Copy our drivers to the connected pin (simulate electrical connection)
let mut new_drivers = self.drivers.clone();
pub fn propagate(&mut self) {
if self.is_propagating {
return;
}
// Merge with existing drivers on the connected pin
for (driver, value) in &pin.drivers {
new_drivers.insert(driver.clone(), *value);
}
self.is_propagating = true;
pin.drivers = new_drivers;
pin.last_update = Instant::now();
pin.recalculate_value();
// Use a scope guard to ensure the flag is reset even on panic
// This prevents the pin from becoming permanently unable to propagate
struct PropagationGuard<'a> {
flag: &'a mut bool,
}
impl<'a> Drop for PropagationGuard<'_> {
fn drop(&mut self) {
*self.flag = false;
}
}
let _guard = PropagationGuard {
flag: &mut self.is_propagating,
};
for connected_pin in &self.connected_pins {
if let Ok(mut pin) = connected_pin.lock() {
// Copy all our drivers with their strengths
for (driver_name, (value, strength)) in &self.drivers {
pin.set_driver_with_strength(Some(driver_name.clone()), *value, *strength);
}
}
}
// Guard will be dropped here, resetting is_propagating to false
}
fn recalculate_value(&mut self) {
if self.drivers.is_empty() {
self.settled_value = PinValue::HighZ;
self.last_update = Instant::now();
return;
}
@ -195,6 +215,7 @@ impl Pin {
}
if max_strength == DriveStrength::HighImpedance {
self.settled_value = PinValue::HighZ;
self.last_update = Instant::now();
return;
}
@ -215,6 +236,8 @@ impl Pin {
self.settled_value = PinValue::HighZ;
}
self.last_update = Instant::now();
// Propagate to connected pins
self.propagate();
}

View file

@ -353,4 +353,4 @@ fn test_intel_4001_state_machine_reset() {
// Should have returned to Idle state
assert_eq!(rom.get_timing_state(), TimingState::Idle);
}
}

View file

@ -544,4 +544,4 @@ fn test_complete_state_machine_progression() {
println!("✅ Output Port Operation completed");
println!("✅ Complete unified state machine progression test passed!");
}
}

View file

@ -250,4 +250,4 @@ fn test_intel_4003_reset_functionality() {
remove_pin_driver(&sr, "RESET");
println!("=== Intel 4003 Reset Functionality Test Complete ===");
}
}