rusty_emu/src/components/memory/intel_4002.rs
Jan-Peter von Hunnius e64d3edbd8 Sleep time for threads extended
Fix for ActiveBus check (thread did not run)
2025-09-24 15:13:17 +02:00

505 lines
No EOL
15 KiB
Rust
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

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 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(name: String) -> Self {
// Intel 4002 has 40 nibbles of RAM (320 bits)
// 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,
}
}
pub fn initialize_ram(&mut self, data: &[u8]) -> Result<(), String> {
if data.len() > 40 {
return Err("Data exceeds RAM capacity (40 nibbles)".to_string());
}
for (i, &byte) in data.iter().enumerate() {
self.memory[i] = byte & 0x0F; // Store only lower 4 bits
}
Ok(())
}
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 & 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 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(10));
}
}
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);
}
}