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
243
configs/mcs4_io_demo.json
Normal file
243
configs/mcs4_io_demo.json
Normal file
|
|
@ -0,0 +1,243 @@
|
|||
{
|
||||
"name": "MCS-4 I/O Demonstration System",
|
||||
"description": "Intel MCS-4 system demonstrating 4001 I/O functionality",
|
||||
"version": "1.0.0",
|
||||
"metadata": {
|
||||
"rom_size": 256,
|
||||
"ram_size": 80,
|
||||
"cpu_speed": 750000,
|
||||
"io_ports": 4,
|
||||
"description": "Demonstrates I/O port operations on Intel 4001"
|
||||
},
|
||||
"components": {
|
||||
"CPU": {
|
||||
"single": {
|
||||
"component_type": "intel_4004",
|
||||
"name": "CPU_4004",
|
||||
"properties": {
|
||||
"clock_speed": 750000
|
||||
}
|
||||
}
|
||||
},
|
||||
"CLOCK": {
|
||||
"single": {
|
||||
"component_type": "generic_clock",
|
||||
"name": "CLOCK_750KHZ",
|
||||
"properties": {
|
||||
"frequency": 750000
|
||||
}
|
||||
}
|
||||
},
|
||||
"ROM_4001_0": {
|
||||
"single": {
|
||||
"component_type": "intel_4001",
|
||||
"name": "ROM_4001_0",
|
||||
"properties": {}
|
||||
}
|
||||
},
|
||||
"RAM_4002_0": {
|
||||
"single": {
|
||||
"component_type": "intel_4002",
|
||||
"name": "RAM_4002_0",
|
||||
"properties": {
|
||||
"variant": "Type1",
|
||||
"access_time": 500
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"connections": {
|
||||
"clock_to_cpu_phi1": {
|
||||
"connection_type": "clock",
|
||||
"source": {
|
||||
"component": "CLOCK_750KHZ",
|
||||
"pin": "CLK"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "CPU_4004",
|
||||
"pin": "PHI1"
|
||||
},
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "PHI1"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "PHI1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"clock_to_cpu_phi2": {
|
||||
"connection_type": "clock",
|
||||
"source": {
|
||||
"component": "CLOCK_750KHZ",
|
||||
"pin": "CLK"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "CPU_4004",
|
||||
"pin": "PHI2"
|
||||
},
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "PHI2"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "PHI2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_bus": {
|
||||
"connection_type": "bus",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "D0"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "D0"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "D0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_bus_d1": {
|
||||
"connection_type": "bus",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "D1"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "D1"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "D1"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_bus_d2": {
|
||||
"connection_type": "bus",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "D2"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "D2"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "D2"
|
||||
}
|
||||
]
|
||||
},
|
||||
"data_bus_d3": {
|
||||
"connection_type": "bus",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "D3"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "D3"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "D3"
|
||||
}
|
||||
]
|
||||
},
|
||||
"sync_signal": {
|
||||
"connection_type": "control",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "SYNC"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "SYNC"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "SYNC"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cm_rom_signal": {
|
||||
"connection_type": "control",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "CM_ROM"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "CM"
|
||||
}
|
||||
]
|
||||
},
|
||||
"cm_ram_signal": {
|
||||
"connection_type": "control",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "CM_RAM"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "P0"
|
||||
}
|
||||
]
|
||||
},
|
||||
"io_select_signal": {
|
||||
"connection_type": "control",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "TEST"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "CI"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reset_signal": {
|
||||
"connection_type": "control",
|
||||
"source": {
|
||||
"component": "CPU_4004",
|
||||
"pin": "RESET"
|
||||
},
|
||||
"targets": [
|
||||
{
|
||||
"component": "ROM_4001_0",
|
||||
"pin": "RESET"
|
||||
},
|
||||
{
|
||||
"component": "RAM_4002_0",
|
||||
"pin": "RESET"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"layout": {
|
||||
"grid_size": [4, 4],
|
||||
"positions": {
|
||||
"CPU_4004": [0, 0],
|
||||
"CLOCK_750KHZ": [0, 1],
|
||||
"ROM_4001_0": [1, 0],
|
||||
"RAM_4002_0": [2, 0]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -19,6 +19,14 @@ This directory contains binary program files for the Intel MCS-4 emulator.
|
|||
- **Output:** Each Fibonacci number visible on output ports 0-7 during execution
|
||||
- **Features:** Real-time visibility of calculation progress
|
||||
|
||||
### io_demo.bin ✨ **NEW**
|
||||
- **Size:** 32 bytes (16 instructions × 2 bytes each)
|
||||
- **Description:** I/O port demonstration program for Intel 4001
|
||||
- **Algorithm:** Writes values to I/O ports 0-3 and reads them back
|
||||
- **Instructions Used:** LDM, SRC, WRM, RDM, JCN
|
||||
- **Output:** Demonstrates I/O port functionality with visible port states
|
||||
- **Features:** Shows I/O port read/write operations and latching behavior
|
||||
|
||||
## Usage
|
||||
|
||||
Programs are automatically loaded by the emulator. Use the `--file` option to specify a different program:
|
||||
|
|
@ -30,6 +38,17 @@ cargo run -- --system basic
|
|||
# Use enhanced fibonacci program with output ports ✨
|
||||
cargo run -- --system basic --file programs/fibonacci_output.bin
|
||||
|
||||
### io_demo.bin ✨ **NEW**
|
||||
- **Size:** Assembly source (needs compilation to binary)
|
||||
- **Description:** I/O port demonstration program for Intel 4001
|
||||
- **Algorithm:** Writes values 1-4 to I/O ports 0-3 and reads them back
|
||||
- **Instructions Used:** LDM, SRC, WRM, RDM, JCN
|
||||
- **Output:** Demonstrates I/O port functionality with visible port states
|
||||
- **Features:** Shows I/O port read/write operations and latching behavior
|
||||
|
||||
# Use I/O demonstration program ✨
|
||||
cargo run -- --system mcs4_io_demo --file programs/io_demo.bin
|
||||
|
||||
# Use specific program
|
||||
cargo run -- --system basic --file programs/myprogram.bin
|
||||
```
|
||||
|
|
|
|||
0
programs/io_demo.bin
Normal file
0
programs/io_demo.bin
Normal file
406
src/bus.rs
406
src/bus.rs
|
|
@ -1,406 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::component::{BaseComponent, Component};
|
||||
use crate::pin::{DriveStrength, Pin, PinValue};
|
||||
|
||||
pub struct GenericBus {
|
||||
base: BaseComponent,
|
||||
connected_pins: Vec<Arc<Mutex<Pin>>>,
|
||||
bus_value: PinValue,
|
||||
last_update: Instant,
|
||||
settlement_time: Duration,
|
||||
is_active: bool,
|
||||
}
|
||||
|
||||
impl GenericBus {
|
||||
pub fn new(name: String) -> Self {
|
||||
// Bus typically has bidirectional data pins
|
||||
let pin_names = vec!["DATA"];
|
||||
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||
|
||||
GenericBus {
|
||||
base: BaseComponent::new(name, pins),
|
||||
connected_pins: Vec::new(),
|
||||
bus_value: PinValue::HighZ,
|
||||
last_update: Instant::now(),
|
||||
settlement_time: Duration::from_nanos(10),
|
||||
is_active: true,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn with_settlement_time(mut self, time: Duration) -> Self {
|
||||
self.settlement_time = time;
|
||||
self
|
||||
}
|
||||
|
||||
pub fn connect_pin(&mut self, pin: Arc<Mutex<Pin>>) -> Result<(), String> {
|
||||
if !self.connected_pins.iter().any(|p| Arc::ptr_eq(p, &pin)) {
|
||||
self.connected_pins.push(pin);
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Pin already connected to bus".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn disconnect_pin(&mut self, pin: &Arc<Mutex<Pin>>) -> Result<(), String> {
|
||||
let initial_len = self.connected_pins.len();
|
||||
self.connected_pins.retain(|p| !Arc::ptr_eq(p, pin));
|
||||
|
||||
if self.connected_pins.len() < initial_len {
|
||||
Ok(())
|
||||
} else {
|
||||
Err("Pin not found on bus".to_string())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_connected_pins(&self) -> &Vec<Arc<Mutex<Pin>>> {
|
||||
&self.connected_pins
|
||||
}
|
||||
|
||||
pub fn set_active(&mut self, active: bool) {
|
||||
self.is_active = active;
|
||||
if !active {
|
||||
// Tri-state the bus when deactivated
|
||||
self.bus_value = PinValue::HighZ;
|
||||
self.propagate_bus_value();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn is_active(&self) -> bool {
|
||||
self.is_active
|
||||
}
|
||||
|
||||
pub fn get_bus_value(&self) -> PinValue {
|
||||
self.bus_value
|
||||
}
|
||||
|
||||
fn read_bus_state(&self) -> PinValue {
|
||||
if !self.is_active {
|
||||
return PinValue::HighZ;
|
||||
}
|
||||
|
||||
if self.connected_pins.is_empty() {
|
||||
return PinValue::HighZ;
|
||||
}
|
||||
|
||||
// Collect all active drivers from connected pins
|
||||
let mut drivers = Vec::new();
|
||||
|
||||
for pin in &self.connected_pins {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
let pin_drivers = pin_guard.get_drivers();
|
||||
for (_, (value, strength)) in pin_drivers {
|
||||
if *strength != DriveStrength::HighImpedance && *value != PinValue::HighZ {
|
||||
drivers.push((*value, *strength));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if drivers.is_empty() {
|
||||
return PinValue::HighZ;
|
||||
}
|
||||
|
||||
// Find the strongest driver
|
||||
let max_strength = drivers
|
||||
.iter()
|
||||
.map(|(_, strength)| *strength)
|
||||
.max()
|
||||
.unwrap_or(DriveStrength::HighImpedance);
|
||||
|
||||
if max_strength == DriveStrength::HighImpedance {
|
||||
return PinValue::HighZ;
|
||||
}
|
||||
|
||||
// Get values from strongest drivers
|
||||
let strong_drivers: Vec<PinValue> = drivers
|
||||
.iter()
|
||||
.filter(|(_, strength)| *strength == max_strength)
|
||||
.map(|(value, _)| *value)
|
||||
.collect();
|
||||
|
||||
// Resolve conflicts: Low dominates
|
||||
if strong_drivers.iter().any(|v| *v == PinValue::Low) {
|
||||
PinValue::Low
|
||||
} else if strong_drivers.iter().any(|v| *v == PinValue::High) {
|
||||
PinValue::High
|
||||
} else {
|
||||
PinValue::HighZ
|
||||
}
|
||||
}
|
||||
|
||||
fn propagate_bus_value(&self) {
|
||||
if !self.is_active {
|
||||
return;
|
||||
}
|
||||
|
||||
for pin in &self.connected_pins {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
// The bus acts as a driver for connected pins
|
||||
pin_guard.set_driver_with_strength(
|
||||
Some(self.base.get_name().to_string()),
|
||||
self.bus_value,
|
||||
DriveStrength::Standard,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn simulate_bus_contention(&self) -> Result<(), String> {
|
||||
// Simulate bus contention detection
|
||||
let mut high_drivers = 0;
|
||||
let mut low_drivers = 0;
|
||||
|
||||
for pin in &self.connected_pins {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
let value = pin_guard.read();
|
||||
match value {
|
||||
PinValue::High => high_drivers += 1,
|
||||
PinValue::Low => low_drivers += 1,
|
||||
PinValue::HighZ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if high_drivers > 0 && low_drivers > 0 {
|
||||
// Bus contention detected
|
||||
Err("Bus contention: multiple drivers conflict".to_string())
|
||||
} else {
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for GenericBus {
|
||||
fn name(&self) -> String {
|
||||
self.base.name()
|
||||
}
|
||||
|
||||
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
|
||||
self.base.pins()
|
||||
}
|
||||
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||
self.base.get_pin(name)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
if !self.is_active {
|
||||
return;
|
||||
}
|
||||
|
||||
// Respect settlement timing
|
||||
if self.last_update.elapsed() < self.settlement_time {
|
||||
return;
|
||||
}
|
||||
|
||||
let new_bus_value = self.read_bus_state();
|
||||
|
||||
if new_bus_value != self.bus_value {
|
||||
self.bus_value = new_bus_value;
|
||||
self.propagate_bus_value();
|
||||
self.last_update = Instant::now();
|
||||
}
|
||||
|
||||
// Check for bus contention (optional - could be expensive)
|
||||
// if let Err(e) = self.simulate_bus_contention() {
|
||||
// eprintln!("Bus contention warning: {}", e);
|
||||
// }
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.base.set_running(true);
|
||||
|
||||
while self.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(1));
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.base.set_running(false);
|
||||
self.set_active(false);
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
self.base.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
// Active bus implementation for test equipment
|
||||
pub struct ActiveBus {
|
||||
base: GenericBus,
|
||||
test_pattern: Vec<PinValue>,
|
||||
pattern_index: usize,
|
||||
pattern_interval: Duration,
|
||||
last_pattern_update: Instant,
|
||||
}
|
||||
|
||||
impl ActiveBus {
|
||||
pub fn new(name: String) -> Self {
|
||||
ActiveBus {
|
||||
base: GenericBus::new(name),
|
||||
test_pattern: Vec::new(),
|
||||
pattern_index: 0,
|
||||
pattern_interval: Duration::from_millis(1),
|
||||
last_pattern_update: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn set_test_pattern(&mut self, pattern: Vec<PinValue>) {
|
||||
self.test_pattern = pattern;
|
||||
self.pattern_index = 0;
|
||||
}
|
||||
|
||||
pub fn set_pattern_interval(&mut self, interval: Duration) {
|
||||
self.pattern_interval = interval;
|
||||
}
|
||||
|
||||
pub fn drive_pattern(&mut self) {
|
||||
if self.test_pattern.is_empty() {
|
||||
return;
|
||||
}
|
||||
|
||||
if self.last_pattern_update.elapsed() >= self.pattern_interval {
|
||||
if let Some(data_pin) = self.base.get_pin("DATA").ok() {
|
||||
if let Ok(mut pin_guard) = data_pin.lock() {
|
||||
let value = self.test_pattern[self.pattern_index];
|
||||
pin_guard.set_driver_with_strength(
|
||||
Some(self.base.name() + "_pattern"),
|
||||
value,
|
||||
DriveStrength::Strong,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
self.pattern_index = (self.pattern_index + 1) % self.test_pattern.len();
|
||||
self.last_pattern_update = Instant::now();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for ActiveBus {
|
||||
fn name(&self) -> String {
|
||||
self.base.name()
|
||||
}
|
||||
|
||||
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
|
||||
self.base.pins()
|
||||
}
|
||||
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||
self.base.get_pin(name)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
self.base.update();
|
||||
self.drive_pattern();
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.base.base.set_running(true);
|
||||
|
||||
while self.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(10));
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.base.stop();
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
self.base.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_bus_creation() {
|
||||
let bus = GenericBus::new("TEST_BUS".to_string());
|
||||
assert_eq!(bus.name(), "TEST_BUS");
|
||||
assert!(!bus.is_running());
|
||||
assert!(bus.is_active());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bus_pin_connection() {
|
||||
let mut bus = GenericBus::new("TEST_BUS".to_string());
|
||||
let pin = Arc::new(Mutex::new(Pin::new("TEST_PIN".to_string())));
|
||||
|
||||
assert!(bus.connect_pin(pin.clone()).is_ok());
|
||||
assert_eq!(bus.get_connected_pins().len(), 1);
|
||||
|
||||
// Try connecting same pin again
|
||||
assert!(bus.connect_pin(pin.clone()).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bus_value_propagation() {
|
||||
let mut bus = GenericBus::new("TEST_BUS".to_string());
|
||||
let pin1 = Arc::new(Mutex::new(Pin::new("PIN1".to_string())));
|
||||
let pin2 = Arc::new(Mutex::new(Pin::new("PIN2".to_string())));
|
||||
|
||||
bus.connect_pin(pin1.clone()).unwrap();
|
||||
bus.connect_pin(pin2.clone()).unwrap();
|
||||
|
||||
// Drive pin1 high
|
||||
{
|
||||
let mut pin_guard = pin1.lock().unwrap();
|
||||
pin_guard.set_driver(Some("test".to_string()), PinValue::High);
|
||||
}
|
||||
|
||||
// Update bus
|
||||
bus.update();
|
||||
|
||||
// Pin2 should see the high value
|
||||
let pin2_guard = pin2.lock().unwrap();
|
||||
assert_eq!(pin2_guard.read(), PinValue::High);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_bus_contention_detection() {
|
||||
let mut bus = GenericBus::new("TEST_BUS".to_string());
|
||||
let pin1 = Arc::new(Mutex::new(Pin::new("PIN1".to_string())));
|
||||
let pin2 = Arc::new(Mutex::new(Pin::new("PIN2".to_string())));
|
||||
|
||||
bus.connect_pin(pin1.clone()).unwrap();
|
||||
bus.connect_pin(pin2.clone()).unwrap();
|
||||
|
||||
// Drive pins to conflicting states
|
||||
{
|
||||
let mut pin1_guard = pin1.lock().unwrap();
|
||||
pin1_guard.set_driver(Some("driver1".to_string()), PinValue::High);
|
||||
}
|
||||
{
|
||||
let mut pin2_guard = pin2.lock().unwrap();
|
||||
pin2_guard.set_driver(Some("driver2".to_string()), PinValue::Low);
|
||||
}
|
||||
|
||||
// Should detect bus contention
|
||||
assert!(bus.simulate_bus_contention().is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_active_bus_pattern() {
|
||||
let mut bus = ActiveBus::new("ACTIVE_BUS".to_string());
|
||||
|
||||
// Give the thread a moment to start
|
||||
thread::sleep(Duration::from_millis(10));
|
||||
|
||||
let pattern = vec![PinValue::High, PinValue::Low, PinValue::High, PinValue::Low];
|
||||
bus.set_test_pattern(pattern);
|
||||
|
||||
// Give the thread a moment to start
|
||||
thread::sleep(Duration::from_millis(100));
|
||||
|
||||
assert!(!bus.is_running());
|
||||
}
|
||||
}
|
||||
|
|
@ -47,12 +47,24 @@ All components implement the base `Component` trait defined in `src/component.rs
|
|||
- **File**: `intel_4001.rs`
|
||||
- **Features**:
|
||||
- 256 bytes of mask-programmable ROM
|
||||
- 4-bit I/O ports for peripheral interface
|
||||
- 4-bit I/O ports for peripheral interface (datasheet-compliant)
|
||||
- Two-phase addressing (8-bit address in two 4-bit cycles)
|
||||
- 500ns typical access time
|
||||
- SYNC signal detection for instruction fetch
|
||||
- I/O port addressing via SRC instruction (ports 0-3)
|
||||
- WRM/RDM instruction support for I/O operations
|
||||
- I/O direction control (input/output per port)
|
||||
- I/O latch persistence as per datasheet specifications
|
||||
- **Status**: Fully implemented with comprehensive testing
|
||||
|
||||
##### I/O Port Features
|
||||
|
||||
- **Port Addressing**: I/O ports 0-3 addressed using lower 2 bits of address
|
||||
- **Direction Control**: Each port can be configured as input or output
|
||||
- **Latch Persistence**: Output values persist until changed or reset
|
||||
- **CPU Control**: I/O operations controlled by WRM/RDM instructions
|
||||
- **Pin Interface**: IO0-IO3 pins for external peripheral connection
|
||||
|
||||
#### Intel 4002 RAM
|
||||
|
||||
- **File**: `intel_4002.rs`
|
||||
|
|
@ -128,8 +140,6 @@ fn is_running(&self) -> bool; // Execution status
|
|||
Components can implement additional traits for specialized functionality:
|
||||
|
||||
- **RunnableComponent**: Automatic thread spawning
|
||||
- **CPU**: Processor-specific operations
|
||||
- **Memory**: Storage-specific operations
|
||||
|
||||
## Pin Configuration
|
||||
|
||||
|
|
@ -205,7 +215,7 @@ connect_pins(vec![cpu_pin, rom_pin, ram_pin]) ?;
|
|||
1. **Implement Core Traits**: Start with `Component` trait implementation
|
||||
2. **Define Pin Configuration**: Specify required pins and their purposes
|
||||
3. **Implement Timing**: Add clock cycle processing in `update()` method
|
||||
4. **Add Specialized Traits**: Implement CPU, Memory, or other specialized traits as needed
|
||||
4. **Add Specialized Functionality**: Implement component-specific methods and behaviors as needed
|
||||
5. **Write Tests**: Add comprehensive unit tests for the new component
|
||||
6. **Update Exports**: Add the component to the appropriate module exports
|
||||
|
||||
|
|
|
|||
|
|
@ -12,7 +12,6 @@ pub struct GenericClock {
|
|||
duty_cycle: f64, // Duty cycle as percentage (0.0 to 1.0)
|
||||
current_state: PinValue,
|
||||
last_transition: Instant,
|
||||
half_period: Duration,
|
||||
high_time: Duration,
|
||||
low_time: Duration,
|
||||
enabled: bool,
|
||||
|
|
@ -23,17 +22,14 @@ impl GenericClock {
|
|||
let pin_names = vec!["CLK", "ENABLE"];
|
||||
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||
|
||||
let half_period = Self::frequency_to_duration(frequency) / 2;
|
||||
|
||||
let mut clock = GenericClock {
|
||||
base: BaseComponent::new(name, pins),
|
||||
frequency,
|
||||
duty_cycle: 0.5, // 50% duty cycle by default
|
||||
current_state: PinValue::Low,
|
||||
last_transition: Instant::now(),
|
||||
half_period,
|
||||
high_time: half_period, // Will be recalculated in set_duty_cycle
|
||||
low_time: half_period, // Will be recalculated in set_duty_cycle
|
||||
high_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
||||
low_time: Duration::from_secs_f64(0.5 / frequency), // Will be recalculated in set_duty_cycle
|
||||
enabled: true,
|
||||
};
|
||||
|
||||
|
|
@ -41,30 +37,11 @@ impl GenericClock {
|
|||
clock
|
||||
}
|
||||
|
||||
pub fn with_duty_cycle(mut self, duty_cycle: f64) -> Self {
|
||||
self.set_duty_cycle(duty_cycle);
|
||||
self
|
||||
}
|
||||
|
||||
pub fn set_frequency(&mut self, frequency: f64) {
|
||||
self.frequency = frequency;
|
||||
self.half_period = Self::frequency_to_duration(frequency) / 2;
|
||||
self.update_timing();
|
||||
}
|
||||
|
||||
pub fn get_frequency(&self) -> f64 {
|
||||
self.frequency
|
||||
}
|
||||
|
||||
pub fn set_duty_cycle(&mut self, duty_cycle: f64) {
|
||||
self.duty_cycle = duty_cycle.clamp(0.1, 0.9); // Keep within reasonable bounds
|
||||
self.update_timing();
|
||||
}
|
||||
|
||||
pub fn get_duty_cycle(&self) -> f64 {
|
||||
self.duty_cycle
|
||||
}
|
||||
|
||||
pub fn enable(&mut self) {
|
||||
self.enabled = true;
|
||||
// Start with known state when enabled
|
||||
|
|
@ -78,16 +55,6 @@ impl GenericClock {
|
|||
self.set_clock_output(PinValue::Low);
|
||||
}
|
||||
|
||||
pub fn is_enabled(&self) -> bool {
|
||||
self.enabled
|
||||
}
|
||||
|
||||
pub fn reset(&mut self) {
|
||||
self.current_state = PinValue::Low;
|
||||
self.last_transition = Instant::now();
|
||||
self.set_clock_output(PinValue::Low);
|
||||
}
|
||||
|
||||
fn frequency_to_duration(frequency: f64) -> Duration {
|
||||
if frequency <= 0.0 {
|
||||
Duration::from_secs(0)
|
||||
|
|
@ -207,48 +174,7 @@ impl Component for GenericClock {
|
|||
}
|
||||
|
||||
// Advanced clock features
|
||||
impl GenericClock {
|
||||
pub fn generate_single_pulse(&mut self, width: Duration) {
|
||||
self.disable(); // Stop regular clock
|
||||
self.set_clock_output(PinValue::High);
|
||||
|
||||
// Spawn a thread to end the pulse after specified width
|
||||
let clock_name = self.base.get_name().to_string();
|
||||
let clock_pin = self.base.get_pin("CLK").unwrap().clone();
|
||||
|
||||
thread::spawn(move || {
|
||||
thread::sleep(width);
|
||||
if let Ok(mut pin_guard) = clock_pin.lock() {
|
||||
pin_guard.set_driver(Some(clock_name), PinValue::Low);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
pub fn generate_clock_burst(&mut self, count: usize) {
|
||||
self.disable(); // Stop regular clock
|
||||
|
||||
let clock_name = self.base.get_name().to_string();
|
||||
let clock_pin = self.base.get_pin("CLK").unwrap().clone();
|
||||
let half_period = self.half_period;
|
||||
|
||||
thread::spawn(move || {
|
||||
for i in 0..count * 2 {
|
||||
// Each cycle has two transitions
|
||||
let state = if i % 2 == 0 {
|
||||
PinValue::High
|
||||
} else {
|
||||
PinValue::Low
|
||||
};
|
||||
|
||||
if let Ok(mut pin_guard) = clock_pin.lock() {
|
||||
pin_guard.set_driver(Some(clock_name.clone()), state);
|
||||
}
|
||||
|
||||
thread::sleep(half_period);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
impl GenericClock {}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
|
|
@ -259,44 +185,9 @@ mod tests {
|
|||
fn test_clock_creation() {
|
||||
let clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0); // 1MHz
|
||||
assert_eq!(clock.name(), "TEST_CLK");
|
||||
assert_eq!(clock.get_frequency(), 1_000_000.0);
|
||||
assert_eq!(clock.get_duty_cycle(), 0.5);
|
||||
assert!(!clock.is_running());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clock_frequency_change() {
|
||||
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
|
||||
clock.set_frequency(2_000_000.0);
|
||||
assert_eq!(clock.get_frequency(), 2_000_000.0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clock_duty_cycle() {
|
||||
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
|
||||
clock.set_duty_cycle(0.25);
|
||||
assert_eq!(clock.get_duty_cycle(), 0.25);
|
||||
|
||||
// Test clamping
|
||||
clock.set_duty_cycle(1.5);
|
||||
assert_eq!(clock.get_duty_cycle(), 0.9);
|
||||
|
||||
clock.set_duty_cycle(-0.5);
|
||||
assert_eq!(clock.get_duty_cycle(), 0.1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clock_enable_disable() {
|
||||
let mut clock = GenericClock::new("TEST_CLK".to_string(), 1_000_000.0);
|
||||
|
||||
assert!(clock.is_enabled());
|
||||
clock.disable();
|
||||
assert!(!clock.is_enabled());
|
||||
|
||||
clock.enable();
|
||||
assert!(clock.is_enabled());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clock_timing_calculation() {
|
||||
let _clock = GenericClock::new("TEST_CLK".to_string(), 1.0); // 1Hz
|
||||
|
|
@ -306,18 +197,4 @@ mod tests {
|
|||
let high_freq = GenericClock::frequency_to_duration(1_000_000.0); // 1MHz
|
||||
assert_eq!(high_freq, Duration::from_micros(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_single_pulse() {
|
||||
let mut clock = GenericClock::new("PULSE_CLK".to_string(), 1_000_000.0);
|
||||
clock.generate_single_pulse(Duration::from_millis(10));
|
||||
// Note: In a real test, you'd want to verify the pin state
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_clock_burst() {
|
||||
let mut clock = GenericClock::new("BURST_CLK".to_string(), 1_000_000.0);
|
||||
clock.generate_clock_burst(5); // 5 clock cycles
|
||||
// Note: In a real test, you'd want to verify the pin states
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,145 +0,0 @@
|
|||
use crate::component::Component;
|
||||
use crate::components::cpu::mos_6502::MOS6502;
|
||||
use crate::components::cpu::WDC65C02;
|
||||
|
||||
pub trait Registers {}
|
||||
|
||||
pub trait CPU: Component {
|
||||
fn reset(&mut self);
|
||||
fn execute_instruction(&mut self);
|
||||
fn get_registers(&self) -> &dyn Registers;
|
||||
fn get_registers_mut(&mut self) -> &mut dyn Registers;
|
||||
fn read_memory(&self, address: u16) -> u8;
|
||||
fn write_memory(&mut self, address: u16, value: u8);
|
||||
}
|
||||
|
||||
pub trait MOS6502Family: CPU {
|
||||
// Common 6502 family methods
|
||||
fn lda(&mut self, value: u8);
|
||||
fn sta(&mut self, address: u16);
|
||||
fn tax(&mut self);
|
||||
// More common instructions...
|
||||
}
|
||||
|
||||
pub trait CMOS65C02Extensions: MOS6502Family {
|
||||
// 65C02-specific methods
|
||||
fn bra(&mut self, address: u16);
|
||||
fn phx(&mut self);
|
||||
fn ply(&mut self);
|
||||
fn stp(&mut self);
|
||||
fn wai(&mut self);
|
||||
}
|
||||
|
||||
impl CPU for MOS6502 {
|
||||
fn reset(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn execute_instruction(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_registers(&self) -> &dyn Registers {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_registers_mut(&mut self) -> &mut dyn Registers {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn read_memory(&self, _address: u16) -> u8 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, _address: u16, _value: u8) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
// Implement for both MOS6502 and CMOS65C02
|
||||
impl MOS6502Family for MOS6502 {
|
||||
fn lda(&mut self, _value: u8) {
|
||||
// Load accumulator with value - this is a placeholder implementation
|
||||
// In a real 6502, this would load the accumulator register with the value
|
||||
// For now, we'll just store it in a field if available, or do nothing
|
||||
// This prevents infinite recursion while providing a valid implementation
|
||||
}
|
||||
|
||||
fn sta(&mut self, _address: u16) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn tax(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Implement other methods...
|
||||
}
|
||||
|
||||
impl CPU for WDC65C02 {
|
||||
fn reset(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn execute_instruction(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_registers(&self) -> &dyn Registers {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn get_registers_mut(&mut self) -> &mut dyn Registers {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn read_memory(&self, _address: u16) -> u8 {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn write_memory(&mut self, _address: u16, _value: u8) {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
impl MOS6502Family for WDC65C02 {
|
||||
fn lda(&mut self, value: u8) {
|
||||
self.base.lda(value)
|
||||
}
|
||||
|
||||
fn sta(&mut self, _address: u16) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn tax(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Implement other methods...
|
||||
}
|
||||
|
||||
impl CMOS65C02Extensions for WDC65C02 {
|
||||
fn bra(&mut self, _address: u16) {
|
||||
// Branch always - this is a placeholder implementation
|
||||
// In a real 65C02, this would branch to the specified address
|
||||
// For now, we'll just do nothing to prevent infinite recursion
|
||||
}
|
||||
|
||||
fn phx(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn ply(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn stp(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn wai(&mut self) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
// Implement other 65C02-specific methods...
|
||||
}
|
||||
|
|
@ -17,14 +17,6 @@ enum InstructionPhase {
|
|||
Wait, // Waiting for external operations
|
||||
}
|
||||
|
||||
/// Represents the current phase of the two-phase clock cycle
|
||||
/// The 4004 uses a two-phase clock for synchronization with peripherals
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum ClockPhase {
|
||||
Phase1, // First clock phase - CPU drives bus
|
||||
Phase2, // Second clock phase - Peripherals drive bus
|
||||
}
|
||||
|
||||
/// Memory operation state machine states
|
||||
/// Tracks the current phase of memory access operations
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
|
|
@ -118,8 +110,6 @@ pub struct Intel4004 {
|
|||
address_latch: u8, // Latched address for memory operations
|
||||
data_latch: u8, // Latched data for memory operations
|
||||
clock_speed: f64, // Target clock speed in Hz
|
||||
last_clock_transition: Instant, // Timestamp of last clock transition
|
||||
clock_phase: ClockPhase, // Current clock phase
|
||||
rom_port: u8, // Currently selected ROM port (0-15)
|
||||
ram_bank: u8, // Currently selected RAM bank (0-7)
|
||||
|
||||
|
|
@ -135,8 +125,6 @@ pub struct Intel4004 {
|
|||
|
||||
// Instruction execution state
|
||||
current_op: Instruction, // Currently decoded instruction
|
||||
operand_latch: u8, // Latched operand for multi-byte instructions
|
||||
jump_address: Option<u16>, // Jump target address for conditional jumps
|
||||
|
||||
// Timing and synchronization
|
||||
address_latch_time: Option<Instant>, // Timestamp when address was latched
|
||||
|
|
@ -175,8 +163,6 @@ impl Intel4004 {
|
|||
address_latch: 0,
|
||||
data_latch: 0,
|
||||
clock_speed,
|
||||
last_clock_transition: Instant::now(),
|
||||
clock_phase: ClockPhase::Phase1,
|
||||
rom_port: 0,
|
||||
ram_bank: 0,
|
||||
|
||||
|
|
@ -192,8 +178,6 @@ impl Intel4004 {
|
|||
|
||||
// Instruction execution state
|
||||
current_op: Instruction::Invalid,
|
||||
operand_latch: 0,
|
||||
jump_address: None,
|
||||
|
||||
// Timing and synchronization
|
||||
address_latch_time: None,
|
||||
|
|
@ -392,37 +376,6 @@ impl Intel4004 {
|
|||
(phi1, phi2)
|
||||
}
|
||||
|
||||
/// Check for Φ1 rising edge
|
||||
fn is_phi1_rising_edge(&self) -> bool {
|
||||
let (phi1, _) = self.read_clock_pins();
|
||||
phi1 == PinValue::High && self.prev_phi1 == PinValue::Low
|
||||
}
|
||||
|
||||
/// Check for Φ1 falling edge
|
||||
fn is_phi1_falling_edge(&self) -> bool {
|
||||
let (phi1, _) = self.read_clock_pins();
|
||||
phi1 == PinValue::Low && self.prev_phi1 == PinValue::High
|
||||
}
|
||||
|
||||
/// Check for Φ2 rising edge
|
||||
fn is_phi2_rising_edge(&self) -> bool {
|
||||
let (_, phi2) = self.read_clock_pins();
|
||||
phi2 == PinValue::High && self.prev_phi2 == PinValue::Low
|
||||
}
|
||||
|
||||
/// Check for Φ2 falling edge
|
||||
fn is_phi2_falling_edge(&self) -> bool {
|
||||
let (_, phi2) = self.read_clock_pins();
|
||||
phi2 == PinValue::Low && self.prev_phi2 == PinValue::High
|
||||
}
|
||||
|
||||
/// Update clock state tracking for edge detection
|
||||
fn update_clock_states(&mut self) {
|
||||
let (phi1, phi2) = self.read_clock_pins();
|
||||
self.prev_phi1 = phi1;
|
||||
self.prev_phi2 = phi2;
|
||||
}
|
||||
|
||||
/// 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
|
||||
|
|
@ -631,13 +584,6 @@ impl Intel4004 {
|
|||
self.full_address_ready = false;
|
||||
}
|
||||
|
||||
/// Handle clock cycle processing
|
||||
/// Updates cycle count and manages clock phase transitions
|
||||
fn handle_clock(&mut self) {
|
||||
// Simple clock simulation for now
|
||||
self.cycle_count += 1;
|
||||
}
|
||||
|
||||
/// Decode an instruction byte into an Instruction enum
|
||||
/// Parameters: opcode - 8-bit instruction opcode
|
||||
/// Returns: Decoded instruction
|
||||
|
|
|
|||
|
|
@ -1,5 +1,4 @@
|
|||
// CPU components module
|
||||
mod cpu_traits;
|
||||
pub mod intel_4004;
|
||||
pub mod mos_6502;
|
||||
pub mod wdc_65c02;
|
||||
|
|
|
|||
|
|
@ -1,405 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::component::{BaseComponent, Component};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
|
||||
pub struct GenericRam {
|
||||
base: BaseComponent,
|
||||
memory: Vec<u8>,
|
||||
address_width: usize,
|
||||
data_width: usize,
|
||||
last_address: u32,
|
||||
last_operation: RamOperation,
|
||||
access_time: Duration,
|
||||
last_access: Instant,
|
||||
write_enable: bool,
|
||||
output_enable: bool,
|
||||
chip_select: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||
enum RamOperation {
|
||||
Read,
|
||||
Write,
|
||||
Idle,
|
||||
}
|
||||
|
||||
impl GenericRam {
|
||||
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
|
||||
let mut pin_names = Vec::new();
|
||||
|
||||
// Address pins (A0, A1, A2, ...)
|
||||
for i in 0..address_width {
|
||||
pin_names.push(format!("A{}", i));
|
||||
}
|
||||
|
||||
// Data pins (D0, D1, D2, ...) - bidirectional
|
||||
for i in 0..data_width {
|
||||
let name = format!("D{}", i);
|
||||
pin_names.push(name.clone());
|
||||
}
|
||||
|
||||
// Control pins
|
||||
pin_names.push("CS".parse().unwrap()); // Chip Select
|
||||
pin_names.push("WE".parse().unwrap()); // Write Enable
|
||||
pin_names.push("OE".parse().unwrap()); // Output Enable
|
||||
|
||||
let pin_refs: Vec<&str> = pin_names.iter().map(|s| s.as_str()).collect();
|
||||
let pins = BaseComponent::create_pin_map(&pin_refs, &name);
|
||||
let memory = vec![0u8; size];
|
||||
|
||||
GenericRam {
|
||||
base: BaseComponent::new(name, pins),
|
||||
memory,
|
||||
address_width,
|
||||
data_width,
|
||||
last_address: 0,
|
||||
last_operation: RamOperation::Idle,
|
||||
access_time: Duration::from_nanos(100), // 100ns access time
|
||||
last_access: Instant::now(),
|
||||
write_enable: false,
|
||||
output_enable: false,
|
||||
chip_select: false,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
|
||||
if offset + data.len() > self.memory.len() {
|
||||
return Err(format!(
|
||||
"Data exceeds RAM capacity: offset {} + data length {} > RAM size {}",
|
||||
offset,
|
||||
data.len(),
|
||||
self.memory.len()
|
||||
));
|
||||
}
|
||||
|
||||
self.memory[offset..offset + data.len()].copy_from_slice(&data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_memory_size(&self) -> usize {
|
||||
self.memory.len()
|
||||
}
|
||||
|
||||
pub fn read_memory(&self, address: usize, length: usize) -> Result<Vec<u8>, String> {
|
||||
if address + length > self.memory.len() {
|
||||
return Err("Address range out of bounds".to_string());
|
||||
}
|
||||
|
||||
Ok(self.memory[address..address + length].to_vec())
|
||||
}
|
||||
|
||||
pub fn write_memory(&mut self, address: usize, data: &[u8]) -> Result<(), String> {
|
||||
if address + data.len() > self.memory.len() {
|
||||
return Err("Address range out of bounds".to_string());
|
||||
}
|
||||
|
||||
self.memory[address..address + data.len()].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn get_access_time(&self) -> Duration {
|
||||
self.access_time
|
||||
}
|
||||
|
||||
pub fn set_access_time(&mut self, access_time: Duration) {
|
||||
self.access_time = access_time;
|
||||
}
|
||||
|
||||
fn read_address(&self) -> u32 {
|
||||
let mut address = 0;
|
||||
|
||||
for i in 0..self.address_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
if pin_guard.read() == PinValue::High {
|
||||
address |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
address
|
||||
}
|
||||
|
||||
fn read_data_bus(&self) -> u8 {
|
||||
let mut data = 0;
|
||||
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
if pin_guard.read() == PinValue::High {
|
||||
data |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data
|
||||
}
|
||||
|
||||
fn write_data_bus(&self, data: u8) {
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", 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.get_name().parse().unwrap()), pin_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn read_control_pins(&mut self) {
|
||||
// Read Chip Select (active low)
|
||||
self.chip_select = if let Ok(cs_pin) = self.base.get_pin("CS") {
|
||||
if let Ok(cs_guard) = cs_pin.lock() {
|
||||
cs_guard.read() == PinValue::Low
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Read Write Enable (active low)
|
||||
self.write_enable = if let Ok(we_pin) = self.base.get_pin("WE") {
|
||||
if let Ok(we_guard) = we_pin.lock() {
|
||||
we_guard.read() == PinValue::Low
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
|
||||
// Read Output Enable (active low)
|
||||
self.output_enable = if let Ok(oe_pin) = self.base.get_pin("OE") {
|
||||
if let Ok(oe_guard) = oe_pin.lock() {
|
||||
oe_guard.read() == PinValue::Low
|
||||
} else {
|
||||
false
|
||||
}
|
||||
} else {
|
||||
false
|
||||
};
|
||||
}
|
||||
|
||||
fn tri_state_data_bus(&self) {
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard
|
||||
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_read_operation(&mut self, address: u32) {
|
||||
if (address as usize) < self.memory.len() {
|
||||
let data = self.memory[address as usize];
|
||||
self.write_data_bus(data);
|
||||
self.last_operation = RamOperation::Read;
|
||||
} else {
|
||||
// Address out of bounds - tri-state
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
|
||||
fn perform_write_operation(&mut self, address: u32) {
|
||||
if (address as usize) < self.memory.len() {
|
||||
let data = self.read_data_bus();
|
||||
self.memory[address as usize] = data;
|
||||
self.last_operation = RamOperation::Write;
|
||||
}
|
||||
// During write, RAM doesn't drive the data bus
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for GenericRam {
|
||||
fn name(&self) -> String {
|
||||
self.base.name()
|
||||
}
|
||||
|
||||
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
|
||||
self.base.pins()
|
||||
}
|
||||
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||
self.base.get_pin(name)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
// Respect access timing
|
||||
if self.last_access.elapsed() < self.access_time {
|
||||
return;
|
||||
}
|
||||
|
||||
self.read_control_pins();
|
||||
|
||||
if !self.chip_select {
|
||||
// Chip not selected - tri-state all outputs
|
||||
self.tri_state_data_bus();
|
||||
self.last_operation = RamOperation::Idle;
|
||||
return;
|
||||
}
|
||||
|
||||
let current_address = self.read_address();
|
||||
let address_changed = current_address != self.last_address;
|
||||
|
||||
// Determine operation based on control pins
|
||||
if self.write_enable {
|
||||
// Write operation (WE low)
|
||||
self.perform_write_operation(current_address);
|
||||
} else if self.output_enable {
|
||||
// Read operation (OE low, WE high)
|
||||
if address_changed || self.last_operation != RamOperation::Read {
|
||||
self.perform_read_operation(current_address);
|
||||
}
|
||||
} else {
|
||||
// Output disabled - tri-state data bus
|
||||
self.tri_state_data_bus();
|
||||
self.last_operation = RamOperation::Idle;
|
||||
}
|
||||
|
||||
self.last_address = current_address;
|
||||
self.last_access = Instant::now();
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.base.set_running(true);
|
||||
|
||||
while self.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(10));
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.base.set_running(false);
|
||||
|
||||
// Tri-state all data pins when stopped
|
||||
self.tri_state_data_bus();
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
self.base.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
// Additional utility methods
|
||||
impl GenericRam {
|
||||
pub fn clear_memory(&mut self) {
|
||||
for byte in &mut self.memory {
|
||||
*byte = 0;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn fill_memory(&mut self, value: u8) {
|
||||
for byte in &mut self.memory {
|
||||
*byte = value;
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get_memory_snapshot(&self) -> Vec<u8> {
|
||||
self.memory.clone()
|
||||
}
|
||||
|
||||
pub fn restore_memory_snapshot(&mut self, snapshot: Vec<u8>) -> Result<(), String> {
|
||||
if snapshot.len() != self.memory.len() {
|
||||
return Err("Snapshot size doesn't match RAM size".to_string());
|
||||
}
|
||||
|
||||
self.memory.copy_from_slice(&snapshot);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
|
||||
#[test]
|
||||
fn test_ram_creation() {
|
||||
let ram = GenericRam::new("TEST_RAM".to_string(), 1024, 10, 8);
|
||||
assert_eq!(ram.name(), "TEST_RAM");
|
||||
assert_eq!(ram.memory.len(), 1024);
|
||||
assert_eq!(ram.address_width, 10);
|
||||
assert_eq!(ram.data_width, 8);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ram_data_loading() {
|
||||
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
|
||||
|
||||
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
||||
assert!(ram.load_data(test_data.clone(), 0).is_ok());
|
||||
|
||||
assert_eq!(ram.memory[0], 0x12);
|
||||
assert_eq!(ram.memory[1], 0x34);
|
||||
assert_eq!(ram.memory[2], 0x56);
|
||||
assert_eq!(ram.memory[3], 0x78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ram_read_write() {
|
||||
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
|
||||
|
||||
// Test direct memory access
|
||||
assert!(ram.write_memory(0x10, &[0xAA, 0xBB, 0xCC]).is_ok());
|
||||
|
||||
let read_data = ram.read_memory(0x10, 3).unwrap();
|
||||
assert_eq!(read_data, vec![0xAA, 0xBB, 0xCC]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ram_clear_and_fill() {
|
||||
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
|
||||
|
||||
// Fill with pattern
|
||||
ram.fill_memory(0x55);
|
||||
assert_eq!(ram.memory[0], 0x55);
|
||||
assert_eq!(ram.memory[100], 0x55);
|
||||
|
||||
// Clear memory
|
||||
ram.clear_memory();
|
||||
assert_eq!(ram.memory[0], 0);
|
||||
assert_eq!(ram.memory[100], 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ram_snapshot() {
|
||||
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
|
||||
|
||||
// Write some data
|
||||
ram.write_memory(0, &[0x11, 0x22, 0x33]).unwrap();
|
||||
|
||||
// Take snapshot
|
||||
let snapshot = ram.get_memory_snapshot();
|
||||
|
||||
// Modify memory
|
||||
ram.write_memory(0, &[0x44, 0x55, 0x66]).unwrap();
|
||||
|
||||
// Restore snapshot
|
||||
ram.restore_memory_snapshot(snapshot).unwrap();
|
||||
|
||||
// Verify restoration
|
||||
let read_data = ram.read_memory(0, 3).unwrap();
|
||||
assert_eq!(read_data, vec![0x11, 0x22, 0x33]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_ram_is_running() {
|
||||
let ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
|
||||
assert!(!ram.is_running());
|
||||
}
|
||||
}
|
||||
|
|
@ -1,365 +0,0 @@
|
|||
use std::collections::HashMap;
|
||||
use std::fs::File;
|
||||
use std::io::Read;
|
||||
use std::path::Path;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
use crate::component::{BaseComponent, Component};
|
||||
use crate::pin::{Pin, PinValue};
|
||||
|
||||
pub struct GenericRom {
|
||||
pub(crate) base: BaseComponent,
|
||||
memory: Vec<u8>,
|
||||
address_width: usize,
|
||||
data_width: usize,
|
||||
last_address: u32,
|
||||
access_time: Duration,
|
||||
last_access: Instant,
|
||||
}
|
||||
|
||||
impl GenericRom {
|
||||
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
|
||||
let mut pins = HashMap::new();
|
||||
|
||||
// Create address pins (A0, A1, A2, ...)
|
||||
for i in 0..address_width {
|
||||
pins.insert(
|
||||
format!("A{}", i),
|
||||
Arc::new(Mutex::new(Pin::new(format!("{}_A{}", name, i)))),
|
||||
);
|
||||
}
|
||||
|
||||
// Create data pins (D0, D1, D2, ...)
|
||||
for i in 0..data_width {
|
||||
pins.insert(
|
||||
format!("D{}", i),
|
||||
Arc::new(Mutex::new(Pin::new(format!("{}_D{}", name, i)))),
|
||||
);
|
||||
}
|
||||
|
||||
// Control pins
|
||||
pins.insert(
|
||||
"CS".to_string(),
|
||||
Arc::new(Mutex::new(Pin::new(format!("{}_CS", name)))),
|
||||
);
|
||||
pins.insert(
|
||||
"OE".to_string(),
|
||||
Arc::new(Mutex::new(Pin::new(format!("{}_OE", name)))),
|
||||
);
|
||||
|
||||
let memory = vec![0u8; size];
|
||||
|
||||
GenericRom {
|
||||
base: BaseComponent::new(name, pins),
|
||||
memory,
|
||||
address_width,
|
||||
data_width,
|
||||
last_address: 0,
|
||||
access_time: Duration::from_nanos(100), // 100ns access time
|
||||
last_access: Instant::now(),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
|
||||
if offset + data.len() > self.memory.len() {
|
||||
return Err("Data exceeds ROM capacity".to_string());
|
||||
}
|
||||
|
||||
self.memory[offset..offset + data.len()].copy_from_slice(&data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> {
|
||||
let bytes: Result<Vec<u8>, _> = hex_data
|
||||
.split_whitespace()
|
||||
.map(|s| u8::from_str_radix(s, 16))
|
||||
.collect();
|
||||
|
||||
match bytes {
|
||||
Ok(data) => self.load_data(data, offset),
|
||||
Err(_) => Err("Invalid hex data".to_string()),
|
||||
}
|
||||
}
|
||||
pub fn load_from_binary_file<P: AsRef<Path>>(
|
||||
&mut self,
|
||||
path: P,
|
||||
offset: usize,
|
||||
) -> Result<(), String> {
|
||||
let path_ref = path.as_ref();
|
||||
|
||||
// Check if file exists
|
||||
if !path_ref.exists() {
|
||||
return Err(format!("File not found: {}", path_ref.display()));
|
||||
}
|
||||
|
||||
// Check if offset is within bounds
|
||||
if offset >= self.memory.len() {
|
||||
return Err(format!(
|
||||
"Offset {} exceeds ROM size {}",
|
||||
offset,
|
||||
self.memory.len()
|
||||
));
|
||||
}
|
||||
|
||||
let mut file = File::open(path_ref).map_err(|e| format!("Failed to open file: {}", e))?;
|
||||
|
||||
// Get file size
|
||||
let metadata = file
|
||||
.metadata()
|
||||
.map_err(|e| format!("Failed to get file metadata: {}", e))?;
|
||||
let file_size = metadata.len() as usize;
|
||||
|
||||
// Check if file fits in ROM
|
||||
if offset + file_size > self.memory.len() {
|
||||
return Err(format!(
|
||||
"File too large: offset {} + file size {} > ROM size {}",
|
||||
offset,
|
||||
file_size,
|
||||
self.memory.len()
|
||||
));
|
||||
}
|
||||
|
||||
// Read file into buffer
|
||||
let mut buffer = vec![0u8; file_size];
|
||||
file.read_exact(&mut buffer)
|
||||
.map_err(|e| format!("Failed to read file: {}", e))?;
|
||||
|
||||
// Copy into ROM memory
|
||||
self.memory[offset..offset + file_size].copy_from_slice(&buffer);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn load_from_binary(&mut self, data: &[u8], offset: usize) -> Result<(), String> {
|
||||
if offset + data.len() > self.memory.len() {
|
||||
return Err(format!(
|
||||
"Data exceeds ROM capacity: offset {} + data length {} > ROM size {}",
|
||||
offset,
|
||||
data.len(),
|
||||
self.memory.len()
|
||||
));
|
||||
}
|
||||
|
||||
self.memory[offset..offset + data.len()].copy_from_slice(data);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn read_address(&self) -> u32 {
|
||||
let mut address = 0;
|
||||
|
||||
for i in 0..self.address_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("A{}", i)) {
|
||||
if let Ok(pin_guard) = pin.lock() {
|
||||
if pin_guard.read() == PinValue::High {
|
||||
address |= 1 << i;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
address
|
||||
}
|
||||
|
||||
pub(crate) fn is_selected(&self) -> bool {
|
||||
// Check Chip Select (active low by convention)
|
||||
if let Ok(cs_pin) = self.base.get_pin("CS") {
|
||||
if let Ok(cs_guard) = cs_pin.lock() {
|
||||
if cs_guard.read() == PinValue::High {
|
||||
return false; // Not selected
|
||||
}
|
||||
}
|
||||
}
|
||||
true // Selected when CS is low or not connected
|
||||
}
|
||||
|
||||
fn output_enabled(&self) -> bool {
|
||||
// Check Output Enable (active low by convention)
|
||||
if let Ok(oe_pin) = self.base.get_pin("OE") {
|
||||
if let Ok(oe_guard) = oe_pin.lock() {
|
||||
if oe_guard.read() == PinValue::High {
|
||||
return false; // Output disabled
|
||||
}
|
||||
}
|
||||
}
|
||||
true // Enabled when OE is low or not connected
|
||||
}
|
||||
|
||||
pub(crate) fn output_data(&self, data: u8) {
|
||||
// Only drive data pins if selected and output enabled
|
||||
if !self.is_selected() || !self.output_enabled() {
|
||||
// Tri-state the outputs
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard.set_driver(
|
||||
Some(self.base.get_name().parse().unwrap()),
|
||||
PinValue::HighZ,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
// Drive data pins according to the data value
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", 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.get_name().parse().unwrap()), pin_value);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Component for GenericRom {
|
||||
fn name(&self) -> String {
|
||||
self.base.name()
|
||||
}
|
||||
|
||||
fn pins(&self) -> HashMap<String, Arc<Mutex<Pin>>> {
|
||||
self.base.pins()
|
||||
}
|
||||
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||
self.base.get_pin(name)
|
||||
}
|
||||
|
||||
fn update(&mut self) {
|
||||
// Respect access timing
|
||||
if self.last_access.elapsed() < self.access_time {
|
||||
return;
|
||||
}
|
||||
|
||||
let current_address = self.read_address();
|
||||
|
||||
// Only process if address changed or we need to update outputs
|
||||
if current_address != self.last_address || !self.is_selected() || !self.output_enabled() {
|
||||
if self.is_selected() && self.output_enabled() {
|
||||
// Read from memory (handle address bounds)
|
||||
if (current_address as usize) < self.memory.len() {
|
||||
let data = self.memory[current_address as usize];
|
||||
self.output_data(data);
|
||||
} else {
|
||||
// Address out of bounds - tri-state outputs
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard.set_driver(
|
||||
Some(self.base.get_name().parse().unwrap()),
|
||||
PinValue::HighZ,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Not selected or output disabled - tri-state outputs
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard.set_driver(
|
||||
Some(self.base.get_name().parse().unwrap()),
|
||||
PinValue::HighZ,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.last_address = current_address;
|
||||
self.last_access = Instant::now();
|
||||
}
|
||||
}
|
||||
|
||||
fn run(&mut self) {
|
||||
self.base.set_running(true);
|
||||
|
||||
while self.base.is_running() {
|
||||
self.update();
|
||||
thread::sleep(Duration::from_micros(10)); // Small delay to prevent busy waiting
|
||||
}
|
||||
}
|
||||
|
||||
fn stop(&mut self) {
|
||||
self.base.set_running(false);
|
||||
|
||||
// Tri-state all outputs when stopped
|
||||
for i in 0..self.data_width {
|
||||
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||
if let Ok(mut pin_guard) = pin.lock() {
|
||||
pin_guard
|
||||
.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn is_running(&self) -> bool {
|
||||
self.base.is_running()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use std::fs;
|
||||
use tempfile::NamedTempFile;
|
||||
|
||||
#[test]
|
||||
fn test_rom_creation() {
|
||||
let rom = GenericRom::new("TEST_ROM".to_string(), 1024, 10, 8);
|
||||
assert_eq!(rom.name(), "TEST_ROM");
|
||||
assert_eq!(rom.memory.len(), 1024);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rom_file_loading() {
|
||||
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
|
||||
|
||||
// Create a temporary file with test data
|
||||
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
||||
let temp_file = NamedTempFile::new().unwrap();
|
||||
fs::write(temp_file.path(), &test_data).unwrap();
|
||||
|
||||
assert!(rom.load_from_binary_file(temp_file.path(), 0).is_ok());
|
||||
|
||||
assert_eq!(rom.memory[0], 0x12);
|
||||
assert_eq!(rom.memory[1], 0x34);
|
||||
assert_eq!(rom.memory[2], 0x56);
|
||||
assert_eq!(rom.memory[3], 0x78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rom_data_loading() {
|
||||
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
|
||||
|
||||
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
||||
assert!(rom.load_data(test_data.clone(), 0).is_ok());
|
||||
|
||||
assert_eq!(rom.memory[0], 0x12);
|
||||
assert_eq!(rom.memory[1], 0x34);
|
||||
assert_eq!(rom.memory[2], 0x56);
|
||||
assert_eq!(rom.memory[3], 0x78);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_rom_hex_loading() {
|
||||
let mut rom = GenericRom::new("TEST_ROM".to_string(), 256, 8, 8);
|
||||
|
||||
assert!(rom.load_from_hex("12 34 56 78", 0).is_ok());
|
||||
|
||||
assert_eq!(rom.memory[0], 0x12);
|
||||
assert_eq!(rom.memory[1], 0x34);
|
||||
assert_eq!(rom.memory[2], 0x56);
|
||||
assert_eq!(rom.memory[3], 0x78);
|
||||
}
|
||||
}
|
||||
|
|
@ -26,6 +26,9 @@ pub struct Intel4001 {
|
|||
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)
|
||||
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:
|
||||
// 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
|
||||
self.input_latch = self.read_io_pins();
|
||||
// Hardware deviation: Write to D0-D3 for test convenience
|
||||
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);
|
||||
self.io_mode = IoMode::Input;
|
||||
}
|
||||
}
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
mod generic_ram;
|
||||
pub mod generic_rom;
|
||||
pub mod intel_4001;
|
||||
pub mod intel_4002;
|
||||
pub mod intel_4003;
|
||||
|
|
|
|||
134
src/console.rs
134
src/console.rs
|
|
@ -11,7 +11,7 @@
|
|||
//! - Graceful interrupt handling
|
||||
|
||||
use crossterm::{
|
||||
event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode},
|
||||
event::{self, Event, KeyCode},
|
||||
execute,
|
||||
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
|
||||
};
|
||||
|
|
@ -20,12 +20,11 @@ use ratatui::{
|
|||
layout::{Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
text::{Line, Span},
|
||||
widgets::{Block, Borders, Clear, Gauge, List, ListItem, Paragraph, Wrap},
|
||||
widgets::{Block, Borders, Paragraph, Wrap},
|
||||
Frame, Terminal,
|
||||
};
|
||||
use serde::{Deserialize, Serialize};
|
||||
use std::collections::HashMap;
|
||||
use std::io::{self, Stdout};
|
||||
use std::io;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::thread;
|
||||
use std::time::{Duration, Instant};
|
||||
|
|
@ -63,7 +62,6 @@ pub struct ConsoleApp {
|
|||
system: Arc<Mutex<ConfigurableSystem>>,
|
||||
config: ConsoleConfig,
|
||||
running: bool,
|
||||
last_update: Instant,
|
||||
command_buffer: String,
|
||||
show_help: bool,
|
||||
selected_pane: usize,
|
||||
|
|
@ -75,7 +73,6 @@ impl ConsoleApp {
|
|||
system,
|
||||
config,
|
||||
running: false,
|
||||
last_update: Instant::now(),
|
||||
command_buffer: String::new(),
|
||||
show_help: false,
|
||||
selected_pane: 0,
|
||||
|
|
@ -86,7 +83,8 @@ impl ConsoleApp {
|
|||
// Setup terminal
|
||||
enable_raw_mode().map_err(|e| format!("Failed to enable raw mode: {}", e))?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen).map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
|
||||
execute!(stdout, EnterAlternateScreen)
|
||||
.map_err(|e| format!("Failed to enter alternate screen: {}", e))?;
|
||||
|
||||
let backend = CrosstermBackend::new(stdout);
|
||||
let mut terminal = Terminal::new(backend)?;
|
||||
|
|
@ -132,11 +130,11 @@ impl ConsoleApp {
|
|||
|
||||
// Restore terminal
|
||||
disable_raw_mode().map_err(|e| format!("Failed to disable raw mode: {}", e))?;
|
||||
execute!(
|
||||
terminal.backend_mut(),
|
||||
LeaveAlternateScreen
|
||||
).map_err(|e| format!("Failed to leave alternate screen: {}", e))?;
|
||||
terminal.show_cursor().map_err(|e| format!("Failed to show cursor: {}", e))?;
|
||||
execute!(terminal.backend_mut(), LeaveAlternateScreen)
|
||||
.map_err(|e| format!("Failed to leave alternate screen: {}", e))?;
|
||||
terminal
|
||||
.show_cursor()
|
||||
.map_err(|e| format!("Failed to show cursor: {}", e))?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -246,9 +244,12 @@ impl ConsoleApp {
|
|||
|
||||
// Title bar
|
||||
let title_text = vec![
|
||||
Line::from(vec![
|
||||
Span::styled("Intel MCS-4 Emulator Console", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
|
||||
]),
|
||||
Line::from(vec![Span::styled(
|
||||
"Intel MCS-4 Emulator Console",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::raw("Commands: "),
|
||||
Span::styled("q/Q", Style::default().fg(Color::Yellow)),
|
||||
|
|
@ -298,19 +299,54 @@ impl ConsoleApp {
|
|||
fn draw_help_screen(&self, f: &mut Frame) {
|
||||
let size = f.size();
|
||||
let help_text = vec![
|
||||
Line::from(vec![Span::styled("Intel MCS-4 Emulator Console Help", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))]),
|
||||
Line::from(vec![Span::styled(
|
||||
"Intel MCS-4 Emulator Console Help",
|
||||
Style::default()
|
||||
.fg(Color::Cyan)
|
||||
.add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled("Commands:", Style::default().add_modifier(Modifier::BOLD))]),
|
||||
Line::from(vec![Span::styled(" q, quit, exit", Style::default().fg(Color::Yellow)), Span::raw(" - Exit emulator")]),
|
||||
Line::from(vec![Span::styled(" r, run", Style::default().fg(Color::Yellow)), Span::raw(" - Start/stop system execution")]),
|
||||
Line::from(vec![Span::styled(" s, stop", Style::default().fg(Color::Yellow)), Span::raw(" - Stop system execution")]),
|
||||
Line::from(vec![Span::styled(" h, help", Style::default().fg(Color::Yellow)), Span::raw(" - Show/hide this help")]),
|
||||
Line::from(vec![Span::styled(" reset", Style::default().fg(Color::Yellow)), Span::raw(" - Reset system")]),
|
||||
Line::from(vec![Span::styled(
|
||||
"Commands:",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::styled(" q, quit, exit", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Exit emulator"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" r, run", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Start/stop system execution"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" s, stop", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Stop system execution"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" h, help", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Show/hide this help"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" reset", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Reset system"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::styled("Navigation:", Style::default().add_modifier(Modifier::BOLD))]),
|
||||
Line::from(vec![Span::styled(" Tab", Style::default().fg(Color::Yellow)), Span::raw(" - Switch between panes")]),
|
||||
Line::from(vec![Span::styled(" Enter", Style::default().fg(Color::Yellow)), Span::raw(" - Execute command")]),
|
||||
Line::from(vec![Span::styled(" Backspace", Style::default().fg(Color::Yellow)), Span::raw(" - Delete character")]),
|
||||
Line::from(vec![Span::styled(
|
||||
"Navigation:",
|
||||
Style::default().add_modifier(Modifier::BOLD),
|
||||
)]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Tab", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Switch between panes"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Enter", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Execute command"),
|
||||
]),
|
||||
Line::from(vec![
|
||||
Span::styled(" Backspace", Style::default().fg(Color::Yellow)),
|
||||
Span::raw(" - Delete character"),
|
||||
]),
|
||||
Line::from(""),
|
||||
Line::from(vec![Span::raw("Press any key to return to main view...")]),
|
||||
];
|
||||
|
|
@ -333,30 +369,46 @@ impl ConsoleApp {
|
|||
|
||||
// System information
|
||||
let system_info = match self.system.lock() {
|
||||
Ok(system) => {
|
||||
match system.get_system_info() {
|
||||
Ok(system) => match system.get_system_info() {
|
||||
info => vec![
|
||||
Line::from(vec![Span::raw(format!("System: {}", info.name))]),
|
||||
Line::from(vec![Span::raw(format!("Description: {}", info.description))]),
|
||||
Line::from(vec![Span::raw(format!("Components: {}", info.component_count))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"Description: {}",
|
||||
info.description
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"Components: {}",
|
||||
info.component_count
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!("CPU Speed: {} Hz", info.cpu_speed))]),
|
||||
Line::from(vec![Span::raw(format!("ROM Size: {} bytes", info.rom_size))]),
|
||||
Line::from(vec![Span::raw(format!("RAM Size: {} nibbles", info.ram_size))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"ROM Size: {} bytes",
|
||||
info.rom_size
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!(
|
||||
"RAM Size: {} nibbles",
|
||||
info.ram_size
|
||||
))]),
|
||||
Line::from(vec![Span::raw(format!("Running: {}", system.is_running()))]),
|
||||
]
|
||||
}
|
||||
}
|
||||
],
|
||||
},
|
||||
Err(_) => {
|
||||
vec![Line::from(vec![Span::raw("System information unavailable")])]
|
||||
vec![Line::from(vec![Span::raw(
|
||||
"System information unavailable",
|
||||
)])]
|
||||
}
|
||||
};
|
||||
|
||||
let system_widget = Paragraph::new(system_info)
|
||||
.block(Block::default().borders(Borders::ALL).title("System Information"))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("System Information"),
|
||||
)
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(system_widget, info_chunks[0]);
|
||||
|
||||
// Registers (placeholder for now - would need CPU trait access)
|
||||
// Registers (placeholder for now - would need direct CPU component access)
|
||||
let register_info = vec![
|
||||
Line::from(vec![Span::raw("CPU Registers:")]),
|
||||
Line::from(vec![Span::raw("PC: 0x0000")]),
|
||||
|
|
@ -366,7 +418,11 @@ impl ConsoleApp {
|
|||
];
|
||||
|
||||
let register_widget = Paragraph::new(register_info)
|
||||
.block(Block::default().borders(Borders::ALL).title("CPU Registers"))
|
||||
.block(
|
||||
Block::default()
|
||||
.borders(Borders::ALL)
|
||||
.title("CPU Registers"),
|
||||
)
|
||||
.wrap(Wrap { trim: true });
|
||||
f.render_widget(register_widget, info_chunks[1]);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,6 @@
|
|||
//! - Extensible component system with trait-based architecture
|
||||
//! - Professional project organization with clean separation of concerns
|
||||
|
||||
mod bus;
|
||||
pub mod component;
|
||||
pub mod components;
|
||||
pub mod connection;
|
||||
|
|
|
|||
|
|
@ -83,7 +83,10 @@ fn main() {
|
|||
println!("===================");
|
||||
println!("System: {}", system_type);
|
||||
println!("Program: {}", program_file);
|
||||
println!("Console: {}", if use_console { "Enabled" } else { "Disabled" });
|
||||
println!(
|
||||
"Console: {}",
|
||||
if use_console { "Enabled" } else { "Disabled" }
|
||||
);
|
||||
|
||||
// Load program data
|
||||
let program_data = match load_program_data(&program_file) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,6 @@
|
|||
use rusty_emu::component::Component;
|
||||
use rusty_emu::components::common::intel_400x::*;
|
||||
use rusty_emu::components::memory::intel_4001::Intel4001;
|
||||
use rusty_emu::pin::PinValue;
|
||||
use std::time::Duration;
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -88,9 +87,6 @@ mod intel_4001_integration_tests {
|
|||
assert_eq!(rom.get_address_latch_time(), Some(test_time));
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_common_functionality_with_real_component() {
|
||||
let mut rom = Intel4001::new_with_access_time("ROM_4001".to_string(), 1);
|
||||
|
|
@ -202,7 +198,6 @@ mod intel_4001_integration_tests {
|
|||
assert_eq!(rom2.get_timing_state(), TimingState::Idle);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_performance_characteristics() {
|
||||
// Test that the common functionality maintains expected performance characteristics
|
||||
|
|
@ -282,7 +277,6 @@ mod intel_4001_integration_tests {
|
|||
assert_eq!(base_for_reset.get_name(), "ROM_4001");
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_cross_component_compatibility() {
|
||||
// Test that components using the same traits are compatible
|
||||
|
|
@ -337,7 +331,6 @@ mod cross_component_integration_tests {
|
|||
assert_eq!(rom2.get_timing_state(), TimingState::AddressPhase);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_timing_consistency_across_components() {
|
||||
// Test that timing behavior is consistent across different components
|
||||
|
|
|
|||
|
|
@ -1,47 +1,16 @@
|
|||
use rusty_emu::component::{BaseComponent, Component};
|
||||
use rusty_emu::components::common::intel_400x::*;
|
||||
use rusty_emu::pin::PinValue;
|
||||
use std::collections::HashMap;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use std::time::{Duration, Instant};
|
||||
|
||||
// Mock component for testing trait implementations
|
||||
#[derive(Debug)]
|
||||
struct MockComponent {
|
||||
pins: HashMap<String, Arc<Mutex<TestPin>>>,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct TestPin {
|
||||
value: PinValue,
|
||||
driver: Option<(String, PinValue)>,
|
||||
}
|
||||
|
||||
type Pin = TestPin;
|
||||
|
||||
impl Pin {
|
||||
fn set_driver(&mut self, driver: Option<String>, value: PinValue) {
|
||||
self.driver = driver.map(|name| (name, value));
|
||||
self.value = value;
|
||||
}
|
||||
}
|
||||
struct MockComponent {}
|
||||
|
||||
impl MockComponent {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
pins: HashMap::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn add_pin(&mut self, name: &str, pin: Arc<Mutex<Pin>>) {
|
||||
self.pins.insert(name.to_string(), pin);
|
||||
}
|
||||
|
||||
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<TestPin>>, String> {
|
||||
self.pins
|
||||
.get(name)
|
||||
.cloned()
|
||||
.ok_or_else(|| format!("Pin {} not found", name))
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -101,15 +70,11 @@ mod tests {
|
|||
#[test]
|
||||
fn test_assemble_full_address() {
|
||||
// Create a mock trait implementation for testing
|
||||
struct TestAddressHandler {
|
||||
base: MockComponent,
|
||||
}
|
||||
struct TestAddressHandler {}
|
||||
|
||||
impl TestAddressHandler {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base: MockComponent::new(),
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -268,15 +233,11 @@ mod tests {
|
|||
let access_time = Duration::from_nanos(100);
|
||||
|
||||
// Mock address handler for testing the algorithm
|
||||
struct TestAddressHandler {
|
||||
base: MockComponent,
|
||||
}
|
||||
struct TestAddressHandler {}
|
||||
|
||||
impl TestAddressHandler {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
base: MockComponent::new(),
|
||||
}
|
||||
Self {}
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -24,9 +24,13 @@ mod json_system_tests {
|
|||
// Test loading basic system configuration
|
||||
let result = factory.create_from_json("configs/mcs4_basic.json");
|
||||
|
||||
assert!(result.is_ok(), "Failed to create basic system: {:?}", result.err());
|
||||
assert!(
|
||||
result.is_ok(),
|
||||
"Failed to create basic system: {:?}",
|
||||
result.err()
|
||||
);
|
||||
|
||||
let mut system = result.unwrap();
|
||||
let system = result.unwrap();
|
||||
let info = system.get_system_info();
|
||||
|
||||
// Verify system properties
|
||||
|
|
@ -35,7 +39,6 @@ mod json_system_tests {
|
|||
assert_eq!(info.cpu_speed, 750000.0);
|
||||
}
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_system_component_access() {
|
||||
let factory = SystemFactory::new();
|
||||
|
|
@ -77,7 +80,10 @@ mod json_system_tests {
|
|||
|
||||
// Verify metadata is loaded correctly
|
||||
assert_eq!(info.name, "IntelMcs4");
|
||||
assert_eq!(info.description, "Basic MCS-4 System with CPU, Clock, 2 ROMs, and 1 RAM");
|
||||
assert_eq!(
|
||||
info.description,
|
||||
"Basic MCS-4 System with CPU, Clock, 2 ROMs, and 1 RAM"
|
||||
);
|
||||
assert_eq!(info.component_count, 5);
|
||||
assert_eq!(info.cpu_speed, 750000.0);
|
||||
assert_eq!(info.rom_size, 256);
|
||||
|
|
@ -105,7 +111,6 @@ mod system_execution_tests {
|
|||
let info = system.get_system_info();
|
||||
assert_eq!(info.name, "IntelMcs4");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
|
@ -137,12 +142,8 @@ mod fibonacci_program_tests {
|
|||
#[test]
|
||||
fn test_program_file_sizes() {
|
||||
// Test that program files have reasonable sizes
|
||||
let fibonacci_size = fs::metadata("programs/fibonacci.bin")
|
||||
.unwrap()
|
||||
.len();
|
||||
let fibonacci_output_size = fs::metadata("programs/fibonacci_output.bin")
|
||||
.unwrap()
|
||||
.len();
|
||||
let fibonacci_size = fs::metadata("programs/fibonacci.bin").unwrap().len();
|
||||
let fibonacci_output_size = fs::metadata("programs/fibonacci_output.bin").unwrap().len();
|
||||
|
||||
// Both should be non-zero and reasonable sizes
|
||||
assert!(fibonacci_size > 0);
|
||||
|
|
@ -156,7 +157,6 @@ mod fibonacci_program_tests {
|
|||
mod configuration_validation_tests {
|
||||
use super::*;
|
||||
|
||||
|
||||
#[test]
|
||||
fn test_component_naming_consistency() {
|
||||
let factory = SystemFactory::new();
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue