More flexible bus system that takes VIC-20 address mapping and multiplexed buses like the MCS-4 into account.

Broken currently, subject to fixes
This commit is contained in:
Jan-Peter von Hunnius 2025-09-22 17:48:16 +02:00
parent ddac8865fa
commit ba1b0ddc22
14 changed files with 1000 additions and 68 deletions

162
src/bus.rs Normal file
View file

@ -0,0 +1,162 @@
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use crate::{Component, Pin, PinValue};
pub trait BusDevice: Send {
/// Called on each simulation cycle to update device state based on pin states
fn update(&mut self);
/// Connect this device to bus pins
fn connect_to_bus(&mut self, bus_pins: Vec<Arc<Pin>>);
/// Optional: report address range for debugging/monitoring
fn get_address_range(&self) -> Option<std::ops::Range<u64>> {
None
}
/// Optional: get device name for debugging
fn get_name(&self) -> &str;
}
#[derive(Clone)]
pub struct GenericBus {
base: crate::component::BaseComponent,
connected_devices: Vec<Arc<Mutex<dyn BusDevice>>>,
bus_pins: HashMap<String, Arc<Pin>>, // Named bus lines (like "A0", "D0", "R/W")
}
impl GenericBus {
pub fn new(name: String, pin_definitions: &[(&str, PinValue)]) -> Self {
let mut base = crate::component::BaseComponent::new(name.clone());
let mut bus_pins = HashMap::new();
// Create bus pins
for (pin_name, initial_value) in pin_definitions {
let pin = base.add_pin(
pin_name.to_string(),
*initial_value,
false, // Bus doesn't drive pins by default
);
bus_pins.insert(pin_name.to_string(), pin);
}
Self {
base,
connected_devices: Vec::new(),
bus_pins,
}
}
pub fn connect_device(&mut self, device: Arc<Mutex<dyn BusDevice>>) {
// Get all bus pins and connect them to the device
let bus_pins: Vec<Arc<Pin>> = self.bus_pins.values().cloned().collect();
if let Ok(mut device) = device.lock() {
device.connect_to_bus(bus_pins);
}
self.connected_devices.push(device);
}
pub fn get_pin(&self, name: &str) -> Option<Arc<Pin>> {
self.bus_pins.get(name).cloned()
}
/// The key function: resolve pin conflicts and propagate values
fn resolve_bus_conflicts(&self) {
for (pin_name, bus_pin) in &self.bus_pins {
self.resolve_pin_state(bus_pin);
}
}
fn resolve_pin_state(&self, bus_pin: &Arc<Pin>) {
let connections = bus_pin.connections.lock().unwrap();
// Check if any device is driving the pin low (wired-AND behavior)
let mut any_driving_low = false;
let mut any_driving_high = false;
let mut any_driving = false;
for connected_pin in connections.iter() {
let output_enable = connected_pin.output_enable.lock().unwrap();
let value = connected_pin.value.read().unwrap();
if *output_enable {
any_driving = true;
match *value {
PinValue::Low => any_driving_low = true,
PinValue::High => any_driving_high = true,
PinValue::HighZ => {} // High-Z doesn't drive
}
}
}
// Determine the resolved bus state
let resolved_value = if any_driving_low {
// If any device pulls low, bus is low (wired-AND)
PinValue::Low
} else if any_driving_high && !any_driving_low {
// If devices are driving high but no one is pulling low
PinValue::High
} else if any_driving {
// Mixed driving states (shouldn't happen in proper design)
PinValue::Low // Safe fallback: low wins
} else {
// No one is driving - high impedance
PinValue::HighZ
};
// Update the bus pin to reflect the resolved state
// BUT: the bus itself doesn't drive the pin - it just reflects the resolved state
// In real hardware, the bus pin's state emerges from the connected devices
drop(connections);
// We need to update the pin's value without "driving" it
// This is tricky because our Pin struct couples value with output enable
// Let's create a helper method to update the resolved value
self.update_pin_resolved_value(bus_pin, resolved_value);
}
fn update_pin_resolved_value(&self, pin: &Arc<Pin>, value: PinValue) {
// This is a challenge: we want to set the pin's value without claiming to "drive" it
// We need to extend our Pin struct to support bus resolution
let mut current_value = pin.value.write().unwrap();
*current_value = value;
// Note: we don't touch output_enable - the bus isn't driving, just reflecting
}
}
impl Component for GenericBus {
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> {
// Bus update cycle:
// 1. First, let all devices update based on current pin states
for device in &self.connected_devices {
if let Ok(mut device) = device.lock() {
device.update();
}
}
// 2. Then resolve bus conflicts and propagate pin states
self.resolve_bus_conflicts();
Ok(())
}
fn run(&mut self) {
self.base.run();
}
fn stop(&mut self) {
self.base.stop();
}
}

