use crate::memory::bank::bank::{Bank, Location};
use crate::memory::bank::bank_index::{BankIndex, BankRegisters, BankRegisterId, RamStatus};
use crate::memory::cpu::cpu_address::CpuAddress;
use crate::memory::read_result::ReadResult;
const PRG_MEMORY_START: CpuAddress = CpuAddress::new(0x6000);
pub struct PrgMemory {
layout: PrgLayout,
max_bank_count: u16,
bank_size: usize,
bank_count: u16,
raw_memory: Vec<u8>,
work_ram_sections: Vec<WorkRam>,
}
impl PrgMemory {
pub fn new(
layout: PrgLayout,
max_bank_count: u16,
bank_size: usize,
raw_memory: Vec<u8>,
) -> PrgMemory {
layout.validate_bank_size_multiples(bank_size);
let bank_count;
if raw_memory.len() % bank_size == 0 {
bank_count = (raw_memory.len() / bank_size)
.try_into()
.expect("Way too many banks.");
} else if !raw_memory.is_empty() && bank_size % raw_memory.len() == 0 {
bank_count = 1;
} else {
panic!("Bad PRG length: {} . Bank size: {} .", raw_memory.len(), bank_size);
}
let mut prg_memory = PrgMemory {
layout,
max_bank_count,
bank_size,
bank_count,
raw_memory,
work_ram_sections: Vec::new(),
};
for window in prg_memory.layout.0 {
if window.prg_bank.is_work_ram() {
prg_memory.work_ram_sections.push(WorkRam::new(window.size()));
}
}
let bank_count = prg_memory.bank_count();
assert!(bank_count <= prg_memory.max_bank_count,
"Bank count: {bank_count}, max: {max_bank_count}");
if prg_memory.raw_memory.len() >= usize::from(bank_count) * bank_size {
assert_eq!(
prg_memory.raw_memory.len(),
usize::from(bank_count) * bank_size,
"Bad PRG data size.",
);
}
prg_memory
}
pub fn bank_count(&self) -> u16 {
self.bank_count
}
pub fn last_bank_index(&self) -> u16 {
self.bank_count() - 1
}
pub fn peek(&self, registers: &BankRegisters, address: CpuAddress) -> ReadResult {
match self.address_to_prg_index(registers, address) {
PrgMemoryIndex::None => ReadResult::OPEN_BUS,
PrgMemoryIndex::MappedMemory {index, ram_status } => {
use RamStatus::*;
match ram_status {
Disabled =>
ReadResult::OPEN_BUS,
ReadOnlyZeros =>
ReadResult::full(0),
ReadOnly | ReadWrite =>
ReadResult::full(self.raw_memory[index % self.raw_memory.len()]),
}
}
PrgMemoryIndex::WorkRam { section_id, index, ram_status} => {
let work_ram = &self.work_ram_sections[section_id];
use RamStatus::*;
match ram_status {
Disabled =>
ReadResult::OPEN_BUS,
ReadOnlyZeros =>
ReadResult::full(0),
ReadOnly | ReadWrite =>
ReadResult::full(work_ram.data[index]),
}
}
}
}
pub fn write(&mut self, registers: &BankRegisters, address: CpuAddress, value: u8) {
match self.address_to_prg_index(registers, address) {
PrgMemoryIndex::None => {}
PrgMemoryIndex::MappedMemory { index, ram_status } => {
if ram_status == RamStatus::ReadWrite {
self.raw_memory[index] = value;
}
}
PrgMemoryIndex::WorkRam { section_id, index, ram_status} => {
let work_ram = &mut self.work_ram_sections[section_id];
if ram_status == RamStatus::ReadWrite {
work_ram.data[index] = value;
}
}
}
}
pub fn resolve_selected_bank_indexes(&self, registers: &BankRegisters) -> Vec<u16> {
let mut indexes = Vec::new();
for window in self.layout.0 {
if let Some(bank_index) = window.bank_index(registers) {
let raw_index = bank_index.to_u16(self.bank_count());
indexes.push(raw_index);
}
}
indexes
}
pub fn window_at(&self, start: u16) -> &PrgWindow {
self.window_with_index_at(start).0
}
pub fn set_layout(&mut self, windows: PrgLayout) {
windows.validate_bank_size_multiples(self.bank_size);
self.layout = windows;
}
fn address_to_prg_index(&self, registers: &BankRegisters, address: CpuAddress) -> PrgMemoryIndex {
assert!(address >= PRG_MEMORY_START);
let windows = &self.layout.0;
assert!(!windows.is_empty());
for i in 0..windows.len() {
if i == windows.len() - 1 || address < windows[i + 1].start {
let bank_offset = address.to_raw() - windows[i].start.to_raw();
let window;
if let Bank::MirrorOf(mirrored_window_start) = windows[i].prg_bank {
window = self.window_at(mirrored_window_start);
} else {
window = &windows[i];
}
let prg_memory_index = match window.prg_bank {
Bank::Empty => PrgMemoryIndex::None,
Bank::MirrorOf(_) => panic!("A mirrored bank must mirror a non-mirrored bank."),
Bank::Rom(Location::Fixed(bank_index)) => {
let mut raw_bank_index = bank_index.to_usize(self.bank_count());
let window_multiple = window.size() / self.bank_size;
raw_bank_index &= !(window_multiple >> 1);
let index = raw_bank_index * self.bank_size + bank_offset as usize;
PrgMemoryIndex::MappedMemory { index, ram_status: RamStatus::ReadOnly }
}
Bank::Ram(Location::Fixed(bank_index), status_register_id) => {
let ram_status: RamStatus = status_register_id
.map_or(RamStatus::ReadWrite, |id| registers.ram_status(id));
let mut raw_bank_index = bank_index.to_usize(self.bank_count());
let window_multiple = window.size() / self.bank_size;
raw_bank_index &= !(window_multiple >> 1);
let index = raw_bank_index * self.bank_size + bank_offset as usize;
PrgMemoryIndex::MappedMemory { index, ram_status }
}
Bank::Rom(Location::Switchable(register_id)) => {
let mut raw_bank_index = registers.get(register_id)
.to_usize(self.bank_count());
let window_multiple = window.size() / self.bank_size;
raw_bank_index &= !(window_multiple >> 1);
let index = raw_bank_index * self.bank_size + bank_offset as usize;
PrgMemoryIndex::MappedMemory { index, ram_status: RamStatus::ReadOnly }
}
Bank::Ram(Location::Switchable(register_id), status_register_id) => {
let ram_status: RamStatus = status_register_id
.map_or(RamStatus::ReadWrite, |id| registers.ram_status(id));
let mut raw_bank_index = registers.get(register_id)
.to_usize(self.bank_count());
let window_multiple = window.size() / self.bank_size;
raw_bank_index &= !(window_multiple >> 1);
let index = raw_bank_index * self.bank_size + bank_offset as usize;
PrgMemoryIndex::MappedMemory { index, ram_status }
}
Bank::Rom(_) => todo!("Meta registers"),
Bank::Ram(_, _) => todo!("Meta registers"),
Bank::WorkRam(status_register_id) => {
let ram_status: RamStatus = status_register_id
.map_or(RamStatus::ReadWrite, |id| registers.ram_status(id));
let mut index = usize::from(bank_offset);
let mut result = None;
for (section_id, work_ram_section) in self.work_ram_sections.iter().enumerate() {
if index < work_ram_section.data.len() {
result = Some(PrgMemoryIndex::WorkRam { section_id, index, ram_status });
break;
}
index -= work_ram_section.data.len();
}
result.unwrap()
}
};
return prg_memory_index;
}
}
unreachable!();
}
fn window_with_index_at(&self, start: u16) -> (&PrgWindow, usize) {
for (index, window) in self.layout.0.iter().enumerate() {
if window.start.to_raw() == start {
return (window, index);
}
}
panic!("No window exists at {start:?}");
}
}
#[derive(Clone, Copy)]
pub struct PrgLayout(&'static [PrgWindow]);
impl PrgLayout {
pub const fn new(windows: &'static [PrgWindow]) -> PrgLayout {
assert!(!windows.is_empty(), "No PRG windows specified.");
assert!(windows[0].start.to_raw() == 0x6000,
"The first PRG window must start at 0x6000.");
assert!(windows[windows.len() - 1].end.to_raw() == 0xFFFF,
"The last PRG window must end at 0xFFFF.");
let mut i = 1;
while i < windows.len() {
assert!(windows[i].start.to_raw() == windows[i - 1].end.to_raw() + 1,
"There must be no gaps nor overlap between PRG windows.");
i += 1;
}
PrgLayout(windows)
}
pub fn windows(&self) -> &[PrgWindow] {
self.0
}
const fn validate_bank_size_multiples(&self, bank_size: usize) {
let mut i = 0;
while i < self.0.len() {
let window = self.0[i];
if !matches!(window.prg_bank, Bank::WorkRam(_) | Bank::Empty | Bank::MirrorOf(_))
&& window.size() % bank_size != 0 {
panic!("Window size must be a multiple of bank size.");
}
i += 1;
}
}
pub fn active_register_ids(&self) -> Vec<BankRegisterId> {
self.0.iter()
.filter_map(|window| window.register_id())
.collect()
}
}
enum PrgMemoryIndex {
None,
WorkRam { section_id: usize, index: usize, ram_status: RamStatus },
MappedMemory { index: usize, ram_status: RamStatus },
}
#[derive(Clone, Copy)]
pub struct PrgWindow {
start: CpuAddress,
end: CpuAddress,
prg_bank: Bank,
}
impl PrgWindow {
fn bank_index(self, registers: &BankRegisters) -> Option<BankIndex> {
self.prg_bank.bank_index(registers)
}
const fn size(self) -> usize {
(self.end.to_raw() - self.start.to_raw() + 1) as usize
}
fn register_id(self) -> Option<BankRegisterId> {
if let Bank::Rom(Location::Switchable(id)) | Bank::Ram(Location::Switchable(id), _) = self.prg_bank {
Some(id)
} else {
None
}
}
pub const fn new(start: u16, end: u16, size: usize, prg_bank: Bank) -> PrgWindow {
assert!(end > start);
assert!(end as usize - start as usize + 1 == size);
PrgWindow {
start: CpuAddress::new(start),
end: CpuAddress::new(end),
prg_bank,
}
}
}
#[derive(Clone)]
struct WorkRam {
data: Vec<u8>,
}
impl WorkRam {
fn new(size: usize) -> WorkRam {
WorkRam {
data: vec![0; size],
}
}
}