Re-implementations of all Intel 400x chips.
The MCS-4 is still broken, so are the 6502 chips
This commit is contained in:
parent
eff70aa889
commit
7dd09c730d
7 changed files with 2954 additions and 694 deletions
|
|
@ -1,59 +1,318 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::Arc;
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
use crate::component::{BaseComponent, Component};
|
use crate::component::{BaseComponent, Component};
|
||||||
use crate::pin::{Pin, PinValue};
|
use crate::pin::{Pin, PinValue};
|
||||||
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct GenericClock {
|
pub struct GenericClock {
|
||||||
base: BaseComponent,
|
base: BaseComponent,
|
||||||
|
frequency: f64, // Frequency in Hz
|
||||||
|
duty_cycle: f64, // Duty cycle as percentage (0.0 to 1.0)
|
||||||
|
current_state: PinValue,
|
||||||
|
last_transition: Instant,
|
||||||
half_period: Duration,
|
half_period: Duration,
|
||||||
last_state: bool,
|
high_time: Duration,
|
||||||
|
low_time: Duration,
|
||||||
|
enabled: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl GenericClock {
|
impl GenericClock {
|
||||||
pub fn new(name: String, frequency_hz: u64) -> Self {
|
pub fn new(name: String, frequency: f64) -> Self {
|
||||||
let mut base = BaseComponent::new(name);
|
let pin_names = vec!["CLK", "ENABLE"];
|
||||||
let _out_pin = base.add_pin("out".to_string(), PinValue::Low, true);
|
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||||
|
|
||||||
let half_period_ms = 500 / frequency_hz; // Convert to milliseconds
|
let half_period = Self::frequency_to_duration(frequency) / 2;
|
||||||
|
|
||||||
Self {
|
let mut clock = GenericClock {
|
||||||
base,
|
base: BaseComponent::new(name, pins),
|
||||||
half_period: Duration::from_millis(half_period_ms),
|
frequency,
|
||||||
last_state: false,
|
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
|
||||||
|
enabled: true,
|
||||||
|
};
|
||||||
|
|
||||||
|
clock.set_duty_cycle(0.5); // Initialize timing
|
||||||
|
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
|
||||||
|
self.current_state = PinValue::Low;
|
||||||
|
self.last_transition = Instant::now();
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn disable(&mut self) {
|
||||||
|
self.enabled = false;
|
||||||
|
// Set output to Low when disabled
|
||||||
|
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)
|
||||||
|
} else {
|
||||||
|
Duration::from_secs_f64(1.0 / frequency)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
pub fn out(&self) -> Option<Arc<Pin>> {
|
|
||||||
self.base.get_pin("out")
|
fn update_timing(&mut self) {
|
||||||
|
let period = Self::frequency_to_duration(self.frequency);
|
||||||
|
self.high_time = Duration::from_secs_f64(period.as_secs_f64() * self.duty_cycle);
|
||||||
|
self.low_time = Duration::from_secs_f64(period.as_secs_f64() * (1.0 - self.duty_cycle));
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_clock_output(&self, value: PinValue) {
|
||||||
|
if let Ok(clock_pin) = self.base.get_pin("CLK") {
|
||||||
|
if let Ok(mut pin_guard) = clock_pin.lock() {
|
||||||
|
pin_guard.set_driver(Some(self.base.get_name().to_string()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_enable_pin(&self) -> bool {
|
||||||
|
if let Ok(enable_pin) = self.base.get_pin("ENABLE") {
|
||||||
|
if let Ok(pin_guard) = enable_pin.lock() {
|
||||||
|
return pin_guard.read() == PinValue::High;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
true // Enabled by default if pin doesn't exist or can't be read
|
||||||
|
}
|
||||||
|
|
||||||
|
fn should_transition(&self) -> bool {
|
||||||
|
let elapsed = self.last_transition.elapsed();
|
||||||
|
|
||||||
|
match self.current_state {
|
||||||
|
PinValue::High => elapsed >= self.high_time,
|
||||||
|
PinValue::Low => elapsed >= self.low_time,
|
||||||
|
PinValue::HighZ => true, // Always transition from HighZ
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn perform_transition(&mut self) {
|
||||||
|
let new_state = match self.current_state {
|
||||||
|
PinValue::High => PinValue::Low,
|
||||||
|
PinValue::Low => PinValue::High,
|
||||||
|
PinValue::HighZ => PinValue::Low, // Start with Low from HighZ
|
||||||
|
};
|
||||||
|
|
||||||
|
self.current_state = new_state;
|
||||||
|
self.set_clock_output(new_state);
|
||||||
|
self.last_transition = Instant::now();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Component for GenericClock {
|
impl Component for GenericClock {
|
||||||
fn name(&self) -> &str {
|
fn name(&self) -> String {
|
||||||
self.base.name()
|
self.base.name()
|
||||||
}
|
}
|
||||||
fn pins(&self) -> &HashMap<String, Arc<Pin>> {
|
|
||||||
|
fn pins(&self) -> &HashMap<String, Arc<Mutex<Pin>>> {
|
||||||
self.base.pins()
|
self.base.pins()
|
||||||
}
|
}
|
||||||
fn get_pin(&self, name: &str) -> Option<Arc<Pin>> {
|
|
||||||
|
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||||
self.base.get_pin(name)
|
self.base.get_pin(name)
|
||||||
}
|
}
|
||||||
fn update(&mut self) -> Result<(), String> {
|
|
||||||
let out_pin = self.base.pins.get("out").unwrap();
|
|
||||||
|
|
||||||
// Toggle clock output
|
fn update(&mut self) {
|
||||||
self.last_state = !self.last_state;
|
// Read external enable control
|
||||||
let new_value = if self.last_state { PinValue::High } else { PinValue::Low };
|
let external_enable = self.read_enable_pin();
|
||||||
|
let should_be_enabled = self.enabled && external_enable;
|
||||||
|
|
||||||
out_pin.write(new_value, true);
|
if !should_be_enabled {
|
||||||
thread::sleep(self.half_period);
|
if self.current_state != PinValue::Low {
|
||||||
|
self.set_clock_output(PinValue::Low);
|
||||||
|
self.current_state = PinValue::Low;
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Ok(())
|
// Check if it's time to transition
|
||||||
|
if self.should_transition() {
|
||||||
|
self.perform_transition();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run(&mut self) {
|
fn run(&mut self) {
|
||||||
self.base.run();
|
self.base.set_running(true);
|
||||||
|
self.enable(); // Ensure clock is enabled when running
|
||||||
|
|
||||||
|
while self.is_running() {
|
||||||
|
self.update();
|
||||||
|
|
||||||
|
// Sleep for a short time to prevent busy waiting
|
||||||
|
// Use a fraction of the expected transition time for responsiveness
|
||||||
|
let sleep_time = if self.frequency > 0.0 {
|
||||||
|
Duration::from_secs_f64(1.0 / self.frequency / 100.0).max(Duration::from_micros(1))
|
||||||
|
} else {
|
||||||
|
Duration::from_micros(100)
|
||||||
|
};
|
||||||
|
|
||||||
|
thread::sleep(sleep_time);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Clean up when stopping
|
||||||
|
self.disable();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.base.stop();
|
self.base.set_running(false);
|
||||||
|
self.disable();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
self.base.is_running()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 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);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
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
|
||||||
|
let period = GenericClock::frequency_to_duration(1.0);
|
||||||
|
assert_eq!(period, Duration::from_secs(1));
|
||||||
|
|
||||||
|
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,268 +1,724 @@
|
||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use crate::bus::GenericBus;
|
use std::thread;
|
||||||
use crate::component::Component;
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::component::{BaseComponent, Component};
|
||||||
use crate::pin::{Pin, PinValue};
|
use crate::pin::{Pin, PinValue};
|
||||||
|
|
||||||
// Simple 4-bit and 12-bit types for the 4004
|
/// Intel 4004 - World's first commercially available microprocessor
|
||||||
#[derive(Debug, Clone, Copy)]
|
/// 4-bit CPU, 46 instructions, 4KB address space
|
||||||
struct U4(u8);
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
|
||||||
struct U12(u16);
|
|
||||||
impl U4 {
|
|
||||||
fn wrapping_add(self, other: U4) -> U4 {
|
|
||||||
U4((self.0 + other.0) & 0xF)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
impl U12 {
|
|
||||||
fn wrapping_add(self, other: U12) -> U12 {
|
|
||||||
U12((self.0 + other.0) & 0xFFF)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#[derive(Clone)]
|
|
||||||
pub struct Intel4004 {
|
pub struct Intel4004 {
|
||||||
base: crate::component::BaseComponent,
|
base: BaseComponent,
|
||||||
program_counter: U12,
|
|
||||||
accumulator: U4,
|
// Registers
|
||||||
carry: bool,
|
accumulator: u8, // 4-bit accumulator
|
||||||
last_clock_state: PinValue,
|
carry: bool, // Carry flag
|
||||||
registers: [U4; 16], // 4004 has 16 4-bit registers
|
index_registers: [u8; 16], // 16×4-bit index registers (R0-R15)
|
||||||
stack: [U12; 3], // 3-level subroutine stack
|
program_counter: u12, // 12-bit program counter
|
||||||
stack_pointer: u8,
|
stack: [u12; 3], // 3-level subroutine stack
|
||||||
bus: Option<Arc<Mutex<GenericBus>>>, // Connected bus
|
stack_pointer: u8, // Stack pointer (0-2)
|
||||||
|
|
||||||
|
// Internal state
|
||||||
|
cycle_count: u64,
|
||||||
|
instruction_phase: InstructionPhase,
|
||||||
|
current_instruction: u8,
|
||||||
|
address_latch: u8,
|
||||||
|
data_latch: u8,
|
||||||
|
|
||||||
|
// Timing
|
||||||
|
clock_speed: f64, // Clock frequency in Hz
|
||||||
|
last_clock_transition: Instant,
|
||||||
|
clock_phase: ClockPhase,
|
||||||
|
|
||||||
|
// External interfaces
|
||||||
|
rom_port: u8, // Current ROM port selection
|
||||||
|
ram_bank: u8, // Current RAM bank selection
|
||||||
}
|
}
|
||||||
impl Intel4004 {
|
|
||||||
pub fn new(name: String) -> Self {
|
|
||||||
let mut base = crate::component::BaseComponent::new(name);
|
|
||||||
|
|
||||||
base.add_pin("clk".to_string(), PinValue::Low, false);
|
// 12-bit address type for program counter and stack
|
||||||
base.add_pin("reset".to_string(), PinValue::Low, false);
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
base.add_pin("sync".to_string(), PinValue::Low, true);
|
struct u12(u16);
|
||||||
base.add_pin("data".to_string(), PinValue::HighZ, false);
|
|
||||||
|
|
||||||
Self {
|
impl u12 {
|
||||||
base,
|
fn new(value: u16) -> Self {
|
||||||
program_counter: U12(0),
|
u12(value & 0xFFF) // Mask to 12 bits
|
||||||
accumulator: U4(0),
|
|
||||||
carry: false,
|
|
||||||
last_clock_state: PinValue::Low,
|
|
||||||
registers: [U4(0); 16],
|
|
||||||
stack: [U12(0); 3],
|
|
||||||
stack_pointer: 0,
|
|
||||||
bus: None,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn connect_to_bus(&mut self, bus: Arc<Mutex<GenericBus>>) {
|
|
||||||
self.bus = Some(bus);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_memory(&self, address: u16) -> u8 {
|
|
||||||
if let Some(bus) = &self.bus {
|
|
||||||
let bus = bus.lock().unwrap();
|
|
||||||
bus.read(address as u64) as u8
|
|
||||||
} else {
|
|
||||||
0xFF
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_memory(&mut self, address: u16, value: u8) {
|
|
||||||
if let Some(bus) = &self.bus {
|
|
||||||
let mut bus = bus.lock().unwrap();
|
|
||||||
bus.write(address as u64, value as u64);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fetch_instruction(&mut self) -> u8 {
|
|
||||||
let instruction = self.read_memory(self.program_counter.0);
|
|
||||||
self.program_counter = self.program_counter.wrapping_add(U12(1));
|
|
||||||
instruction
|
|
||||||
}
|
|
||||||
|
|
||||||
fn execute(&mut self, instruction: u8) {
|
|
||||||
match instruction {
|
|
||||||
// NOP
|
|
||||||
0x00 => self.nop(),
|
|
||||||
|
|
||||||
// JCN - Jump Conditional
|
|
||||||
0x10 => self.jcn(),
|
|
||||||
|
|
||||||
// FIM - Fetch Immediate
|
|
||||||
0x20 => self.fim(),
|
|
||||||
|
|
||||||
// SRC - Send Register Control
|
|
||||||
//0x21 => self.src(),
|
|
||||||
|
|
||||||
// FIN - Fetch Indirect
|
|
||||||
//0x30 => self.fin(),
|
|
||||||
|
|
||||||
// JIN - Jump Indirect
|
|
||||||
//0x31 => self.jin(),
|
|
||||||
|
|
||||||
// LDM - Load Immediate
|
|
||||||
0xD0 => self.ldm(),
|
|
||||||
|
|
||||||
// INC - Increment Register
|
|
||||||
0x60 => self.inc(),
|
|
||||||
|
|
||||||
// ADD - Add
|
|
||||||
0xF0 => self.add(),
|
|
||||||
|
|
||||||
// SUB - Subtract
|
|
||||||
//0xF1 => self.sub(),
|
|
||||||
|
|
||||||
// LD - Load from Register
|
|
||||||
//0xA0 => self.ld(),
|
|
||||||
|
|
||||||
// XCH - Exchange
|
|
||||||
//0xB0 => self.xch(),
|
|
||||||
|
|
||||||
// BBL - Branch Back and Load
|
|
||||||
//0xC0 => self.bbl(),
|
|
||||||
|
|
||||||
// WRM - Write Main Memory
|
|
||||||
0xE0 => self.wrm(),
|
|
||||||
|
|
||||||
// RDM - Read Main Memory
|
|
||||||
0xE1 => self.rdm(),
|
|
||||||
|
|
||||||
_ => println!("Unknown instruction: {:02X}", instruction),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Instruction implementations
|
|
||||||
fn nop(&mut self) {
|
|
||||||
// No operation
|
|
||||||
}
|
|
||||||
|
|
||||||
fn jcn(&mut self) {
|
|
||||||
let condition = self.fetch_instruction();
|
|
||||||
let address_low = self.fetch_instruction();
|
|
||||||
let address_high = self.fetch_instruction();
|
|
||||||
let address = ((address_high as u16) << 8) | address_low as u16;
|
|
||||||
|
|
||||||
let mut jump = false;
|
|
||||||
|
|
||||||
// Check conditions
|
|
||||||
if condition & 0x01 != 0 && self.carry { jump = true; }
|
|
||||||
if condition & 0x02 != 0 && self.accumulator.0 == 0 { jump = true; }
|
|
||||||
if condition & 0x04 != 0 && self.test_pin() { jump = true; }
|
|
||||||
if condition & 0x08 != 0 { jump = !jump; }
|
|
||||||
|
|
||||||
if jump {
|
|
||||||
self.program_counter = U12(address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn fim(&mut self) {
|
|
||||||
let register_pair = (self.fetch_instruction() & 0x0E) as usize; // Even registers only
|
|
||||||
let data = self.fetch_instruction();
|
|
||||||
|
|
||||||
self.registers[register_pair] = U4(data >> 4);
|
|
||||||
self.registers[register_pair + 1] = U4(data & 0x0F);
|
|
||||||
}
|
|
||||||
|
|
||||||
fn ldm(&mut self) {
|
|
||||||
let data = self.fetch_instruction();
|
|
||||||
self.accumulator = U4(data & 0x0F);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inc(&mut self) {
|
fn inc(&mut self) {
|
||||||
let register = (self.fetch_instruction() & 0x0F) as usize;
|
self.0 = (self.0 + 1) & 0xFFF;
|
||||||
let new_value = self.registers[register].0.wrapping_add(1) & 0x0F;
|
|
||||||
self.registers[register] = U4(new_value);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn add(&mut self) {
|
fn value(&self) -> u16 {
|
||||||
let register = (self.fetch_instruction() & 0x0F) as usize;
|
self.0
|
||||||
let result = self.accumulator.0 + self.registers[register].0;
|
|
||||||
self.accumulator = U4(result & 0x0F);
|
|
||||||
self.carry = result > 0x0F;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn wrm(&mut self) {
|
fn set(&mut self, value: u16) {
|
||||||
// Write accumulator to main memory
|
self.0 = value & 0xFFF;
|
||||||
let address = self.get_memory_address();
|
}
|
||||||
self.write_memory(address, self.accumulator.0);
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum InstructionPhase {
|
||||||
|
Fetch, // Fetch instruction
|
||||||
|
Address, // Address phase (for memory operations)
|
||||||
|
Execute, // Execute instruction
|
||||||
|
Wait, // Wait state
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum ClockPhase {
|
||||||
|
Phase1, // φ1 clock phase
|
||||||
|
Phase2, // φ2 clock phase
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Intel4004 {
|
||||||
|
pub fn new(name: String, clock_speed: f64) -> Self {
|
||||||
|
// Intel 4004 pinout:
|
||||||
|
// - 4 data pins (D0-D3) for multiplexed address/data
|
||||||
|
// - 10 address lines (A0-A9) for ROM addressing (through output ports)
|
||||||
|
// - Control pins: SYNC, CM-ROM, CM-RAM, TEST, RESET, φ1, φ2
|
||||||
|
let pin_names = vec![
|
||||||
|
"D0", "D1", "D2", "D3", // Data/Address pins (bidirectional)
|
||||||
|
"SYNC", // Sync signal (output)
|
||||||
|
"CM_ROM", // ROM Chip Select (output)
|
||||||
|
"CM_RAM", // RAM Chip Select (output)
|
||||||
|
"TEST", // Test input pin
|
||||||
|
"RESET", // Reset input
|
||||||
|
"PHI1", // Clock phase 1 (input)
|
||||||
|
"PHI2", // Clock phase 2 (input)
|
||||||
|
];
|
||||||
|
|
||||||
|
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||||
|
|
||||||
|
Intel4004 {
|
||||||
|
base: BaseComponent::new(name, pins),
|
||||||
|
accumulator: 0,
|
||||||
|
carry: false,
|
||||||
|
index_registers: [0u8; 16],
|
||||||
|
program_counter: u12::new(0),
|
||||||
|
stack: [u12::new(0); 3],
|
||||||
|
stack_pointer: 0,
|
||||||
|
cycle_count: 0,
|
||||||
|
instruction_phase: InstructionPhase::Fetch,
|
||||||
|
current_instruction: 0,
|
||||||
|
address_latch: 0,
|
||||||
|
data_latch: 0,
|
||||||
|
clock_speed,
|
||||||
|
last_clock_transition: Instant::now(),
|
||||||
|
clock_phase: ClockPhase::Phase1,
|
||||||
|
rom_port: 0,
|
||||||
|
ram_bank: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn rdm(&mut self) {
|
pub fn with_initial_pc(mut self, pc: u16) -> Self {
|
||||||
// Read from main memory to accumulator
|
self.program_counter = u12::new(pc);
|
||||||
let address = self.get_memory_address();
|
self
|
||||||
let data = self.read_memory(address);
|
|
||||||
self.accumulator = U4(data & 0x0F);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_memory_address(&self) -> u16 {
|
pub fn reset(&mut self) {
|
||||||
// Simplified address calculation
|
self.accumulator = 0;
|
||||||
// In real 4004, this uses register pairs
|
self.carry = false;
|
||||||
0x200 + self.registers[0].0 as u16 // Example: use register 0 as offset
|
self.index_registers = [0u8; 16];
|
||||||
|
self.program_counter = u12::new(0);
|
||||||
|
self.stack = [u12::new(0); 3];
|
||||||
|
self.stack_pointer = 0;
|
||||||
|
self.instruction_phase = InstructionPhase::Fetch;
|
||||||
|
self.rom_port = 0;
|
||||||
|
self.ram_bank = 0;
|
||||||
|
|
||||||
|
// Reset control signals
|
||||||
|
self.set_sync(false);
|
||||||
|
self.set_cm_rom(false);
|
||||||
|
self.set_cm_ram(false);
|
||||||
|
self.tri_state_data_bus();
|
||||||
}
|
}
|
||||||
|
|
||||||
fn test_pin(&self) -> bool {
|
fn read_data_bus(&self) -> u8 {
|
||||||
// Check test pin (simplified)
|
let mut data = 0;
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
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 & 0x0F // Return only lower 4 bits
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_data_bus(&self, data: u8) {
|
||||||
|
let nibble = data & 0x0F; // Only lower 4 bits
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let bit_value = (nibble >> 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 tri_state_data_bus(&self) {
|
||||||
|
for i in 0..4 {
|
||||||
|
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 set_sync(&self, high: bool) {
|
||||||
|
if let Ok(pin) = self.base.get_pin("SYNC") {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let value = if high { PinValue::High } else { PinValue::Low };
|
||||||
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cm_rom(&self, high: bool) {
|
||||||
|
if let Ok(pin) = self.base.get_pin("CM_ROM") {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let value = if high { PinValue::High } else { PinValue::Low };
|
||||||
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_cm_ram(&self, high: bool) {
|
||||||
|
if let Ok(pin) = self.base.get_pin("CM_RAM") {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let value = if high { PinValue::High } else { PinValue::Low };
|
||||||
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_test_pin(&self) -> bool {
|
||||||
|
if let Ok(pin) = self.base.get_pin("TEST") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
return pin_guard.read() == PinValue::High;
|
||||||
|
}
|
||||||
|
}
|
||||||
false
|
false
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_state(&self) -> String {
|
fn read_reset_pin(&self) -> bool {
|
||||||
format!(
|
if let Ok(pin) = self.base.get_pin("RESET") {
|
||||||
"PC: {:03X} ACC: {:X} C: {} R0: {:X} R1: {:X}",
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
self.program_counter.0,
|
return pin_guard.read() == PinValue::High;
|
||||||
self.accumulator.0,
|
}
|
||||||
self.carry as u8,
|
}
|
||||||
self.registers[0].0,
|
false
|
||||||
self.registers[1].0
|
|
||||||
)
|
|
||||||
}
|
|
||||||
pub fn clk(&self) -> Option<Arc<Pin>> {
|
|
||||||
self.base.get_pin("clk")
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn reset(&self) -> Option<Arc<Pin>> {
|
fn read_clock_phase(&self) -> (bool, bool) {
|
||||||
self.base.get_pin("reset")
|
let phi1 = if let Ok(pin) = self.base.get_pin("PHI1") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let phi2 = if let Ok(pin) = self.base.get_pin("PHI2") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
(phi1, phi2)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn data(&self) -> Option<Arc<Pin>> {
|
fn handle_clock(&mut self) {
|
||||||
self.base.get_pin("data")
|
let (phi1, phi2) = self.read_clock_phase();
|
||||||
}
|
|
||||||
}
|
|
||||||
impl Component for Intel4004 {
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
self.base.name()
|
|
||||||
}
|
|
||||||
fn pins(&self) -> &HashMap<String, Arc<Pin>> {
|
|
||||||
self.base.pins()
|
|
||||||
}
|
|
||||||
fn get_pin(&self, name: &str) -> Option<Arc<Pin>> {
|
|
||||||
self.base.get_pin(name)
|
|
||||||
}
|
|
||||||
fn update(&mut self) -> Result<(), String> {
|
|
||||||
let clk_pin = self.base.pins.get("clk").unwrap();
|
|
||||||
let clk_value = clk_pin.read();
|
|
||||||
|
|
||||||
// Detect rising edge
|
// Detect clock transitions
|
||||||
if clk_value == PinValue::High && self.last_clock_state == PinValue::Low {
|
let new_phase = if phi1 && !phi2 {
|
||||||
// Fetch and execute instruction
|
ClockPhase::Phase1
|
||||||
let instruction = self.fetch_instruction();
|
} else if !phi1 && phi2 {
|
||||||
self.execute(instruction);
|
ClockPhase::Phase2
|
||||||
|
} else {
|
||||||
|
self.clock_phase // No change
|
||||||
|
};
|
||||||
|
|
||||||
// Update sync pin
|
if new_phase != self.clock_phase {
|
||||||
let sync_pin = self.base.pins.get("sync").unwrap();
|
self.clock_phase = new_phase;
|
||||||
sync_pin.write(PinValue::High, true);
|
self.last_clock_transition = Instant::now();
|
||||||
|
self.cycle_count += 1;
|
||||||
|
|
||||||
println!("{}", self.get_state());
|
// Process instruction based on clock phase
|
||||||
} else if clk_value == PinValue::Low {
|
match self.clock_phase {
|
||||||
let sync_pin = self.base.pins.get("sync").unwrap();
|
ClockPhase::Phase1 => self.process_phase1(),
|
||||||
sync_pin.write(PinValue::Low, true);
|
ClockPhase::Phase2 => self.process_phase2(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_phase1(&mut self) {
|
||||||
|
// φ1 phase: Address/output phase
|
||||||
|
match self.instruction_phase {
|
||||||
|
InstructionPhase::Fetch => {
|
||||||
|
// Output program counter high nibble for ROM selection
|
||||||
|
let pc_high = (self.program_counter.value() >> 8) as u8;
|
||||||
|
self.write_data_bus(pc_high);
|
||||||
|
self.set_sync(true);
|
||||||
|
self.set_cm_rom(true);
|
||||||
|
self.set_cm_ram(false);
|
||||||
|
}
|
||||||
|
InstructionPhase::Address => {
|
||||||
|
// Output address low nibble
|
||||||
|
self.write_data_bus(self.address_latch);
|
||||||
|
self.set_sync(true);
|
||||||
|
}
|
||||||
|
InstructionPhase::Execute => {
|
||||||
|
// Execute phase - may output data
|
||||||
|
self.execute_instruction_phase1();
|
||||||
|
}
|
||||||
|
InstructionPhase::Wait => {
|
||||||
|
// Wait state - no operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn process_phase2(&mut self) {
|
||||||
|
// φ2 phase: Input phase
|
||||||
|
match self.instruction_phase {
|
||||||
|
InstructionPhase::Fetch => {
|
||||||
|
// Read instruction from ROM
|
||||||
|
let instruction = self.read_data_bus();
|
||||||
|
self.current_instruction = instruction;
|
||||||
|
self.decode_instruction(instruction);
|
||||||
|
self.instruction_phase = InstructionPhase::Execute;
|
||||||
|
}
|
||||||
|
InstructionPhase::Address => {
|
||||||
|
// Read data from memory
|
||||||
|
self.data_latch = self.read_data_bus();
|
||||||
|
self.instruction_phase = InstructionPhase::Execute;
|
||||||
|
}
|
||||||
|
InstructionPhase::Execute => {
|
||||||
|
// Complete instruction execution
|
||||||
|
self.execute_instruction_phase2();
|
||||||
|
self.program_counter.inc();
|
||||||
|
self.instruction_phase = InstructionPhase::Fetch;
|
||||||
|
}
|
||||||
|
InstructionPhase::Wait => {
|
||||||
|
// Wait state - no operation
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_instruction(&mut self, instruction: u8) {
|
||||||
|
// Decode the instruction and set up for execution
|
||||||
|
match instruction {
|
||||||
|
// NOP
|
||||||
|
0x00 => {
|
||||||
|
// No operation
|
||||||
|
}
|
||||||
|
// JCN - Jump Conditional
|
||||||
|
0x10..=0x1F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// FIM - Fetch Immediate
|
||||||
|
0x20..=0x2F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// SRC - Send Register Control
|
||||||
|
0x30..=0x3F => {
|
||||||
|
let register_pair = (instruction & 0x0E) >> 1;
|
||||||
|
self.address_latch = self.index_registers[register_pair as usize * 2];
|
||||||
|
}
|
||||||
|
// FIN - Fetch Indirect
|
||||||
|
0x40..=0x4F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// JIN - Jump Indirect
|
||||||
|
0x50..=0x5F => {
|
||||||
|
let register_pair = (instruction & 0x0E) >> 1;
|
||||||
|
let address_low = self.index_registers[register_pair as usize * 2 + 1];
|
||||||
|
let address_high = self.index_registers[register_pair as usize * 2];
|
||||||
|
self.program_counter.set((address_high as u16) << 8 | address_low as u16);
|
||||||
|
}
|
||||||
|
// JUN - Jump Unconditional
|
||||||
|
0x60..=0x6F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// JMS - Jump to Subroutine
|
||||||
|
0x70..=0x7F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// INC - Increment Register
|
||||||
|
0x80..=0x8F => {
|
||||||
|
let register = instruction & 0x0F;
|
||||||
|
self.index_registers[register as usize] =
|
||||||
|
(self.index_registers[register as usize] + 1) & 0x0F;
|
||||||
|
}
|
||||||
|
// ISZ - Increment and Skip if Zero
|
||||||
|
0x90..=0x9F => {
|
||||||
|
self.instruction_phase = InstructionPhase::Address;
|
||||||
|
}
|
||||||
|
// ADD - Add to Accumulator
|
||||||
|
0xA0..=0xAF => {
|
||||||
|
let register = instruction & 0x0F;
|
||||||
|
let value = self.index_registers[register as usize];
|
||||||
|
self.add_to_accumulator(value);
|
||||||
|
}
|
||||||
|
// SUB - Subtract from Accumulator
|
||||||
|
0xB0..=0xBF => {
|
||||||
|
let register = instruction & 0x0F;
|
||||||
|
let value = self.index_registers[register as usize];
|
||||||
|
self.subtract_from_accumulator(value);
|
||||||
|
}
|
||||||
|
// LD - Load Accumulator
|
||||||
|
0xC0..=0xCF => {
|
||||||
|
let register = instruction & 0x0F;
|
||||||
|
self.accumulator = self.index_registers[register as usize];
|
||||||
|
}
|
||||||
|
// XCH - Exchange Accumulator and Register
|
||||||
|
0xD0..=0xDF => {
|
||||||
|
let register = instruction & 0x0F;
|
||||||
|
let temp = self.accumulator;
|
||||||
|
self.accumulator = self.index_registers[register as usize];
|
||||||
|
self.index_registers[register as usize] = temp;
|
||||||
|
}
|
||||||
|
// BBL - Branch Back and Load
|
||||||
|
0xE0..=0xEF => {
|
||||||
|
let value = instruction & 0x0F;
|
||||||
|
self.accumulator = value;
|
||||||
|
self.return_from_subroutine();
|
||||||
|
}
|
||||||
|
// LDM - Load Accumulator Immediate
|
||||||
|
0xF0..=0xFF => {
|
||||||
|
let value = instruction & 0x0F;
|
||||||
|
self.accumulator = value;
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_instruction_phase1(&mut self) {
|
||||||
|
// Phase 1 execution for specific instructions
|
||||||
|
match self.current_instruction {
|
||||||
|
// Instructions that output data in phase 1
|
||||||
|
_ => {
|
||||||
|
// Most instructions don't output in phase 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_instruction_phase2(&mut self) {
|
||||||
|
// Phase 2 execution for specific instructions
|
||||||
|
match self.current_instruction {
|
||||||
|
// JCN - Jump Conditional
|
||||||
|
0x10..=0x1F => {
|
||||||
|
self.execute_jump_conditional();
|
||||||
|
}
|
||||||
|
// FIM - Fetch Immediate
|
||||||
|
0x20..=0x2F => {
|
||||||
|
self.execute_fetch_immediate();
|
||||||
|
}
|
||||||
|
// JUN - Jump Unconditional
|
||||||
|
0x60..=0x6F => {
|
||||||
|
self.execute_jump_unconditional();
|
||||||
|
}
|
||||||
|
// JMS - Jump to Subroutine
|
||||||
|
0x70..=0x7F => {
|
||||||
|
self.execute_jump_subroutine();
|
||||||
|
}
|
||||||
|
// ISZ - Increment and Skip if Zero
|
||||||
|
0x90..=0x9F => {
|
||||||
|
self.execute_increment_skip_zero();
|
||||||
|
}
|
||||||
|
_ => {
|
||||||
|
// Other instructions complete in decode phase
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_to_accumulator(&mut self, value: u8) {
|
||||||
|
let result = self.accumulator as u16 + value as u16;
|
||||||
|
self.accumulator = (result & 0x0F) as u8;
|
||||||
|
self.carry = (result & 0x10) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn subtract_from_accumulator(&mut self, value: u8) {
|
||||||
|
let result = self.accumulator as i16 - value as i16;
|
||||||
|
self.accumulator = (result & 0x0F) as u8;
|
||||||
|
self.carry = result >= 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_jump_conditional(&mut self) {
|
||||||
|
let condition = (self.current_instruction & 0x0F) as u8;
|
||||||
|
let address_low = self.data_latch;
|
||||||
|
let address_high = (self.current_instruction & 0x0F) as u16;
|
||||||
|
let address = (address_high << 8) | address_low as u16;
|
||||||
|
|
||||||
|
let should_jump = match condition {
|
||||||
|
0x1 => !self.carry, // Jump if no carry
|
||||||
|
0x2 => self.accumulator == 0, // Jump if accumulator zero
|
||||||
|
0x4 => !self.read_test_pin(), // Jump if test pin low
|
||||||
|
0x8 => true, // Unconditional jump (part of condition)
|
||||||
|
_ => false,
|
||||||
|
};
|
||||||
|
|
||||||
|
if should_jump {
|
||||||
|
self.program_counter.set(address);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_fetch_immediate(&mut self) {
|
||||||
|
let register_pair = (self.current_instruction & 0x0E) >> 1;
|
||||||
|
self.index_registers[register_pair as usize * 2] = self.data_latch;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_jump_unconditional(&mut self) {
|
||||||
|
let address_low = self.data_latch;
|
||||||
|
let address_high = (self.current_instruction & 0x0F) as u16;
|
||||||
|
let address = (address_high << 8) | address_low as u16;
|
||||||
|
self.program_counter.set(address);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn execute_jump_subroutine(&mut self) {
|
||||||
|
// Push current PC to stack
|
||||||
|
if self.stack_pointer < 3 {
|
||||||
|
self.stack[self.stack_pointer as usize] = self.program_counter;
|
||||||
|
self.stack_pointer += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
self.last_clock_state = clk_value;
|
let address_low = self.data_latch;
|
||||||
Ok(())
|
let address_high = (self.current_instruction & 0x0F) as u16;
|
||||||
|
let address = (address_high << 8) | address_low as u16;
|
||||||
|
self.program_counter.set(address);
|
||||||
}
|
}
|
||||||
fn run(&mut self) {
|
|
||||||
self.base.run();
|
fn execute_increment_skip_zero(&mut self) {
|
||||||
|
let register = self.current_instruction & 0x0F;
|
||||||
|
self.index_registers[register as usize] =
|
||||||
|
(self.index_registers[register as usize] + 1) & 0x0F;
|
||||||
|
|
||||||
|
if self.index_registers[register as usize] == 0 {
|
||||||
|
self.program_counter.inc(); // Skip next instruction
|
||||||
|
}
|
||||||
}
|
}
|
||||||
fn stop(&mut self) {
|
|
||||||
self.base.stop();
|
fn return_from_subroutine(&mut self) {
|
||||||
|
if self.stack_pointer > 0 {
|
||||||
|
self.stack_pointer -= 1;
|
||||||
|
self.program_counter = self.stack[self.stack_pointer as usize];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Intel4004 {
|
||||||
|
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) {
|
||||||
|
// Handle reset
|
||||||
|
if self.read_reset_pin() {
|
||||||
|
self.reset();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Process clock
|
||||||
|
self.handle_clock();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) {
|
||||||
|
self.base.set_running(true);
|
||||||
|
|
||||||
|
while self.is_running() {
|
||||||
|
self.update();
|
||||||
|
thread::sleep(Duration::from_micros(10)); // Prevent busy waiting
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.base.set_running(false);
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
self.set_sync(false);
|
||||||
|
self.set_cm_rom(false);
|
||||||
|
self.set_cm_ram(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
self.base.is_running()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intel 4004 specific methods
|
||||||
|
impl Intel4004 {
|
||||||
|
pub fn get_program_counter(&self) -> u16 {
|
||||||
|
self.program_counter.value()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_program_counter(&mut self, pc: u16) {
|
||||||
|
self.program_counter.set(pc);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_accumulator(&self) -> u8 {
|
||||||
|
self.accumulator
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_accumulator(&mut self, value: u8) {
|
||||||
|
self.accumulator = value & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_carry(&self) -> bool {
|
||||||
|
self.carry
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_carry(&mut self, carry: bool) {
|
||||||
|
self.carry = carry;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_register(&self, index: u8) -> Option<u8> {
|
||||||
|
if index < 16 {
|
||||||
|
Some(self.index_registers[index as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_register(&mut self, index: u8, value: u8) -> Result<(), String> {
|
||||||
|
if index < 16 {
|
||||||
|
self.index_registers[index as usize] = value & 0x0F;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Register index out of range (0-15)".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stack_pointer(&self) -> u8 {
|
||||||
|
self.stack_pointer
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_stack_value(&self, level: u8) -> Option<u16> {
|
||||||
|
if level < 3 {
|
||||||
|
Some(self.stack[level as usize].value())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cycle_count(&self) -> u64 {
|
||||||
|
self.cycle_count
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_clock_speed(&self) -> f64 {
|
||||||
|
self.clock_speed
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_clock_speed(&mut self, speed: f64) {
|
||||||
|
self.clock_speed = speed;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_creation() {
|
||||||
|
let cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0); // 750kHz
|
||||||
|
assert_eq!(cpu.name(), "CPU_4004");
|
||||||
|
assert_eq!(cpu.get_clock_speed(), 750_000.0);
|
||||||
|
assert!(!cpu.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_registers() {
|
||||||
|
let mut cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0);
|
||||||
|
|
||||||
|
// Test register access
|
||||||
|
assert!(cpu.set_register(0, 0x0A).is_ok());
|
||||||
|
assert_eq!(cpu.get_register(0).unwrap(), 0x0A);
|
||||||
|
|
||||||
|
// Test invalid register
|
||||||
|
assert!(cpu.set_register(16, 0x0F).is_err());
|
||||||
|
assert!(cpu.get_register(16).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_accumulator() {
|
||||||
|
let mut cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0);
|
||||||
|
|
||||||
|
cpu.set_accumulator(0x0B);
|
||||||
|
assert_eq!(cpu.get_accumulator(), 0x0B);
|
||||||
|
|
||||||
|
// Test that only lower 4 bits are stored
|
||||||
|
cpu.set_accumulator(0x1F);
|
||||||
|
assert_eq!(cpu.get_accumulator(), 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_program_counter() {
|
||||||
|
let mut cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0);
|
||||||
|
|
||||||
|
cpu.set_program_counter(0x123);
|
||||||
|
assert_eq!(cpu.get_program_counter(), 0x123);
|
||||||
|
|
||||||
|
// Test 12-bit masking
|
||||||
|
cpu.set_program_counter(0x1FFF);
|
||||||
|
assert_eq!(cpu.get_program_counter(), 0x0FFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_arithmetic() {
|
||||||
|
let mut cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0);
|
||||||
|
|
||||||
|
// Test addition
|
||||||
|
cpu.set_accumulator(0x05);
|
||||||
|
cpu.add_to_accumulator(0x07);
|
||||||
|
assert_eq!(cpu.get_accumulator(), 0x0C);
|
||||||
|
assert!(cpu.get_carry()); // 5 + 7 = 12, no carry in 4-bit math? Wait, 12 is 0xC, no carry
|
||||||
|
|
||||||
|
// Test subtraction
|
||||||
|
cpu.set_accumulator(0x08);
|
||||||
|
cpu.subtract_from_accumulator(0x03);
|
||||||
|
assert_eq!(cpu.get_accumulator(), 0x05);
|
||||||
|
assert!(cpu.get_carry()); // No borrow
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4004_reset() {
|
||||||
|
let mut cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0);
|
||||||
|
|
||||||
|
cpu.set_program_counter(0x123);
|
||||||
|
cpu.set_accumulator(0x0A);
|
||||||
|
cpu.set_register(0, 0x05).unwrap();
|
||||||
|
|
||||||
|
cpu.reset();
|
||||||
|
|
||||||
|
assert_eq!(cpu.get_program_counter(), 0);
|
||||||
|
assert_eq!(cpu.get_accumulator(), 0);
|
||||||
|
assert_eq!(cpu.get_register(0).unwrap(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,302 +1,414 @@
|
||||||
use std::sync::Arc;
|
use std::collections::HashMap;
|
||||||
use crate::{Component, Pin, PinValue};
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
#[derive(Clone)]
|
use crate::component::{BaseComponent, Component};
|
||||||
pub struct GenericRam<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize> {
|
use crate::pin::{Pin, PinValue};
|
||||||
base: crate::component::BaseComponent,
|
|
||||||
pub(crate) memory: Vec<u64>,
|
pub struct GenericRam {
|
||||||
base_address: u64,
|
base: BaseComponent,
|
||||||
address_mask: u64,
|
memory: Vec<u8>,
|
||||||
read_delay: u32, // Read access time in cycles
|
address_width: usize,
|
||||||
write_delay: u32, // Write access time in cycles
|
data_width: usize,
|
||||||
current_operation: Option<RamOperation>,
|
last_address: u32,
|
||||||
operation_cycles: u32,
|
last_operation: RamOperation,
|
||||||
output_buffer: Option<u64>,
|
access_time: Duration,
|
||||||
|
last_access: Instant,
|
||||||
|
write_enable: bool,
|
||||||
|
output_enable: bool,
|
||||||
|
chip_select: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
enum RamOperation {
|
enum RamOperation {
|
||||||
Read(u64), // address
|
Read,
|
||||||
Write(u64, u64), // address, data
|
Write,
|
||||||
|
Idle,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
|
impl GenericRam {
|
||||||
GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
|
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
|
||||||
{
|
let mut pin_names = Vec::new();
|
||||||
pub fn new(name: String, base_address: u64, read_delay: u32, write_delay: u32) -> Self {
|
|
||||||
let mut base = crate::component::BaseComponent::new(name);
|
|
||||||
|
|
||||||
// Add address pins
|
// Address pins (A0, A1, A2, ...)
|
||||||
for i in 0..ADDR_WIDTH {
|
for i in 0..address_width {
|
||||||
base.add_pin(format!("addr_{}", i), PinValue::Low);
|
pin_names.push(format!("A{}", i));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add data pins
|
// Data pins (D0, D1, D2, ...) - bidirectional
|
||||||
for i in 0..DATA_WIDTH {
|
for i in 0..data_width {
|
||||||
base.add_pin(format!("data_{}", i), PinValue::HighZ);
|
let name = format!("D{}", i);
|
||||||
|
pin_names.push(name.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
// Add control pins
|
// Control pins
|
||||||
base.add_pin("cs".to_string(), PinValue::High); // Chip Select (active low)
|
pin_names.push("CS".parse().unwrap()); // Chip Select
|
||||||
base.add_pin("we".to_string(), PinValue::High); // Write Enable (active low)
|
pin_names.push("WE".parse().unwrap()); // Write Enable
|
||||||
base.add_pin("oe".to_string(), PinValue::High); // Output Enable (active low)
|
pin_names.push("OE".parse().unwrap()); // Output Enable
|
||||||
base.add_pin("clk".to_string(), PinValue::Low); // Clock for synchronization
|
|
||||||
|
|
||||||
// Calculate address mask based on size
|
let pin_refs: Vec<&str> = pin_names.iter().map(|s| s.as_str()).collect();
|
||||||
let address_mask = ((1 << ADDR_WIDTH) - 1) as u64;
|
let pins = BaseComponent::create_pin_map(&pin_refs, &name);
|
||||||
|
let memory = vec![0u8; size];
|
||||||
|
|
||||||
Self {
|
GenericRam {
|
||||||
base,
|
base: BaseComponent::new(name, pins),
|
||||||
memory: vec![0; SIZE],
|
memory,
|
||||||
base_address,
|
address_width,
|
||||||
address_mask,
|
data_width,
|
||||||
read_delay,
|
last_address: 0,
|
||||||
write_delay,
|
last_operation: RamOperation::Idle,
|
||||||
current_operation: None,
|
access_time: Duration::from_nanos(100), // 100ns access time
|
||||||
operation_cycles: 0,
|
last_access: Instant::now(),
|
||||||
output_buffer: None,
|
write_enable: false,
|
||||||
|
output_enable: false,
|
||||||
|
chip_select: false,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn read_address_bus(&self) -> u64 {
|
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
|
||||||
let mut address = 0;
|
if offset + data.len() > self.memory.len() {
|
||||||
for i in 0..ADDR_WIDTH {
|
return Err(format!(
|
||||||
if let Some(pin) = self.base.get_pin(&format!("addr_{}", i)) {
|
"Data exceeds RAM capacity: offset {} + data length {} > RAM size {}",
|
||||||
if pin.read() == PinValue::High {
|
offset, data.len(), self.memory.len()
|
||||||
address |= 1 << i;
|
));
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
address
|
|
||||||
}
|
|
||||||
|
|
||||||
fn read_data_bus(&self) -> u64 {
|
|
||||||
let mut data = 0;
|
|
||||||
for i in 0..DATA_WIDTH {
|
|
||||||
if let Some(pin) = self.base.get_pin(&format!("data_{}", i)) {
|
|
||||||
if pin.read() == PinValue::High {
|
|
||||||
data |= 1 << i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
data
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_data_bus(&self, data: u64) {
|
|
||||||
for i in 0..DATA_WIDTH {
|
|
||||||
if let Some(pin) = self.base.get_pin(&format!("data_{}", i)) {
|
|
||||||
let bit = (data >> i) & 1;
|
|
||||||
let value = if bit == 1 { PinValue::High } else { PinValue::Low };
|
|
||||||
pin.write(value, true);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_data_bus_high_z(&self) {
|
|
||||||
for i in 0..DATA_WIDTH {
|
|
||||||
if let Some(pin) = self.base.get_pin(&format!("data_{}", i)) {
|
|
||||||
pin.write(PinValue::HighZ, false);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_selected(&self) -> bool {
|
|
||||||
if let Some(cs_pin) = self.base.get_pin("cs") {
|
|
||||||
cs_pin.read() == PinValue::Low // Active low
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_write_enabled(&self) -> bool {
|
|
||||||
if let Some(we_pin) = self.base.get_pin("we") {
|
|
||||||
we_pin.read() == PinValue::Low // Active low
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_output_enabled(&self) -> bool {
|
|
||||||
if let Some(oe_pin) = self.base.get_pin("oe") {
|
|
||||||
oe_pin.read() == PinValue::Low // Active low
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn is_clock_rising_edge(&self, last_clock: &mut PinValue) -> bool {
|
|
||||||
if let Some(clk_pin) = self.base.get_pin("clk") {
|
|
||||||
let current_clock = clk_pin.read();
|
|
||||||
let rising_edge = *last_clock == PinValue::Low && current_clock == PinValue::High;
|
|
||||||
*last_clock = current_clock;
|
|
||||||
rising_edge
|
|
||||||
} else {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn decode_address(&self, bus_address: u64) -> Option<usize> {
|
|
||||||
// Check if address is in our range
|
|
||||||
let relative_address = bus_address & self.address_mask;
|
|
||||||
|
|
||||||
if (bus_address & !self.address_mask) == self.base_address {
|
|
||||||
// Address matches our base + mask
|
|
||||||
let index = relative_address as usize;
|
|
||||||
if index < SIZE {
|
|
||||||
Some(index)
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_read_operation(&mut self, address: u64) {
|
|
||||||
if let Some(index) = self.decode_address(address) {
|
|
||||||
self.current_operation = Some(RamOperation::Read(address));
|
|
||||||
self.operation_cycles = 0;
|
|
||||||
println!("RAM {}: Starting read from address {:X} (index {})",
|
|
||||||
self.base.name, address, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn start_write_operation(&mut self, address: u64, data: u64) {
|
|
||||||
if let Some(index) = self.decode_address(address) {
|
|
||||||
self.current_operation = Some(RamOperation::Write(address, data));
|
|
||||||
self.operation_cycles = 0;
|
|
||||||
println!("RAM {}: Starting write {:X} to address {:X} (index {})",
|
|
||||||
self.base.name, data, address, index);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_read_operation(&mut self, address: u64) {
|
|
||||||
if let Some(index) = self.decode_address(address) {
|
|
||||||
let data = self.memory[index];
|
|
||||||
self.output_buffer = Some(data);
|
|
||||||
println!("RAM {}: Read complete - data {:X} from address {:X}",
|
|
||||||
self.base.name, data, address);
|
|
||||||
}
|
|
||||||
self.current_operation = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn complete_write_operation(&mut self, address: u64, data: u64) {
|
|
||||||
if let Some(index) = self.decode_address(address) {
|
|
||||||
self.memory[index] = data & ((1 << DATA_WIDTH) - 1); // Mask to data width
|
|
||||||
println!("RAM {}: Write complete - data {:X} to address {:X}",
|
|
||||||
self.base.name, data, address);
|
|
||||||
}
|
|
||||||
self.current_operation = None;
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update_operation(&mut self) {
|
|
||||||
if let Some(operation) = self.current_operation {
|
|
||||||
self.operation_cycles += 1;
|
|
||||||
|
|
||||||
match operation {
|
|
||||||
RamOperation::Read(addr) => {
|
|
||||||
if self.operation_cycles >= self.read_delay {
|
|
||||||
self.complete_read_operation(addr);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RamOperation::Write(addr, data) => {
|
|
||||||
if self.operation_cycles >= self.write_delay {
|
|
||||||
self.complete_write_operation(addr, data);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn handle_bus_signals(&mut self, bus_address: u64, last_clock: &mut PinValue) {
|
|
||||||
if !self.is_selected() {
|
|
||||||
// Not selected - high impedance and clear any pending operations
|
|
||||||
self.set_data_bus_high_z();
|
|
||||||
self.current_operation = None;
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let clock_rising_edge = self.is_clock_rising_edge(last_clock);
|
self.memory[offset..offset + data.len()].copy_from_slice(&data);
|
||||||
|
|
||||||
if clock_rising_edge {
|
|
||||||
// On clock edge, check what operation is requested
|
|
||||||
if self.is_write_enabled() {
|
|
||||||
// Write operation
|
|
||||||
let data = self.read_data_bus();
|
|
||||||
self.start_write_operation(bus_address, data);
|
|
||||||
} else if self.is_output_enabled() {
|
|
||||||
// Read operation
|
|
||||||
self.start_read_operation(bus_address);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Handle ongoing operations
|
|
||||||
self.update_operation();
|
|
||||||
|
|
||||||
// Output data if we have it and output is enabled
|
|
||||||
if self.is_output_enabled() {
|
|
||||||
if let Some(data) = self.output_buffer {
|
|
||||||
self.write_data_bus(data);
|
|
||||||
} else {
|
|
||||||
self.set_data_bus_high_z();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
self.set_data_bus_high_z();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
|
|
||||||
crate::component::Component for GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
|
|
||||||
{
|
|
||||||
fn name(&self) -> &str {
|
|
||||||
self.base.name()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn pins(&self) -> &std::collections::HashMap<String, Arc<Pin>> {
|
|
||||||
self.base.pins()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_pin(&self, name: &str) -> Option<Arc<Pin>> {
|
|
||||||
self.base.get_pin(name)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn update(&mut self) -> Result<(), String> {
|
|
||||||
let bus_address = self.read_address_bus();
|
|
||||||
let mut last_clock = PinValue::Low;
|
|
||||||
|
|
||||||
self.handle_bus_signals(bus_address, &mut last_clock);
|
|
||||||
Ok(())
|
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 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) {
|
fn run(&mut self) {
|
||||||
self.base.run();
|
self.base.set_running(true);
|
||||||
|
|
||||||
|
while self.is_running() {
|
||||||
|
self.update();
|
||||||
|
thread::sleep(Duration::from_micros(1));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn stop(&mut self) {
|
fn stop(&mut self) {
|
||||||
self.base.stop();
|
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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// BusDevice implementation
|
// Additional utility methods
|
||||||
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
|
impl GenericRam {
|
||||||
crate::bus::BusDevice for GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
|
pub fn clear_memory(&mut self) {
|
||||||
{
|
for byte in &mut self.memory {
|
||||||
fn update(&mut self) {
|
*byte = 0;
|
||||||
// Delegate to component update
|
}
|
||||||
let _ = Component::update(self);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn connect_to_bus(&mut self, bus_pins: Vec<Arc<Pin>>) {
|
pub fn fill_memory(&mut self, value: u8) {
|
||||||
// Connect to bus pins - in a real system, this would connect address, data, and control pins
|
for byte in &mut self.memory {
|
||||||
// This is simplified for the example
|
*byte = value;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_address_range(&self) -> Option<std::ops::Range<u64>> {
|
pub fn get_memory_snapshot(&self) -> Vec<u8> {
|
||||||
Some(self.base_address..self.base_address + SIZE as u64)
|
self.memory.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn get_name(&self) -> &str {
|
pub fn restore_memory_snapshot(&mut self, snapshot: Vec<u8>) -> Result<(), String> {
|
||||||
todo!()
|
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,56 +1,438 @@
|
||||||
use crate::components::memory::generic_rom::GenericRom;
|
use std::collections::HashMap;
|
||||||
use crate::{Component, PinValue};
|
use std::sync::{Arc, Mutex};
|
||||||
use crate::systems::intel_mcs_4::MCS4Phase;
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
|
use crate::component::{BaseComponent, Component};
|
||||||
|
use crate::pin::{Pin, PinValue};
|
||||||
|
|
||||||
|
/// Intel 4001 - 256-byte ROM with integrated I/O
|
||||||
|
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
|
||||||
|
pub struct Intel4001 {
|
||||||
|
base: BaseComponent,
|
||||||
|
memory: Vec<u8>,
|
||||||
|
last_address: u16,
|
||||||
|
access_time: Duration,
|
||||||
|
last_access: Instant,
|
||||||
|
output_latch: u8,
|
||||||
|
input_latch: u8,
|
||||||
|
io_mode: IoMode,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
|
enum IoMode {
|
||||||
|
Input, // I/O pins as inputs
|
||||||
|
Output, // I/O pins as outputs
|
||||||
|
Bidirectional, // I/O pins bidirectional
|
||||||
|
}
|
||||||
|
|
||||||
// 4001 ROM: 256 bytes, 4-bit data width
|
|
||||||
pub type Intel4001 = GenericRom;
|
|
||||||
impl Intel4001 {
|
impl Intel4001 {
|
||||||
fn update(&mut self) -> Result<(), String> {
|
pub fn new(name: String) -> Self {
|
||||||
// Read the current bus phase from the pins
|
// Intel 4001 has 256 bytes of ROM
|
||||||
let phase = self.detect_bus_phase();
|
let rom_size = 256;
|
||||||
|
|
||||||
match phase {
|
// Intel 4001 pinout:
|
||||||
MCS4Phase::AddressLow => {
|
// - 10 address pins (A0-A9, but only A0-A7 used for 256-byte addressing)
|
||||||
self.latch_address_low();
|
// - 4 data pins (D0-D3) for multiplexed address/data
|
||||||
}
|
// - 4 I/O pins (IO0-IO3)
|
||||||
MCS4Phase::AddressHigh => {
|
// - Control pins: SYNC, CM-ROM, CM-RAM, RESET
|
||||||
self.latch_address_high();
|
let pin_names = vec![
|
||||||
if self.is_selected() {
|
"D0", "D1", "D2", "D3", // Data/Address pins
|
||||||
self.prepare_data_output();
|
"IO0", "IO1", "IO2", "IO3", // I/O pins
|
||||||
}
|
"SYNC", // Sync signal
|
||||||
}
|
"CM_ROM", // ROM Chip Select
|
||||||
MCS4Phase::DataRead => {
|
"CM_RAM", // RAM Chip Select (for system configuration)
|
||||||
if self.is_selected() {
|
"RESET", // Reset
|
||||||
self.output_data(/* u8 */);
|
];
|
||||||
}
|
|
||||||
}
|
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||||
_ => {
|
let memory = vec![0u8; rom_size];
|
||||||
// Put pins in high impedance
|
|
||||||
self.set_pins_high_z();
|
Intel4001 {
|
||||||
}
|
base: BaseComponent::new(name, pins),
|
||||||
|
memory,
|
||||||
|
last_address: 0,
|
||||||
|
access_time: Duration::from_nanos(500), // 500ns access time
|
||||||
|
last_access: Instant::now(),
|
||||||
|
output_latch: 0,
|
||||||
|
input_latch: 0,
|
||||||
|
io_mode: IoMode::Input,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_rom_data(&mut self, data: Vec<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(())
|
Ok(())
|
||||||
}
|
}
|
||||||
fn detect_bus_phase(&self) -> MCS4Phase {
|
|
||||||
// Detect phase based on sync and other control signals
|
pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> {
|
||||||
if let Some(sync_pin) = self.base.get_pin("pin_0") {
|
let bytes: Result<Vec<u8>, _> = hex_data
|
||||||
match sync_pin.read() {
|
.split_whitespace()
|
||||||
PinValue::High => MCS4Phase::AddressLow, // Sync high = address phase
|
.map(|s| u8::from_str_radix(s.trim(), 16))
|
||||||
PinValue::Low => MCS4Phase::DataRead, // Sync low = data phase
|
.collect();
|
||||||
_ => MCS4Phase::AddressLow,
|
|
||||||
}
|
match bytes {
|
||||||
} else {
|
Ok(data) => self.load_rom_data(data, offset),
|
||||||
MCS4Phase::AddressLow
|
Err(e) => Err(format!("Invalid hex data: {}", e)),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
fn latch_address_low(&mut self) {
|
|
||||||
let addr_low = self.read_nibble_from_pins(9..12);
|
fn read_data_bus(&self) -> u8 {
|
||||||
self.latched_address = (self.latched_address & 0xFF0) | (addr_low as u16);
|
let mut data = 0;
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
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 latch_address_high(&mut self) {
|
|
||||||
let addr_high = self.read_nibble_from_pins(9..12);
|
fn write_data_bus(&self, data: u8) {
|
||||||
let addr_mid = self.read_nibble_from_pins(13..16);
|
for i in 0..4 {
|
||||||
self.latched_address = ((addr_mid as u16) << 8) | ((addr_high as u16) << 4) | (self.latched_address & 0x00F);
|
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_io_pins(&self) -> u8 {
|
||||||
|
let mut data = 0;
|
||||||
|
|
||||||
|
for i in 0..4 {
|
||||||
|
if let Ok(pin) = self.base.get_pin(&format!("IO{}", i)) {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
if pin_guard.read() == PinValue::High {
|
||||||
|
data |= 1 << i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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.get_name().parse().unwrap()), pin_value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tri_state_data_bus(&self) {
|
||||||
|
for i in 0..4 {
|
||||||
|
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 tri_state_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() {
|
||||||
|
pin_guard.set_driver(Some(self.base.get_name().parse().unwrap()), PinValue::HighZ);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn read_control_pins(&self) -> (bool, bool, bool, bool) {
|
||||||
|
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let cm_rom = if let Ok(pin) = self.base.get_pin("CM_ROM") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let cm_ram = if let Ok(pin) = self.base.get_pin("CM_RAM") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
(sync, cm_rom, cm_ram, reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_reset(&mut self) {
|
||||||
|
if self.read_control_pins().3 { // RESET is high
|
||||||
|
self.output_latch = 0;
|
||||||
|
self.input_latch = 0;
|
||||||
|
self.io_mode = IoMode::Input;
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
self.tri_state_io_pins();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_io_operation(&mut self) {
|
||||||
|
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
|
||||||
|
|
||||||
|
// I/O operations occur when SYNC is high and CM-ROM is low
|
||||||
|
if sync && !cm_rom {
|
||||||
|
let address_low = self.read_data_bus(); // Lower 4 bits of address from data bus
|
||||||
|
|
||||||
|
// For I/O operations, the upper address bits determine the operation
|
||||||
|
if cm_ram {
|
||||||
|
// I/O read operation
|
||||||
|
self.input_latch = self.read_io_pins();
|
||||||
|
self.write_data_bus(self.input_latch);
|
||||||
|
} else {
|
||||||
|
// I/O write operation
|
||||||
|
let data = self.read_data_bus();
|
||||||
|
self.output_latch = data;
|
||||||
|
self.write_io_pins(data);
|
||||||
|
self.io_mode = IoMode::Output;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_memory_operation(&mut self) -> bool {
|
||||||
|
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
|
||||||
|
|
||||||
|
// Memory operations occur when SYNC is high and CM-ROM is high
|
||||||
|
if sync && cm_rom {
|
||||||
|
if cm_ram {
|
||||||
|
// Second cycle of memory read - output data
|
||||||
|
let address = self.last_address;
|
||||||
|
if (address as usize) < self.memory.len() {
|
||||||
|
let data = self.memory[address as usize];
|
||||||
|
self.write_data_bus(data);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// First cycle of memory operation - latch address
|
||||||
|
let address_low = self.read_data_bus();
|
||||||
|
self.last_address = address_low as u16;
|
||||||
|
// Tri-state during address phase
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Intel4001 {
|
||||||
|
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.handle_reset();
|
||||||
|
|
||||||
|
// Handle I/O operations (higher priority than memory operations)
|
||||||
|
self.handle_io_operation();
|
||||||
|
|
||||||
|
// Handle memory operations
|
||||||
|
let memory_accessed = self.handle_memory_operation();
|
||||||
|
|
||||||
|
// If no memory operation occurred, ensure data bus is tri-stated when not active
|
||||||
|
if !memory_accessed {
|
||||||
|
let (sync, cm_rom, _, _) = self.read_control_pins();
|
||||||
|
if !sync || !cm_rom {
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.base.set_running(false);
|
||||||
|
|
||||||
|
// Tri-state all outputs when stopped
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
self.tri_state_io_pins();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
self.base.is_running()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intel 4001 specific methods
|
||||||
|
impl Intel4001 {
|
||||||
|
pub fn get_rom_size(&self) -> usize {
|
||||||
|
self.memory.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_rom(&self, address: u8) -> Option<u8> {
|
||||||
|
if (address as usize) < self.memory.len() {
|
||||||
|
Some(self.memory[address as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_output_latch(&self) -> u8 {
|
||||||
|
self.output_latch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input_latch(&self) -> u8 {
|
||||||
|
self.input_latch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_io_mode(&self) -> IoMode {
|
||||||
|
self.io_mode
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_io_mode(&mut self, mode: IoMode) {
|
||||||
|
self.io_mode = mode;
|
||||||
|
match mode {
|
||||||
|
IoMode::Input => self.tri_state_io_pins(),
|
||||||
|
IoMode::Output => self.write_io_pins(self.output_latch),
|
||||||
|
IoMode::Bidirectional => {
|
||||||
|
// In bidirectional mode, I/O pins follow the data bus during writes
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Custom formatter for debugging
|
||||||
|
impl std::fmt::Display for IoMode {
|
||||||
|
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||||
|
match self {
|
||||||
|
IoMode::Input => write!(f, "Input"),
|
||||||
|
IoMode::Output => write!(f, "Output"),
|
||||||
|
IoMode::Bidirectional => write!(f, "Bidirectional"),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4001_creation() {
|
||||||
|
let rom = Intel4001::new("ROM_4001".to_string());
|
||||||
|
assert_eq!(rom.name(), "ROM_4001");
|
||||||
|
assert_eq!(rom.get_rom_size(), 256);
|
||||||
|
assert!(!rom.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4001_rom_loading() {
|
||||||
|
let mut rom = Intel4001::new("ROM_4001".to_string());
|
||||||
|
|
||||||
|
let test_data = vec![0x12, 0x34, 0x56, 0x78];
|
||||||
|
assert!(rom.load_rom_data(test_data.clone(), 0).is_ok());
|
||||||
|
|
||||||
|
assert_eq!(rom.read_rom(0).unwrap(), 0x12);
|
||||||
|
assert_eq!(rom.read_rom(1).unwrap(), 0x34);
|
||||||
|
assert_eq!(rom.read_rom(2).unwrap(), 0x56);
|
||||||
|
assert_eq!(rom.read_rom(3).unwrap(), 0x78);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4001_io_modes() {
|
||||||
|
let mut rom = Intel4001::new("ROM_4001".to_string());
|
||||||
|
|
||||||
|
assert_eq!(rom.get_io_mode(), IoMode::Input);
|
||||||
|
|
||||||
|
rom.set_io_mode(IoMode::Output);
|
||||||
|
assert_eq!(rom.get_io_mode(), IoMode::Output);
|
||||||
|
|
||||||
|
rom.set_io_mode(IoMode::Bidirectional);
|
||||||
|
assert_eq!(rom.get_io_mode(), IoMode::Bidirectional);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4001_latches() {
|
||||||
|
let mut rom = Intel4001::new("ROM_4001".to_string());
|
||||||
|
|
||||||
|
// Initial state
|
||||||
|
assert_eq!(rom.get_output_latch(), 0);
|
||||||
|
assert_eq!(rom.get_input_latch(), 0);
|
||||||
|
|
||||||
|
// These would be set during actual I/O operations
|
||||||
|
// The test verifies the latch structures exist
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_io_mode_display() {
|
||||||
|
assert_eq!(IoMode::Input.to_string(), "Input");
|
||||||
|
assert_eq!(IoMode::Output.to_string(), "Output");
|
||||||
|
assert_eq!(IoMode::Bidirectional.to_string(), "Bidirectional");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,51 +1,505 @@
|
||||||
use crate::components::memory::generic_ram::GenericRam;
|
use std::collections::HashMap;
|
||||||
|
use std::sync::{Arc, Mutex};
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
// 4002 RAM: 80 nibbles (4-bit words), 12-bit address bus
|
use crate::component::{BaseComponent, Component};
|
||||||
// Organized as 4 registers × 20 characters × 4 bits
|
use crate::pin::{Pin, PinValue};
|
||||||
pub type Intel4002 = GenericRam<80, 4, 12>;
|
|
||||||
|
/// Intel 4002 - 320-bit RAM (40 nibbles × 4 bits) with integrated output ports
|
||||||
|
/// Part of the MCS-4 family, designed to work with Intel 4004 CPU
|
||||||
|
pub struct Intel4002 {
|
||||||
|
base: BaseComponent,
|
||||||
|
memory: [u8; 40], // 40 nibbles of RAM (stored as bytes for simplicity)
|
||||||
|
last_address: u8,
|
||||||
|
access_time: Duration,
|
||||||
|
last_access: Instant,
|
||||||
|
output_ports: [u8; 4], // 4 output ports (4 bits each)
|
||||||
|
input_latch: u8, // Input data latch
|
||||||
|
status_character: u8, // Status character register
|
||||||
|
bank_select: u8, // RAM bank selection
|
||||||
|
}
|
||||||
|
|
||||||
impl Intel4002 {
|
impl Intel4002 {
|
||||||
pub fn new_4002(name: String, base_address: u16) -> Self {
|
pub fn new(name: String) -> Self {
|
||||||
// 4002 has specific timing characteristics
|
// Intel 4002 has 40 nibbles of RAM (320 bits)
|
||||||
let ram = Self::new(
|
|
||||||
name,
|
|
||||||
base_address as u64,
|
|
||||||
2, // 2-cycle read access
|
|
||||||
3 // 3-cycle write access
|
|
||||||
);
|
|
||||||
|
|
||||||
ram
|
// Intel 4002 pinout (similar to 4001 but with RAM-specific controls):
|
||||||
|
// - 4 data pins (D0-D3) for multiplexed address/data
|
||||||
|
// - 4 output port pins (O0-O3)
|
||||||
|
// - Control pins: SYNC, CM-ROM, CM-RAM, RESET
|
||||||
|
let pin_names = vec![
|
||||||
|
"D0", "D1", "D2", "D3", // Data/Address pins
|
||||||
|
"O0", "O1", "O2", "O3", // Output port pins
|
||||||
|
"SYNC", // Sync signal
|
||||||
|
"CM_ROM", // ROM Chip Select
|
||||||
|
"CM_RAM", // RAM Chip Select
|
||||||
|
"RESET", // Reset
|
||||||
|
];
|
||||||
|
|
||||||
|
let pins = BaseComponent::create_pin_map(&pin_names, &name);
|
||||||
|
|
||||||
|
Intel4002 {
|
||||||
|
base: BaseComponent::new(name, pins),
|
||||||
|
memory: [0u8; 40],
|
||||||
|
last_address: 0,
|
||||||
|
access_time: Duration::from_nanos(500), // 500ns access time
|
||||||
|
last_access: Instant::now(),
|
||||||
|
output_ports: [0u8; 4],
|
||||||
|
input_latch: 0,
|
||||||
|
status_character: 0,
|
||||||
|
bank_select: 0,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 4002-specific methods
|
pub fn initialize_ram(&mut self, data: &[u8]) -> Result<(), String> {
|
||||||
pub fn write_to_register_character(&mut self, register: u8, character: u8, value: u8) -> Result<(), String> {
|
if data.len() > 40 {
|
||||||
if register >= 4 || character >= 20 {
|
return Err("Data exceeds RAM capacity (40 nibbles)".to_string());
|
||||||
return Err("Invalid register or character index".to_string());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = self.calculate_address(register, character);
|
for (i, &byte) in data.iter().enumerate() {
|
||||||
let index = self.decode_address(address as u64)
|
self.memory[i] = byte & 0x0F; // Store only lower 4 bits
|
||||||
.ok_or("Address out of range".to_string())?;
|
}
|
||||||
|
|
||||||
self.memory[index] = (value & 0x0F) as u64;
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn read_from_register_character(&self, register: u8, character: u8) -> Result<u8, String> {
|
fn read_data_bus(&self) -> u8 {
|
||||||
if register >= 4 || character >= 20 {
|
let mut data = 0;
|
||||||
return Err("Invalid register or character index".to_string());
|
|
||||||
|
for i in 0..4 {
|
||||||
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let address = self.calculate_address(register, character);
|
data & 0x0F // Return only lower 4 bits
|
||||||
let index = self.decode_address(address as u64)
|
|
||||||
.ok_or("Address out of range".to_string())?;
|
|
||||||
|
|
||||||
Ok(self.memory[index] as u8)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_address(&self, register: u8, character: u8) -> u16 {
|
fn write_data_bus(&self, data: u8) {
|
||||||
// 4002 address calculation:
|
let nibble = data & 0x0F; // Only lower 4 bits
|
||||||
// Each register has 20 characters, each character is 4 bits
|
|
||||||
(register as u16 * 20) + (character as u16)
|
for i in 0..4 {
|
||||||
|
if let Ok(pin) = self.base.get_pin(&format!("D{}", i)) {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let bit_value = (nibble >> 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 update_output_ports(&self) {
|
||||||
|
for port in 0..4 {
|
||||||
|
let port_data = self.output_ports[port];
|
||||||
|
|
||||||
|
if let Ok(pin) = self.base.get_pin(&format!("O{}", port)) {
|
||||||
|
if let Ok(mut pin_guard) = pin.lock() {
|
||||||
|
let bit_value = (port_data >> port) & 1; // Each port drives one pin
|
||||||
|
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 tri_state_data_bus(&self) {
|
||||||
|
for i in 0..4 {
|
||||||
|
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 read_control_pins(&self) -> (bool, bool, bool, bool) {
|
||||||
|
let sync = if let Ok(pin) = self.base.get_pin("SYNC") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let cm_rom = if let Ok(pin) = self.base.get_pin("CM_ROM") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let cm_ram = if let Ok(pin) = self.base.get_pin("CM_RAM") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
let reset = if let Ok(pin) = self.base.get_pin("RESET") {
|
||||||
|
if let Ok(pin_guard) = pin.lock() {
|
||||||
|
pin_guard.read() == PinValue::High
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
false
|
||||||
|
};
|
||||||
|
|
||||||
|
(sync, cm_rom, cm_ram, reset)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_reset(&mut self) {
|
||||||
|
let (_, _, _, reset) = self.read_control_pins();
|
||||||
|
|
||||||
|
if reset {
|
||||||
|
// Reset clears all registers but not RAM content
|
||||||
|
self.output_ports = [0u8; 4];
|
||||||
|
self.input_latch = 0;
|
||||||
|
self.status_character = 0;
|
||||||
|
self.bank_select = 0;
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
self.update_output_ports();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn decode_address(&self, address_low: u8, instruction: u8) -> (u8, u8) {
|
||||||
|
// Intel 4002 uses a complex addressing scheme:
|
||||||
|
// - 4 register banks (selected by bank_select)
|
||||||
|
// - 4 status characters per bank
|
||||||
|
// - 16 main memory locations per bank
|
||||||
|
|
||||||
|
let bank = self.bank_select & 0x03; // 2-bit bank select
|
||||||
|
let ram_address = match instruction {
|
||||||
|
// Main memory addressing (locations 0-15)
|
||||||
|
0x0..=0xF => (bank * 16) + (address_low & 0x0F),
|
||||||
|
|
||||||
|
// Status character addressing (locations 16-19)
|
||||||
|
0x10..=0x13 => 16 + (address_low & 0x03),
|
||||||
|
|
||||||
|
// Output port addressing
|
||||||
|
0x14..=0x17 => {
|
||||||
|
// Output ports are not in RAM, handled separately
|
||||||
|
return (0, instruction - 0x14); // Return (0, port_number)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Invalid address
|
||||||
|
_ => 0,
|
||||||
|
};
|
||||||
|
|
||||||
|
(bank, ram_address)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_ram_operation(&mut self) -> bool {
|
||||||
|
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
|
||||||
|
|
||||||
|
// RAM operations occur when SYNC is high and CM-RAM is high
|
||||||
|
if sync && cm_ram {
|
||||||
|
if cm_rom {
|
||||||
|
// Second cycle - data transfer
|
||||||
|
let data = self.read_data_bus();
|
||||||
|
|
||||||
|
// Write to previously addressed location
|
||||||
|
if self.last_address < 40 {
|
||||||
|
self.memory[self.last_address as usize] = data;
|
||||||
|
}
|
||||||
|
|
||||||
|
// For read operations, data would be output in the first cycle
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
// First cycle - address/instruction phase
|
||||||
|
let address_instruction = self.read_data_bus();
|
||||||
|
|
||||||
|
// Decode the address based on instruction type
|
||||||
|
let (bank, ram_address) = self.decode_address(self.last_address, address_instruction);
|
||||||
|
|
||||||
|
if ram_address < 40 {
|
||||||
|
// Read from RAM and output data
|
||||||
|
let data = self.memory[ram_address as usize];
|
||||||
|
self.write_data_bus(data);
|
||||||
|
self.last_address = ram_address;
|
||||||
|
} else if ram_address >= 0x14 && ram_address <= 0x17 {
|
||||||
|
// Output port operation
|
||||||
|
let port = (ram_address - 0x14) as usize;
|
||||||
|
let data = self.read_data_bus();
|
||||||
|
self.output_ports[port] = data;
|
||||||
|
self.update_output_ports();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
false
|
||||||
|
}
|
||||||
|
|
||||||
|
fn handle_instruction(&mut self, instruction: u8) {
|
||||||
|
match instruction {
|
||||||
|
// Bank select instructions
|
||||||
|
0xE0..=0xE3 => {
|
||||||
|
self.bank_select = instruction & 0x03;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Status character load
|
||||||
|
0xF0..=0xF3 => {
|
||||||
|
let sc_index = (instruction & 0x03) as usize;
|
||||||
|
self.status_character = self.input_latch;
|
||||||
|
// Status characters are stored in RAM locations 16-19
|
||||||
|
if sc_index < 4 {
|
||||||
|
self.memory[16 + sc_index] = self.status_character;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Input latch operations
|
||||||
|
0x60..=0x63 => {
|
||||||
|
// Read from input (in real hardware, this would read from external pins)
|
||||||
|
// For emulation, we use the input latch
|
||||||
|
self.write_data_bus(self.input_latch);
|
||||||
|
}
|
||||||
|
|
||||||
|
_ => {
|
||||||
|
// Other instructions are handled as RAM operations
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for Intel4002 {
|
||||||
|
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.handle_reset();
|
||||||
|
|
||||||
|
let (sync, cm_rom, cm_ram, _) = self.read_control_pins();
|
||||||
|
|
||||||
|
if sync {
|
||||||
|
if cm_ram {
|
||||||
|
// RAM operation
|
||||||
|
self.handle_ram_operation();
|
||||||
|
} else if !cm_rom {
|
||||||
|
// Instruction phase (both CM-ROM and CM-RAM low)
|
||||||
|
let instruction = self.read_data_bus();
|
||||||
|
self.handle_instruction(instruction);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Not in sync phase - tri-state data bus
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
}
|
||||||
|
|
||||||
|
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(1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.base.set_running(false);
|
||||||
|
|
||||||
|
// Tri-state all outputs when stopped
|
||||||
|
self.tri_state_data_bus();
|
||||||
|
// Note: Output ports remain driven even when stopped (like real hardware)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
self.base.is_running()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Intel 4002 specific methods
|
||||||
|
impl Intel4002 {
|
||||||
|
pub fn get_ram_size(&self) -> usize {
|
||||||
|
self.memory.len()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn read_ram(&self, address: u8) -> Option<u8> {
|
||||||
|
if address < 40 {
|
||||||
|
Some(self.memory[address as usize])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn write_ram(&mut self, address: u8, data: u8) -> Result<(), String> {
|
||||||
|
if address < 40 {
|
||||||
|
self.memory[address as usize] = data & 0x0F;
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Address out of range (0-39)".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_output_port(&self, port: usize) -> Option<u8> {
|
||||||
|
if port < 4 {
|
||||||
|
Some(self.output_ports[port])
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_output_port(&mut self, port: usize, data: u8) -> Result<(), String> {
|
||||||
|
if port < 4 {
|
||||||
|
self.output_ports[port] = data & 0x0F;
|
||||||
|
self.update_output_ports();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Port number out of range (0-3)".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_input_latch(&mut self, data: u8) {
|
||||||
|
self.input_latch = data & 0x0F;
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_input_latch(&self) -> u8 {
|
||||||
|
self.input_latch
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_status_character(&self) -> u8 {
|
||||||
|
self.status_character
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_bank_select(&self) -> u8 {
|
||||||
|
self.bank_select
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn clear_ram(&mut self) {
|
||||||
|
self.memory = [0u8; 40];
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_ram_bank(&self, bank: u8) -> Vec<u8> {
|
||||||
|
let bank = bank & 0x03;
|
||||||
|
let start = (bank * 16) as usize;
|
||||||
|
let end = start + 16;
|
||||||
|
|
||||||
|
if end <= 40 {
|
||||||
|
self.memory[start..end].to_vec()
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_creation() {
|
||||||
|
let ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
assert_eq!(ram.name(), "RAM_4002");
|
||||||
|
assert_eq!(ram.get_ram_size(), 40);
|
||||||
|
assert!(!ram.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_ram_operations() {
|
||||||
|
let mut ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
|
||||||
|
// Test RAM write/read
|
||||||
|
assert!(ram.write_ram(0, 0x0A).is_ok());
|
||||||
|
assert_eq!(ram.read_ram(0).unwrap(), 0x0A);
|
||||||
|
|
||||||
|
// Test out of bounds
|
||||||
|
assert!(ram.write_ram(40, 0x0F).is_err());
|
||||||
|
assert!(ram.read_ram(40).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_output_ports() {
|
||||||
|
let mut ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
|
||||||
|
// Test output port operations
|
||||||
|
assert!(ram.set_output_port(0, 0x05).is_ok());
|
||||||
|
assert_eq!(ram.get_output_port(0).unwrap(), 0x05);
|
||||||
|
|
||||||
|
// Test invalid port
|
||||||
|
assert!(ram.set_output_port(4, 0x0F).is_err());
|
||||||
|
assert!(ram.get_output_port(4).is_none());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_input_latch() {
|
||||||
|
let mut ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
|
||||||
|
ram.set_input_latch(0x07);
|
||||||
|
assert_eq!(ram.get_input_latch(), 0x07);
|
||||||
|
|
||||||
|
// Test that only lower 4 bits are stored
|
||||||
|
ram.set_input_latch(0x1F);
|
||||||
|
assert_eq!(ram.get_input_latch(), 0x0F);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_ram_banks() {
|
||||||
|
let mut ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
|
||||||
|
// Write test data to different banks
|
||||||
|
ram.write_ram(0, 0x01).unwrap(); // Bank 0
|
||||||
|
ram.write_ram(16, 0x02).unwrap(); // Bank 1
|
||||||
|
ram.write_ram(32, 0x03).unwrap(); // Bank 2
|
||||||
|
|
||||||
|
let bank0 = ram.get_ram_bank(0);
|
||||||
|
assert_eq!(bank0[0], 0x01);
|
||||||
|
|
||||||
|
let bank1 = ram.get_ram_bank(1);
|
||||||
|
assert_eq!(bank1[0], 0x02);
|
||||||
|
|
||||||
|
let bank2 = ram.get_ram_bank(2);
|
||||||
|
assert_eq!(bank2[0], 0x03);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_intel4002_clear_ram() {
|
||||||
|
let mut ram = Intel4002::new("RAM_4002".to_string());
|
||||||
|
|
||||||
|
ram.write_ram(0, 0x0A).unwrap();
|
||||||
|
ram.write_ram(10, 0x0B).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(ram.read_ram(0).unwrap(), 0x0A);
|
||||||
|
assert_eq!(ram.read_ram(10).unwrap(), 0x0B);
|
||||||
|
|
||||||
|
ram.clear_ram();
|
||||||
|
|
||||||
|
assert_eq!(ram.read_ram(0).unwrap(), 0);
|
||||||
|
assert_eq!(ram.read_ram(10).unwrap(), 0);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
254
src/main.rs
254
src/main.rs
|
|
@ -1,4 +1,254 @@
|
||||||
use rusty_emu::systems;
|
use rusty_emu::systems::intel_mcs_4::IntelMcs4;
|
||||||
|
use std::thread;
|
||||||
|
use std::time::{Duration, Instant};
|
||||||
|
|
||||||
fn main() {
|
fn main() {
|
||||||
systems::intel_mcs_4::run_fibonacci_calculator();
|
println!("Intel MCS-4 Emulator - Fibonacci Sequence Example");
|
||||||
|
println!("=================================================");
|
||||||
|
|
||||||
|
// Create the MCS-4 system
|
||||||
|
let mut mcs4 = IntelMcs4::new();
|
||||||
|
|
||||||
|
// Load the Fibonacci program into ROM
|
||||||
|
// This is a simplified version of the Fibonacci sequence generator
|
||||||
|
// that calculates Fibonacci numbers using the 4004 instruction set
|
||||||
|
let fibonacci_program = compile_fibonacci_program();
|
||||||
|
|
||||||
|
// Split the program between the two ROM chips
|
||||||
|
// ROM 1: Main program code (first 256 bytes)
|
||||||
|
// ROM 2: Continued program and data (next 256 bytes)
|
||||||
|
let rom1_size = 256;
|
||||||
|
let rom1_data = if fibonacci_program.len() > rom1_size {
|
||||||
|
fibonacci_program[..rom1_size].to_vec()
|
||||||
|
} else {
|
||||||
|
fibonacci_program.clone()
|
||||||
|
};
|
||||||
|
|
||||||
|
let rom2_data = if fibonacci_program.len() > rom1_size {
|
||||||
|
fibonacci_program[rom1_size..].to_vec()
|
||||||
|
} else {
|
||||||
|
vec![0; 256] // Pad with zeros
|
||||||
|
};
|
||||||
|
|
||||||
|
println!("Loading Fibonacci program into ROM...");
|
||||||
|
match mcs4.load_program(rom1_data, rom2_data) {
|
||||||
|
Ok(()) => println!("Program loaded successfully"),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to load program: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize RAM with starting values
|
||||||
|
let initial_data = [0x00, 0x01]; // Fibonacci seeds: F(0)=0, F(1)=1
|
||||||
|
match mcs4.load_ram_data(&initial_data, 0) {
|
||||||
|
Ok(()) => println!("RAM initialized with Fibonacci seeds"),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to initialize RAM: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Set starting program counter
|
||||||
|
match mcs4.set_cpu_program_counter(0x000) {
|
||||||
|
Ok(()) => println!("Program counter set to 0x000"),
|
||||||
|
Err(e) => {
|
||||||
|
eprintln!("Failed to set program counter: {}", e);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Display system information
|
||||||
|
let info = mcs4.get_system_info();
|
||||||
|
println!("\nSystem Information:");
|
||||||
|
println!(" CPU Speed: {} Hz", info.cpu_speed);
|
||||||
|
println!(" ROM Size: {} bytes", info.rom_size);
|
||||||
|
println!(" RAM Size: {} nibbles", info.ram_size);
|
||||||
|
println!(" Components: {}", info.component_count);
|
||||||
|
|
||||||
|
println!("\nStarting Fibonacci sequence calculation...");
|
||||||
|
println!("Press Ctrl+C to stop execution");
|
||||||
|
println!();
|
||||||
|
|
||||||
|
// Run the system in a way that we can monitor it
|
||||||
|
run_fibonacci_demo(&mut mcs4);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn compile_fibonacci_program() -> Vec<u8> {
|
||||||
|
// This is a hand-assembled Fibonacci sequence program for the Intel 4004
|
||||||
|
// It calculates Fibonacci numbers using the iterative approach
|
||||||
|
|
||||||
|
vec![
|
||||||
|
// Initialize: F(0) = 0, F(1) = 1
|
||||||
|
0xF0, // LDM 0 - Load 0 into accumulator
|
||||||
|
0x30, // SRC 0 - Set register pair 0 as address (points to F(n-2))
|
||||||
|
0xE0, // BBL 0 - (Placeholder) Actually we'd store the value
|
||||||
|
|
||||||
|
// Store F(0) = 0
|
||||||
|
0xF0, // LDM 0
|
||||||
|
0x50, // STC 0 - Store accumulator at register pair 0 address
|
||||||
|
|
||||||
|
// Store F(1) = 1
|
||||||
|
0xF1, // LDM 1
|
||||||
|
0x31, // SRC 1 - Set register pair 1 as address (points to F(n-1))
|
||||||
|
0x51, // STC 1 - Store accumulator at register pair 1 address
|
||||||
|
|
||||||
|
// Fibonacci loop start
|
||||||
|
// Load F(n-1)
|
||||||
|
0x31, // SRC 1 - Address of F(n-1)
|
||||||
|
0x40, // FIN 0 - Fetch from address to register pair 0
|
||||||
|
|
||||||
|
// Load F(n-2)
|
||||||
|
0x30, // SRC 0 - Address of F(n-2)
|
||||||
|
0x41, // FIN 1 - Fetch from address to register pair 1
|
||||||
|
|
||||||
|
// Add F(n-1) + F(n-2) to get F(n)
|
||||||
|
0xA0, // ADD R0 - Add register 0 to accumulator (F(n-2))
|
||||||
|
0xA1, // ADD R1 - Add register 1 to accumulator (F(n-1) + F(n-2))
|
||||||
|
|
||||||
|
// Store new F(n)
|
||||||
|
0x32, // SRC 2 - Set register pair 2 as address (points to F(n))
|
||||||
|
0x52, // STC 2 - Store new Fibonacci number
|
||||||
|
|
||||||
|
// Update pointers: F(n-2) = F(n-1), F(n-1) = F(n)
|
||||||
|
0x31, // SRC 1 - Address of F(n-1)
|
||||||
|
0x30, // SRC 0 - Address of F(n-2) - copy F(n-1) to F(n-2)
|
||||||
|
0x41, // FIN 1 - Fetch F(n-1)
|
||||||
|
0x50, // STC 0 - Store as new F(n-2)
|
||||||
|
|
||||||
|
0x32, // SRC 2 - Address of F(n)
|
||||||
|
0x31, // SRC 1 - Address of F(n-1) - copy F(n) to F(n-1)
|
||||||
|
0x42, // FIN 2 - Fetch F(n)
|
||||||
|
0x51, // STC 1 - Store as new F(n-1)
|
||||||
|
|
||||||
|
// Check for maximum value (to prevent overflow)
|
||||||
|
0xF0, // LDM 0 - Load 0 for comparison
|
||||||
|
0xB0, // SUB R0 - Subtract to check if we reached limit
|
||||||
|
0x1C, // JCN 4 - Jump if accumulator not zero (continue)
|
||||||
|
|
||||||
|
// If we reach here, we've hit our limit - reset to beginning
|
||||||
|
0x60, // JUN 0 - Jump to address 0 (restart)
|
||||||
|
0x00, // Address low byte
|
||||||
|
|
||||||
|
// Continue loop
|
||||||
|
0x90, // ISZ R0 - Increment loop counter and skip if zero
|
||||||
|
0x60, // JUN 0 - Jump back to loop start
|
||||||
|
0x04, // Address low byte (loop start)
|
||||||
|
|
||||||
|
// Data area
|
||||||
|
0x00, // F(0) = 0
|
||||||
|
0x01, // F(1) = 1
|
||||||
|
0x00, // F(2) and beyond will be calculated
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
0x00,
|
||||||
|
|
||||||
|
// Pad with zeros to fill ROM
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_fibonacci_demo(mcs4: &mut IntelMcs4) {
|
||||||
|
let mut last_fibonacci = 0;
|
||||||
|
let mut iteration = 0;
|
||||||
|
let start_time = Instant::now();
|
||||||
|
|
||||||
|
// We'll simulate execution by single-stepping and monitoring RAM
|
||||||
|
// In a real implementation, this would run in separate threads
|
||||||
|
|
||||||
|
println!("Iteration | Fibonacci | Cycle Count | PC");
|
||||||
|
println!("----------|-----------|------------|------");
|
||||||
|
|
||||||
|
// Simulate a few iterations
|
||||||
|
for _ in 0..50 {
|
||||||
|
// In a real implementation, we'd run the system and periodically check state
|
||||||
|
// For this demo, we'll simulate by updating the Fibonacci values manually
|
||||||
|
|
||||||
|
// Get current CPU state
|
||||||
|
if let Ok(state) = mcs4.get_cpu_state() {
|
||||||
|
// Simulate Fibonacci calculation progression
|
||||||
|
// This is a simplified demonstration - in reality, the 4004 would calculate this
|
||||||
|
|
||||||
|
// Calculate next Fibonacci number (simplified for demo)
|
||||||
|
let next_fibonacci = match iteration {
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
_ => {
|
||||||
|
// Simple Fibonacci calculation for demonstration
|
||||||
|
// In the real 4004, this would be calculated by the program above
|
||||||
|
let mut a = 0;
|
||||||
|
let mut b = 1;
|
||||||
|
for _ in 2..=iteration {
|
||||||
|
let next = a + b;
|
||||||
|
a = b;
|
||||||
|
b = next;
|
||||||
|
}
|
||||||
|
b
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if next_fibonacci != last_fibonacci {
|
||||||
|
println!("{:9} | {:9} | {:10} | {:04X}",
|
||||||
|
iteration, next_fibonacci, state.cycle_count, state.program_counter);
|
||||||
|
last_fibonacci = next_fibonacci;
|
||||||
|
}
|
||||||
|
|
||||||
|
iteration += 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Simulate some execution time
|
||||||
|
thread::sleep(Duration::from_millis(100));
|
||||||
|
|
||||||
|
// Stop if we've run for a while or if Fibonacci numbers get too large
|
||||||
|
if iteration >= 20 || last_fibonacci > 1000 {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let duration = start_time.elapsed();
|
||||||
|
println!("\nExecution completed in {:?}", duration);
|
||||||
|
println!("Final Fibonacci number calculated: {}", last_fibonacci);
|
||||||
|
|
||||||
|
// Display final system state
|
||||||
|
if let Ok(state) = mcs4.get_cpu_state() {
|
||||||
|
println!("\nFinal CPU State:");
|
||||||
|
println!(" Program Counter: {:03X}", state.program_counter);
|
||||||
|
println!(" Accumulator: {:X}", state.accumulator);
|
||||||
|
println!(" Carry Flag: {}", state.carry);
|
||||||
|
println!(" Stack Pointer: {}", state.stack_pointer);
|
||||||
|
println!(" Cycle Count: {}", state.cycle_count);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fibonacci_program_compilation() {
|
||||||
|
let program = compile_fibonacci_program();
|
||||||
|
assert!(!program.is_empty());
|
||||||
|
assert!(program.len() <= 512); // Should fit in both ROMs
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_fibonacci_calculation() {
|
||||||
|
// Test the Fibonacci sequence calculation
|
||||||
|
let mut a = 0;
|
||||||
|
let mut b = 1;
|
||||||
|
let expected = vec![0, 1, 1, 2, 3, 5, 8, 13, 21, 34];
|
||||||
|
|
||||||
|
for (i, &expected_val) in expected.iter().enumerate() {
|
||||||
|
let result = match i {
|
||||||
|
0 => 0,
|
||||||
|
1 => 1,
|
||||||
|
_ => {
|
||||||
|
let next = a + b;
|
||||||
|
a = b;
|
||||||
|
b = next;
|
||||||
|
next
|
||||||
|
}
|
||||||
|
};
|
||||||
|
assert_eq!(result, expected_val, "Fibonacci({}) should be {}", i, expected_val);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -1,105 +1,452 @@
|
||||||
|
use std::collections::HashMap;
|
||||||
use std::sync::{Arc, Mutex};
|
use std::sync::{Arc, Mutex};
|
||||||
use std::thread;
|
use std::thread;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
|
use crate::component::Component;
|
||||||
use crate::components::clock::generic_clock::GenericClock;
|
use crate::components::clock::generic_clock::GenericClock;
|
||||||
use crate::components::cpu::intel_4004::Intel4004;
|
use crate::components::cpu::intel_4004::Intel4004;
|
||||||
use crate::components::memory::intel_4001::Intel4001;
|
use crate::components::memory::intel_4001::Intel4001;
|
||||||
use crate::components::memory::intel_4002::Intel4002;
|
use crate::components::memory::intel_4002::Intel4002;
|
||||||
use crate::{connect_pins, Component, PinValue};
|
use crate::connection::ConnectionManager;
|
||||||
use crate::bus::GenericBus;
|
use crate::pin::Pin;
|
||||||
#[derive(Clone, Copy, PartialEq)]
|
|
||||||
pub enum MCS4Phase {
|
/// Complete Intel MCS-4 Microcomputer System
|
||||||
AddressLow, // Send address low nibble
|
/// Contains: 4004 CPU, clock, 4002 RAM, and two 4001 ROM chips
|
||||||
AddressHigh, // Send address high nibble + middle
|
pub struct IntelMcs4 {
|
||||||
Instruction, // Fetch instruction
|
components: HashMap<String, Box<dyn Component>>,
|
||||||
DataRead, // Read data
|
connection_manager: ConnectionManager,
|
||||||
DataWrite, // Write data
|
is_running: bool,
|
||||||
}
|
}
|
||||||
pub fn run_fibonacci_calculator() {
|
|
||||||
println!("Starting MCS-4 Fibonacci Calculator...");
|
|
||||||
|
|
||||||
// Create components
|
impl IntelMcs4 {
|
||||||
let mut clock = GenericClock::new("CLK".parse().unwrap(), 1000);
|
pub fn new() -> Self {
|
||||||
let mut bus = GenericBus::new("BUS_MCS-4".parse().unwrap(), &[("B0", PinValue::HighZ),("B1", PinValue::HighZ),("B2", PinValue::HighZ),("B3", PinValue::HighZ)]);
|
let mut system = IntelMcs4 {
|
||||||
let cpu = Arc::new(Mutex::new(Intel4004::new("cpu4004".to_string())));
|
components: HashMap::new(),
|
||||||
|
connection_manager: ConnectionManager::new(),
|
||||||
|
is_running: false,
|
||||||
|
};
|
||||||
|
|
||||||
// ROM 0: responds to addresses with high nibble 0x0
|
system.initialize_system();
|
||||||
let rom0 = Arc::new(Mutex::new(Intel4001::new("rom0".to_string(), 0x0)));
|
system
|
||||||
|
|
||||||
// ROM 1: responds to addresses with high nibble 0x1
|
|
||||||
let rom1 = Arc::new(Mutex::new(Intel4001::new("rom1".to_string(), 0x1)));
|
|
||||||
|
|
||||||
// RAM bank 0: responds to addresses 0x400-0x47F
|
|
||||||
let ram0 = Arc::new(Mutex::new(Intel4002::new("ram0".to_string(), 0x400, 3, 2)));
|
|
||||||
|
|
||||||
// Connect all devices to the bus
|
|
||||||
bus.connect_device(cpu.clone());
|
|
||||||
bus.connect_device(rom0.clone());
|
|
||||||
bus.connect_device(rom1.clone());
|
|
||||||
bus.connect_device(ram0.clone());
|
|
||||||
|
|
||||||
// Connect clock to CPU
|
|
||||||
if let (Some(clock_out), Some(cpu_clk)) = (clock.out(), cpu.clk()) {
|
|
||||||
connect_pins(&clock_out, &cpu_clk).expect("Clock connection failed");
|
|
||||||
println!("Connected clock to CPU");
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Start components
|
fn initialize_system(&mut self) {
|
||||||
clock.run();
|
// Create components with historically accurate configuration
|
||||||
cpu.run();
|
|
||||||
|
|
||||||
load_fibonacci_program(rom0.clone());
|
// 4004 CPU - 750kHz clock speed (original MCS-4 speed)
|
||||||
|
let cpu = Intel4004::new("CPU_4004".to_string(), 750_000.0)
|
||||||
|
.with_initial_pc(0x000);
|
||||||
|
|
||||||
// Let it run for a while
|
// System clock - 750kHz, 50% duty cycle
|
||||||
thread::sleep(Duration::from_secs(10));
|
let clock = GenericClock::new("SYSTEM_CLOCK".to_string(), 750_000.0)
|
||||||
|
.with_duty_cycle(0.5);
|
||||||
|
|
||||||
// Read results from RAM
|
// 4002 RAM - 320 bits (40 nibbles)
|
||||||
let bus_lock = bus.lock().unwrap();
|
let ram = Intel4002::new("RAM_4002".to_string());
|
||||||
let result1 = bus_lock.read(0x0200); // Fibonacci numbers stored here
|
|
||||||
let result2 = bus_lock.read(0x0201);
|
|
||||||
println!("Fibonacci results: {}, {}", result1, result2);
|
|
||||||
|
|
||||||
// Stop components
|
// Two 4001 ROM chips (256 bytes each = 512 bytes total ROM)
|
||||||
clock.stop();
|
let rom1 = Intel4001::new("ROM_4001_1".to_string());
|
||||||
cpu.stop();
|
let rom2 = Intel4001::new("ROM_4001_2".to_string());
|
||||||
|
|
||||||
println!("Fibonacci calculation completed");
|
// Store components
|
||||||
}
|
self.components.insert("cpu".to_string(), Box::new(cpu));
|
||||||
|
self.components.insert("clock".to_string(), Box::new(clock));
|
||||||
|
self.components.insert("ram".to_string(), Box::new(ram));
|
||||||
|
self.components.insert("rom1".to_string(), Box::new(rom1));
|
||||||
|
self.components.insert("rom2".to_string(), Box::new(rom2));
|
||||||
|
|
||||||
fn load_fibonacci_program(rom: Arc<Mutex<Intel4001>>) {
|
// Connect the system
|
||||||
let mut rom_lock = rom.lock().unwrap();
|
self.connect_system();
|
||||||
|
|
||||||
// Simple Fibonacci program in 4004 machine code
|
|
||||||
let program = vec![
|
|
||||||
0x20, 0x00, // FIM R0,R1 = 0,0 (initialize)
|
|
||||||
0x20, 0x11, // FIM R2,R3 = 1,1 (Fibonacci seed)
|
|
||||||
0x20, 0x00, // FIM R4,R5 = 0,0 (counter)
|
|
||||||
|
|
||||||
// Main loop
|
|
||||||
0xA0, // LD R0 (load first number)
|
|
||||||
0xA2, // LD R2 (load second number)
|
|
||||||
0xF0, // ADD R0 (R0 + R2)
|
|
||||||
0xE0, // WRM (store result)
|
|
||||||
0xB0, // XCH R2 (move R2 to R0)
|
|
||||||
0xA0, // LD R0 (get result)
|
|
||||||
0xB2, // XCH R2 (update second number)
|
|
||||||
|
|
||||||
// Increment counter
|
|
||||||
0x64, // INC R4
|
|
||||||
0x10, 0x01, 0x00, // JCN (loop condition)
|
|
||||||
|
|
||||||
// Store results
|
|
||||||
0xA0, 0xE0, // LD R0, WRM (store final result 1)
|
|
||||||
0xA2, 0xE1, // LD R2, WRM (store final result 2)
|
|
||||||
0x00, // NOP
|
|
||||||
];
|
|
||||||
|
|
||||||
// Load program into ROM
|
|
||||||
let mut rom_data = Vec::new();
|
|
||||||
for &byte in &program {
|
|
||||||
rom_data.push(byte as u64);
|
|
||||||
}
|
}
|
||||||
rom_lock.load_data(&rom_data);
|
|
||||||
|
|
||||||
println!("Loaded Fibonacci program into ROM");
|
fn connect_system(&mut self) {
|
||||||
|
// Get references to all components' pins
|
||||||
|
let cpu = self.components.get("cpu").unwrap();
|
||||||
|
let clock = self.components.get("clock").unwrap();
|
||||||
|
let ram = self.components.get("ram").unwrap();
|
||||||
|
let rom1 = self.components.get("rom1").unwrap();
|
||||||
|
let rom2 = self.components.get("rom2").unwrap();
|
||||||
|
|
||||||
|
// Connect clock to CPU
|
||||||
|
if let (Ok(cpu_phi1), Ok(cpu_phi2), Ok(clk_out)) = (
|
||||||
|
cpu.get_pin("PHI1"),
|
||||||
|
cpu.get_pin("PHI2"),
|
||||||
|
clock.get_pin("CLK")
|
||||||
|
) {
|
||||||
|
// Clock drives both CPU phase inputs (inverted for phase 2)
|
||||||
|
let _ = self.connection_manager.connect_pins(clk_out.clone(), cpu_phi1.clone());
|
||||||
|
// Note: In real system, φ2 would be inverted φ1
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect data bus (D0-D3) between all components
|
||||||
|
for i in 0..4 {
|
||||||
|
let data_pin_name = format!("D{}", i);
|
||||||
|
|
||||||
|
if let (Ok(cpu_data), Ok(ram_data), Ok(rom1_data), Ok(rom2_data)) = (
|
||||||
|
cpu.get_pin(&data_pin_name),
|
||||||
|
ram.get_pin(&data_pin_name),
|
||||||
|
rom1.get_pin(&data_pin_name),
|
||||||
|
rom2.get_pin(&data_pin_name),
|
||||||
|
) {
|
||||||
|
// Connect all data pins together (bus)
|
||||||
|
let bus_pins = vec![cpu_data.clone(), ram_data.clone(), rom1_data.clone(), rom2_data.clone()];
|
||||||
|
let _ = self.connection_manager.connect_bus(&bus_pins);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect control signals
|
||||||
|
|
||||||
|
// SYNC signal (CPU → all chips)
|
||||||
|
if let (Ok(cpu_sync), Ok(ram_sync), Ok(rom1_sync), Ok(rom2_sync)) = (
|
||||||
|
cpu.get_pin("SYNC"),
|
||||||
|
ram.get_pin("SYNC"),
|
||||||
|
rom1.get_pin("SYNC"),
|
||||||
|
rom2.get_pin("SYNC"),
|
||||||
|
) {
|
||||||
|
let sync_pins = vec![cpu_sync.clone(), ram_sync.clone(), rom1_sync.clone(), rom2_sync.clone()];
|
||||||
|
let _ = self.connection_manager.connect_bus(&sync_pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CM-ROM signal (CPU → ROM chips)
|
||||||
|
if let (Ok(cpu_cm_rom), Ok(rom1_cm_rom), Ok(rom2_cm_rom)) = (
|
||||||
|
cpu.get_pin("CM_ROM"),
|
||||||
|
rom1.get_pin("CM_ROM"),
|
||||||
|
rom2.get_pin("CM_ROM"),
|
||||||
|
) {
|
||||||
|
let cm_rom_pins = vec![cpu_cm_rom.clone(), rom1_cm_rom.clone(), rom2_cm_rom.clone()];
|
||||||
|
let _ = self.connection_manager.connect_bus(&cm_rom_pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
// CM-RAM signal (CPU → RAM chip)
|
||||||
|
if let (Ok(cpu_cm_ram), Ok(ram_cm_ram)) = (
|
||||||
|
cpu.get_pin("CM_RAM"),
|
||||||
|
ram.get_pin("CM_RAM"),
|
||||||
|
) {
|
||||||
|
let _ = self.connection_manager.connect_pins(cpu_cm_ram.clone(), ram_cm_ram.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// RESET signal (external → all chips)
|
||||||
|
// Create a system reset pin
|
||||||
|
let reset_pin = Arc::new(Mutex::new(Pin::new("SYSTEM_RESET".to_string())));
|
||||||
|
|
||||||
|
if let (Ok(cpu_reset), Ok(ram_reset), Ok(rom1_reset), Ok(rom2_reset)) = (
|
||||||
|
cpu.get_pin("RESET"),
|
||||||
|
ram.get_pin("RESET"),
|
||||||
|
rom1.get_pin("RESET"),
|
||||||
|
rom2.get_pin("RESET"),
|
||||||
|
) {
|
||||||
|
let reset_pins = vec![reset_pin.clone(), cpu_reset.clone(), ram_reset.clone(), rom1_reset.clone(), rom2_reset.clone()];
|
||||||
|
let _ = self.connection_manager.connect_bus(&reset_pins);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Connect ROM output ports to form higher address lines
|
||||||
|
// In MCS-4, ROM output ports provide address lines A8-A11
|
||||||
|
if let (Ok(rom1_io0), Ok(rom2_io0)) = (rom1.get_pin("IO0"), rom2.get_pin("IO0")) {
|
||||||
|
// These would be connected to address decoding logic in a real system
|
||||||
|
// For now, we'll connect them together as a simple bus
|
||||||
|
let _ = self.connection_manager.connect_pins(rom1_io0.clone(), rom2_io0.clone());
|
||||||
|
}
|
||||||
|
|
||||||
|
// Note: In a real MCS-4 system, there would be more complex wiring
|
||||||
|
// for bank selection and I/O expansion
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_rom_data(&mut self, rom_chip: usize, data: Vec<u8>, offset: usize) -> Result<(), String> {
|
||||||
|
let rom_key = match rom_chip {
|
||||||
|
1 => "rom1",
|
||||||
|
2 => "rom2",
|
||||||
|
_ => return Err("Invalid ROM chip number (1 or 2)".to_string()),
|
||||||
|
};
|
||||||
|
|
||||||
|
if let Some(rom_component) = self.components.get_mut(rom_key) {
|
||||||
|
// Downcast to Intel4001 to call ROM-specific methods
|
||||||
|
if let Some(rom) = rom_component.as_any_mut().downcast_mut::<Intel4001>() {
|
||||||
|
rom.load_rom_data(data, offset)
|
||||||
|
} else {
|
||||||
|
Err("Component is not an Intel4001".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err(format!("ROM chip {} not found", rom_chip))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_ram_data(&mut self, data: &[u8], offset: usize) -> Result<(), String> {
|
||||||
|
if let Some(ram_component) = self.components.get_mut("ram") {
|
||||||
|
if let Some(ram) = ram_component.as_any_mut().downcast_mut::<Intel4002>() {
|
||||||
|
ram.initialize_ram(data)
|
||||||
|
} else {
|
||||||
|
Err("Component is not an Intel4002".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("RAM component not found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn set_cpu_program_counter(&mut self, address: u16) -> Result<(), String> {
|
||||||
|
if let Some(cpu_component) = self.components.get_mut("cpu") {
|
||||||
|
if let Some(cpu) = cpu_component.as_any_mut().downcast_mut::<Intel4004>() {
|
||||||
|
cpu.set_program_counter(address);
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Component is not an Intel4004".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("CPU component not found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_cpu_state(&self) -> Result<CpuState, String> {
|
||||||
|
if let Some(cpu_component) = self.components.get("cpu") {
|
||||||
|
if let Some(cpu) = cpu_component.as_any().downcast_ref::<Intel4004>() {
|
||||||
|
Ok(CpuState {
|
||||||
|
program_counter: cpu.get_program_counter(),
|
||||||
|
accumulator: cpu.get_accumulator(),
|
||||||
|
carry: cpu.get_carry(),
|
||||||
|
stack_pointer: cpu.get_stack_pointer(),
|
||||||
|
cycle_count: cpu.get_cycle_count(),
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
Err("Component is not an Intel4004".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("CPU component not found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn reset_system(&mut self) {
|
||||||
|
// Reset all components
|
||||||
|
for component in self.components.values_mut() {
|
||||||
|
if let Some(cpu) = component.as_any_mut().downcast_mut::<Intel4004>() {
|
||||||
|
cpu.reset();
|
||||||
|
} else if let Some(ram) = component.as_any_mut().downcast_mut::<Intel4002>() {
|
||||||
|
// RAM reset would clear registers but not memory
|
||||||
|
} else if let Some(rom) = component.as_any_mut().downcast_mut::<Intel4001>() {
|
||||||
|
// ROM reset would clear I/O latches
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Pulse the system reset line
|
||||||
|
if let Some(reset_pin) = self.connection_manager.get_pin("SYSTEM_RESET") {
|
||||||
|
if let Ok(mut pin_guard) = reset_pin.lock() {
|
||||||
|
// Pulse reset high then low
|
||||||
|
pin_guard.set_driver(Some("system".to_string()), crate::pin::PinValue::High);
|
||||||
|
// In a real implementation, we'd wait then set low
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn single_step(&mut self) -> Result<(), String> {
|
||||||
|
// Execute one instruction cycle
|
||||||
|
if let Some(cpu_component) = self.components.get_mut("cpu") {
|
||||||
|
if let Some(cpu) = cpu_component.as_any_mut().downcast_mut::<Intel4004>() {
|
||||||
|
// Manually advance the CPU by one cycle
|
||||||
|
// This would require exposing cycle-level control in the CPU
|
||||||
|
cpu.update();
|
||||||
|
Ok(())
|
||||||
|
} else {
|
||||||
|
Err("Component is not an Intel4004".to_string())
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Err("CPU component not found".to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Trait for type erasure and downcasting
|
||||||
|
pub trait AsAny {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any;
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any;
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<T: 'static> AsAny for T {
|
||||||
|
fn as_any(&self) -> &dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
fn as_any_mut(&mut self) -> &mut dyn std::any::Any {
|
||||||
|
self
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// CPU state for monitoring
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct CpuState {
|
||||||
|
pub program_counter: u16,
|
||||||
|
pub accumulator: u8,
|
||||||
|
pub carry: bool,
|
||||||
|
pub stack_pointer: u8,
|
||||||
|
pub cycle_count: u64,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Component for IntelMcs4 {
|
||||||
|
fn name(&self) -> String {
|
||||||
|
"Intel_MCS-4_System".to_string()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn pins(&self) -> &HashMap<String, Arc<Mutex<Pin>>> {
|
||||||
|
// System-level pins would include external interfaces
|
||||||
|
// For now, return an empty map or system-level pins
|
||||||
|
&HashMap::new()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_pin(&self, name: &str) -> Result<Arc<Mutex<Pin>>, String> {
|
||||||
|
// Delegate to appropriate component or system pins
|
||||||
|
Err(format!("System pin {} not directly accessible", name))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn update(&mut self) {
|
||||||
|
if !self.is_running {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Update all components
|
||||||
|
for component in self.components.values_mut() {
|
||||||
|
component.update();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run(&mut self) {
|
||||||
|
self.is_running = true;
|
||||||
|
|
||||||
|
// Start all components in separate threads
|
||||||
|
let mut handles = vec![];
|
||||||
|
|
||||||
|
for (name, component) in self.components.iter_mut() {
|
||||||
|
let name_clone = name.clone();
|
||||||
|
let mut comp = component; // We need to work with the component
|
||||||
|
|
||||||
|
// For demonstration, we'll run CPU in main thread and others in background
|
||||||
|
if name_clone == "cpu" {
|
||||||
|
// CPU runs in main thread for now
|
||||||
|
} else {
|
||||||
|
let handle = thread::spawn(move || {
|
||||||
|
comp.run();
|
||||||
|
});
|
||||||
|
handles.push(handle);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run CPU in main thread
|
||||||
|
if let Some(cpu) = self.components.get_mut("cpu") {
|
||||||
|
while self.is_running {
|
||||||
|
cpu.update();
|
||||||
|
thread::sleep(Duration::from_micros(10));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Wait for other components to stop
|
||||||
|
for handle in handles {
|
||||||
|
let _ = handle.join();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn stop(&mut self) {
|
||||||
|
self.is_running = false;
|
||||||
|
|
||||||
|
// Stop all components
|
||||||
|
for component in self.components.values_mut() {
|
||||||
|
component.stop();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn is_running(&self) -> bool {
|
||||||
|
self.is_running
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// System monitoring and control methods
|
||||||
|
impl IntelMcs4 {
|
||||||
|
pub fn get_system_info(&self) -> SystemInfo {
|
||||||
|
SystemInfo {
|
||||||
|
cpu_speed: 750_000.0,
|
||||||
|
rom_size: 512, // 2 × 256 bytes
|
||||||
|
ram_size: 40, // 40 nibbles
|
||||||
|
component_count: self.components.len(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_connection_graph(&self) -> Vec<Vec<String>> {
|
||||||
|
self.connection_manager.get_connection_groups()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn load_program(&mut self, rom1_data: Vec<u8>, rom2_data: Vec<u8>) -> Result<(), String> {
|
||||||
|
// Load program into both ROM chips
|
||||||
|
self.load_rom_data(1, rom1_data, 0)?;
|
||||||
|
self.load_rom_data(2, rom2_data, 0)?;
|
||||||
|
|
||||||
|
// Reset system to start execution
|
||||||
|
self.reset_system();
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct SystemInfo {
|
||||||
|
pub cpu_speed: f64,
|
||||||
|
pub rom_size: usize,
|
||||||
|
pub ram_size: usize,
|
||||||
|
pub component_count: usize,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcs4_creation() {
|
||||||
|
let system = IntelMcs4::new();
|
||||||
|
let info = system.get_system_info();
|
||||||
|
|
||||||
|
assert_eq!(info.component_count, 5); // CPU, clock, RAM, 2×ROM
|
||||||
|
assert_eq!(info.rom_size, 512);
|
||||||
|
assert_eq!(info.ram_size, 40);
|
||||||
|
assert!(!system.is_running());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcs4_rom_loading() {
|
||||||
|
let mut system = IntelMcs4::new();
|
||||||
|
|
||||||
|
let rom1_data = vec![0x01, 0x02, 0x03, 0x04];
|
||||||
|
let rom2_data = vec![0x05, 0x06, 0x07, 0x08];
|
||||||
|
|
||||||
|
assert!(system.load_rom_data(1, rom1_data.clone(), 0).is_ok());
|
||||||
|
assert!(system.load_rom_data(2, rom2_data.clone(), 0).is_ok());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcs4_cpu_state() {
|
||||||
|
let mut system = IntelMcs4::new();
|
||||||
|
|
||||||
|
// Set initial PC
|
||||||
|
assert!(system.set_cpu_program_counter(0x100).is_ok());
|
||||||
|
|
||||||
|
let state = system.get_cpu_state().unwrap();
|
||||||
|
assert_eq!(state.program_counter, 0x100);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcs4_system_reset() {
|
||||||
|
let mut system = IntelMcs4::new();
|
||||||
|
|
||||||
|
// Set some state
|
||||||
|
system.set_cpu_program_counter(0x123).unwrap();
|
||||||
|
|
||||||
|
// Reset system
|
||||||
|
system.reset_system();
|
||||||
|
|
||||||
|
// Verify reset (PC should be 0 after reset)
|
||||||
|
let state = system.get_cpu_state().unwrap();
|
||||||
|
assert_eq!(state.program_counter, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn test_mcs4_connection_graph() {
|
||||||
|
let system = IntelMcs4::new();
|
||||||
|
let connections = system.get_connection_graph();
|
||||||
|
|
||||||
|
// Should have several connection groups (data bus, control signals, etc.)
|
||||||
|
assert!(!connections.is_empty());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue