FDS emulation attempt and questions

This is an archive of a topic from NESdev BBS, taken in mid-October 2019 before a server upgrade.
View original topic
FDS emulation attempt and questions
by on (#91302)
Last night I struggled implementing FDS support in my emulator.
I eventually got it working (as in it loads games successfully), but I am not happy with my implementation.*

This is my current implementation. (Sound emulation parts are omitted for brevity.) Though it works, search for instances of "WRONG" in the source.

http://pastebin.ca/2127813

Questions. (For now considering only reading operation mode.)

– Does the disk reading position advance when disk IRQ is disabled? Logically it should, because the reading position is an aspect of the disk system, not the 2C33 which produces the IRQs, but I could not get my code working without that hack.

– Can $4030 bit 1 indicate that data was transferred from/to disk even when the IRQ is disabled? I.e. could you possibly transfer data from/to disk with IRQ disabled, by just polling $4030 to see when $4024 can be updated or $4031 can be read?

– In FCEU, clearing $4025 bit 6, when it was previously set, and bit 4 is zero, decrements the reading position by two bytes. I could not get my code working unless I did that too. Does that really happen on real FDS? If not, what should I do instead?

– In general, the flowchart of my tick() is based half on experimentation (when disksys.rom stops giving error messages) and half on documentation. I would like it to be correct.


*) Not the least because it is more than third of the length of the original version of my entire NES emulator ;)