View file

@ -1,5 +1,5 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::{Arc, Mutex}; use std::sync::Arc;
use std::thread; use std::thread;
use std::time::Duration; use std::time::Duration;
use crate::component::{BaseComponent, Component}; use crate::component::{BaseComponent, Component};
@ -8,7 +8,6 @@ use crate::pin::{Pin, PinValue};
#[derive(Clone)] #[derive(Clone)]
pub struct GenericClock { pub struct GenericClock {
base: BaseComponent, base: BaseComponent,
frequency: u64,
half_period: Duration, half_period: Duration,
last_state: bool, last_state: bool,
} }
@ -21,7 +20,6 @@ impl GenericClock {
Self { Self {
base, base,
frequency: frequency_hz,
half_period: Duration::from_millis(half_period_ms), half_period: Duration::from_millis(half_period_ms),
last_state: false, last_state: false,
} }

View file

@ -1,57 +1,231 @@
use std::collections::HashMap; use std::collections::HashMap;
use std::sync::Arc; use std::sync::{Arc, Mutex};
use crate::component::{BaseComponent, Component}; use crate::bus::GenericBus;
use crate::component::Component;
use crate::pin::{Pin, PinValue}; use crate::pin::{Pin, PinValue};
// Simple 4-bit and 12-bit types for the 4004 // Simple 4-bit and 12-bit types for the 4004
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct u4(u8); struct U4(u8);
#[derive(Debug, Clone, Copy)] #[derive(Debug, Clone, Copy)]
struct u12(u16); struct U12(u16);
impl u4 { impl U4 {
fn wrapping_add(self, other: u4) -> u4 { fn wrapping_add(self, other: U4) -> U4 {
u4((self.0 + other.0) & 0xF) U4((self.0 + other.0) & 0xF)
} }
} }
impl u12 { impl U12 {
fn wrapping_add(self, other: u12) -> u12 { fn wrapping_add(self, other: U12) -> U12 {
u12((self.0 + other.0) & 0xFFF) U12((self.0 + other.0) & 0xFFF)
} }
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Intel4004 { pub struct Intel4004 {
base: BaseComponent, base: crate::component::BaseComponent,
program_counter: u12, program_counter: U12,
accumulator: u4, accumulator: U4,
carry: bool,
last_clock_state: PinValue, last_clock_state: PinValue,
registers: [U4; 16], // 4004 has 16 4-bit registers
stack: [U12; 3], // 3-level subroutine stack
stack_pointer: u8,
bus: Option<Arc<Mutex<GenericBus>>>, // Connected bus
} }
impl Intel4004 { impl Intel4004 {
pub fn new(name: String) -> Self { pub fn new(name: String) -> Self {
let mut base = BaseComponent::new(name); let mut base = crate::component::BaseComponent::new(name);
// Add essential pins base.add_pin("clk".to_string(), PinValue::Low, false);
base.add_pin("clk".to_string(), PinValue::Low, false); // Input pin base.add_pin("reset".to_string(), PinValue::Low, false);
base.add_pin("reset".to_string(), PinValue::Low, false); // Input pin base.add_pin("sync".to_string(), PinValue::Low, true);
base.add_pin("data".to_string(), PinValue::HighZ, false); // Bidirectional base.add_pin("data".to_string(), PinValue::HighZ, false);
Self { Self {
base, base,
program_counter: u12(0), program_counter: U12(0),
accumulator: u4(0), accumulator: U4(0),
carry: false,
last_clock_state: PinValue::Low, 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) {
let register = (self.fetch_instruction() & 0x0F) as usize;
let new_value = self.registers[register].0.wrapping_add(1) & 0x0F;
self.registers[register] = U4(new_value);
}
fn add(&mut self) {
let register = (self.fetch_instruction() & 0x0F) as usize;
let result = self.accumulator.0 + self.registers[register].0;
self.accumulator = U4(result & 0x0F);
self.carry = result > 0x0F;
}
fn wrm(&mut self) {
// Write accumulator to main memory
let address = self.get_memory_address();
self.write_memory(address, self.accumulator.0);
}
fn rdm(&mut self) {
// Read from main memory to accumulator
let address = self.get_memory_address();
let data = self.read_memory(address);
self.accumulator = U4(data & 0x0F);
}
fn get_memory_address(&self) -> u16 {
// Simplified address calculation
// In real 4004, this uses register pairs
0x200 + self.registers[0].0 as u16 // Example: use register 0 as offset
}
fn test_pin(&self) -> bool {
// Check test pin (simplified)
false
}
pub fn get_state(&self) -> String {
format!(
"PC: {:03X} ACC: {:X} C: {} R0: {:X} R1: {:X}",
self.program_counter.0,
self.accumulator.0,
self.carry as u8,
self.registers[0].0,
self.registers[1].0
)
}
pub fn clk(&self) -> Option<Arc<Pin>> { pub fn clk(&self) -> Option<Arc<Pin>> {
self.base.get_pin("clk") self.base.get_pin("clk")
} }
pub fn reset(&self) -> Option<Arc<Pin>> { pub fn reset(&self) -> Option<Arc<Pin>> {
self.base.get_pin("reset") self.base.get_pin("reset")
} }
pub fn data(&self) -> Option<Arc<Pin>> { pub fn data(&self) -> Option<Arc<Pin>> {
self.base.get_pin("data") self.base.get_pin("data")
} }
} }
impl Component for Intel4004 { impl Component for Intel4004 {
fn name(&self) -> &str { fn name(&self) -> &str {
self.base.name() self.base.name()
@ -63,28 +237,23 @@ impl Component for Intel4004 {
self.base.get_pin(name) self.base.get_pin(name)
} }
fn update(&mut self) -> Result<(), String> { fn update(&mut self) -> Result<(), String> {
// Read clock pin (check if any connected component is driving it low)
let clk_pin = self.base.pins.get("clk").unwrap(); let clk_pin = self.base.pins.get("clk").unwrap();
let clk_value = clk_pin.read(); let clk_value = clk_pin.read();
// Detect rising edge // Detect rising edge
if clk_value == PinValue::High && self.last_clock_state == PinValue::Low { if clk_value == PinValue::High && self.last_clock_state == PinValue::Low {
// Simple fetch-execute cycle // Fetch and execute instruction
self.program_counter = self.program_counter.wrapping_add(u12(1)); let instruction = self.fetch_instruction();
self.execute(instruction);
// Read reset pin // Update sync pin
let reset_pin = self.base.pins.get("reset").unwrap(); let sync_pin = self.base.pins.get("sync").unwrap();
let reset_value = reset_pin.read(); sync_pin.write(PinValue::High, true);
if reset_value == PinValue::High { println!("{}", self.get_state());
self.program_counter = u12(0); } else if clk_value == PinValue::Low {
self.accumulator = u4(0); let sync_pin = self.base.pins.get("sync").unwrap();
println!("CPU {}: RESET", self.name()); sync_pin.write(PinValue::Low, true);
} else {
// For now, just print state
println!("CPU {}: PC={:03X}, ACC={:X}",
self.name(), self.program_counter.0, self.accumulator.0);
}
} }
self.last_clock_state = clk_value; self.last_clock_state = clk_value;

View file

@ -115,7 +115,7 @@ impl Component for MOS6502 {
fn pins(&self) -> &HashMap<String, Arc<Pin>> { fn pins(&self) -> &HashMap<String, Arc<Pin>> {
todo!() todo!()
} }
fn get_pin(&self, name: &str) -> Option<Arc<Pin>> { fn get_pin(&self, _name: &str) -> Option<Arc<Pin>> {
todo!() todo!()
} }
// Implement Component trait methods... // Implement Component trait methods...

View file

@ -0,0 +1,303 @@
use std::ops::Range;
use std::sync::Arc;
use crate::{Component, Pin, PinValue};
#[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>,
}
#[derive(Clone, Copy, PartialEq)]
enum RamOperation {
Read(u64), // address
Write(u64, u64), // address, data
}
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);
// Add address pins
for i in 0..ADDR_WIDTH {
base.add_pin(format!("addr_{}", i), PinValue::Low, false);
}
// Add data pins
for i in 0..DATA_WIDTH {
base.add_pin(format!("data_{}", i), PinValue::HighZ, false);
}
// Add control pins
base.add_pin("cs".to_string(), PinValue::High, false); // Chip Select (active low)
base.add_pin("we".to_string(), PinValue::High, false); // Write Enable (active low)
base.add_pin("oe".to_string(), PinValue::High, false); // Output Enable (active low)
base.add_pin("clk".to_string(), PinValue::Low, false); // Clock for synchronization
// Calculate address mask based on size
let address_mask = ((1 << ADDR_WIDTH) - 1) as u64;
Self {
base,
memory: vec![0; SIZE],
base_address,
address_mask,
read_delay,
write_delay,
current_operation: None,
operation_cycles: 0,
output_buffer: None,
}
}
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;
}
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);
Ok(())
}
fn run(&mut self) {
self.base.run();
}
fn stop(&mut self) {
self.base.stop();
}
}
// BusDevice implementation
impl<const SIZE: usize, const DATA_WIDTH: usize, const ADDR_WIDTH: usize>
crate::bus::BusDevice for GenericRam<SIZE, DATA_WIDTH, ADDR_WIDTH>
{
fn update(&mut self) {
// Delegate to component update
let _ = Component::update(self);
}
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 get_address_range(&self) -> Option<std::ops::Range<u64>> {
Some(self.base_address..self.base_address + SIZE as u64)
}
fn get_name(&self) -> &str {
todo!()
}
}

View file

@ -0,0 +1,118 @@
use std::collections::HashMap;
use std::ops::Range;
use std::sync::Arc;
use crate::{Component, Pin, PinValue};
use crate::bus::BusDevice;
pub struct GenericRom<const SIZE: usize, const DATA_WIDTH: usize> {
pub(crate) base: crate::component::BaseComponent,
memory: HashMap<u64, u64>, // Generic memory storage
data_width: usize,
address_range: std::ops::Range<u64>,
pub latched_address: ()
}
impl<const SIZE: usize, const DATA_WIDTH: usize> GenericRom<SIZE, DATA_WIDTH> {
pub fn new(name: String, base_address: u64) -> Self {
let mut base = crate::component::BaseComponent::new(name);
// Add data pins based on width
for i in 0..DATA_WIDTH {
base.add_pin(format!("data_{}", i), PinValue::HighZ, false);
}
// Add control pins
base.add_pin("cs".to_string(), PinValue::Low, false);
base.add_pin("oe".to_string(), PinValue::Low, false);
let address_range = base_address..base_address + SIZE as u64;
Self {
base,
memory: HashMap::new(),
data_width: DATA_WIDTH,
address_range,
latched_address: (),
}
}
pub fn load_data(&mut self, data: &[u64]) {
for (i, &word) in data.iter().enumerate() {
if i < SIZE {
self.memory.insert(self.address_range.start + i as u64, word);
}
}
}
pub fn is_selected(&self, address: u64) -> bool {
if let Some(cs_pin) = self.base.get_pin("cs") {
cs_pin.read() == PinValue::High && self.address_range.contains(&address)
} else {
false
}
}
fn get_current_address(&self) -> u64 {
// In a real system, this would read address from connected address pins
// For now, return a default address
0
}
fn output_data(&self, data: u64) {
for i in 0..DATA_WIDTH {
if let Some(pin) = self.base.get_pin(&format!("data_{}", i)) {
let bit_value = (data >> i) & 1;
let pin_value = if bit_value == 1 { PinValue::High } else { PinValue::Low };
pin.write(pin_value, true); // Drive the pin
}
}
}
fn set_data_pins_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); // High impedance, not driving
}
}
}
}
impl<const SIZE: usize, const DATA_WIDTH: usize> BusDevice for GenericRom<SIZE, DATA_WIDTH> {
fn update(&mut self) {
// Check if we're selected and output is enabled
if let (Some(cs_pin), Some(oe_pin)) = (self.base.get_pin("cs"), self.base.get_pin("oe")) {
let is_selected = cs_pin.read() == PinValue::High;
let output_enabled = oe_pin.read() == PinValue::Low; // Active low
if is_selected && output_enabled {
// Read address from address pins (simplified - in real system, address would come from bus)
// For now, we'll use a dummy address - this would be connected to address pins in a real system
let address = self.get_current_address();
// Read data from ROM
let data = self.read(address);
// Output data to data pins
self.output_data(data);
} else {
// Put data pins in high impedance when not selected
self.set_data_pins_high_z();
}
}
}
fn connect_to_bus(&mut self, bus_pins: Vec<Arc<Pin>>) {
// Connect data pins
for (i, bus_pin) in bus_pins.iter().enumerate() {
if let Some(rom_pin) = self.base.get_pin(&format!("data_{}", i)) {
crate::connection::connect_pins(&rom_pin, bus_pin).unwrap();
}
}
}
fn get_address_range(&self) -> Option<std::ops::Range<u64>> {
Some(self.address_range.clone())
}
fn get_name(&self) -> &str {
todo!()
}
}

View file

@ -0,0 +1,56 @@
use crate::components::memory::generic_rom::GenericRom;
use crate::{Component, PinValue};
use crate::systems::intel_mcs_4::MCS4Phase;
// 4001 ROM: 256 bytes, 4-bit data width
pub type Intel4001 = GenericRom<256, 4>;
impl Intel4001 {
fn update(&mut self) -> Result<(), String> {
// Read the current bus phase from the pins
let phase = self.detect_bus_phase();
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();
}
}
_ => {
// Put pins in high impedance
self.set_pins_high_z();
}
}
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
}
}
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 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);
}
}

