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,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);
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue