spectrusty_peripherals/storage/
microdrives.rs

1/*
2    Copyright (C) 2020-2022  Rafal Michalski
3
4    This file is part of SPECTRUSTY, a Rust library for building emulators.
5
6    For the full copyright notice, see the lib.rs file.
7*/
8//! ZX Microdrives for the ZX Interface 1.
9use core::ops::{Index, IndexMut};
10use core::num::{NonZeroU8, NonZeroU16};
11use core::fmt;
12use core::iter::{Enumerate, FilterMap, Zip, IntoIterator};
13use core::slice;
14use std::vec;
15
16#[cfg(feature = "snapshot")] mod serde;
17#[cfg(feature = "snapshot")]
18use ::serde::{Serialize, Deserialize};
19
20use ::bitvec::prelude::*;
21
22use spectrusty_core::clock::{FTs, TimestampOps};
23
24/// The maximum additional T-states the Interface 1 will halt Z80 during IN/OUT with DATA port.
25///
26///
27/// Normally accessing port 0xe7 will halt the Z80 until the Interface I has collected 8 bits from the
28/// microdrive head. Reading from this port while the drive motor is off results in halting the Spectrum.
29pub const MAX_HALT_TS: FTs = 2 * BYTE_TS as FTs;
30
31/// The number of T-states to signal to the control unit that the IF-1 has hanged the Spectrum indefinitely
32/// (IN 0 bug).
33pub const HALT_FOREVER_TS: Option<NonZeroU16> = unsafe {
34    Some(NonZeroU16::new_unchecked(u16::max_value()))
35};
36
37/// [Sector] reference iterator of the [IntoIterator] implementation for [MicroCartridge].
38pub type MicroCartridgeSecIter<'a> = FilterMap<
39                                            Zip<
40                                                slice::Iter<'a, Sector>,
41                                                bitvec::slice::Iter<'a, u32, LocalBits>
42                                            >,
43                                            &'a dyn Fn(
44                                                (&'a Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>)
45                                            ) -> Option<&'a Sector>
46                                        >;
47
48/// [Sector] mutable reference iterator of the [IntoIterator] implementation for [MicroCartridge].
49pub type MicroCartridgeSecIterMut<'a> = FilterMap<
50                                            Zip<
51                                                slice::IterMut<'a, Sector>,
52                                                bitvec::slice::Iter<'a, u32, LocalBits>
53                                            >,
54                                            &'a dyn Fn(
55                                                (&'a mut Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>)
56                                            ) -> Option<&'a mut Sector>
57                                        >;
58/// An iterator returned by [MicroCartridge::iter_with_indices] method.
59pub type MicroCartridgeIdSecIter<'a> = FilterMap<
60                                            Enumerate<
61                                                Zip<
62                                                    slice::Iter<'a, Sector>,
63                                                    bitvec::slice::Iter<'a, u32, LocalBits>
64                                            >>,
65                                            &'a dyn Fn(
66                                                (usize,
67                                                (&'a Sector, BitRef<'a, bitvec::ptr::Const, u32, LocalBits>))
68                                            ) -> Option<(u8, &'a Sector)>
69                                        >;
70/// The maximum number of emulated physical ZX Microdrive tape sectors.
71pub const MAX_SECTORS: usize = 256;
72/// The maximum number of usable ZX Microdrive tape sectors.
73pub const MAX_USABLE_SECTORS: usize = 254;
74/// The maximum number of drives that the ZX Interface 1 software can handle.
75pub const MAX_DRIVES: usize = 8;
76
77/// The size of the sector header in bytes, excluding the 12 preamble bytes.
78pub const HEAD_SIZE: usize = 15;
79/// The size of the sector data in bytes, excluding the 12 preamble bytes.
80pub const DATA_SIZE: usize = 528;
81// The size of the sector preamble.
82const PREAMBLE_SIZE: u16 = 12;
83
84// T-states per byte written or read by the drive
85const BYTE_TS: u32 = 162;
86// T-states per preamble after 10 zeroes and 2 0xFFs;
87const HEAD_START_TS: u32 = 1945;
88// Header T-states including preamble
89const HEAD_TS: u32 = 4375; // 1,25 ms
90// First gap T-states
91const GAP1_TS: u32 = 13125; // 3,75 ms
92// Data preamble T-states after 10 zeroes and 2 0xFFs;
93const DATA_START_TS: u32 = 1964;
94// Data T-states including preamble
95const DATA_TS: u32 = 87500; // 25 ms
96// Second gap T-states
97const GAP2_TS: u32 = 24500; // 7 ms
98// T-states / sector
99const SECTOR_TS: u32 = HEAD_TS + GAP1_TS + DATA_TS + GAP2_TS; // 37 ms
100
101const SECTOR_MAP_SIZE: usize = (MAX_SECTORS + 31)/32;
102
103/// This struct represents a single [MicroCartridge] tape sector.
104#[derive(Clone, Copy)]
105#[repr(C, packed)]
106pub struct Sector {
107    /// Header data.
108    pub head: [u8;HEAD_SIZE],
109    /// Block data.
110    pub data: [u8;DATA_SIZE],
111}
112
113/// This struct represents an emulated Microdrive tape cartridge.
114///
115/// It consist of up to [MAX_SECTORS] [Sector]s. Instances of this struct can be "inserted" into one
116/// of 8 [ZxMicrodrives]'s emulator drives.
117#[derive(Clone)]
118#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
119#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
120pub struct MicroCartridge {
121    sectors: Box<[Sector]>,
122    sector_map: [u32;SECTOR_MAP_SIZE],
123    tape_cursor: TapeCursor,
124    protec: bool,
125    written: Option<NonZeroU16>
126}
127
128/// Implementation of this type emulates ZX Microdrives.
129///
130/// Used by [ZX Interface 1][crate::bus::zxinterface1::ZxInterface1BusDevice] emulator.
131#[derive(Clone, Default, Debug)]
132#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
133#[cfg_attr(feature = "snapshot", serde(rename_all = "camelCase"))]
134pub struct ZxMicrodrives<T> {
135    drives: [Option<MicroCartridge>;MAX_DRIVES],
136    write: bool,
137    erase: bool,
138    comms_clk: bool,
139    motor_on_drive: Option<NonZeroU8>,
140    last_ts: T
141}
142
143#[derive(Clone, Copy, Default, Debug, PartialEq)]
144pub(crate) struct CartridgeState {
145    pub gap: bool,
146    pub syn: bool,
147    pub write_protect: bool
148}
149
150#[derive(Clone, Copy, Debug, PartialEq)]
151#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
152enum SecPosition {
153    Preamble1(u16),
154    Header(u16),
155    Gap1,
156    Preamble2(u16),
157    Data(u16),
158    Gap2,
159}
160
161#[derive(Clone, Copy, Debug, PartialEq)]
162#[cfg_attr(feature = "snapshot", derive(Serialize, Deserialize))]
163struct TapeCursor {
164    cursor: u32,
165    sector: u8,
166    secpos: SecPosition,
167}
168
169impl Default for Sector {
170    fn default() -> Self {
171        Sector {
172            head: [!0;HEAD_SIZE],
173            data: [!0;DATA_SIZE],
174        }
175    }
176}
177
178impl Default for TapeCursor {
179    fn default() -> Self {
180        TapeCursor {
181            cursor: 0,
182            sector: 0,
183            secpos: SecPosition::Preamble1(0)
184        }
185    }
186}
187
188impl Default for MicroCartridge {
189    fn default() -> Self {
190        MicroCartridge {
191            sectors: vec![Sector::default();MAX_SECTORS].into_boxed_slice(),
192            sector_map: [0;SECTOR_MAP_SIZE],
193            tape_cursor: TapeCursor::default(),
194            protec: false,
195            written: None
196        }
197    }
198}
199
200
201impl fmt::Debug for MicroCartridge {
202    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
203        write!(f, "MicroCartridge {{ sectors: {}, formatted: {}, protected: {:?}, writing: {:?}, cursor: {:?} }}",
204            self.sectors.len(),
205            self.sector_map.view_bits::<LocalBits>().count_ones(),
206            self.protec,
207            self.written,
208            self.tape_cursor
209        )
210    }
211}
212
213impl MicroCartridge {
214    /// Returns the current drive's head position counted in sectors as floating point value.
215    ///
216    /// The fractional part indicates how far the head position is within a sector.
217    pub fn head_at(&self) -> f32 {
218        let TapeCursor { sector, cursor, .. } = self.tape_cursor;
219        sector as f32 + cursor as f32 / SECTOR_TS as f32
220    }
221    /// Returns the number of the emulated physical sectors on the tape.
222    #[inline]
223    pub fn max_sectors(&self) -> usize {
224        self.sectors.len()
225    }
226    /// Returns the number of formatted sectors.
227    pub fn count_formatted(&self) -> usize {
228        self.sector_map.view_bits::<LocalBits>().count_ones()
229    }
230    /// Returns an iterator of formatted sectors with their original indices.
231    pub fn iter_with_indices(&self) -> MicroCartridgeIdSecIter<'_> {
232        let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
233        self.sectors.iter().zip(iter_map).enumerate().filter_map(&|(i, (s, valid))| {
234            if *valid { Some((i as u8, s)) } else { None }
235        })
236    }
237    /// Returns `true` if the cartridge is write protected.
238    #[inline]
239    pub fn is_write_protected(&self) -> bool {
240        self.protec
241    }
242    /// Changes the write protected flag of the cartridge.
243    #[inline]
244    pub fn set_write_protected(&mut self, protect: bool) {
245        self.protec = protect;
246    }
247    /// Returns `true` if the given `sector` is formatted.
248    ///
249    /// # Panics
250    /// Panics if `sector` equals to or is above the `max_sectors` limit.
251    #[inline]
252    pub fn is_sector_formatted(&self, sector: u8) -> bool {
253        self.sector_map.view_bits::<LocalBits>()[sector as usize]
254    }
255    /// Creates a new instance of [MicroCartridge] with provided sectors.
256    ///
257    /// # Note
258    /// Content of the sectors is not verified and is assumed to be properly formatted.
259    ///
260    /// # Panics
261    /// The number of sectors provided must not be greater than [MAX_USABLE_SECTORS].
262    /// `max_sectors` must not be 0 and must be grater or equal to the number of provided sectors and
263    ///  must not be greater than [MAX_SECTORS].
264    pub fn new_with_sectors<S: Into<Vec<Sector>>>(
265            sectors: S,
266            write_protect: bool,
267            max_sectors: usize
268        ) -> Self
269    {
270        let mut sectors = sectors.into();
271        assert!(max_sectors > 0 && max_sectors <= MAX_SECTORS &&
272                sectors.len() <= max_sectors && sectors.len() <= MAX_USABLE_SECTORS);
273        let mut sector_map = [0u32;SECTOR_MAP_SIZE];
274        sector_map.view_bits_mut::<LocalBits>()[0..sectors.len()].fill(true);
275        sectors.resize(max_sectors, Sector::default());
276        let sectors = sectors.into_boxed_slice();
277        MicroCartridge {
278            sectors,
279            sector_map,
280            tape_cursor: TapeCursor::default(),
281            protec: write_protect,
282            written: None
283        }
284    }
285    /// Creates a new instance of [MicroCartridge] with custom `max_sectors` number.
286    ///
287    /// # Panics
288    /// `max_sectors` must not be 0 and must not be greater than [MAX_SECTORS].
289    pub fn new(max_sectors: usize) -> Self {
290        assert!(max_sectors > 0 && max_sectors <= MAX_SECTORS);
291        MicroCartridge {
292            sectors: vec![Sector::default();max_sectors].into_boxed_slice(),
293            sector_map: [0;SECTOR_MAP_SIZE],
294            tape_cursor: TapeCursor::default(),
295            protec: false,
296            written: None
297        }
298    }
299}
300
301/// Iterates through formatted sectors.
302impl<'a> IntoIterator for &'a MicroCartridge {
303    type Item = &'a Sector;
304    type IntoIter = MicroCartridgeSecIter<'a>;
305    #[inline]
306    fn into_iter(self) -> Self::IntoIter {
307        let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
308        self.sectors.iter().zip(iter_map).filter_map(&|(s, valid)| {
309            if *valid { Some(s) } else { None }
310        })
311    }
312}
313
314/// Iterates through formatted sectors.
315impl<'a> IntoIterator for &'a mut MicroCartridge {
316    type Item = &'a mut Sector;
317    type IntoIter = MicroCartridgeSecIterMut<'a>;
318    #[inline]
319    fn into_iter(self) -> Self::IntoIter {
320        let iter_map = self.sector_map.view_bits::<LocalBits>().iter();
321        self.sectors.iter_mut().zip(iter_map).filter_map(&|(s, valid)| {
322            if *valid { Some(s) } else { None }
323        })
324    }
325}
326
327impl Index<u8> for MicroCartridge {
328    type Output = Sector;
329    #[inline]
330    fn index(&self, index: u8) -> &Self::Output {
331        &self.sectors[index as usize]
332    }
333}
334
335impl IndexMut<u8> for MicroCartridge {
336    #[inline]
337    fn index_mut(&mut self, index: u8) -> &mut Self::Output {
338        &mut self.sectors[index as usize]
339    }
340}
341
342const HEAD_PREAMBLE: u32 = 0;
343const HEAD_SYN_END:  u32 = HEAD_START_TS - 1;
344const HEAD_START:    u32 = HEAD_START_TS;
345const HEAD_END:      u32 = HEAD_TS - 1;
346const GAP1_START:    u32 = HEAD_TS;
347const GAP1_END:      u32 = HEAD_TS + GAP1_TS - 1;
348const DATA_PREAMBLE: u32 = HEAD_TS + GAP1_TS;
349const DATA_SYN_END:  u32 = DATA_PREAMBLE + DATA_START_TS - 1;
350const DATA_START:    u32 = DATA_PREAMBLE + DATA_START_TS;
351const DATA_END:      u32 = HEAD_TS + GAP1_TS + DATA_TS - 1;
352const GAP2_START:    u32 = HEAD_TS + GAP1_TS + DATA_TS;
353
354impl SecPosition {
355    #[inline]
356    fn from_sector_cursor(cursor: u32) -> Self {
357        assert_eq!((HEAD_END + 1 - HEAD_START) / HEAD_SIZE as u32, BYTE_TS);
358        assert_eq!((DATA_END + 1 - DATA_START) / DATA_SIZE as u32, BYTE_TS);
359        match cursor {
360            HEAD_PREAMBLE..=HEAD_SYN_END => SecPosition::Preamble1(((cursor - HEAD_PREAMBLE)/BYTE_TS) as u16),
361            HEAD_START..=HEAD_END        => SecPosition::Header(((cursor - HEAD_START)/BYTE_TS) as u16),
362            GAP1_START..=GAP1_END        => SecPosition::Gap1,
363            DATA_PREAMBLE..=DATA_SYN_END => SecPosition::Preamble2(((cursor - DATA_PREAMBLE)/BYTE_TS) as u16),
364            DATA_START..=DATA_END        => SecPosition::Data(((cursor - DATA_START)/BYTE_TS) as u16),
365            GAP2_START..=core::u32::MAX  => SecPosition::Gap2,
366        }
367    }
368}
369
370impl TapeCursor {
371    #[inline]
372    fn forward(&mut self, ts: u32, seclen: u32) { // cursor + sectors
373        let mut cursor = self.cursor.saturating_add(ts);
374        if cursor >= SECTOR_TS {
375            let diffsec = cursor / SECTOR_TS;
376            self.sector = Self::add_sectors(self.sector, diffsec, seclen);
377            cursor %= SECTOR_TS;
378        }
379        self.cursor = cursor;
380        self.secpos = SecPosition::from_sector_cursor(cursor);
381    }
382
383    #[inline]
384    fn add_sectors(sector: u8, delta: u32, seclen: u32) -> u8 {
385        ((sector as u32 + delta) % seclen) as u8
386    }
387}
388
389impl MicroCartridge {
390    #[inline(always)]
391    fn set_valid_sector(&mut self, sector: u8, valid: bool) {
392        self.sector_map.view_bits_mut::<LocalBits>().set(sector as usize, valid);
393    }
394
395    #[inline(always)]
396    fn clear_all_sectors(&mut self) {
397        for p in self.sector_map.iter_mut() {
398            *p = 0
399        }
400    }
401
402    // called when erasing began
403    fn erase_start(&mut self, delta_ts: u32) {
404        self.forward(delta_ts);
405        let TapeCursor { sector, secpos, .. } = self.tape_cursor;
406        self.written = None;
407        if self.is_sector_formatted(sector) {
408            match secpos {
409                SecPosition::Gap2 |
410                SecPosition::Gap1 => {} // synchronized sector write may follow
411                _ => { // otherwise clear sector
412                    self.set_valid_sector(sector, false);
413                }
414            }
415        }
416    }
417
418    // called when writing began or just erasing ended / motor stopped etc
419    fn erase_forward(&mut self, delta_ts: u32) {
420        let prev_cursor = self.tape_cursor;
421        self.forward(delta_ts);
422        let TapeCursor { sector, secpos, .. } = self.tape_cursor;
423        if prev_cursor.sector != sector { // clear all previous sectors
424            if delta_ts >= SECTOR_TS * (self.sectors.len() as u32 - 1) {
425                self.clear_all_sectors();
426            }
427            else {
428                let prev_sector = if let SecPosition::Gap2 = prev_cursor.secpos {
429                    TapeCursor::add_sectors(prev_cursor.sector, 1, self.sectors.len() as u32)
430                }
431                else {
432                    prev_cursor.sector
433                };
434                if sector < prev_sector {
435                    self.sector_map.view_bits_mut::<LocalBits>()[prev_sector.into()..].fill(false);
436                    self.sector_map.view_bits_mut::<LocalBits>()[..sector.into()].fill(false);
437                }
438                else {
439                    self.sector_map.view_bits_mut::<LocalBits>()[prev_sector.into()..=sector.into()].fill(false);
440                }
441            }
442        }
443        else {
444            match (prev_cursor.secpos, secpos) {
445                // synchronized write has ended
446                (SecPosition::Gap2, SecPosition::Gap2)|
447                // synchronized write should follow
448                (SecPosition::Gap1, SecPosition::Gap1)|
449                (SecPosition::Gap1, SecPosition::Preamble2(..))|
450                (SecPosition::Preamble2(..), SecPosition::Preamble2(..)) => {}
451                _ => { // otherwise clear sector
452                    self.set_valid_sector(sector, false);
453                }
454            }
455        }
456    }
457
458    fn write_end(&mut self, delta_ts: u32) { // switched r/w w -> r, erase off, or motor off
459        self.forward(delta_ts);
460        // println!("wr: {:?}, delta: {} sec: {} cur: {} {:?}", self.written, delta_ts, self.tape_cursor.sector, self.tape_cursor.cursor, self.tape_cursor.secpos);
461        if let Some(written) = self.written {
462            if delta_ts < 2*BYTE_TS {
463                const HEAD_SIZE_MIN: u16 = PREAMBLE_SIZE + HEAD_SIZE as u16;
464                const HEAD_SIZE_MAX: u16 = HEAD_SIZE_MIN + 55;
465                // const DATA_SIZE_MIN: u16 = PREAMBLE_SIZE + DATA_SIZE as u16;
466                // const DATA_SIZE_MAX: u16 = DATA_SIZE_MIN + 110;
467                #[allow(clippy::single_match)]
468                match (written.get(), self.tape_cursor.secpos) {
469                    (HEAD_SIZE_MIN..=HEAD_SIZE_MAX, SecPosition::Gap1) => {
470                        // this may yield a "valid" sector with invalid data, but harmless
471                        self.set_valid_sector(self.tape_cursor.sector, true);
472                    }
473                    // (DATA_SIZE_MIN..=DATA_SIZE_MAX, SecPosition::Gap2) => {
474                    //     println!("ok valid data");
475                    // }
476                    _ => {}
477                }
478            }
479        }
480        self.written = None;
481    }
482
483    fn write_data_forward(&mut self, data: u8, delta_ts: u32) -> u16 {
484        if let Some(written) = self.written {
485            self.written = NonZeroU16::new(written.get().saturating_add(1));
486            self.forward(delta_ts);
487            let TapeCursor { sector, cursor, secpos } = self.tape_cursor;
488            if delta_ts < BYTE_TS*3/2 {
489                // println!("wr: {}, data: {:x}, sec: {} cur: {} {:?}", written.get(), data, sector, cursor, secpos);
490                match (written.get(), data, secpos) {
491                    (wr, 0x00, SecPosition::Preamble1(offs @ 1..=9))|
492                    (wr, 0xff, SecPosition::Preamble1(offs @ 10..=11)) if wr == offs => {
493                        return (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16;
494                    }
495                    (wr, _, SecPosition::Preamble1(PREAMBLE_SIZE)) if wr == PREAMBLE_SIZE => {
496                        self.sectors[sector as usize].head[0] = data;
497                        return (HEAD_START + BYTE_TS - cursor) as u16;
498                    }
499                    (wr, _, SecPosition::Header(offset)) if wr == PREAMBLE_SIZE + offset => {
500                        self.sectors[sector as usize].head[offset as usize] = data;
501                        return (BYTE_TS - (cursor - HEAD_START) % BYTE_TS) as u16;
502                    }
503                    (wr, _, SecPosition::Gap1) if wr >= PREAMBLE_SIZE + HEAD_SIZE as u16 => {
504                        return (BYTE_TS - (cursor - GAP1_START) % BYTE_TS) as u16;
505                    }
506                    (wr, 0x00, SecPosition::Preamble2(offs @ 1..=9))|
507                    (wr, 0xff, SecPosition::Preamble2(offs @ 10..=11)) if wr == offs => {
508                        return (BYTE_TS - (cursor - DATA_PREAMBLE) % BYTE_TS) as u16;
509                    }
510                    (wr, _, SecPosition::Preamble2(PREAMBLE_SIZE)) if wr == PREAMBLE_SIZE => {
511                        self.sectors[sector as usize].data[0] = data;
512                        return (DATA_START + BYTE_TS - cursor) as u16;
513                    }
514                    (wr, _, SecPosition::Data(offset)) if wr == PREAMBLE_SIZE + offset => {
515                        self.sectors[sector as usize].data[offset as usize] = data;
516                        return (BYTE_TS - (cursor - DATA_START) % BYTE_TS) as u16;
517                    }
518                    (wr, _, SecPosition::Gap2) if wr >= PREAMBLE_SIZE + DATA_SIZE as u16 => {
519                        return (BYTE_TS - (cursor - GAP2_START) % BYTE_TS) as u16;
520                    }
521                    _=> {}
522                }
523            }
524            self.set_valid_sector(sector, false); // just erase all sector
525            (BYTE_TS - cursor % BYTE_TS) as u16
526        }
527        else {
528            // println!("start: {}", delta_ts);
529            self.erase_forward(delta_ts);
530            self.written = NonZeroU16::new(1);
531            let TapeCursor { mut sector, secpos, .. } = self.tape_cursor;
532            // println!("sector: {} cur: {} wr: {:?}, data: {:x}, {:?}", sector, self.tape_cursor.cursor, self.written, data, secpos);
533            if data == 0 {
534                if self.is_sector_formatted(sector) {
535                    // println!("overwrite data block");
536                    if let SecPosition::Preamble2(0..=2)|SecPosition::Gap1 = secpos { // synchronized write data sector
537                        self.tape_cursor.cursor = DATA_PREAMBLE;
538                        self.tape_cursor.secpos = SecPosition::Preamble2(0);
539                        return BYTE_TS as u16;
540                    }
541                }
542                if let SecPosition::Gap2 = secpos { // write starts on next sector
543                    sector = TapeCursor::add_sectors(sector, 1, self.sectors.len() as u32);
544                }
545                // println!("begin sector");
546                // write start somewhere in the middle of the sector, we just make it the new beginning of a sector 
547                self.tape_cursor.sector = sector;
548                self.tape_cursor.cursor = 0;
549                self.tape_cursor.secpos = SecPosition::Preamble1(0);
550            }
551            self.set_valid_sector(sector, false); // just erase this sector
552            BYTE_TS as u16
553        }
554    }
555
556    fn read_data_forward(&mut self, delta_ts: u32) -> (u8, u16) { // data, delay
557        self.forward(delta_ts);
558        let TapeCursor { sector, cursor, secpos, .. } = self.tape_cursor;
559        if self.is_sector_formatted(sector) {
560            // println!("sec: {} cur: {} {:?} {:?}", sector, cursor, secpos, res);
561            return match secpos {
562                SecPosition::Preamble1(10..=11) => {
563                    let data = self.sectors[sector as usize].head[0];
564                    let delay = HEAD_START + BYTE_TS - cursor;
565                    (data, delay as u16)
566                }
567                SecPosition::Header(offset) => {
568                    let data = self.sectors[sector as usize].head[offset as usize];
569                    let delay = BYTE_TS - (cursor - HEAD_START) % BYTE_TS;
570                    (data, delay as u16)
571                }
572                SecPosition::Preamble1(..) => {
573                    (0, (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16)
574                }
575                SecPosition::Gap1 => {
576                    (!0, (BYTE_TS - (cursor - GAP1_START) % BYTE_TS) as u16)
577                }
578                SecPosition::Preamble2(10..=11) => {
579                    let data = self.sectors[sector as usize].data[0];
580                    let delay = DATA_START + BYTE_TS - cursor;
581                    (data, delay as u16)
582                }
583                SecPosition::Data(offset) => {
584                    let data = self.sectors[sector as usize].data[offset as usize];
585                    let delay = BYTE_TS - (cursor - DATA_START) % BYTE_TS;
586                    (data, delay as u16)
587                }
588                SecPosition::Preamble2(..) => {
589                    (0, (BYTE_TS - (cursor - HEAD_PREAMBLE) % BYTE_TS) as u16)
590                }
591                SecPosition::Gap2 => {
592                    let data = self.sectors[sector as usize].data[DATA_SIZE - 1];
593                    (data, (BYTE_TS - (cursor - GAP2_START) % BYTE_TS) as u16)
594                }
595            }
596        }
597        (!0, (BYTE_TS - cursor % BYTE_TS) as u16)
598    }
599
600    #[inline(always)]
601    fn forward(&mut self, delta_ts: u32) {
602        self.tape_cursor.forward(delta_ts, self.sectors.len() as u32);
603    }
604
605    #[inline]
606    fn gap_syn_protect(&self) -> CartridgeState {
607        let mut gap = false;
608        let mut syn = false;
609        let TapeCursor { sector, secpos, .. } = self.tape_cursor;
610        if self.is_sector_formatted(sector) {
611            match secpos {
612                SecPosition::Preamble1(0..=9)|
613                SecPosition::Preamble2(0..=9) => {
614                    gap = true;
615                }
616                SecPosition::Preamble1(10..=11)|
617                SecPosition::Preamble2(10..=11) => {
618                    gap = true;
619                    syn = true;
620                }
621                _ => {}
622            };
623        }
624        CartridgeState {
625            gap, syn, write_protect: self.protec
626        }
627    }
628}
629
630impl<T> ZxMicrodrives<T> {
631    /// Inserts a `cartridge` into the `drive_index` optionally returning a cartridge
632    /// that was previously in the same drive.
633    ///
634    /// `drive_index` is a drive index number from 0 to 7.
635    ///
636    /// # Panics
637    /// Panics if the `drive_index` is above 7.
638    pub fn replace_cartridge(
639            &mut self,
640            drive_index: usize,
641            cartridge: MicroCartridge
642        ) -> Option<MicroCartridge>
643    {
644        assert!(drive_index < MAX_DRIVES);
645        let prev_cartridge = self.drives[drive_index].replace(cartridge);
646        if self.erase {
647            if let Some(cartridge) = self.drive_if_current(drive_index) {
648                cartridge.erase_start(0);
649            }
650        }
651        prev_cartridge
652    }
653    /// Removes and optionally returns a cartridge from the `drive_index`.
654    ///
655    /// `drive_index` is a drive index number from 0 to 7.
656    ///
657    /// # Panics
658    /// Panics if the `drive_index` is above 7.
659    pub fn take_cartridge(&mut self, index: usize) -> Option<MicroCartridge> {
660        assert!(index < MAX_DRIVES);
661        self.drives[index].take()
662    }
663    /// Returns `true` if a cartridge is present in the `drive_index`.
664    ///
665    /// `drive_index` is a drive index number from 0 to 7.
666    ///
667    /// # Panics
668    /// Panics if the `drive_index` is above 7.
669    pub fn is_cartridge_inserted(&mut self, index: usize) -> bool {
670        assert!(index < MAX_DRIVES);
671        self.drives[index].is_some()
672    }
673    /// Returns a reference to a cartridge if it's present in the `drive_index`.
674    ///
675    /// `drive_index` is a drive index number from 0 to 7.
676    ///
677    /// # Panics
678    /// Panics if the `drive_index` is above 7.
679    pub fn cartridge_at(&self, index: usize) -> Option<&MicroCartridge> {
680        assert!(index < MAX_DRIVES);
681        self.drives[index].as_ref()
682    }
683    /// Returns a reference to the cartridge that is being currently in use along with its drive index.
684    pub fn cartridge_in_use(&self) -> Option<(usize, &MicroCartridge)> {
685        self.motor_on_drive.and_then(move |drive_on| {
686            let drive_index = (drive_on.get() - 1) as usize;
687            self.drives[drive_index & 7].as_ref()
688            .map(|mc| (drive_index, mc))
689        })
690    }
691
692    fn current_drive(&mut self) -> Option<&mut MicroCartridge> {
693        self.motor_on_drive.and_then(move |drive_on|
694            self.drives[(drive_on.get() - 1) as usize & 7].as_mut()
695        )
696    }
697
698    fn drive_if_current(&mut self, index: usize) -> Option<&mut MicroCartridge> {
699        self.motor_on_drive.and_then(move |drive_on| {
700            let drive_index = (drive_on.get() - 1) as usize;
701            if drive_index == index {
702                self.drives[drive_index & 7].as_mut()
703            }
704            else {
705                None
706            }
707        })
708    }
709}
710
711impl<T: TimestampOps> ZxMicrodrives<T> {
712    fn vts_diff_update(&mut self, timestamp: T) -> u32 {
713        let delta_ts = timestamp.diff_from(self.last_ts);
714        self.last_ts = timestamp;
715        debug_assert!(delta_ts >= 0);
716        delta_ts as u32
717    }
718
719    pub(crate) fn reset(&mut self, timestamp: T) {
720        let delta_ts = self.vts_diff_update(timestamp);
721        self.stop_motor(delta_ts);
722        self.write = false;
723        self.erase = false;
724        self.comms_clk = false;
725    }
726
727    pub(crate) fn update_timestamp(&mut self, timestamp: T) {
728        let delta_ts = self.vts_diff_update(timestamp);
729        let (erase, write) = (self.erase, self.write);
730        if let Some(cartridge) = self.current_drive() {
731            if erase && (!write || delta_ts > 2*BYTE_TS) {
732                cartridge.erase_forward(delta_ts);
733            }
734            else {
735                cartridge.forward(delta_ts);
736            }
737        }
738    }
739    /// This method should be called after each emulated frame.
740    pub(crate) fn next_frame(&mut self, eof_timestamp: T) {
741        self.last_ts = self.last_ts.saturating_sub(eof_timestamp);
742    }
743
744    pub(crate) fn read_state(&mut self, timestamp: T) -> CartridgeState {
745        let delta_ts = self.vts_diff_update(timestamp);
746        let erase = self.erase;
747        if let Some(cartridge) = self.current_drive() {
748            if erase {
749                cartridge.erase_forward(delta_ts);
750            }
751            else {
752                cartridge.forward(delta_ts);
753            }
754            return cartridge.gap_syn_protect();
755        }
756        CartridgeState::default()
757    }
758
759    // called when one of: erase, r/w, comms clk has changed
760    // NOTE: comms_out is just a data
761    pub(crate) fn write_control(
762            &mut self,
763            timestamp: T,
764            erase: bool,
765            write: bool,
766            comms_clk: bool,
767            comms_out: bool
768        )
769    {
770        let delta_ts = self.vts_diff_update(timestamp);
771        if comms_clk != self.comms_clk {
772            self.comms_clk = comms_clk;
773            if comms_clk { // change drive motor
774                if comms_out { // turn on first drive
775                    self.stop_motor(delta_ts);
776                    self.motor_on_drive = NonZeroU8::new(1);
777                }
778                else { // shift drive motor signal
779                    self.motor_on_drive = self.stop_motor(delta_ts).and_then(|n_drive|
780                        if n_drive.get() == 8 {
781                            None
782                        } else {
783                            NonZeroU8::new(n_drive.get() + 1)
784                        }
785                    );
786                }
787                // allow to erase immediately if erase is set (wrongly, but possibly)
788                self.erase = false;
789                self.write = false;
790            }
791        }
792
793        if erase && !self.erase {
794            if let Some(cartridge) = self.current_drive() {
795                cartridge.erase_start(delta_ts);
796            }
797        }
798        else if self.erase {
799            if !write && self.write {
800                if let Some(cartridge) = self.current_drive() {
801                    cartridge.write_end(delta_ts);
802                }
803            }
804            else if (write && !self.write) || (!erase && self.erase) {
805                if let Some(cartridge) = self.current_drive() {
806                    cartridge.erase_forward(delta_ts);
807                }
808            }
809        }
810        self.erase = erase;
811        self.write = write;
812    }
813
814    pub(crate) fn write_data(&mut self, data: u8, timestamp: T) -> u16 {
815        let delta_ts = self.vts_diff_update(timestamp);
816        if self.write && self.erase { // what happens when write is on and erase off?
817            if let Some(cartridge) = self.current_drive() {
818                return cartridge.write_data_forward(data, delta_ts);
819            }
820        }
821        0
822    }
823
824    pub(crate) fn read_data(&mut self, timestamp: T) -> (u8, Option<NonZeroU16>) {
825        let delta_ts = self.vts_diff_update(timestamp);
826        if self.erase {
827            if let Some(cartridge) = self.current_drive() {
828                cartridge.erase_forward(delta_ts);
829            }
830        }
831        if !(self.write || self.erase) {
832            if let Some(cartridge) = self.current_drive() {
833                let (data, delay) = cartridge.read_data_forward(delta_ts);
834                return (data, NonZeroU16::new(delay))
835            }
836        }
837        // we could hang Spectrum here according to ZX Interface 1 IN 0 bug.
838        (!0, HALT_FOREVER_TS)
839    }
840
841    fn stop_motor(&mut self, delta_ts: u32) -> Option<NonZeroU8> {
842        let motor_on_drive = self.motor_on_drive;
843        if self.erase {
844            if let Some(cartridge) = self.current_drive() {
845                cartridge.erase_forward(delta_ts);
846                cartridge.written = None;
847            }
848        }
849        self.motor_on_drive = None;
850        motor_on_drive
851    }
852}
853
854#[cfg(test)]
855mod tests {
856    use core::num::Wrapping;
857    use spectrusty_core::clock::FTs;
858    use spectrusty_core::z80emu::host::TsCounter;
859    use super::*;
860    type UlaTsCounter = TsCounter<FTs>;
861    type TestMicrodrives = ZxMicrodrives<FTs>;
862
863    const EOF: FTs = 69888;
864
865    fn is_eof(tsc: TsCounter<FTs>) -> bool {
866        (*tsc).0 > EOF - 69
867    }
868
869    fn wrap_frame(tsc: &mut TsCounter<FTs>) {
870        while is_eof(*tsc) {
871            **tsc -= Wrapping(EOF);
872        }
873    }
874
875    #[test]
876    fn microdrives_works() {
877        let mut drive: TestMicrodrives = Default::default();
878        assert_eq!(drive.write, false);
879        assert_eq!(drive.erase, false);
880        assert_eq!(drive.comms_clk, false);
881        assert_eq!(drive.motor_on_drive, None);
882        assert_eq!(drive.read_data(10), (!0, NonZeroU16::new(65535)));
883        assert_eq!(drive.last_ts, 10);
884        assert_eq!(drive.write_data(0xAA, 11), 0);
885        assert_eq!(drive.last_ts, 11);
886        assert_eq!(drive.read_state(20), CartridgeState{ gap: false, syn: false, write_protect: false});
887        assert_eq!(drive.last_ts, 20);
888        drive.write_control(30, true, false, true, true);
889        assert_eq!(drive.write, false);
890        assert_eq!(drive.erase, true);
891        assert_eq!(drive.comms_clk, true);
892        assert_eq!(drive.last_ts, 30);
893        assert_eq!(drive.motor_on_drive, NonZeroU8::new(1));
894        drive.write_control(40, true, false, false, false);
895        assert_eq!(drive.write, false);
896        assert_eq!(drive.erase, true);
897        assert_eq!(drive.comms_clk, false);
898        assert_eq!(drive.last_ts, 40);
899        assert_eq!(drive.motor_on_drive, NonZeroU8::new(1));
900        assert_eq!(drive.replace_cartridge(1, MicroCartridge::new(5)).is_none(), true);
901        drive.write_control(40, true, false, true, false);
902        // will write header block
903        drive.write_control(50, true, true, false, false);
904        assert_eq!(drive.write, true);
905        assert_eq!(drive.erase, true);
906        assert_eq!(drive.comms_clk, false);
907        assert_eq!(drive.last_ts, 50);
908        assert_eq!(drive.motor_on_drive, NonZeroU8::new(2));
909        assert_eq!(drive.read_state(60), CartridgeState { gap: false, syn: false, write_protect: false});
910        let mut utsc = UlaTsCounter::from(224);
911        for _ in 0..10 {
912            let delay = drive.write_data(0, (*utsc).0);
913            println!("delay 0x00: {} {:?}", delay, utsc);
914            *utsc += Wrapping(delay as FTs + 21 + 16);
915        }
916        for _ in 0..2 {
917            let delay = drive.write_data(!0, (*utsc).0);
918            println!("delay 0xff: {} {:?}", delay, utsc);
919            *utsc += Wrapping(delay as FTs + 11 + 13);
920        }
921        for i in 1..19 {
922            let delay = drive.write_data(i, (*utsc).0);
923            println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
924            *utsc += Wrapping(delay as FTs + 21 + 16);
925        }
926        let cartridge = drive.current_drive().unwrap();
927        assert_eq!(cartridge.sector_map, [0x00000000, 0x00000000, 0x00000000, 0x00000000,
928                                          0x00000000, 0x00000000, 0x00000000, 0x00000000]);
929        assert_eq!(cartridge.written, NonZeroU16::new(30));
930        assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
931        assert_eq!(&cartridge.sectors[0].data[..], &[!0u8;528][..]);
932        assert_eq!(cartridge.tape_cursor, TapeCursor {
933            cursor: GAP1_START + 2 * BYTE_TS + 21 + 16,
934            sector: 0,
935            secpos: SecPosition::Gap1,
936        });
937        *utsc += Wrapping(36);
938        // writing ends, erasing contitnues
939        drive.write_control((*utsc).0, true, false, false, false);
940        let cartridge = drive.current_drive().unwrap();
941        assert_eq!(cartridge.written, None);
942        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
943        assert_eq!(sector_map[0], true);
944        for valid in &sector_map[1..] {
945            assert_eq!(*valid, false);
946        }
947        assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
948        assert_eq!(&cartridge.sectors[0].data[..], &[!0u8;528][..]);
949        assert_eq!(cartridge.tape_cursor, TapeCursor {
950            cursor: GAP1_START + 3 * BYTE_TS + 21 + 16 + 36,
951            sector: 0,
952            secpos: SecPosition::Gap1,
953        });
954        // erasing gap
955        *utsc += Wrapping(11443);
956        // will write data block
957        drive.write_control((*utsc).0, true, true, false, false);
958        *utsc += Wrapping(48);
959        for _ in 0..10 {
960            let delay = drive.write_data(0, (*utsc).0);
961            println!("delay 0x00: {} {:?}", delay, utsc);
962            *utsc += Wrapping(delay as FTs + 11 + 13);
963        }
964        for _ in 0..2 {
965            let delay = drive.write_data(!0, (*utsc).0);
966            println!("delay 0xff: {} {:?}", delay, utsc);
967            *utsc += Wrapping(delay as FTs + 19);
968        }
969        for i in 1..630u16 { // format block
970            if is_eof(utsc) {
971                drive.update_timestamp((*utsc).0);
972                drive.next_frame(EOF);
973                wrap_frame(&mut utsc);
974            }
975            let delay = drive.write_data(!(i as u8), (*utsc).0);
976            println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
977            *utsc += Wrapping(delay as FTs + 11 + 13);
978        }
979        let cartridge = drive.current_drive().unwrap();
980        assert_eq!(cartridge.written, NonZeroU16::new(641));
981        assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
982        for (i, data) in cartridge.sectors[0].data.iter().enumerate() {
983            assert_eq!(!(i + 1) as u8, *data);
984        }
985        assert_eq!(cartridge.tape_cursor, TapeCursor {
986            cursor: 121224, sector: 0, secpos: SecPosition::Gap2 });
987        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
988        assert_eq!(sector_map[0], true);
989        // writing ends, erasing contitnues
990        drive.write_control((*utsc).0, true, false, false, false);
991        let cartridge = drive.current_drive().unwrap();
992        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
993        assert_eq!(sector_map[0], true);
994        // erasing gap
995        *utsc += Wrapping(8941);
996        // writing ends
997        drive.write_control((*utsc).0, false, false, false, false);
998        let cartridge = drive.current_drive().unwrap();
999        assert_eq!(cartridge.tape_cursor, TapeCursor {
1000            cursor: 827, sector: 1, secpos: SecPosition::Preamble1(5) });
1001        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
1002        assert_eq!(sector_map[0], true);
1003        for valid in &sector_map[1..] {
1004            assert_eq!(*valid, false);
1005        }
1006
1007        fn find_gap_sync(mut utsc: UlaTsCounter, drive: &mut TestMicrodrives) -> UlaTsCounter {
1008            let mut counter = 0;
1009            while !drive.read_state((*utsc).0).gap {
1010                counter += 1;
1011                *utsc += Wrapping(108);
1012                if is_eof(utsc) {
1013                    drive.update_timestamp((*utsc).0);
1014                    drive.next_frame(EOF);
1015                    wrap_frame(&mut utsc);
1016                }
1017            }
1018            println!("counter: {}", counter);
1019            for _ in 1..6 {
1020                *utsc += Wrapping(88);
1021                assert!(drive.read_state((*utsc).0).gap);
1022            }
1023            *utsc += Wrapping(25);
1024            'outer: loop {
1025                for i in 1..=60 {
1026                    let state = drive.read_state((*utsc).0);
1027                    *utsc += Wrapping(38);
1028                    assert!(state.gap);
1029                    if state.syn { break 'outer }
1030                    println!("sync not found: {}", i);
1031                }
1032                assert!(false, "failed to find syn");
1033            }
1034            utsc
1035        }
1036
1037        // find a gap and read header
1038        *utsc += Wrapping(108);
1039        utsc = find_gap_sync(utsc, &mut drive);
1040        let cartridge = drive.current_drive().unwrap();
1041        println!("{:?} {:?}", cartridge.tape_cursor, utsc);
1042        *utsc += Wrapping(92);
1043        for i in 1..=15 {
1044            let (data, delay) = drive.read_data((*utsc).0);
1045            println!("data: {:02x} delay: {:?}  {:?}", data, delay, utsc);
1046            assert_eq!(i, data);
1047            *utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
1048        }
1049        let (data, delay) = drive.read_data((*utsc).0);
1050        println!("data: {:02x} delay: {:?}  {:?}", data, delay, utsc);
1051        assert_eq!(data, !0);
1052        *utsc += Wrapping(delay.unwrap().get() as FTs + 121);
1053
1054        // find a gap and read data
1055        assert_eq!(drive.read_state((*utsc).0).gap, false);
1056        *utsc += Wrapping(10000);
1057        assert_eq!(drive.read_state((*utsc).0).gap, false);
1058        utsc = find_gap_sync(utsc, &mut drive);
1059        let cartridge = drive.current_drive().unwrap();
1060        println!("{:?} {:?}", cartridge.tape_cursor, utsc);
1061        *utsc += Wrapping(92);
1062        for i in 1..=528 {
1063            let (data, delay) = drive.read_data((*utsc).0);
1064            println!("data: {:02x} delay: {:?}  {:?}", data, delay, utsc);
1065            assert_eq!(!i as u8, data);
1066            *utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
1067            if is_eof(utsc) {
1068                drive.update_timestamp((*utsc).0);
1069                drive.next_frame(EOF);
1070                wrap_frame(&mut utsc);
1071            }
1072        }
1073        let (data, delay) = drive.read_data((*utsc).0);
1074        println!("data: {:02x} delay: {:?}  {:?}", data, delay, utsc);
1075        assert_eq!(data, 239);
1076        *utsc += Wrapping(delay.unwrap().get() as FTs + 21);
1077
1078        // find a gap again but this time override data after reading header
1079        *utsc += Wrapping(10800);
1080        utsc = find_gap_sync(utsc, &mut drive);
1081        let cartridge = drive.current_drive().unwrap();
1082        println!("{:?} {:?}", cartridge.tape_cursor, utsc);
1083        *utsc += Wrapping(92);
1084        for i in 1..=15 {
1085            let (data, delay) = drive.read_data((*utsc).0);
1086            println!("data: {:02x} delay: {:?}  {:?}", data, delay, utsc);
1087            assert_eq!(i, data);
1088            *utsc += Wrapping(delay.unwrap().get() as FTs + 21 + 16);
1089        }
1090        // overwrite data sector
1091        assert_eq!(drive.read_state((*utsc).0), CartridgeState {
1092            gap: false, syn: false, write_protect: false});
1093        *utsc += Wrapping(98);
1094        drive.write_control((*utsc).0, true, false, false, false);
1095        *utsc += Wrapping(9524);
1096        drive.write_control((*utsc).0, true, true, false, false);
1097        // now write data
1098        *utsc += Wrapping(47);
1099        for _ in 0..10 {
1100            let delay = drive.write_data(0, (*utsc).0);
1101            println!("delay 0x00: {} {:?}", delay, utsc);
1102            *utsc += Wrapping(delay as FTs + 21);
1103        }
1104        for _ in 0..2 {
1105            let delay = drive.write_data(!0, (*utsc).0);
1106            println!("delay 0xff: {} {:?}", delay, utsc);
1107            *utsc += Wrapping(delay as FTs + 21);
1108        }
1109        for i in 0..531u16 { // format block
1110            if is_eof(utsc) {
1111                drive.update_timestamp((*utsc).0);
1112                drive.next_frame(EOF);
1113                wrap_frame(&mut utsc);
1114            }
1115            let delay = drive.write_data(0xA5^(i as u8), (*utsc).0);
1116            println!("delay: 0x{:02x} {} {:?}", i, delay, utsc);
1117            *utsc += Wrapping(delay as FTs + 21 + 16);
1118        }
1119        let cartridge = drive.current_drive().unwrap();
1120        assert_eq!(cartridge.written, NonZeroU16::new(543));
1121        assert_eq!(cartridge.sectors[0].head, [1u8,2,3,4,5,6,7,8,9,10,11,12,13,14,15]);
1122        for (i, data) in cartridge.sectors[0].data.iter().enumerate() {
1123            assert_eq!(0xA5^(i as u8), *data);
1124        }
1125        assert_eq!(cartridge.tape_cursor, TapeCursor {
1126            cursor: 105361, sector: 0, secpos: SecPosition::Gap2 });
1127        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
1128        assert_eq!(sector_map[0], true);
1129        // writing ends, erasing continues for short time
1130        *utsc += Wrapping(18);
1131        drive.write_control((*utsc).0, true, false, false, false);
1132        // writing ends
1133        *utsc += Wrapping(112);
1134        drive.write_control((*utsc).0, false, false, false, false);
1135        let cartridge = drive.current_drive().unwrap();
1136        assert_eq!(cartridge.tape_cursor, TapeCursor {
1137            cursor: 105653, sector: 0, secpos: SecPosition::Gap2 });
1138        let sector_map = cartridge.sector_map.view_bits::<LocalBits>();
1139        assert_eq!(sector_map[0], true);
1140        for valid in &sector_map[1..] {
1141            assert_eq!(*valid, false);
1142        }
1143        // turn all motors off
1144        *utsc += Wrapping(1000);
1145        if is_eof(utsc) {
1146            drive.update_timestamp((*utsc).0);
1147            drive.next_frame(EOF);
1148            wrap_frame(&mut utsc);
1149        }
1150        for i in 0..7 {
1151            assert_eq!(drive.motor_on_drive, NonZeroU8::new(i + 2));
1152            drive.write_control((*utsc).0, true, false, true, false);
1153            *utsc += Wrapping(3500);
1154            drive.write_control((*utsc).0, true, false, false, false);
1155            *utsc += Wrapping(3500);
1156        }
1157        assert_eq!(drive.motor_on_drive, None);
1158    }
1159}