Added I/O demo system

CLean up of unused components and functions
This commit is contained in:
Jan-Peter von Hunnius 2025-09-29 21:42:37 +02:00
parent 77ede95bce
commit 7d06837608
20 changed files with 714 additions and 1924 deletions

View file

@ -61,17 +61,6 @@ pub enum RamVariant {
Type2, // 4002-2
}
/// Intel 4002 timing constants (based on datasheet specifications)
/// These represent the actual hardware timing requirements
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 OUTPUT_DISABLE: Duration = Duration::from_nanos(150); // T_OUTPUT_DISABLE
const RAM_ACCESS: Duration = Duration::from_nanos(500); // RAM access time
}
impl Intel400xClockHandling for Intel4002 {
fn get_base(&self) -> &BaseComponent {
&self.base
@ -364,27 +353,6 @@ impl Intel4002 {
(phi1, phi2)
}
fn is_phi1_falling_edge(&self) -> bool {
let (phi1, _) = self.read_clock_pins();
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
}
fn is_phi2_rising_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
}
fn is_phi2_falling_edge(&self) -> bool {
let (_, phi2) = self.read_clock_pins();
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
}
fn update_clock_states(&mut self) {
let (phi1, phi2) = self.read_clock_pins();
self.prev_phi1 = phi1;
self.prev_phi2 = phi2;
}
/// Read all control pins from CPU
/// Returns: (sync, cm, p0, reset)
/// Hardware: Control pins determine operation type and chip state
@ -475,69 +443,6 @@ impl Intel4002 {
}
}
/// Decode RAM address based on Intel 4002 addressing scheme
/// Hardware: Complex addressing with 4 register banks, status characters, and output ports
/// Parameters: address_low - Low nibble from CPU, instruction - Instruction/opcode
/// Returns: (bank_number, effective_address)
fn decode_address(&self, address_low: u8, instruction: u8) -> (u8, u8) {
// 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
// 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
}
// 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
}
(bank, ram_address)
}
/// Check if instruction is related to status character operations
fn is_status_character_instruction(&self, instruction: u8) -> bool {
matches!(instruction, 0x10..=0x13)
}
/// Check if instruction is a RAM-related instruction
fn is_ram_instruction(&self, instruction: u8) -> bool {
matches!(instruction, 0x00..=0x0F | 0x10..=0x17)
}
/// Handle RAM bank selection instructions
/// Hardware: DCL (Designate Command Line) instructions select RAM banks
fn handle_bank_selection(&mut self, instruction: u8) {
match instruction {
// Bank select instructions (DCL)
0xE0..=0xE3 => {
self.bank_select = instruction & 0x03;
}
_ => {}
}
}
/// Handle status character operations
/// Hardware: Status characters are separate 4-bit latches, not part of RAM
fn handle_status_character(&mut self, instruction: u8) {
match instruction {
// Status character load instructions
0xF0..=0xF3 => {
let sc_index = (instruction & 0x03) as usize;
if sc_index < 4 {
// Load status character from input latch into separate latch
self.status_characters[sc_index] = self.input_latch;
}
}
_ => {}
}
}
/// Handle output port operations
/// Hardware: Output ports are separate from RAM, continuously driven
fn handle_output_port_operation(&mut self, port: usize, data: u8) {
@ -689,27 +594,6 @@ impl Intel4002 {
}
}
/// Update timing state with more precise hardware timing
fn update_timing_state(&mut self) {
match self.ram_state {
RamState::AddressPhase => {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= TimingConstants::ADDRESS_SETUP {
self.start_data_operation();
}
}
}
RamState::ReadData => {
if let Some(latch_time) = self.address_latch_time {
if latch_time.elapsed() >= TimingConstants::DATA_VALID {
// Data should be valid now
}
}
}
_ => {}
}
}
/// Transition to data operation state
fn start_data_operation(&mut self) {
// Determine operation type based on control signals and address
@ -803,57 +687,6 @@ impl Intel4002 {
self.current_instruction = 0;
}
/// Check if RAM should drive the bus
fn should_drive_bus(&self) -> bool {
let (sync, _cm, p0, _) = self.read_control_pins();
// Only drive bus during data phase when selected
sync && p0 && self.ram_state == RamState::ReadData
}
/// Update data bus drivers with proper contention prevention
fn update_data_bus_drivers(&self) {
if self.should_drive_bus() {
// Drive bus with RAM data
let data = self.read_ram_data();
self.write_data_bus(data);
} else {
// High impedance when not selected
self.tri_state_data_bus();
}
}
/// Read RAM data at current address
fn read_ram_data(&self) -> u8 {
if self.full_address_ready && (self.last_address as usize) < self.memory.len() {
self.memory[self.last_address as usize]
} else {
0
}
}
/// Handle instruction cycle synchronization
fn handle_instruction_cycle(&mut self) {
let (sync, _cm, _p0, _) = self.read_control_pins();
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();
// Decode instruction to determine if it's for RAM
if self.is_ram_instruction(self.current_instruction) {
self.prepare_ram_operation();
}
}
}
/// Prepare for RAM operation based on instruction
fn prepare_ram_operation(&mut self) {
// This would set up the RAM for the upcoming operation
// based on the decoded instruction
}
/// Handle special RAM instructions (bank select, status characters, etc.)
/// Hardware: These are handled during instruction cycles, not memory cycles
fn handle_special_instructions(&mut self, instruction: u8) {
@ -1358,40 +1191,6 @@ mod tests {
assert_eq!(d0_pin.lock().unwrap().read(), PinValue::HighZ);
}
#[test]
fn test_bank_selection() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Test bank selection instructions
ram.handle_bank_selection(0xE0);
assert_eq!(ram.get_bank_select(), 0);
ram.handle_bank_selection(0xE1);
assert_eq!(ram.get_bank_select(), 1);
ram.handle_bank_selection(0xE2);
assert_eq!(ram.get_bank_select(), 2);
ram.handle_bank_selection(0xE3);
assert_eq!(ram.get_bank_select(), 3);
}
#[test]
fn test_status_character_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
// Set input latch
ram.set_input_latch(0x0A);
// Test status character load
ram.handle_status_character(0xF0);
assert_eq!(ram.get_status_character(0).unwrap(), 0x0A); // Status character 0
ram.set_input_latch(0x0B);
ram.handle_status_character(0xF1);
assert_eq!(ram.get_status_character(1).unwrap(), 0x0B); // Status character 1
}
#[test]
fn test_output_port_operations() {
let mut ram = Intel4002::new("RAM_4002".to_string());
@ -1421,31 +1220,6 @@ mod tests {
assert_eq!(ram.get_input_latch(), 0x07);
}
#[test]
fn test_ram_addressing_scheme() {
let ram = Intel4002::new("RAM_4002".to_string());
// Test address decoding for different instruction types
// For instruction 0x05 with address_low 0x00: should be bank 0, address 0
let (bank, address) = ram.decode_address(0x00, 0x05);
assert_eq!(bank, 0);
assert_eq!(address, 0); // Bank 0, address 0 (instruction 0x05 addresses location 0)
// Test with different address_low value
let (bank, address) = ram.decode_address(0x05, 0x00);
assert_eq!(bank, 0);
assert_eq!(address, 5); // Bank 0, address 5
// Test status character addressing (returns 0xFF marker)
let (_bank, address) = ram.decode_address(0x00, 0x10);
assert_eq!(address, 0xFF); // Status character marker
// Test output port addressing (returns 0xFF marker)
// For output port 0x14, we need to pass 0x14 as address_low
let (_bank, address) = ram.decode_address(0x14, 0x00);
assert_eq!(address, 0xFF); // Output port marker
}
#[test]
fn test_ram_initialization() {
let mut ram = Intel4002::new("RAM_4002".to_string());
@ -1517,33 +1291,4 @@ mod tests {
// Verify output port addressing is set up correctly
// This would require more complex setup in a real implementation
}
#[test]
fn test_bus_contention_prevention() {
let mut ram = Intel4002::new_with_access_time("RAM_4002".to_string(), 1);
// Initially should not drive bus
assert!(!ram.should_drive_bus());
// Set up RAM operation
let sync_pin = ram.get_pin("SYNC").unwrap();
let p0_pin = ram.get_pin("P0").unwrap();
{
let mut sync_guard = sync_pin.lock().unwrap();
let mut p0_guard = p0_pin.lock().unwrap();
sync_guard.set_driver(Some("TEST".to_string()), PinValue::High);
p0_guard.set_driver(Some("TEST".to_string()), PinValue::High);
}
// Start memory operation
ram.update();
// Should still not drive bus until data phase
assert!(!ram.should_drive_bus());
// Test that bus drivers are updated correctly
ram.update_data_bus_drivers();
// In a real test, we would verify the pin states
}
}