use super::Rom;
use super::flash::{Flash, ReadMode, sr};
#[derive(Clone, Debug, PartialEq, Eq)]
pub enum Nce1 {
Rom(Rom),
Flash(Box<Flash>),
BankedRom(Rom),
}
const FLASH_BLOB_HEADER: usize = 7;
impl Nce1 {
pub fn read(&self, addr: u32, latch: u8) -> u8 {
let _ = latch;
match self {
Nce1::Rom(rom) => rom.read(addr),
Nce1::Flash(f) => f.read_banked(addr, latch),
Nce1::BankedRom(rom) => {
rom.read(Flash::banked_address(addr, latch) as u32)
}
}
}
pub fn nibbles(&self) -> &[u8] {
match self {
Nce1::Rom(rom) | Nce1::BankedRom(rom) => rom.as_slice(),
Nce1::Flash(f) => f.nibbles(),
}
}
pub fn reset(&mut self) {
if let Nce1::Flash(f) = self {
f.reset();
f.set_write_enabled(false);
}
}
#[cfg(any(test, feature = "internals"))]
pub fn flash(&self) -> Option<&Flash> {
match self {
Nce1::Flash(f) => Some(f),
Nce1::Rom(_) | Nce1::BankedRom(_) => None,
}
}
pub fn set_write_enabled(&mut self, enabled: bool) {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) => {
let _ = enabled;
}
Nce1::Flash(f) => f.set_write_enabled(enabled),
}
}
pub fn nce3_read(&self, addr: u32, latch: u8) -> Option<u8> {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) => {
let _ = (addr, latch);
None
}
Nce1::Flash(f) => Some(f.read(Flash::write_address(addr, latch))),
}
}
pub fn nce3_write(&mut self, addr: u32, latch: u8, nibble: u8) -> bool {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) => {
let _ = (addr, latch, nibble);
false
}
Nce1::Flash(f) => {
f.write_banked(addr, latch, nibble);
true
}
}
}
pub fn generation(&self) -> u64 {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) => 0,
Nce1::Flash(f) => f.generation(),
}
}
pub fn state_blob(&self) -> Vec<u8> {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) => Vec::new(),
Nce1::Flash(f) => {
let mut b = Vec::with_capacity(FLASH_BLOB_HEADER + super::flash::FLASH_BYTES);
b.extend_from_slice(&f.lock_bits().to_le_bytes());
b.push(f.status());
b.push(mode_code(f.read_mode()));
b.push(u8::from(f.wp_low()));
b.extend(f.to_packed());
b
}
}
}
pub fn load_state_blob(&mut self, blob: &[u8]) -> Result<(), &'static str> {
match self {
Nce1::Rom(_) | Nce1::BankedRom(_) if blob.is_empty() => Ok(()),
Nce1::Rom(_) | Nce1::BankedRom(_) => Err("ROM takes no NCE1 state"),
Nce1::Flash(f) => {
if blob.len() != FLASH_BLOB_HEADER + super::flash::FLASH_BYTES {
return Err("flash state has the wrong size");
}
let (head, image) = blob.split_at(FLASH_BLOB_HEADER);
let mode = mode_from_code(head[5]).ok_or("unknown flash read mode")?;
let wp_low = match head[6] {
0 => false,
1 => true,
_ => return Err("flash WP# not 0 or 1"),
};
let status = head[4];
if status & sr::READY == 0
|| status & (sr::ERASE_SUSPENDED | sr::PROGRAM_SUSPENDED) != 0
{
return Err("flash status not ready");
}
let mut chip = Flash::from_packed(image).map_err(|_| "flash image size")?;
chip.set_lock_bits(u32::from_le_bytes([head[0], head[1], head[2], head[3]]));
chip.restore_cui(head[4], mode);
chip.set_wp_low(wp_low);
chip.set_write_enabled(f.write_enabled());
chip.follow(f);
**f = chip;
Ok(())
}
}
}
}
fn mode_code(m: ReadMode) -> u8 {
match m {
ReadMode::Array => 0,
ReadMode::Identifier => 1,
ReadMode::Query => 2,
ReadMode::Status => 3,
ReadMode::ExtendedStatus => 4,
}
}
fn mode_from_code(c: u8) -> Option<ReadMode> {
Some(match c {
0 => ReadMode::Array,
1 => ReadMode::Identifier,
2 => ReadMode::Query,
3 => ReadMode::Status,
4 => ReadMode::ExtendedStatus,
_ => return None,
})
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn rom_reads_through() {
let n = Nce1::Rom(Rom::from_packed(&[0x21, 0x43]));
assert_eq!(n.read(2, 0x3F), 3);
assert_eq!(n.read(5, 0), 2);
assert_eq!(n.nibbles(), &[1, 2, 3, 4]);
let mut n = n;
n.set_write_enabled(true);
assert_eq!(n.nce3_read(0, 0), None);
assert!(!n.nce3_write(0, 0, 5));
assert!(n.state_blob().is_empty());
assert!(n.load_state_blob(&[]).is_ok());
assert!(n.load_state_blob(&[1]).is_err());
}
#[test]
fn banked_rom_mirrors_eight_banks() {
let mut nibbles = vec![0u8; 8 * 0x4_0000];
for b in 0..8 {
nibbles[b * 0x4_0000] = b as u8;
}
let n = Nce1::BankedRom(Rom::from_nibbles(nibbles));
assert_eq!(n.read(0x4_0000, 0x05), 5);
assert_eq!(n.read(0x4_0000, 0x0D), 5);
assert_eq!(n.read(0, 0x30), 3);
assert_eq!(n.read(0x8_0000, 0x30), 3);
assert_eq!(n.nce3_read(0, 0), None);
assert!(n.state_blob().is_empty());
}
#[test]
fn flash_reads_banked_and_round_trips_state() {
let mut img = vec![0u8; super::super::flash::FLASH_BYTES];
img[3 * 0x2_0000] = 0x0C; let mut n = Nce1::Flash(Box::new(Flash::from_packed(&img).unwrap()));
assert_eq!(n.read(0, 0x30), 0xC);
assert_eq!(n.read(0x4_0000, 0x30), 0);
assert_eq!(n.read(0x4_0000, 0x03), 0xC);
assert!(n.nce3_write(0, 0, 0x0));
n.set_write_enabled(true);
for (a, v) in [(0, 0x0), (1, 0x4), (0x10, 0x0), (0x11, 0x0)] {
assert!(n.nce3_write(a, 0, v)); }
assert_eq!(
n.nce3_read(0x10, 0),
Some(0x0),
"status mode reads #80 low nibble"
);
assert_eq!(n.nce3_read(0x11, 0), Some(0x8));
let blob = n.state_blob();
let mut m = Nce1::Flash(Box::new(Flash::erased()));
m.load_state_blob(&blob).unwrap();
assert_eq!(m.flash().unwrap().nibbles()[0x10], 0);
assert_eq!(m.flash().unwrap().read_mode(), ReadMode::Status);
assert!(m.load_state_blob(&blob[1..]).is_err());
n.reset();
assert_eq!(n.flash().unwrap().read_mode(), ReadMode::Array);
assert!(!n.flash().unwrap().write_enabled());
}
}