feat(cpu): add A1/A2/A3 address cycle timing and fix pin propagation
This commit is contained in:
parent
3cdf99630c
commit
20884b57c3
17 changed files with 4165 additions and 270 deletions
|
|
@ -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);
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue