Re-implementations of all Intel 400x chips.

The MCS-4 is still broken, so are the 6502 chips
This commit is contained in:
Jan-Peter von Hunnius 2025-09-22 19:32:44 +02:00
parent eff70aa889
commit 7dd09c730d
7 changed files with 2954 additions and 694 deletions

View file

@ -1,302 +1,414 @@
use std::sync::Arc;
use crate::{Component, Pin, PinValue};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
#[derive(Clone)]
pub struct GenericRam<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize> {
base: crate::component::BaseComponent,
pub(crate) memory: Vec<u64>,
base_address: u64,
address_mask: u64,
read_delay: u32, // Read access time in cycles
write_delay: u32, // Write access time in cycles
current_operation: Option<RamOperation>,
operation_cycles: u32,
output_buffer: Option<u64>,
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
pub struct GenericRam {
base: BaseComponent,
memory: Vec<u8>,
address_width: usize,
data_width: usize,
last_address: u32,
last_operation: RamOperation,
access_time: Duration,
last_access: Instant,
write_enable: bool,
output_enable: bool,
chip_select: bool,
}
#[derive(Clone, Copy, PartialEq)]
#[derive(Debug, Clone, Copy, PartialEq)]
enum RamOperation {
Read(u64), // address
Write(u64, u64), // address, data
Read,
Write,
Idle,
}
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
{
pub fn new(name: String, base_address: u64, read_delay: u32, write_delay: u32) -> Self {
let mut base = crate::component::BaseComponent::new(name);
impl GenericRam {
pub fn new(name: String, size: usize, address_width: usize, data_width: usize) -> Self {
let mut pin_names = Vec::new();
// Add address pins
for i in 0..ADDR_WIDTH {
base.add_pin(format!("addr_{}", i), PinValue::Low);
// Address pins (A0, A1, A2, ...)
for i in 0..address_width {
pin_names.push(format!("A{}", i));
}
// Add data pins
for i in 0..DATA_WIDTH {
base.add_pin(format!("data_{}", i), PinValue::HighZ);
// Data pins (D0, D1, D2, ...) - bidirectional
for i in 0..data_width {
let name = format!("D{}", i);
pin_names.push(name.clone());
}
// Add control pins
base.add_pin("cs".to_string(), PinValue::High); // Chip Select (active low)
base.add_pin("we".to_string(), PinValue::High); // Write Enable (active low)
base.add_pin("oe".to_string(), PinValue::High); // Output Enable (active low)
base.add_pin("clk".to_string(), PinValue::Low); // Clock for synchronization
// Control pins
pin_names.push("CS".parse().unwrap()); // Chip Select
pin_names.push("WE".parse().unwrap()); // Write Enable
pin_names.push("OE".parse().unwrap()); // Output Enable
// Calculate address mask based on size
let address_mask = ((1 << ADDR_WIDTH) - 1) as u64;
let pin_refs: Vec<&str> = pin_names.iter().map(|s| s.as_str()).collect();
let pins = BaseComponent::create_pin_map(&pin_refs, &name);
let memory = vec![0u8; size];
Self {
base,
memory: vec![0; SIZE],
base_address,
address_mask,
read_delay,
write_delay,
current_operation: None,
operation_cycles: 0,
output_buffer: None,
GenericRam {
base: BaseComponent::new(name, pins),
memory,
address_width,
data_width,
last_address: 0,
last_operation: RamOperation::Idle,
access_time: Duration::from_nanos(100), // 100ns access time
last_access: Instant::now(),
write_enable: false,
output_enable: false,
chip_select: false,
}
}
fn read_address_bus(&self) -> u64 {
let mut address = 0;
for i in 0..ADDR_WIDTH {
if let Some(pin) = self.base.get_pin(&format!("addr_{}", i)) {
if pin.read() == PinValue::High {
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;
pub fn load_data(&mut self, data: Vec<u8>, offset: usize) -> Result<(), String> {
if offset + data.len() > self.memory.len() {
return Err(format!(
"Data exceeds RAM capacity: offset {} + data length {} > RAM size {}",
offset, data.len(), self.memory.len()
));
}
let clock_rising_edge = self.is_clock_rising_edge(last_clock);
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);
self.memory[offset..offset + data.len()].copy_from_slice(&data);
Ok(())
}
fn run(&mut self) {
self.base.run();
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(())
}
fn stop(&mut self) {
self.base.stop();
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();
}
}
// BusDevice implementation
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
crate::bus::BusDevice for GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
{
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) {
// Delegate to component update
let _ = Component::update(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 connect_to_bus(&mut self, bus_pins: Vec<Arc<Pin>>) {
// Connect to bus pins - in a real system, this would connect address, data, and control pins
// This is simplified for the example
fn run(&mut self) {
self.base.set_running(true);
while self.is_running() {
self.update();
thread::sleep(Duration::from_micros(1));
}
}
fn get_address_range(&self) -> Option<std::ops::Range<u64>> {
Some(self.base_address..self.base_address + SIZE as u64)
fn stop(&mut self) {
self.base.set_running(false);
// Tri-state all data pins when stopped
self.tri_state_data_bus();
}
fn get_name(&self) -> &str {
todo!()
fn is_running(&self) -> bool {
self.base.is_running()
}
}
// Additional utility methods
impl GenericRam {
pub fn clear_memory(&mut self) {
for byte in &mut self.memory {
*byte = 0;
}
}
pub fn fill_memory(&mut self, value: u8) {
for byte in &mut self.memory {
*byte = value;
}
}
pub fn get_memory_snapshot(&self) -> Vec<u8> {
self.memory.clone()
}
pub fn restore_memory_snapshot(&mut self, snapshot: Vec<u8>) -> Result<(), String> {
if snapshot.len() != self.memory.len() {
return Err("Snapshot size doesn't match RAM size".to_string());
}
self.memory.copy_from_slice(&snapshot);
Ok(())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_ram_creation() {
let ram = GenericRam::new("TEST_RAM".to_string(), 1024, 10, 8);
assert_eq!(ram.name(), "TEST_RAM");
assert_eq!(ram.memory.len(), 1024);
assert_eq!(ram.address_width, 10);
assert_eq!(ram.data_width, 8);
}
#[test]
fn test_ram_data_loading() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
let test_data = vec![0x12, 0x34, 0x56, 0x78];
assert!(ram.load_data(test_data.clone(), 0).is_ok());
assert_eq!(ram.memory[0], 0x12);
assert_eq!(ram.memory[1], 0x34);
assert_eq!(ram.memory[2], 0x56);
assert_eq!(ram.memory[3], 0x78);
}
#[test]
fn test_ram_read_write() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Test direct memory access
assert!(ram.write_memory(0x10, &[0xAA, 0xBB, 0xCC]).is_ok());
let read_data = ram.read_memory(0x10, 3).unwrap();
assert_eq!(read_data, vec![0xAA, 0xBB, 0xCC]);
}
#[test]
fn test_ram_clear_and_fill() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Fill with pattern
ram.fill_memory(0x55);
assert_eq!(ram.memory[0], 0x55);
assert_eq!(ram.memory[100], 0x55);
// Clear memory
ram.clear_memory();
assert_eq!(ram.memory[0], 0);
assert_eq!(ram.memory[100], 0);
}
#[test]
fn test_ram_snapshot() {
let mut ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
// Write some data
ram.write_memory(0, &[0x11, 0x22, 0x33]).unwrap();
// Take snapshot
let snapshot = ram.get_memory_snapshot();
// Modify memory
ram.write_memory(0, &[0x44, 0x55, 0x66]).unwrap();
// Restore snapshot
ram.restore_memory_snapshot(snapshot).unwrap();
// Verify restoration
let read_data = ram.read_memory(0, 3).unwrap();
assert_eq!(read_data, vec![0x11, 0x22, 0x33]);
}
#[test]
fn test_ram_is_running() {
let ram = GenericRam::new("TEST_RAM".to_string(), 256, 8, 8);
assert!(!ram.is_running());
}
}

View file

@ -1,56 +1,438 @@
use crate::components::memory::generic_rom::GenericRom;
use crate::{Component, PinValue};
use crate::systems::intel_mcs_4::MCS4Phase;
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use std::thread;
use std::time::{Duration, Instant};
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
/// 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 {
fn update(&mut self) -> Result<(), String> {
// Read the current bus phase from the pins
let phase = self.detect_bus_phase();
pub fn new(name: String) -> Self {
// Intel 4001 has 256 bytes of ROM
let rom_size = 256;
match phase {
MCS4Phase::AddressLow => {
self.latch_address_low();
}
MCS4Phase::AddressHigh => {
self.latch_address_high();
if self.is_selected() {
self.prepare_data_output();
}
}
MCS4Phase::DataRead => {
if self.is_selected() {
self.output_data(/* u8 */);
}
}
_ => {
// Put pins in high impedance
self.set_pins_high_z();
}
// Intel 4001 pinout:
// - 10 address pins (A0-A9, but only A0-A7 used for 256-byte addressing)
// - 4 data pins (D0-D3) for multiplexed address/data
// - 4 I/O pins (IO0-IO3)
// - Control pins: SYNC, CM-ROM, CM-RAM, RESET
let pin_names = vec![
"D0", "D1", "D2", "D3", // Data/Address pins
"IO0", "IO1", "IO2", "IO3", // I/O pins
"SYNC", // Sync signal
"CM_ROM", // ROM Chip Select
"CM_RAM", // RAM Chip Select (for system configuration)
"RESET", // Reset
];
let pins = BaseComponent::create_pin_map(&pin_names, &name);
let memory = vec![0u8; rom_size];
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(())
}
fn detect_bus_phase(&self) -> MCS4Phase {
// Detect phase based on sync and other control signals
if let Some(sync_pin) = self.base.get_pin("pin_0") {
match sync_pin.read() {
PinValue::High => MCS4Phase::AddressLow, // Sync high = address phase
PinValue::Low => MCS4Phase::DataRead, // Sync low = data phase
_ => MCS4Phase::AddressLow,
}
} else {
MCS4Phase::AddressLow
pub fn load_from_hex(&mut self, hex_data: &str, offset: usize) -> Result<(), String> {
let bytes: Result<Vec<u8>, _> = hex_data
.split_whitespace()
.map(|s| u8::from_str_radix(s.trim(), 16))
.collect();
match bytes {
Ok(data) => self.load_rom_data(data, offset),
Err(e) => Err(format!("Invalid hex data: {}", e)),
}
}
fn latch_address_low(&mut self) {
let addr_low = self.read_nibble_from_pins(9..12);
self.latched_address = (self.latched_address & 0xFF0) | (addr_low as u16);
fn read_data_bus(&self) -> u8 {
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);
let addr_mid = self.read_nibble_from_pins(13..16);
self.latched_address = ((addr_mid as u16) << 8) | ((addr_high as u16) << 4) | (self.latched_address & 0x00F);
fn write_data_bus(&self, data: u8) {
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 = (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");
}
}

View file

@ -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
// Organized as 4 registers × 20 characters × 4 bits
pub type Intel4002 = GenericRam<80, 4, 12>;
use crate::component::{BaseComponent, Component};
use crate::pin::{Pin, PinValue};
/// 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 {
pub fn new_4002(name: String, base_address: u16) -> Self {
// 4002 has specific timing characteristics
let ram = Self::new(
name,
base_address as u64,
2, // 2-cycle read access
3 // 3-cycle write access
);
pub fn new(name: String) -> Self {
// Intel 4002 has 40 nibbles of RAM (320 bits)
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 write_to_register_character(&mut self, register: u8, character: u8, value: u8) -> Result<(), String> {
if register >= 4 || character >= 20 {
return Err("Invalid register or character index".to_string());
pub fn initialize_ram(&mut self, data: &[u8]) -> Result<(), String> {
if data.len() > 40 {
return Err("Data exceeds RAM capacity (40 nibbles)".to_string());
}
let address = self.calculate_address(register, character);
let index = self.decode_address(address as u64)
.ok_or("Address out of range".to_string())?;
self.memory[index] = (value & 0x0F) as u64;
for (i, &byte) in data.iter().enumerate() {
self.memory[i] = byte & 0x0F; // Store only lower 4 bits
}
Ok(())
}
pub fn read_from_register_character(&self, register: u8, character: u8) -> Result<u8, String> {
if register >= 4 || character >= 20 {
return Err("Invalid register or character index".to_string());
fn read_data_bus(&self) -> u8 {
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;
}
}
}
}
let address = self.calculate_address(register, character);
let index = self.decode_address(address as u64)
.ok_or("Address out of range".to_string())?;
Ok(self.memory[index] as u8)
data & 0x0F // Return only lower 4 bits
}
fn calculate_address(&self, register: u8, character: u8) -> u16 {
// 4002 address calculation:
// Each register has 20 characters, each character is 4 bits
(register as u16 * 20) + (character as u16)
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 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);
}
}