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

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;