View file

@ -0,0 +1,51 @@
use crate::components::memory::generic_ram::GenericRam;
// 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>;
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
);
ram
}
// 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());
}
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;
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());
}
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)
}
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)
}
}

View file

@ -0,0 +1,4 @@
pub mod intel_4002;
pub mod intel_4001;
pub mod generic_rom;
mod generic_ram;

View file

@ -1,2 +1,3 @@
pub mod cpu; pub mod cpu;
pub mod clock; pub mod clock;
pub mod memory;

View file

@ -3,6 +3,7 @@ pub mod pin;
pub mod connection; pub mod connection;
pub mod components; pub mod components;
pub mod systems; pub mod systems;
mod bus;
// Re-export commonly used items for easier importing // Re-export commonly used items for easier importing
pub use component::{Component, BaseComponent}; pub use component::{Component, BaseComponent};

View file

@ -1,4 +1,4 @@
use rusty_emu::systems; use rusty_emu::systems;
fn main() { fn main() {
systems::intel_mcs_4::emulation(); systems::intel_mcs_4::run_fibonacci_calculator();
} }

View file

@ -7,45 +7,42 @@ pub enum PinValue {
Low, Low,
HighZ, // High impedance (disconnected) HighZ, // High impedance (disconnected)
} }
// Pin structure with output enable
#[derive(Clone)] #[derive(Clone)]
pub struct Pin { pub struct Pin {
pub name: String, pub name: String,
pub value: Arc<RwLock<PinValue>>, // Use RwLock for multiple readers pub value: Arc<RwLock<PinValue>>,
pub output_enable: Arc<Mutex<bool>>, // Whether this pin is driving the line pub output_enable: Arc<Mutex<bool>>,
pub connections: Arc<Mutex<Vec<Arc<Pin>>>>, pub connections: Arc<Mutex<Vec<Arc<Pin>>>>,
} }
// Component trait
impl Pin { impl Pin {
// Get the effective value considering all connected pins // Get the effective value considering all connected pins
pub fn read(&self) -> PinValue { pub fn read(&self) -> PinValue {
let connections = self.connections.lock().unwrap(); let connections = self.connections.lock().unwrap();
let mut found_high_pin = false;
// Check if any connected pin is actively driving low // Check if any connected pin is actively driving low
for connected_pin in connections.iter() { for connected_pin in connections.iter() {
let output_enable = connected_pin.output_enable.lock().unwrap(); let output_enable = connected_pin.output_enable.lock().unwrap();
let value = connected_pin.value.read().unwrap(); let value = connected_pin.value.read().unwrap();
if *output_enable && *value == PinValue::Low { if *output_enable && *value == PinValue::Low {
return PinValue::Low; return PinValue::Low;
} } else if *output_enable && *value == PinValue::High {
} found_high_pin = true;
// If no one is driving low, check if anyone is driving high
for connected_pin in connections.iter() {
let output_enable = connected_pin.output_enable.lock().unwrap();
let value = connected_pin.value.read().unwrap();
if *output_enable && *value == PinValue::High {
return PinValue::High;
} }
} }
// If no one is driving, return HighZ // If no one is driving, return HighZ
if found_high_pin {
PinValue::Low
} else {
PinValue::HighZ PinValue::HighZ
} }
}
// Set this pin's value and output enable
pub fn write(&self, value: PinValue, output_enable: bool) { pub fn write(&self, value: PinValue, output_enable: bool) {
*self.value.write().unwrap() = value; *self.value.write().unwrap() = value;
*self.output_enable.lock().unwrap() = output_enable; *self.output_enable.lock().unwrap() = output_enable;

View file

@ -1,20 +1,46 @@
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::connection::connect_pins; use crate::components::memory::intel_4001::Intel4001;
use crate::components::memory::intel_4002::Intel4002;
pub fn emulation() { use crate::{connect_pins, Component, PinValue};
println!("Starting MCS-4 emulation with pin state resolution..."); use crate::bus::GenericBus;
#[derive(Clone, Copy, PartialEq)]
pub enum MCS4Phase {
AddressLow, // Send address low nibble
AddressHigh, // Send address high nibble + middle
Instruction, // Fetch instruction
DataRead, // Read data
DataWrite, // Write data
}
pub fn run_fibonacci_calculator() {
println!("Starting MCS-4 Fibonacci Calculator...");
// Create components // Create components
let mut clock = GenericClock::new("sys_clock".to_string(), 2); // 2Hz clock let mut clock = GenericClock::new("CLK".parse().unwrap(), 1000);
let mut cpu = Intel4004::new("cpu4004".to_string()); let mut bus = GenericBus::new("BUS_MCS-4".parse().unwrap(), &[("B0", PinValue::HighZ),("B1", PinValue::HighZ),("B2", PinValue::HighZ),("B3", PinValue::HighZ)]);
let cpu = Arc::new(Mutex::new(Intel4004::new("cpu4004".to_string())));
// Connect clock to CPU using the connection macro // ROM 0: responds to addresses with high nibble 0x0
let rom0 = Arc::new(Mutex::new(Intel4001::new("rom0".to_string(), 0x0)));
// 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()) { if let (Some(clock_out), Some(cpu_clk)) = (clock.out(), cpu.clk()) {
connect_pins(&clock_out, &cpu_clk).expect("Connection could not be made!"); connect_pins(&clock_out, &cpu_clk).expect("Clock connection failed");
println!("Connected clock to CPU"); println!("Connected clock to CPU");
} }
@ -22,12 +48,58 @@ pub fn emulation() {
clock.run(); clock.run();
cpu.run(); cpu.run();
load_fibonacci_program(rom0.clone());
// Let it run for a while // Let it run for a while
thread::sleep(Duration::from_secs(5)); thread::sleep(Duration::from_secs(10));
// Read results from RAM
let bus_lock = bus.lock().unwrap();
let result1 = bus_lock.read(0x0200); // Fibonacci numbers stored here
let result2 = bus_lock.read(0x0201);
println!("Fibonacci results: {}, {}", result1, result2);
// Stop components // Stop components
clock.stop(); clock.stop();
cpu.stop(); cpu.stop();
println!("Emulation completed"); println!("Fibonacci calculation completed");
}
fn load_fibonacci_program(rom: Arc<Mutex<Intel4001>>) {
let mut rom_lock = rom.lock().unwrap();
// 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");
} }