use core::ops::{Index, IndexMut};
use core::num::{NonZeroU8, NonZeroU16};
use core::fmt;
use core::iter::{Enumerate, FilterMap, Zip, IntoIterator};
use core::slice;
use std::vec;
#[cfg(feature = "snapshot")] mod serde;
#[cfg(feature = "snapshot")]
use ::serde::{Serialize, Deserialize};
use ::bitvec::prelude::*;
use spectrusty_core::clock::{FTs, TimestampOps};
pub const MAX_HALT_TS: FTs = 2 * BYTE_TS as FTs;
pub const HALT_FOREVER_TS: Option<NonZeroU16> = unsafe {
Some(NonZeroU16::new_unchecked(u16::max_value()))
};
pub type MicroCartridgeSecIter<'a> = FilterMap<
Zip<
slice::Iter<'a, Sector>,
bitvec::slice::Iter<'a, u32, LocalBits>
>,
&'a dyn Fn(
(&'a Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>)
) -> Option<&'a Sector>
>;
pub type MicroCartridgeSecIterMut<'a> = FilterMap<
Zip<
slice::IterMut<'a, Sector>,
bitvec::slice::Iter<'a, u32, LocalBits>
>,
&'a dyn Fn(
(&'a mut Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>)
) -> Option<&'a mut Sector>
>;
pub type MicroCartridgeIdSecIter<'a> = FilterMap<
Enumerate<
Zip<
slice::Iter<'a, Sector>,
bitvec::slice::Iter<'a, u32, LocalBits>
>>,
&'a dyn Fn(
(usize,
(&'a Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>))
) -> Option<(u8, &'a Sector)>
>;
pub const MAX_SECTORS: usize = 256;
pub const MAX_USABLE_SECTORS: usize = 254;
pub const MAX_DRIVES: usize = 8;
pub const HEAD_SIZE: usize = 15;
pub const DATA_SIZE: usize = 528;
const PREAMBLE_SIZE: u16 = 12;
const BYTE_TS: u32 = 162;
const HEAD_START_TS: u32 = 1945;
const HEAD_TS: u32 = 4375; const GAP1_TS: u32 = 13125; const DATA_START_TS: u32 = 1964;
const DATA_TS: u32 = 87500; const GAP2_TS: u32 = 24500; const SECTOR_TS: u32 = HEAD_TS + GAP1_TS + DATA_TS + GAP2_TS;
const SECTOR_MAP_SIZE: usize = (MAX_SECTORS + 31)/32;
#[derive(Clone, Copy)]
#[repr(C, packed)]
pub struct Sector {
pub head: [u8;HEAD_SIZE],
pub data: [u8;DATA_SIZE],
}
#[derive(Clone)]
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
pub struct MicroCartridge {
sectors: Box<[Sector]>,
sector_map: [u32;SECTOR_MAP_SIZE],
tape_cursor: TapeCursor,
protec: bool,
written: Option<NonZeroU16>
}
#[derive(Clone, Default, Debug)]
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
pub struct ZxMicrodrives<T> {
drives: [Option<MicroCartridge>;MAX_DRIVES],
write: bool,
erase: bool,
comms_clk: bool,
motor_on_drive: Option<NonZeroU8>,
last_ts: T
}
#[derive(Clone, Copy, Default, Debug, PartialEq)]
pub(crate) struct CartridgeState {
pub gap: bool,
pub syn: bool,
pub write_protect: bool
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
enum SecPosition {
Preamble1(u16),
Header(u16),
Gap1,
Preamble2(u16),
Data(u16),
Gap2,
}
#[derive(Clone, Copy, Debug, PartialEq)]
#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
struct TapeCursor {
cursor: u32,
sector: u8,
secpos: SecPosition,
}
impl Default for Sector {
fn default() -> Self {
Sector {
head: [!0;HEAD_SIZE],
data: [!0;DATA_SIZE],
}
}
}
impl Default for TapeCursor {
fn default() -> Self {
TapeCursor {
cursor: 0,
sector: 0,
secpos: SecPosition::Preamble1(0)
}
}
}
impl Default for MicroCartridge {
fn default() -> Self {
MicroCartridge {
sectors: vec![Sector::default();MAX_SECTORS].into_boxed_slice(),
sector_map: [0;SECTOR_MAP_SIZE],
tape_cursor: TapeCursor::default(),
protec: false,
written: None
}
}
}
impl fmt::Debug for MicroCartridge {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
write!(f, "MicroCartridge {{ sectors: {}, formatted: {}, protected: {:?}, writing: {:?}, cursor: {:?} }}",
self.sectors.len(),
self.sector_map.view_bits::<LocalBits>().count_ones(),
self.protec,
self.written,
self.tape_cursor
)
}
}
impl MicroCartridge {
pub fn head_at(&self) -> f32 {
let TapeCursor { sector, cursor, .. } = self.tape_cursor;
sector as f32 + cursor as f32 / SECTOR_TS as f32
}
#[inline]
pub fn max_sectors(&self) -> usize {
self.sectors.len()
}
pub fn count_formatted(&self) -> usize {
self.sector_map.view_bits::<LocalBits>().count_ones()
}
pub fn iter_with_indices(&self) -> MicroCartridgeIdSecIter<'_> {
let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
self.sectors.iter().zip(iter_map).enumerate().filter_map(&|(i, (s, valid))| {
if *valid { Some((i as u8, s)) } else { None }
})
}
#[inline]
pub fn is_write_protected(&self) -> bool {
self.protec
}
#[inline]
pub fn set_write_protected(&mut self, protect: bool) {
self.protec = protect;
}
#[inline]
pub fn is_sector_formatted(&self, sector: u8) -> bool {
self.sector_map.view_bits::<LocalBits>()[sector as usize]
}
pub fn new_with_sectors<S: Into<Vec<Sector>>>(
sectors: S,
write_protect: bool,
max_sectors: usize
) -> Self
{
let mut sectors = sectors.into();
assert!(max_sectors > 0 && max_sectors <= MAX_SECTORS &&
sectors.len() <= max_sectors && sectors.len() <= MAX_USABLE_SECTORS);
let mut sector_map = [0u32;SECTOR_MAP_SIZE];
sector_map.view_bits_mut::<LocalBits>()[0..sectors.len()].fill(true);
sectors.resize(max_sectors, Sector::default());
let sectors = sectors.into_boxed_slice();
MicroCartridge {
sectors,
sector_map,
tape_cursor: TapeCursor::default(),
protec: write_protect,
written: None
}
}
pub fn new(max_sectors: usize) -> Self {
assert!(max_sectors > 0 && max_sectors <= MAX_SECTORS);
MicroCartridge {
sectors: vec![Sector::default();max_sectors].into_boxed_slice(),
sector_map: [0;SECTOR_MAP_SIZE],
tape_cursor: TapeCursor::default(),
protec: false,
written: None
}
}
}
impl<'a> IntoIterator for &'a MicroCartridge {
type Item = &'a Sector;
type IntoIter = MicroCartridgeSecIter<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
self.sectors.iter().zip(iter_map).filter_map(&|(s, valid)| {
if *valid { Some(s) } else { None }
})
}
}
impl<'a> IntoIterator for &'a mut MicroCartridge {
type Item = &'a mut Sector;
type IntoIter = MicroCartridgeSecIterMut<'a>;
#[inline]
fn into_iter(self) -> Self::IntoIter {
let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
self.sectors.iter_mut().zip(iter_map).filter_map(&|(s, valid)| {
if *valid { Some(s) } else { None }
})
}
}
impl Index<u8> for MicroCartridge {
type Output = Sector;
#[inline]
fn index(&self, index: u8) -> &Self::Output {
&self.sectors[index as usize]
}
}
impl IndexMut<u8> for MicroCartridge {
#[inline]
fn index_mut(&mut self, index: u8) -> &mut Self::Output {
&mut self.sectors[index as usize]
}
}
const HEAD_PREAMBLE: u32 = 0;
const HEAD_SYN_END: u32 = HEAD_START_TS - 1;
const HEAD_START: u32 = HEAD_START_TS;
const HEAD_END: u32 = HEAD_TS - 1;
const GAP1_START: u32 = HEAD_TS;
const GAP1_END: u32 = HEAD_TS + GAP1_TS - 1;
const DATA_PREAMBLE: u32 = HEAD_TS + GAP1_TS;
const DATA_SYN_END: u32 = DATA_PREAMBLE + DATA_START_TS - 1;
const DATA_START: u32 = DATA_PREAMBLE + DATA_START_TS;
const DATA_END: u32 = HEAD_TS + GAP1_TS + DATA_TS - 1;
const GAP2_START: u32 = HEAD_TS + GAP1_TS + DATA_TS;
impl SecPosition {
#[inline]
fn from_sector_cursor(cursor: u32) -> Self {
assert_eq!((HEAD_END + 1 - HEAD_START) / HEAD_SIZE as u32, BYTE_TS);
assert_eq!((DATA_END + 1 - DATA_START) / DATA_SIZE as u32, BYTE_TS);
match cursor {
HEAD_PREAMBLE..=HEAD_SYN_END => SecPosition::Preamble1(((cursor - HEAD_PREAMBLE)/BYTE_TS) as u16),
HEAD_START..=HEAD_END => SecPosition::Header(((cursor - HEAD_START)/BYTE_TS) as u16),
GAP1_START..=GAP1_END => SecPosition::Gap1,
DATA_PREAMBLE..=DATA_SYN_END => SecPosition::Preamble2(((cursor - DATA_PREAMBLE)/BYTE_TS) as u16),
DATA_START..=DATA_END => SecPosition::Data(((cursor - DATA_START)/BYTE_TS) as u16),
GAP2_START..=core::u32::MAX => SecPosition::Gap2,
}
}
}
impl TapeCursor {
#[inline]
fn forward(&mut self, ts: u32, seclen: u32) { let mut cursor = self.cursor.saturating_add(ts);
if cursor >= SECTOR_TS {
let diffsec = cursor / SECTOR_TS;
self.sector = Self::add_sectors(self.sector, diffsec, seclen);
cursor %= SECTOR_TS;
}
self.cursor = cursor;
self.secpos = SecPosition::from_sector_cursor(cursor);
}
#[inline]
fn add_sectors(sector: u8, delta: u32, seclen: u32) -> u8 {
((sector as u32 + delta) % seclen) as u8
}
}
impl MicroCartridge {
#[inline(always)]
fn set_valid_sector(&mut self, sector: u8, valid: bool) {
self.sector_map.view_bits_mut::<LocalBits>().set(sector as usize, valid);
}
#[inline(always)]
fn clear_all_sectors(&mut self) {
for p in self.sector_map.iter_mut() {
*p = 0
}
}
fn erase_start(&mut self, delta_ts: u32) {
self.forward(delta_ts);
let TapeCursor { sector, secpos, .. } = self.tape_cursor;
self.written = None;
if self.is_sector_formatted(sector) {
match secpos {
SecPosition::Gap2 |
SecPosition::Gap1 => {} _ => { self.set_valid_sector(sector, false);
}
}
}
}
fn erase_forward(&mut self, delta_ts: u32) {
let prev_cursor = self.tape_cursor;
self.forward(delta_ts);
let TapeCursor { sector, secpos, .. } = self.tape_cursor;
if prev_cursor.sector != sector { if delta_ts >= SECTOR_TS * (self.sectors.len() as u32 - 1) {
self.clear_all_sectors();
}
else {
let prev_sector = if let SecPosition::Gap2 = prev_cursor.secpos {
TapeCursor::add_sectors(prev_cursor.sector, 1, self.sectors.len() as u32)
}
else {
prev_cursor.sector
};
if sector < prev_sector {
self.sector_map.view_bits_mut::<LocalBits>()[prev_sector.into()..].fill(false);
self.sector_map.view_bits_mut::<LocalBits>()[..sector.into()].fill(false);
}
else {
self.sector_map.view_bits_mut::<LocalBits>()[prev_sector.into()..=sector.into()].fill(false);
}
}
}
else {
match (prev_cursor.secpos, secpos) {
(SecPosition::Gap2, SecPosition::Gap2)|
(SecPosition::Gap1, SecPosition::Gap1)|
(SecPosition::Gap1, SecPosition::Preamble2(..))|
(SecPosition::Preamble2(..), SecPosition::Preamble2(..)) => {}
_ => { self.set_valid_sector(sector, false);
}
}
}
}
fn write_end(&mut self, delta_ts: u32) { self.forward(delta_ts);
if let Some(written) = self.written {
if delta_ts < 2*BYTE_TS {
const HEAD_SIZE_MIN: u16 = PREAMBLE_SIZE + HEAD_SIZE as u16;
const HEAD_SIZE_MAX: u16 = HEAD_SIZE_MIN + 55;
#[allow(clippy::single_match)]
match (written.get(), self.tape_cursor.secpos) {
(HEAD_SIZE_MIN..=HEAD_SIZE_MAX, SecPosition::Gap1) => {
self.set_valid_sector(self.tape_cursor.sector, true);
}
_ => {}
}
}
}
self.written = None;
}
fn write_data_forward(&mut self, data: u8, delta_ts: u32) -> u16 {
if let Some(written) = self.written {
self.written = NonZeroU16::new(written.get().saturating_add(1));
self.forward(delta_ts);
let TapeCursor { sector, cursor, secpos } = self.tape_cursor;
if delta_ts < BYTE_TS*3/2 {
match (written.get(), data, secpos) {
(wr, 0x00, SecPosition::Preamble1(offs @ 1..=9))|
(wr, 0xff, SecPosition::Preamble1(offs @ 10..=11)) if wr == offs => {
return (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16;
}
(wr, _, SecPosition::Preamble1(PREAMBLE_SIZE)) if wr == PREAMBLE_SIZE => {
self.sectors[sector as usize].head[0] = data;
return (HEAD_START + BYTE_TS - cursor) as u16;
}
(wr, _, SecPosition::Header(offset)) if wr == PREAMBLE_SIZE + offset => {
self.sectors[sector as usize].head[offset as usize] = data;
return (BYTE_TS - (cursor - HEAD_START) % BYTE_TS) as u16;
}
(wr, _, SecPosition::Gap1) if wr >= PREAMBLE_SIZE + HEAD_SIZE as u16 => {
return (BYTE_TS - (cursor - GAP1_START) % BYTE_TS) as u16;
}
(wr, 0x00, SecPosition::Preamble2(offs @ 1..=9))|
(wr, 0xff, SecPosition::Preamble2(offs @ 10..=11)) if wr == offs => {
return (BYTE_TS - (cursor - DATA_PREAMBLE) % BYTE_TS) as u16;
}
(wr, _, SecPosition::Preamble2(PREAMBLE_SIZE)) if wr == PREAMBLE_SIZE => {
self.sectors[sector as usize].data[0] = data;
return (DATA_START + BYTE_TS - cursor) as u16;
}
(wr, _, SecPosition::Data(offset)) if wr == PREAMBLE_SIZE + offset => {
self.sectors[sector as usize].data[offset as usize] = data;
return (BYTE_TS - (cursor - DATA_START) % BYTE_TS) as u16;
}
(wr, _, SecPosition::Gap2) if wr >= PREAMBLE_SIZE + DATA_SIZE as u16 => {
return (BYTE_TS - (cursor - GAP2_START) % BYTE_TS) as u16;
}
_=> {}
}
}
self.set_valid_sector(sector, false); (BYTE_TS - cursor % BYTE_TS) as u16
}
else {
self.erase_forward(delta_ts);
self.written = NonZeroU16::new(1);
let TapeCursor { mut sector, secpos, .. } = self.tape_cursor;
if data == 0 {
if self.is_sector_formatted(sector) {
if let SecPosition::Preamble2(0..=2)|SecPosition::Gap1 = secpos { self.tape_cursor.cursor = DATA_PREAMBLE;
self.tape_cursor.secpos = SecPosition::Preamble2(0);
return BYTE_TS as u16;
}
}
if let SecPosition::Gap2 = secpos { sector = TapeCursor::add_sectors(sector, 1, self.sectors.len() as u32);
}
self.tape_cursor.sector = sector;
self.tape_cursor.cursor = 0;
self.tape_cursor.secpos = SecPosition::Preamble1(0);
}
self.set_valid_sector(sector, false); BYTE_TS as u16
}
}
fn read_data_forward(&mut self, delta_ts: u32) -> (u8, u16) { self.forward(delta_ts);
let TapeCursor { sector, cursor, secpos, .. } = self.tape_cursor;
if self.is_sector_formatted(sector) {
return match secpos {
SecPosition::Preamble1(10..=11) => {
let data = self.sectors[sector as usize].head[0];
let delay = HEAD_START + BYTE_TS - cursor;
(data, delay as u16)
}
SecPosition::Header(offset) => {
let data = self.sectors[sector as usize].head[offset as usize];
let delay = BYTE_TS - (cursor - HEAD_START) % BYTE_TS;
(data, delay as u16)
}
SecPosition::Preamble1(..) => {
(0, (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16)
}
SecPosition::Gap1 => {
(!0, (BYTE_TS - (cursor - GAP1_START) % BYTE_TS) as u16)
}
SecPosition::Preamble2(10..=11) => {
let data = self.sectors[sector as usize].data[0];
let delay = DATA_START + BYTE_TS - cursor;
(data, delay as u16)
}
SecPosition::Data(offset) => {
let data = self.sectors[sector as usize].data[offset as usize];
let delay = BYTE_TS - (cursor - DATA_START) % BYTE_TS;
(data, delay as u16)
}
SecPosition::Preamble2(..) => {
(0, (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16)
}
SecPosition::Gap2 => {
let data = self.sectors[sector as usize].data[DATA_SIZE - 1];
(data, (BYTE_TS - (cursor - GAP2_START) % BYTE_TS) as u16)
}
}
}
(!0, (BYTE_TS - cursor % BYTE_TS) as u16)
}
#[inline(always)]
fn forward(&mut self, delta_ts: u32) {
self.tape_cursor.forward(delta_ts, self.sectors.len() as u32);
}
#[inline]
fn gap_syn_protect(&self) -> CartridgeState {
let mut gap = false;
let mut syn = false;
let TapeCursor { sector, secpos, .. } = self.tape_cursor;
if self.is_sector_formatted(sector) {
match secpos {
SecPosition::Preamble1(0..=9)|
SecPosition::Preamble2(0..=9) => {
gap = true;
}
SecPosition::Preamble1(10..=11)|
SecPosition::Preamble2(10..=11) => {
gap = true;
syn = true;
}
_ => {}
};
}
CartridgeState {
gap, syn, write_protect: self.protec
}
}
}
impl<T> ZxMicrodrives<T> {
pub fn replace_cartridge(
&mut self,
drive_index: usize,
cartridge: MicroCartridge
) -> Option<MicroCartridge>
{
assert!(drive_index < MAX_DRIVES);
let prev_cartridge = self.drives[drive_index].replace(cartridge);
if self.erase {
if let Some(cartridge) = self.drive_if_current(drive_index) {
cartridge.erase_start(0);
}
}
prev_cartridge
}
pub fn take_cartridge(&mut self, index: usize) -> Option<MicroCartridge> {
assert!(index < MAX_DRIVES);
self.drives[index].take()
}
pub fn is_cartridge_inserted(&mut self, index: usize) -> bool {
assert!(index < MAX_DRIVES);
self.drives[index].is_some()
}
pub fn cartridge_at(&self, index: usize) -> Option<&MicroCartridge> {
assert!(index < MAX_DRIVES);
self.drives[index].as_ref()
}
pub fn cartridge_in_use(&self) -> Option<(usize, &MicroCartridge)> {
self.motor_on_drive.and_then(move |drive_on| {
let drive_index = (drive_on.get() - 1) as usize;
self.drives[drive_index & 7].as_ref()
.map(|mc| (drive_index, mc))
})
}
fn current_drive(&mut self) -> Option<&mut MicroCartridge> {
self.motor_on_drive.and_then(move |drive_on|
self.drives[(drive_on.get() - 1) as usize & 7].as_mut()
)
}
fn drive_if_current(&mut self, index: usize) -> Option<&mut MicroCartridge> {
self.motor_on_drive.and_then(move |drive_on| {
let drive_index = (drive_on.get() - 1) as usize;
if drive_index == index {
self.drives[drive_index & 7].as_mut()
}
else {
None
}
})
}
}
impl<T: TimestampOps> ZxMicrodrives<T> {
fn vts_diff_update(&mut self, timestamp: T) -> u32 {
let delta_ts = timestamp.diff_from(self.last_ts);
self.last_ts = timestamp;
debug_assert!(delta_ts >= 0);
delta_ts as u32
}
pub(crate) fn reset(&mut self, timestamp: T) {
let delta_ts = self.vts_diff_update(timestamp);
self.stop_motor(delta_ts);
self.write = false;
self.erase = false;
self.comms_clk = false;
}
pub(crate) fn update_timestamp(&mut self, timestamp: T) {
let delta_ts = self.vts_diff_update(timestamp);
let (erase, write) = (self.erase, self.write);
if let Some(cartridge) = self.current_drive() {
if erase && (!write || delta_ts > 2*BYTE_TS) {
cartridge.erase_forward(delta_ts);
}
else {
cartridge.forward(delta_ts);
}
}
}
pub(crate) fn next_frame(&mut self, eof_timestamp: T) {
self.last_ts = self.last_ts.saturating_sub(eof_timestamp);
}
pub(crate) fn read_state(&mut self, timestamp: T) -> CartridgeState {
let delta_ts = self.vts_diff_update(timestamp);
let erase = self.erase;
if let Some(cartridge) = self.current_drive() {
if erase {
cartridge.erase_forward(delta_ts);
}
else {
cartridge.forward(delta_ts);
}
return cartridge.gap_syn_protect();
}
CartridgeState::default()
}
pub(crate) fn write_control(
&mut self,
timestamp: T,
erase: bool,
write: bool,
comms_clk: bool,
comms_out: bool
)
{
let delta_ts = self.vts_diff_update(timestamp);
if comms_clk != self.comms_clk {
self.comms_clk = comms_clk;
if comms_clk { if comms_out { self.stop_motor(delta_ts);
self.motor_on_drive = NonZeroU8::new(1);
}
else { self.motor_on_drive = self.stop_motor(delta_ts).and_then(|n_drive|
if n_drive.get() == 8 {
None
} else {
NonZeroU8::new(n_drive.get() + 1)
}
);
}
self.erase = false;
self.write = false;
}
}
if erase && !self.erase {
if let Some(cartridge) = self.current_drive() {
cartridge.erase_start(delta_ts);
}
}
else if self.erase {
if !write && self.write {
if let Some(cartridge) = self.current_drive() {
cartridge.write_end(delta_ts);
}
}
else if (write && !self.write) || (!erase && self.erase) {
if let Some(cartridge) = self.current_drive() {
cartridge.erase_forward(delta_ts);
}
}
}
self.erase = erase;
self.write = write;
}
pub(crate) fn write_data(&mut self, data: u8, timestamp: T) -> u16 {
let delta_ts = self.vts_diff_update(timestamp);
if self.write && self.erase { if let Some(cartridge) = self.current_drive() {
return cartridge.write_data_forward(data, delta_ts);
}
}
0
}
pub(crate) fn read_data(&mut self, timestamp: T) -> (u8, Option<NonZeroU16>) {
let delta_ts = self.vts_diff_update(timestamp);
if self.erase {
if let Some(cartridge) = self.current_drive() {
cartridge.erase_forward(delta_ts);
}
}
if !(self.write || self.erase) {
if let Some(cartridge) = self.current_drive() {
let (data, delay) = cartridge.read_data_forward(delta_ts);
return (data, NonZeroU16::new(delay))
}
}
(!0, HALT_FOREVER_TS)
}
fn stop_motor(&mut self, delta_ts: u32) -> Option<NonZeroU8> {
let motor_on_drive = self.motor_on_drive;
if self.erase {
if let Some(cartridge) = self.current_drive() {
cartridge.erase_forward(delta_ts);
cartridge.written = None;
}
}
self.motor_on_drive = None;
motor_on_drive
}
}
#[cfg(test)]
mod tests {
use core::num::Wrapping;
use spectrusty_core::clock::FTs;
use spectrusty_core::z80emu::host::TsCounter;
use super::*;
type UlaTsCounter = TsCounter<FTs>;
type TestMicrodrives = ZxMicrodrives<FTs>;
const EOF: FTs = 69888;
fn is_eof(tsc: TsCounter<FTs>) -> bool {
(*tsc).0 > EOF - 69
}
fn wrap_frame(tsc: &mut TsCounter<FTs>) {
while is_eof(*tsc) {
**tsc -= Wrapping(EOF);
}
}
#[test]
fn microdrives_works() {
let mut drive: TestMicrodrives = Default::default();
assert_eq!(drive.write, false);
assert_eq!(drive.erase, false);
assert_eq!(drive.comms_clk, false);
assert_eq!(drive.motor_on_drive, None);
assert_eq!(drive.read_data(10), (!0, NonZeroU16::new(65535)));
assert_eq!(drive.last_ts, 10);
assert_eq!(drive.write_data(0xAA, 11), 0);
assert_eq!(drive.last_ts, 11);
assert_eq!(drive.read_state(20), CartridgeState{ gap: false, syn: false, write_protect: false});
assert_eq!(drive.last_ts, 20);
drive.write_control(30, true, false, true, true);
assert_eq!(drive.write, false);
assert_eq!(drive.erase, true);
assert_eq!(drive.comms_clk, true);
assert_eq!(drive.last_ts, 30);
assert_eq!(drive.motor_on_drive, NonZeroU8::new(1));
drive.write_control(40, true, false, false, false);
assert_eq!(drive.write, false);
assert_eq!(drive.erase, true);
assert_eq!(drive.comms_clk, false);
assert_eq!(drive.last_ts, 40);
assert_eq!(drive.motor_on_drive, NonZeroU8::new(1));
assert_eq!(drive.replace_cartridge(1, MicroCartridge::new(5)).is_none(), true);
drive.write_control(40, true, false, true, false);
drive.write_control(50, true, true, false, false);
assert_eq!(drive.write, true);
assert_eq!(drive.erase, true);
assert_eq!(drive.comms_clk, false);
assert_eq!(drive.last_ts, 50);
assert_eq!(drive.motor_on_drive, NonZeroU8::new(2));
assert_eq!(drive.read_state(60), CartridgeState { gap: false, syn: false, write_protect: false});
let mut utsc = UlaTsCounter::from(224);
for _ in 0..10 {
let delay = drive.write_data(0, (*utsc).0);
println!("delay 0x00: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 21 + 16);
}
for _ in 0..2 {
let delay = drive.write_data(!0, (*utsc).0);
println!("delay 0xff: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 11 + 13);
}
for i in 1..19 {
let delay = drive.write_data(i, (*utsc).0);
println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
*utsc += Wrapping(delay as FTs + 21 + 16);
}
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.sector_map, [0x00000000, 0x00000000, 0x00000000, 0x00000000,
0x00000000, 0x00000000, 0x00000000, 0x00000000]);
assert_eq!(cartridge.written, NonZeroU16::new(30));
assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
assert_eq!(&cartridge.sectors[0].data[..], &[!0u8;528][..]);
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: GAP1_START + 2 * BYTE_TS + 21 + 16,
sector: 0,
secpos: SecPosition::Gap1,
});
*utsc += Wrapping(36);
drive.write_control((*utsc).0, true, false, false, false);
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.written, None);
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
for valid in §or_map[1..] {
assert_eq!(*valid, false);
}
assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
assert_eq!(&cartridge.sectors[0].data[..], &[!0u8;528][..]);
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: GAP1_START + 3 * BYTE_TS + 21 + 16 + 36,
sector: 0,
secpos: SecPosition::Gap1,
});
*utsc += Wrapping(11443);
drive.write_control((*utsc).0, true, true, false, false);
*utsc += Wrapping(48);
for _ in 0..10 {
let delay = drive.write_data(0, (*utsc).0);
println!("delay 0x00: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 11 + 13);
}
for _ in 0..2 {
let delay = drive.write_data(!0, (*utsc).0);
println!("delay 0xff: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 19);
}
for i in 1..630u16 { if is_eof(utsc) {
drive.update_timestamp((*utsc).0);
drive.next_frame(EOF);
wrap_frame(&mut utsc);
}
let delay = drive.write_data(!(i as u8), (*utsc).0);
println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
*utsc += Wrapping(delay as FTs + 11 + 13);
}
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.written, NonZeroU16::new(641));
assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
for (i, data) in cartridge.sectors[0].data.iter().enumerate() {
assert_eq!(!(i + 1) as u8, *data);
}
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: 121224, sector: 0, secpos: SecPosition::Gap2 });
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
drive.write_control((*utsc).0, true, false, false, false);
let cartridge = drive.current_drive().unwrap();
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
*utsc += Wrapping(8941);
drive.write_control((*utsc).0, false, false, false, false);
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: 827, sector: 1, secpos: SecPosition::Preamble1(5) });
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
for valid in §or_map[1..] {
assert_eq!(*valid, false);
}
fn find_gap_sync(mut utsc: UlaTsCounter, drive: &mut TestMicrodrives) -> UlaTsCounter {
let mut counter = 0;
while !drive.read_state((*utsc).0).gap {
counter += 1;
*utsc += Wrapping(108);
if is_eof(utsc) {
drive.update_timestamp((*utsc).0);
drive.next_frame(EOF);
wrap_frame(&mut utsc);
}
}
println!("counter: {}", counter);
for _ in 1..6 {
*utsc += Wrapping(88);
assert!(drive.read_state((*utsc).0).gap);
}
*utsc += Wrapping(25);
'outer: loop {
for i in 1..=60 {
let state = drive.read_state((*utsc).0);
*utsc += Wrapping(38);
assert!(state.gap);
if state.syn { break 'outer }
println!("sync not found: {}", i);
}
assert!(false, "failed to find syn");
}
utsc
}
*utsc += Wrapping(108);
utsc = find_gap_sync(utsc, &mut drive);
let cartridge = drive.current_drive().unwrap();
println!("{:?} {:?}", cartridge.tape_cursor, utsc);
*utsc += Wrapping(92);
for i in 1..=15 {
let (data, delay) = drive.read_data((*utsc).0);
println!("data: {:02x} delay: {:?} {:?}", data, delay, utsc);
assert_eq!(i, data);
*utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
}
let (data, delay) = drive.read_data((*utsc).0);
println!("data: {:02x} delay: {:?} {:?}", data, delay, utsc);
assert_eq!(data, !0);
*utsc += Wrapping(delay.unwrap().get() as FTs + 121);
assert_eq!(drive.read_state((*utsc).0).gap, false);
*utsc += Wrapping(10000);
assert_eq!(drive.read_state((*utsc).0).gap, false);
utsc = find_gap_sync(utsc, &mut drive);
let cartridge = drive.current_drive().unwrap();
println!("{:?} {:?}", cartridge.tape_cursor, utsc);
*utsc += Wrapping(92);
for i in 1..=528 {
let (data, delay) = drive.read_data((*utsc).0);
println!("data: {:02x} delay: {:?} {:?}", data, delay, utsc);
assert_eq!(!i as u8, data);
*utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
if is_eof(utsc) {
drive.update_timestamp((*utsc).0);
drive.next_frame(EOF);
wrap_frame(&mut utsc);
}
}
let (data, delay) = drive.read_data((*utsc).0);
println!("data: {:02x} delay: {:?} {:?}", data, delay, utsc);
assert_eq!(data, 239);
*utsc += Wrapping(delay.unwrap().get() as FTs + 21);
*utsc += Wrapping(10800);
utsc = find_gap_sync(utsc, &mut drive);
let cartridge = drive.current_drive().unwrap();
println!("{:?} {:?}", cartridge.tape_cursor, utsc);
*utsc += Wrapping(92);
for i in 1..=15 {
let (data, delay) = drive.read_data((*utsc).0);
println!("data: {:02x} delay: {:?} {:?}", data, delay, utsc);
assert_eq!(i, data);
*utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
}
assert_eq!(drive.read_state((*utsc).0), CartridgeState {
gap: false, syn: false, write_protect: false});
*utsc += Wrapping(98);
drive.write_control((*utsc).0, true, false, false, false);
*utsc += Wrapping(9524);
drive.write_control((*utsc).0, true, true, false, false);
*utsc += Wrapping(47);
for _ in 0..10 {
let delay = drive.write_data(0, (*utsc).0);
println!("delay 0x00: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 21);
}
for _ in 0..2 {
let delay = drive.write_data(!0, (*utsc).0);
println!("delay 0xff: {} {:?}", delay, utsc);
*utsc += Wrapping(delay as FTs + 21);
}
for i in 0..531u16 { if is_eof(utsc) {
drive.update_timestamp((*utsc).0);
drive.next_frame(EOF);
wrap_frame(&mut utsc);
}
let delay = drive.write_data(0xA5^(i as u8), (*utsc).0);
println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
*utsc += Wrapping(delay as FTs + 21 + 16);
}
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.written, NonZeroU16::new(543));
assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
for (i, data) in cartridge.sectors[0].data.iter().enumerate() {
assert_eq!(0xA5^(i as u8), *data);
}
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: 105361, sector: 0, secpos: SecPosition::Gap2 });
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
*utsc += Wrapping(18);
drive.write_control((*utsc).0, true, false, false, false);
*utsc += Wrapping(112);
drive.write_control((*utsc).0, false, false, false, false);
let cartridge = drive.current_drive().unwrap();
assert_eq!(cartridge.tape_cursor, TapeCursor {
cursor: 105653, sector: 0, secpos: SecPosition::Gap2 });
let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
assert_eq!(sector_map[0], true);
for valid in §or_map[1..] {
assert_eq!(*valid, false);
}
*utsc += Wrapping(1000);
if is_eof(utsc) {
drive.update_timestamp((*utsc).0);
drive.next_frame(EOF);
wrap_frame(&mut utsc);
}
for i in 0..7 {
assert_eq!(drive.motor_on_drive, NonZeroU8::new(i + 2));
drive.write_control((*utsc).0, true, false, true, false);
*utsc += Wrapping(3500);
drive.write_control((*utsc).0, true, false, false, false);
*utsc += Wrapping(3500);
}
assert_eq!(drive.motor_on_drive, None);
}
}