Added I/O demo system
CLean up of unused components and functions
This commit is contained in:
parent
77ede95bce
commit
7d06837608
20 changed files with 714 additions and 1924 deletions
|
|
@ -20,12 +20,15 @@ use crate::pin::{Pin, PinValue};
|
|||
/// - SYNC logic simplified for unit testing (may need refinement for CPU integration)
|
||||
pub struct Intel4001 {
|
||||
base: BaseComponent,
|
||||
memory: Vec<u8>, // 256-byte ROM storage
|
||||
last_address: u16, // Last accessed memory address
|
||||
access_time: Duration, // ROM access latency (500ns)
|
||||
output_latch: u8, // 4-bit output latch for I/O operations
|
||||
input_latch: u8, // 4-bit input latch for I/O operations
|
||||
io_mode: IoMode, // Current I/O mode configuration
|
||||
memory: Vec<u8>, // 256-byte ROM storage
|
||||
last_address: u16, // Last accessed memory address
|
||||
access_time: Duration, // ROM access latency (500ns)
|
||||
output_latch: u8, // 4-bit output latch for I/O operations
|
||||
input_latch: u8, // 4-bit input latch for I/O operations
|
||||
io_mode: IoMode, // Current I/O mode configuration
|
||||
io_ports: [u8; 4], // 4 I/O ports (4 bits each) - matches datasheet
|
||||
io_direction: [IoDirection; 4], // I/O direction for each port
|
||||
selected_io_port: Option<usize>, // Currently selected I/O port (0-3)
|
||||
// Clock edge detection
|
||||
prev_phi1: PinValue, // Previous Φ1 clock state for edge detection
|
||||
prev_phi2: PinValue, // Previous Φ2 clock state for edge detection
|
||||
|
|
@ -42,12 +45,19 @@ pub struct Intel4001 {
|
|||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
/// I/O mode configuration for the 4001 ROM
|
||||
/// Determines how the I/O pins are configured during read/write operations
|
||||
enum IoMode {
|
||||
pub enum IoMode {
|
||||
Input, // I/O pins configured as inputs (RDM instruction)
|
||||
Output, // I/O pins configured as outputs (WRM instruction)
|
||||
Bidirectional, // I/O pins bidirectional (not used in 4001)
|
||||
}
|
||||
|
||||
/// I/O direction for each I/O port
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
pub enum IoDirection {
|
||||
Input, // Port configured as input
|
||||
Output, // Port configured as output
|
||||
}
|
||||
|
||||
impl Intel400xClockHandling for Intel4001 {
|
||||
fn get_base(&self) -> &BaseComponent {
|
||||
&self.base
|
||||
|
|
@ -85,6 +95,13 @@ impl Intel400xResetHandling for Intel4001 {
|
|||
self.address_low_nibble = None;
|
||||
self.address_high_nibble = None;
|
||||
self.full_address_ready = false;
|
||||
|
||||
// Reset I/O state
|
||||
self.io_ports = [0u8; 4];
|
||||
self.io_direction = [IoDirection::Input; 4];
|
||||
self.selected_io_port = None;
|
||||
self.io_mode = IoMode::Input; // Reset I/O mode to Input
|
||||
self.tri_state_io_pins();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -182,6 +199,9 @@ impl Intel4001 {
|
|||
output_latch: 0,
|
||||
input_latch: 0,
|
||||
io_mode: IoMode::Input,
|
||||
io_ports: [0u8; 4], // Initialize all I/O ports to 0
|
||||
io_direction: [IoDirection::Input; 4], // Default all ports to input
|
||||
selected_io_port: None, // No I/O port selected initially
|
||||
prev_phi1: PinValue::Low,
|
||||
prev_phi2: PinValue::Low,
|
||||
address_latch_time: None,
|
||||
|
|
@ -254,22 +274,6 @@ impl Intel4001 {
|
|||
data
|
||||
}
|
||||
|
||||
fn write_io_pins(&self, data: u8) {
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
let bit_value = (data >> i) & 1;
|
||||
let pin_value = if bit_value == 1 {
|
||||
PinValue::High
|
||||
} else {
|
||||
PinValue::Low
|
||||
};
|
||||
pin_guard.set_driver(Some(self.base.name()), pin_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -285,16 +289,19 @@ impl Intel4001 {
|
|||
// I/O access: CM=1 (chip_select), CI=1 (io_select)
|
||||
let sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
|
||||
let io_select = self.read_ci_pin();
|
||||
|
||||
if sync && chip_select && !io_select {
|
||||
// Start memory address phase on Φ1 rising edge
|
||||
// Start memory address phase on Φ1 rising edge (ROM access)
|
||||
self.start_memory_address_phase();
|
||||
}
|
||||
|
||||
// Handle I/O operations (higher priority than memory operations)
|
||||
// I/O operations: CM=1 (chip_select), CI=1 (io_select)
|
||||
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
|
||||
self.handle_io_operation();
|
||||
if chip_select && io_select {
|
||||
self.handle_io_operation();
|
||||
}
|
||||
|
||||
// Handle memory address phase operations during Φ1
|
||||
self.handle_memory_address_operations();
|
||||
|
|
@ -334,33 +341,139 @@ impl Intel4001 {
|
|||
}
|
||||
}
|
||||
|
||||
/// Handle I/O port operations
|
||||
/// Set I/O mode for WRM/RDM instruction handling
|
||||
/// Parameters: mode - I/O mode (Input for RDM, Output for WRM)
|
||||
pub fn set_io_mode(&mut self, mode: IoMode) {
|
||||
self.io_mode = mode;
|
||||
}
|
||||
|
||||
/// Get current I/O mode
|
||||
/// Returns: Current I/O mode
|
||||
pub fn get_io_mode(&self) -> IoMode {
|
||||
self.io_mode
|
||||
}
|
||||
|
||||
/// Handle I/O port operations according to Intel 4001 datasheet
|
||||
/// Hardware: I/O operations occur during I/O instructions (WRM/RDM)
|
||||
/// Direction determined by CPU instruction, not manual configuration
|
||||
/// Note: I/O pins remain driven after WRM until another WRM or reset.
|
||||
/// This matches real 4001 hardware where I/O ports are latched.
|
||||
/// Hardware deviation: Model writes IO latch to D0-D3 for test convenience,
|
||||
/// but real hardware reads I/O pins directly.
|
||||
///
|
||||
/// I/O Port Addressing (datasheet):
|
||||
/// - I/O ports are addressed using the lower 2 bits of the address
|
||||
/// - Port 0: Address 0x00, Port 1: Address 0x01, etc.
|
||||
/// - Each port is 4 bits wide (matches the data bus width)
|
||||
///
|
||||
/// I/O Latch Persistence:
|
||||
/// - Output latch persists after WRM until next WRM or reset
|
||||
/// - Input latch overwrites every RDM (I/O input pins are live reads)
|
||||
fn handle_io_operation(&mut self) {
|
||||
let _sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
|
||||
let io_select = self.read_ci_pin(); // Proper CI pin for I/O vs ROM selection
|
||||
|
||||
// I/O operations occur when: CM=1 (chip_select), CI=1 (io_select)
|
||||
// Note: SYNC is NOT required for I/O - only marks instruction fetch (ROM access)
|
||||
if chip_select && io_select {
|
||||
let _address_low = self.read_data_bus(); // I/O port address (lower 4 bits)
|
||||
let port_address = self.read_data_bus() & 0x03; // Lower 2 bits for port selection (0-3)
|
||||
self.selected_io_port = Some(port_address as usize);
|
||||
|
||||
// Real 4001 I/O behavior based on CPU instructions:
|
||||
// RDM instruction: Read from I/O port
|
||||
self.input_latch = self.read_io_pins();
|
||||
// Hardware deviation: Write to D0-D3 for test convenience
|
||||
self.write_data_bus(self.input_latch);
|
||||
self.io_mode = IoMode::Input;
|
||||
// Determine operation type based on current instruction phase
|
||||
// In real hardware, this would be determined by the CPU instruction (WRM vs RDM)
|
||||
match self.io_mode {
|
||||
IoMode::Input => {
|
||||
// RDM instruction: Read from I/O port
|
||||
if let Some(port) = self.selected_io_port {
|
||||
self.input_latch = self.read_io_port(port);
|
||||
// Write input data to data bus for CPU to read
|
||||
self.write_data_bus(self.input_latch);
|
||||
}
|
||||
}
|
||||
IoMode::Output => {
|
||||
// WRM instruction: Write to I/O port
|
||||
if let Some(port) = self.selected_io_port {
|
||||
let data = self.read_data_bus();
|
||||
self.write_io_port(port, data);
|
||||
}
|
||||
}
|
||||
IoMode::Bidirectional => {
|
||||
// Not used in 4001, treat as input
|
||||
if let Some(port) = self.selected_io_port {
|
||||
self.input_latch = self.read_io_port(port);
|
||||
self.write_data_bus(self.input_latch);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Read the CI (I/O select) pin state
|
||||
/// Hardware: CI pin distinguishes I/O vs ROM access when chip selected (CM=1)
|
||||
fn read_ci_pin(&self) -> bool {
|
||||
if let Ok(pin) = self.base.get_pin("CI") {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
pin_guard.read() == PinValue::High
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
/// Read from a specific I/O port
|
||||
/// Parameters: port - I/O port number (0-3)
|
||||
/// Returns: 4-bit value from the I/O port
|
||||
fn read_io_port(&self, port: usize) -> u8 {
|
||||
if port < 4 {
|
||||
match self.io_direction[port] {
|
||||
IoDirection::Input => {
|
||||
// Read from actual I/O pins
|
||||
self.read_io_pins()
|
||||
}
|
||||
IoDirection::Output => {
|
||||
// Read from output latch (for read-back capability)
|
||||
self.io_ports[port]
|
||||
}
|
||||
}
|
||||
} else {
|
||||
0
|
||||
}
|
||||
}
|
||||
|
||||
/// Write to a specific I/O port
|
||||
/// Parameters: port - I/O port number (0-3), data - 4-bit data to write
|
||||
fn write_io_port(&mut self, port: usize, data: u8) {
|
||||
if port < 4 {
|
||||
self.io_ports[port] = data & 0x0F;
|
||||
self.io_direction[port] = IoDirection::Output;
|
||||
self.update_io_pins();
|
||||
}
|
||||
}
|
||||
|
||||
/// Update I/O pins based on current port values and directions
|
||||
/// Hardware: Only output ports drive the I/O pins
|
||||
fn update_io_pins(&self) {
|
||||
for i in 0..4 {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
match self.io_direction[i] {
|
||||
IoDirection::Output => {
|
||||
// Drive pin with output port value
|
||||
let bit_value = (self.io_ports[i]) & 1;
|
||||
let pin_value = if bit_value == 1 {
|
||||
PinValue::High
|
||||
} else {
|
||||
PinValue::Low
|
||||
};
|
||||
pin_guard.set_driver(Some(self.base.name()), pin_value);
|
||||
}
|
||||
IoDirection::Input => {
|
||||
// Input mode - tri-state the pin
|
||||
pin_guard.set_driver(Some(self.base.name()), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -553,7 +666,7 @@ impl Intel4001 {
|
|||
fn handle_data_driving(&mut self) {
|
||||
let sync = self.read_sync_pin();
|
||||
let chip_select = self.read_cm_rom_pin();
|
||||
let io_select = self.read_reset_pin(); // Using reset pin as CI for now
|
||||
let io_select = self.read_ci_pin();
|
||||
|
||||
println!(
|
||||
"DEBUG: {} - handle_data_driving: SYNC={}, CM={}, CI={}, Address_Ready={}",
|
||||
|
|
@ -729,6 +842,61 @@ impl Intel4001 {
|
|||
self.input_latch
|
||||
}
|
||||
|
||||
/// Get I/O port value
|
||||
/// Parameters: port - I/O port number (0-3)
|
||||
/// Returns: Some(4-bit value) if port valid, None if invalid
|
||||
pub fn get_io_port(&self, port: usize) -> Option<u8> {
|
||||
if port < 4 {
|
||||
Some(self.io_ports[port])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set I/O port value (for testing/debugging)
|
||||
/// Parameters: port - I/O port number (0-3), data - 4-bit data to set
|
||||
/// Returns: Ok(()) on success, Err(String) on failure
|
||||
pub fn set_io_port(&mut self, port: usize, data: u8) -> Result<(), String> {
|
||||
if port < 4 {
|
||||
self.io_ports[port] = data & 0x0F;
|
||||
self.io_direction[port] = IoDirection::Output;
|
||||
self.update_io_pins();
|
||||
Ok(())
|
||||
} else {
|
||||
Err("I/O port number out of range (0-3)".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get I/O port direction
|
||||
/// Parameters: port - I/O port number (0-3)
|
||||
/// Returns: Some(direction) if port valid, None if invalid
|
||||
pub fn get_io_direction(&self, port: usize) -> Option<IoDirection> {
|
||||
if port < 4 {
|
||||
Some(self.io_direction[port])
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// Set I/O port direction (for testing/debugging)
|
||||
/// Parameters: port - I/O port number (0-3), direction - I/O direction
|
||||
/// Returns: Ok(()) on success, Err(String) on failure
|
||||
pub fn set_io_direction(&mut self, port: usize, direction: IoDirection) -> Result<(), String> {
|
||||
if port < 4 {
|
||||
self.io_direction[port] = direction;
|
||||
self.update_io_pins();
|
||||
Ok(())
|
||||
} else {
|
||||
Err("I/O port number out of range (0-3)".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
/// Get currently selected I/O port
|
||||
/// Returns: Some(port_number) if a port is selected, None otherwise
|
||||
pub fn get_selected_io_port(&self) -> Option<usize> {
|
||||
self.selected_io_port
|
||||
}
|
||||
|
||||
/// 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) {
|
||||
|
|
@ -756,6 +924,15 @@ impl std::fmt::Display for IoMode {
|
|||
}
|
||||
}
|
||||
|
||||
impl std::fmt::Display for IoDirection {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
IoDirection::Input => write!(f, "Input"),
|
||||
IoDirection::Output => write!(f, "Output"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
|
@ -841,7 +1018,6 @@ mod tests {
|
|||
assert_eq!(rom.read_rom(0x01).unwrap(), 0x34);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_memory_operation_start() {
|
||||
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
||||
|
|
@ -879,9 +1055,6 @@ mod tests {
|
|||
println!("Memory state after Φ1 rising: {:?}", rom.memory_state);
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_clock_driven_memory_fetch() {
|
||||
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1); // 1ns for fast testing
|
||||
|
|
@ -1020,7 +1193,6 @@ mod tests {
|
|||
assert_eq!(d3_pin.lock().unwrap().read(), PinValue::HighZ);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_state_transitions() {
|
||||
let mut rom = Intel4001::new_with_access_time("StateTestROM".to_string(), 1);
|
||||
|
|
@ -1078,4 +1250,94 @@ mod tests {
|
|||
assert!(rom.load_rom_data(vec![0x12], 256).is_err()); // Invalid - out of bounds
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_io_port_operations() {
|
||||
let mut rom = Intel4001::new("IOTestROM".to_string());
|
||||
|
||||
// Test I/O port initialization
|
||||
assert_eq!(rom.get_io_port(0).unwrap(), 0);
|
||||
assert_eq!(rom.get_io_port(1).unwrap(), 0);
|
||||
assert_eq!(rom.get_io_port(2).unwrap(), 0);
|
||||
assert_eq!(rom.get_io_port(3).unwrap(), 0);
|
||||
|
||||
// Test I/O direction initialization
|
||||
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Input);
|
||||
assert_eq!(rom.get_io_direction(1).unwrap(), IoDirection::Input);
|
||||
|
||||
// Test setting I/O port values
|
||||
assert!(rom.set_io_port(0, 0x05).is_ok());
|
||||
assert_eq!(rom.get_io_port(0).unwrap(), 0x05);
|
||||
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Output);
|
||||
|
||||
// Test invalid port access
|
||||
assert!(rom.set_io_port(4, 0x0F).is_err());
|
||||
assert!(rom.get_io_port(4).is_none());
|
||||
assert!(rom.set_io_direction(4, IoDirection::Output).is_err());
|
||||
assert!(rom.get_io_direction(4).is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_io_mode_control() {
|
||||
let mut rom = Intel4001::new("ModeTestROM".to_string());
|
||||
|
||||
// Test initial I/O mode
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Input);
|
||||
|
||||
// Test setting I/O modes
|
||||
rom.set_io_mode(IoMode::Output);
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Output);
|
||||
|
||||
rom.set_io_mode(IoMode::Input);
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Input);
|
||||
|
||||
rom.set_io_mode(IoMode::Bidirectional);
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Bidirectional);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_io_port_selection() {
|
||||
let rom = Intel4001::new("SelectionTestROM".to_string());
|
||||
|
||||
// Initially no port selected
|
||||
assert_eq!(rom.get_selected_io_port(), None);
|
||||
|
||||
// Test I/O port selection during operation
|
||||
// This would be set during actual I/O operations
|
||||
// For testing, we can verify the field exists and can be modified
|
||||
assert_eq!(rom.selected_io_port, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_io_direction_display() {
|
||||
assert_eq!(IoDirection::Input.to_string(), "Input");
|
||||
assert_eq!(IoDirection::Output.to_string(), "Output");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_intel4001_reset_io_behavior() {
|
||||
let mut rom = Intel4001::new("ResetIOTestROM".to_string());
|
||||
|
||||
// Set up some I/O state
|
||||
rom.set_io_port(0, 0x05).unwrap();
|
||||
rom.set_io_port(1, 0x0A).unwrap();
|
||||
rom.set_io_direction(0, IoDirection::Output).unwrap();
|
||||
rom.set_io_direction(1, IoDirection::Output).unwrap();
|
||||
rom.set_io_mode(IoMode::Output);
|
||||
|
||||
// Verify state is set
|
||||
assert_eq!(rom.get_io_port(0).unwrap(), 0x05);
|
||||
assert_eq!(rom.get_io_port(1).unwrap(), 0x0A);
|
||||
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Output);
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Output);
|
||||
|
||||
// Perform reset
|
||||
rom.perform_reset();
|
||||
|
||||
// Verify I/O state is cleared
|
||||
assert_eq!(rom.get_io_port(0).unwrap(), 0);
|
||||
assert_eq!(rom.get_io_port(1).unwrap(), 0);
|
||||
assert_eq!(rom.get_io_direction(0).unwrap(), IoDirection::Input);
|
||||
assert_eq!(rom.get_io_mode(), IoMode::Input);
|
||||
assert_eq!(rom.get_selected_io_port(), None);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue