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:
parent
ddac8865fa
commit
ba1b0ddc22
14 changed files with 1000 additions and 68 deletions
303
src/components/memory/generic_ram.rs
Normal file
303
src/components/memory/generic_ram.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
118
src/components/memory/generic_rom.rs
Normal file
118
src/components/memory/generic_rom.rs
Normal 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!()
|
||||
}
|
||||
}
|
||||
56
src/components/memory/intel_4001.rs
Normal file
56
src/components/memory/intel_4001.rs
Normal 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);
|
||||
}
|
||||
}
|
||||
51
src/components/memory/intel_4002.rs
Normal file
51
src/components/memory/intel_4002.rs
Normal 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)
|
||||
}
|
||||
}
|
||||
4
src/components/memory/mod.rs
Normal file
4
src/components/memory/mod.rs
Normal file
|
|
@ -0,0 +1,4 @@
|
|||
pub mod intel_4002;
|
||||
pub mod intel_4001;
|
||||
pub mod generic_rom;
|
||||
mod generic_ram;
|
||||
Loading…
Add table
Add a link
Reference in a new issue