by on (#91326)
iirc emulating the FDS correctly results in non-working games because the game dumps themselves are incorrect (missing GAP sections and some other crucial items).

Emulators either have to employ hacks or attempt to reconstruct the missing sections in order for it to be operable.

EDIT: some related threads I dug up:
http://nesdev.com/bbs/viewtopic.php?t=3372
http://nesdev.com/bbs/viewtopic.php?t=738

by on (#91330)
Disch wrote:
iirc emulating the FDS correctly results in non-working games because the game dumps themselves are incorrect (missing GAP sections and some other crucial items).

Emulators either have to employ hacks or attempt to reconstruct the missing sections in order for it to be operable.


Allright, thanks. I got it working nicely now, by reading in the disk image in a format that includes the gaps, the start codes and the CRC (not calculated).
Haven't tested writing yet, but at least the reading part now uses a simple and stupid model of operation, with no awkward hacks anywhere, and it works.

I will post the code soonish.

In the meantime, the answers to my own questions:
-- Yes, the disk reading position does advance regardless of whether IRQs are enabled or not. However, the areas of data that are being accessed while the BIOS has the disk IRQ disabled, are usually the CRC (two bytes after the disk file that are not stored in the FDS image), and the "gap", which is a blank region of varying length separating files on the disk (also not stored in the FDS image), and the one-bit that signals the beginning of the new file.
-- Clearing the $4025 bit 6 indicates that the RAM adapter connected to the FDS should set the internal flag (shown at $4032 bit 1) that indicates that the current disk position is outside a file. Setting the bit again makes the adapter wait for the next nonzero bit read from the disk (which is not in the FDS file), before clearing the flag again and continuing transferring bytes from/to the disk. If the bit is cleared during a write, a stream of zero-bytes will be written on the disk as long as the bit is clear. Clearing the bit also clears the CRC accumulator. As long as $4025 bit 6 is clear, the adapter will not notice one-bits on the disk even if it's currently scanning for them the first time.

Answers to questions that I did not ask:
-- Setting the $4025 bit 4 (handle CRC) during reading is done by the FDS BIOS in order to verify the CRC read from the disk. Without the bit set, it would accumulate the CRC instead. After verifying, the bit 4 in $4030 is set if there was a CRC error. During writing, the bit 4 of $4025 causes the contents of the CRC accumulator to be written to the disk. However, when the BIOS is not interested in the validity of the CRC, it will not set the flag after the end of the file, so you should not depend on it. Also one should note that setting the CRC flag does not signal the adapter that the current position is within a gap. After the CRC has been read/written, the adapter will still continue to transfer data bytes, even if it is within the gap.
-- The gap in the between of files should be at least 5 milliseconds long (8950 cycles or 480 bits), or the FDS BIOS will be unable to load files. 976 bits is typical. There is no upper limit, as long as the data fits on the disk. (TODO: Check whether the gap should be longer when the file is to be loaded into VRAM. TODO: Check whether the minimum is different when writing.)
-- The gap in the beginning of the disk (assuming that the FDS is immediately operational when $4032 bit 2 is cleared, which is not true on the real FDS) should be at least 271 milliseconds long (485520 cycles or 26150 bits), or the FDS BIOS will be unable to load files. 28300 bits is typical. There is no upper limit, as long as the data fits on the disk.
-- The time to read 8 bits from the disk must be at least 106 CPU cycles, or the FDS BIOS will be unable to load files. 149 cycles is typical. There is no upper limit.
-- The end of the gap is signalled by a single one-bit. In terms of bytes, you can consider it a $80 byte (this is what gets factored into the CRC calculation as well).
-- The BIOS does not check the actual values returned in $4031 while it thinks it's reading the CRC. The values are still indicated there (I think), so at least theoretically a game could check them. In practise, you can just pass dummy values.

Unanswered questions:
-- Is there a delay to detecting an inserted disk?
-- What kind of delay is there in powering up the motor? I.e. how many cycles from when the ($4025 & 0x03) = 1 to when $4032 bit 1 is cleared.
-- What happens if the disk is ejected while scanning?
-- What happens if the motor is on while the disk is inserted?
-- Whether the function of $4030 bit 1 is set to indicate the completion of 8 bits of data transfer even if $4025 bit 7 is clear, on the real FDS, is still unknown. (No known testcase. The BIOS never polls $4030 when $4025 bit 7 is clear.)
-- Does the byte at $4031 indicate in real time what was read from the disk when $4025 bit 6 is clear, even though such data-assembly is not signalled through $4030 bit 1? (No known testcase. The BIOS never polls $4031 when $4025 bit 6 is clear or when $4032 bit 1 is set.)

by on (#91345)
There is no delay on disk insert. There is a switch that is engaged when the disk is set. If it's ejected then you just unset the disk and the BIOS will throw an error.

The stuff you mentioned about Gaps and CRCs that are missing from the disk really annoyed me when looking at adding FDS support to my emulator. Most games though, atleast official games, use the BIOS for all disk operations. It was easy to just HLE these functions. Works just as well if not better since the time to complete is instant or atleast fully in your control.

by on (#91350)
Sorry to highhack this thread but there is something I really wonder about the FDS.

Normally, the purpose to having a CRC is to compute the CRC after the transmission of data, and compare it with the CRC stored in the transmitted data.
In our case that would be the 6502 computing the CRC of a FDS file when loading it and compare it with the CRC stored on the disc.

But since the 6502 ingores the CRC, what's the point to even have the CRC stored on the disc ? Just having it stored on the disc (and ignored by the CPU) won't detect transmission errors. Or is there something I missed ?

by on (#91351)
Bregalad wrote:
But since the 6502 ingores the CRC, what's the point to even have the CRC stored on the disc ? Just having it stored on the disc (and ignored by the CPU) won't detect transmission errors. Or is there something I missed ?

The RAM adapter will calculate and verify the CRC. (If $4025 bit 4 is set at the end of file.) The CPU can check whether the RAM adapter detected a CRC error by reading $4032 bit 4 after the CRC has been read.
It will detect errors in stored content on the disk, and errors in retrieval of the file data from the disk.
Checking the CRC error is of course voluntary, but the CPU does not need to calculate it, because the hardware does.

by on (#91352)
Oh thanks !
So in fact it's some chip in the RAM adapter who handles serialisation of bytes as well as CRC calculations.

It's weird I don't understand why Nintendo choose to put so many electronics in it when this could have been done with the 6502 instead but oh well.... that's how it is.

I think what's so messy with current FDS documentation is that it's never clear what is in the BIOS, what is done electronically in the RAM adapter and what is done electrically/electronically in the disk drive. This should be clarified and wiki-fied.

Another think that makes me wondering is the varying gap sizes. Since the gap size can be so random, and there is 2 gaps for each files (one before block 3 and one before block 4), the disc capacity is probably not that fixed number (65k or whatever it was) but will decrease as there is more files, as the gaps will have to be stored on the disc as well. In that regard the .fds format is pretty unpractical (unless I missed something) since the disk may be full before the disk side on the image is full.

by on (#91353)
Here is the source code of my newer version. It also includes the audio synthesizer and the disk image parser. Because of some internal changes in the emulator it is no longer so self-contained unfortunately.

http://pastebin.ca/2128362

by on (#91371)
Bregalad wrote:
Another think that makes me wondering is the varying gap sizes. Since the gap size can be so random, and there is 2 gaps for each files (one before block 3 and one before block 4), the disc capacity is probably not that fixed number (65k or whatever it was) but will decrease as there is more files, as the gaps will have to be stored on the disc as well. In that regard the .fds format is pretty unpractical (unless I missed something) since the disk may be full before the disk side on the image is full.


That is a good point, but if you look at FDS images, there is always a fairly good amount of "free space". The amount of bits used in gaps easily fits into this ofcourse.

by on (#91372)
Every emulator I know (with FDS driver) uses a couple of hacks for specific games. I don't know even if such stuff is useful for writing or improving my FDS driver.

by on (#91380)
Bregalad wrote:
It's weird I don't understand why Nintendo choose to put so many electronics in it when this could have been done with the 6502 instead

Three reasons:
  • They didn't have Mr. Wozniak or other Apple engineers on their team who were skilled in doing with software what other manufacturers felt the need to do in hardware.
  • CRC16 costs 66 cycles per byte. I use this byte-at-a-time CRC (with the alternative ending so I can use (d),Y addressing) as a PRNG in some of my newer projects, and it's a lot faster than the bit-at-a-time CRC32 that I had used in LJ65, Concentration Room, and Thwaite, but I still don't call it once per byte.
  • At some scale, the biggest cost of an ASIC is the pins, especially if you're already reserving the bulk of your die size for RAM. The circuit to calculate a CRC in hardware is tiny by comparison: just one more gate than the polynomial counter in the noise channels or in the CIC's program counter.

by on (#91384)
MottZilla wrote:
There is a switch that is engaged when the disk is set. If it's ejected then you just unset the disk and the BIOS will throw an error.

That much was clear -- thanks for the reply anyway --, but I am wondering about the physical function of the drive. Does the motor still spin if signalled to do so, when the disk is out? What if the motor is already running (or instructed to be running) while the disk is inserted? How does it interact with finding the disk data beginning?
And other questions I asked.

by on (#91385)
The motor is definitely disabled when you press the eject button, as nothing prevents you to do that during a load for example.

This is probably one of the only ways to have the reading head rest "in the middle of nowhere" instead of being at it's rest position at the end of the disc.

by on (#91386)
One thing I wonder about the CRC calculation.

The BIOS sets the "check CRC" bit _after_ the first CRC byte has already been read (and presumably, accumulated into the CRC sum, rather than verified). How does that work?

;checks the CRC OK bit at the end of a block
EndOfBlkRead: JSR XferByte; first CRC byte
$E709 LDX #$28; premature file end error #
$E70B LDA $4030
$E70E AND #$40; check "end of disk" status
$E710 BNE XferFail
$E712 LDA $FA
$E714 ORA #$10; set while processing block end mark (CRC)
$E716 STA $FA
$E718 STA $4025
$E71B JSR XferByte; second CRC byte
$E71E LDX #$27; CRC fail error #
$E720 LDA $4030
$E723 AND #$10; test CRC bit
$E725 BNE XferFail
$E727 BEQ ChkDiskSet

Basically, it looks like the flowchart is like this.
Assumptions:
– our emulation model handles 8 bits at time instead of bit per bit.
– the device has motor running and scanning, and the reaching of disk beginning has been confirmed.

[EDIT: See posts on next page for corrected algorithm]

  • after receiving 8 bits in READ MODE:

    If CRC-verify flag was not set, accumulate CRC with the contents of ShiftReg
    If CRC-verify flag was set, XOR the ShiftReg into the CRC low 8 bits

    Fetch the current disk byte into ShiftReg
    If CRC-verify flag was set, XOR the ShiftReg into the CRC high 8 bits
    (Note that $4032 bit 4 (CRC check result) is a bit-wise OR of all bits of the CRC at all times. If the CRC accumulator is zero, the bit is 0. Otherwise, it is 1.)

    Reload $4031 with the contents of ShiftReg
    If $4025 bit 6 is 0, then clear internal flag: GapCovered=0
    If GapCovered=1, then
    Set $4032 bit 1 (to indicate $4031 is filled with data), and signal the IRQ if allowed
    Move to next byte on disk
    If ShiftReg ≠ 0 AND $4025 bit 6 is 1, then set internal flag: GapCovered=1
    Wait ~149 cycles, and repeat
  • when writing 8 bits in WRITE MODE:

    If CRC-verify flag was SET, put the CRC low bits into ShiftReg and shift the CRC right by 8 bits, filling the top with zerobits.
    Store the value from ShiftReg into the disk
    Reload ShiftReg from $4024
    If $4025 bit 6 is 0, then clear internal flag: GapCovered=0
    If GapCovered=1, then
    Set $4032 bit 1 (to indicate $4024 is ready to accept data), and signal the IRQ if allowed
    Move to next byte on disk
    If ShiftReg ≠ 0 AND $4025 bit 6 is 1, then set internal flag: GapCovered=1
    Wait ~149 cycles, and repeat


The moss-colored parts in this listing do the gap-end detection.
The blue parts do CRC.

Disclaimer: This is my analysis based on how the BIOS seems to expect the disk drive to work, and which implementation makes it work fine. It is not verified on actual console.
It is worth noting in READING, that the interval between BIOS setting the CRC-check flag and checking the result of the check is just one disk-byte long. This implies that the adapter is supposed to perform the CRC check of 16 bits during the time of 8 disk bits.
It is also worth noting in WRITING, that the CRC flag is set some time AFTER the IRQ for the first CRC byte is served. This means that you cannot write data into the disk immediately when $4024 is written into (or when IRQ is served), because at that point you do not yet know whether the actual data to be written into the disk is going to be a CRC.
I think, the CRC shifting in writing also clears the CRC register rather than just rotating it. It will continue to write zero-bytes (gap) as long as writing is enabled and the motor is turned on.

Questions: When game / BIOS does not do any I/O writes, does the drive automatically return to the beginning of the disk (and continue running from there) upon reaching the end, or does it shut down? It seems like the BIOS is leaving the drive running after loading all files necessary for launching the game, and this screws up Zelda 2 in my emulator currently: When Zelda 2 begins accessing the disk at the title screen, the disk head is not in the beginning, but somewhere in the middle.

by on (#91388)
Quote:
_after_ the first CRC byte has already been read (and presumably, accumulated into the CRC sum, rather than verified). How does that work?

Apparently the mathematical structure of CRC is such that appending the correct CRC value to a bitstream will cause the running CRC value to return to the starting value (e.g. $0000 or $FFFF).

by on (#91390)
tepples wrote:
Apparently the mathematical structure of CRC is such that appending the correct CRC value to a bitstream will cause the running CRC value to return to the starting value (e.g. $0000 or $FFFF).

At least the algorithm described at http://nesdev.com/FDS%20techni ... erence.txt does not possess such property, in either endianess.

EDIT: Oh, wait, it does, when you append two dummy zero-bytes to the stream before the CRC when encoding, and encode in little-endian. Then the reading algorithm becomes slightly simpler, though the writing algorithm becomes difficult:
  • after receiving 8 bits in READ MODE:

    Fetch the current disk byte into ShiftReg
    Accumulate CRC with the contents of ShiftReg
    (Note that $4032 bit 4 (CRC check result) is a bit-wise OR of all bits of the CRC at all times. If the CRC accumulator is zero, the bit is 0. Otherwise, it is 1.)

    If $4025 bit 6 is 0, then clear internal flag: GapCovered=0
    If GapCovered=1, then
    Set $4032 bit 1 (to indicate $4031 is filled with data), and signal the IRQ if allowed; Then put contents of ShiftReg into $4031
    If the current disk byte was nonzero AND $4025 bit 6 is 1, then set internal flag: GapCovered=1
    Move to next byte on disk
    Wait ~149 cycles, and repeat
  • when writing 8 bits in WRITE MODE:

    if CRC-verify flag was PREVIOUSLY UNSET, Accumulate CRC with the contents of ShiftReg
    If CRC-verify flag is SET, but was PREVIOUSLY UNSET, suddenly accumulate CRC twice with value 0x00.
    If CRC-verify flag is SET, put the CRC low bits into ShiftReg and shift the CRC right by 8 bits, filling the top with zerobits.

    Store the value from ShiftReg into the disk
    Put value 0x00 into ShiftReg
    If $4025 bit 6 is 0, then clear internal flag: GapCovered=0
    If GapCovered=1, then
    Set $4032 bit 1 (to indicate $4024 is ready for next byte), and signal the IRQ if allowed. Then, if CRC-verify flag is unset, put contents of $4024 into ShiftReg.
    If the current disk byte was nonzero AND $4025 bit 6 is 1, then set internal flag: GapCovered=1
    Wait ~149 cycles, and repeat
  • Or merged:

    If READING, fetch the current disk byte into ShiftReg
    if CRC-verify flag was PREVIOUSLY UNSET, Accumulate CRC with the contents of ShiftReg
    If WRITING, and CRC-verify flag is SET, but was PREVIOUSLY UNSET, suddenly accumulate CRC twice with value 0x00.
    If WRITING, and CRC-verify flag is SET, put the CRC low bits into ShiftReg and shift the CRC right by 8 bits, filling the top with zerobits.

    if WRITING, Store the value from ShiftReg into the disk
    If WRITING, Put value 0x00 into ShiftReg
    If $4025 bit 6 is 0, then clear internal flag: GapCovered=0
    If GapCovered=1, then
    Set $4032 bit 1 (to indicate that next CPU-adapter data transfer can happen), and signal the IRQ if allowed. Then put contents of ShiftReg into $4031, and if CRC-verify flag is unset, put contents of $4024 into ShiftReg.
    If the current disk byte was nonzero AND $4025 bit 6 is 1, then set internal flag: GapCovered=1
    Wait ~149 cycles, and repeat


(The write algorithm needs reviewing)

by on (#91482)
To switch a disk side, is the "formula" disk_data[active_side*65550] valid ?

by on (#91483)
Zepper wrote:
To switch a disk side, is the "formula" disk_data[active_side*65550] valid ?

When the disk_data contains the data read from FDS file (excluding "FDS\x1A" header) without any conversion (such as adding gaps & start marks & crc necessary for a low-level emulation), yes.
EDIT: Except the number has to be 65500, not 65550.

by on (#91491)
Bisqwit wrote:
EDIT: Except the number has to be 65500, not 65550.


Yup, my bad. :) Thanks.

by on (#91528)
Here is my refined algorithm on low-level FDS DISK I/O emulation in pseudocode. I have tested both the reading and writing for functional correctness (games I have tried so far do not complain, and the disk contents read/written match their apparent intention perfectly). It does not mean it is proven correct, though. There may be another game that does not like it.
Code:
Register bits:
$4025 bit 0: DontStopMotor
$4025 bit 1: DontScanMedia
$4025 bit 2: DontWrite
$4025 bit 4: TransmitCRC
$4025 bit 6: InData
$4025 bit 7: DiskIRQ_enable
$4030 bit 0: TIMER_IRQ
$4030 bit 1: DataReady
$4030 bit 4: CRC[0..15] != 0x0000
$4030 bit 6: EndOfDisk
$4032 bit 0: DiskNotInserted
$4032 bit 1: DiskNotInserted || !Scanning
$4032 bit 2: DiskNotInserted
$4031 8 bits: ReadData
$4024 8 bits: WriteData
Writing or reading any of these registers does no immediate action aside from reading/storing the relevant bits in memory, and possibly reprogramming nametable mirroring and/or acknowledging an IRQ.

Internal bits:
..... bit .: DiskNotInserted   (0 if disk is inserted, 1 otherwise)
..... bit .: Scanning          (used internally, indicates whether the motor has awakened)
..... bit .: GapCovered        (used internally, indicates whether the end of gap was found)
..... bit .: PreviousCRCflag   (used internally, stores last value of TransmitCRC)
..... bit .: DoIRQ             (used internally as temporary)
..... 8 bits  : ShiftRegister  (used internally to store current disk-transfer byte)
..... 16 bits : CRCaccumulator (used internally for CRC calculation)
..... integer : DiskPosition   (indicates current byte-position in the raw disk data)
..... integer : DELAY          (indicates amount of remaining wait in CPU cycles)

FDS algorithm (invoked on every CPU cycle):
   -- Do nothing if motor is stopped, or if disk is not inserted.
   IF DontStopMotor = 0 OR DiskNotInserted = 1,
      Scanning  := 0
      EndOfDisk := 1
      END CYCLE
   -- At this point, DontStopMotor=1, and DiskNotInserted=0.
   -- Do nothing if scanning is not started and has not been started.
   IF DontScanMedia = 1 AND Scanning = 0,
      END CYCLE
   -- At this point, DontStopMotor=1, and DiskNotInserted=0, and (DontScanMedia=0 OR Scanning=1).
   -- So motor and scanning are permitted. Are they not started yet?
   IF EndOfDisk = 1,
      -- Start with delay.
      DELAY        := 50000   (arbitrary number)
      EndOfDisk    := 0
      -- Set internal variables for starting from the beginning of disk.
      DiskPosition := 0
      GapCovered   := 0
   -- Elapse any pending delay (read-delay, motor spin-up delay)
   IF DELAY > 0,
      DECREMENT DELAY
      END CYCLE

   -- Motor is now running and disk is being scanned. Set the flag thus indicating,
   -- so that we don't get knocked back into halt, when the game sets DontScanMedia=1.
   -- Also this reports in $4032 bit 1.
   -- TODO: Figure out what happens on real hardware, if you set DontScanMedia=1
   --       a few cycles after setting DontScanMedia=0, but before $4032 bit 1 indicates Scanning=1.
   --       In this emulator, such action resets the disk drive without starting scanning,
   --       but there is a possibility that the real hardware would ignore the flag setting.
   Scanning := 1

   -- At this point, DontStopMotor=1, and DiskNotInserted=0, and Scanning=1, and DontScanMedia=irrelevant.
   -- READING?
   IF DontWrite = 1,
      -- Begin read-cycle:
      READ DISK  --> ShiftReg
      IF PreviousCRCflag = 0,
         UPDATE CRCaccumulator WITH ShiftReg
      -- End read-cycle:
      DoIRQ        := DiskIRQ_enable
      -- If InData = 0, we are to think we are in a gap.
      IF InData = 0,
         GapCovered     := 0
         CRCaccumulator := 0
      ELSE, IF ShiftReg != 0x00,
         -- InData = 1. If we found a nonzero byte, we're no longer in the gap.
         GapCovered     := 1
         DoIRQ          := 0  -- Don't signal IRQ for data-begin byte
      -- If we are not in gap, signal the read data.
      IF GapCovered = 1,
         DataReady      := 1
         ReadData       := ShiftReg
         If DoIRQ, SIGNAL IRQ

   -- WRITING?
   IF DontWrite = 0,
      IF TransmitCRC = 0,
         -- If we are not writing CRC, signal the readiness for data.
         -- TODO: Figure out whether DataReady should also be signalled
         --       when InData=0. In this emulator, it will.
         --       The FDS BIOS never polls $4030 when InData=0, nor
         --       never has DiskIRQ_enable=1 when InData=0, but a game
         --       may do direct disk access differently.
         DataReady      := 1
         ShiftReg       := WriteData
         DoIRQ          := DiskIRQ_enable
         If DoIRQ, SIGNAL IRQ

      -- ShiftReg now contains the value of $4025 that was
      -- given to us in the beginning of the write cycle.
      IF InData = 0,
         -- If InData = 0, we are to produce a gap.
         ShiftReg       := 0x00
      IF TransmitCRC = 0,
         UPDATE CRCaccumulator WITH ShiftReg
         -- TODO: Figure out what happens on real hardware if you have
         --       InData=0 and TransmitCRC=1 simultaneously.
         --       Will the CRC (first two bytes thereof anyway) be written?
         --       In this emulator, they will.
      ELSE,
         IF PreviousCRCflag = 0,
            -- If CRC was RAISED, finish the CRC calculation with TWO zero-bytes.
            UPDATE CRCaccumulator WITH 0x00
            UPDATE CRCaccumulator WITH 0x00
         ShiftReg       := CRCaccumulator low 8 bits
         DOWNSHIFT CRCaccumulator by 8 bits
      WRITE DISK <-- ShiftReg
      -- These flags may be useless at the moment:
      GapCovered   := 0
      
   -- After processing this byte, go to next byte.
   PreviousCRCflag := TransmitCRC
   INCREMENT DiskPosition
   IF DiskPosition >= DISK_SIZE,
      -- End of disk reached. Stop motor.
      -- I don't know if this is correct, or if the motor should rewind automatically, or something.
      -- In any case, this will also set the EndOfDisk flag at next cycle.
      DontStopMotor := 0
   ELSE,
      DELAY := 149
   END CYCLE


Updating the 16-bit CRC:
Code:
for(unsigned n = 0x01; n <= 0x80; n = n << 1)
{
  #if 0
    /* OPTION 1: CRC is stored in an at least 17-bit variable */
    if(databyte & n) CRC = CRC | 0x10000;
    if(CRC & 1)      CRC = CRC ^ 0x10810;
    CRC = CRC >> 1;
  #else
    /* OPTION 2: CRC is exactly 16 bit, requires temporary status flag */
    bool c = (CRC & 1);
    CRC = CRC >> 1;
    if(c)            CRC = CRC ^ 0x8408;
    if(databyte & n) CRC = CRC ^ 0x8000;
  #endif
    /*
          6502 Assembler equivalent without tables, for reference only:
             lda databyte
             ldx #8
           - lsr a
             ror crc_hi
             ror crc_lo
             bcc +
              tay
               lda crc_lo : eor #$08 : sta crc_lo
               lda crc_hi : eor #$84 : sta crc_hi
              tya
           + dex
             bne -
          Assuming zeropage-addressing, costs 164..323 cycles.
    */
}

Note that when the CRC is 0 and the databyte is 0, the outcome CRC is also 0. Also, CRC( string + CRC(string + "\x00\x00") ) = 0. This trait happens even if the 0x8408 is changed to some other arbitrary 16-bit integer.