Add tests for the Disk II card (#133)
* Add tests for the DiskII card This change adds basic read tests for nibble-based disks for the DiskII card and fixes a few minor errors. These tests are in preparation for refactoring. * Add write tests These are some basic tests of writing to nibble disks. In the process, one minor bug was found, fixed and documented. * Fix the write tests I misinterpreted something from Sather and thought that the high bit had to be set on the data for writing to happen at all. This is not true. Instead, there is a flux transition every time the high bit is set as the data is left-shifted out of the data register. The erroneous test has been removed. At the same time, I finally understand what `skip` does and documented that. * Add tests for saving and restoring Disk II state These are not exhaustive tests, but they ensure that some basic state is saved and restored.
This commit is contained in:
parent
efe8594845
commit
5b5655b70e
|
@ -336,6 +336,11 @@ export default class DiskII implements Card<State> {
|
|||
dirty: false,
|
||||
}];
|
||||
|
||||
/**
|
||||
* When `1`, the next nibble will be available for read; when `0`,
|
||||
* the card is pretending to wait for data to be shifted in by the
|
||||
* sequencer.
|
||||
*/
|
||||
private skip = 0;
|
||||
/** Last data written by the CPU to card softswitch 0x8D. */
|
||||
private bus = 0;
|
||||
|
@ -520,6 +525,18 @@ export default class DiskII implements Card<State> {
|
|||
* tracks by activating two neighboring coils at once.
|
||||
*/
|
||||
private setPhase(phase: Phase, on: boolean) {
|
||||
// According to Sather, UtA2e, p. 9-12, Drive On/Off and Drive
|
||||
// Select:
|
||||
// Turning a drive on ($C089,X) [...]:
|
||||
// 1. [...]
|
||||
// 5. [...] enables head positioning [...]
|
||||
//
|
||||
// Therefore do nothing if no drive is on.
|
||||
if (!this.on) {
|
||||
this.debug(`ignoring phase ${phase}${on ? ' on' : ' off'}`);
|
||||
return;
|
||||
}
|
||||
|
||||
this.debug(`phase ${phase}${on ? ' on' : ' off'}`);
|
||||
if (on) {
|
||||
this.cur.track += PHASE_DELTA[this.cur.phase][phase] * 2;
|
||||
|
@ -725,6 +742,9 @@ export default class DiskII implements Card<State> {
|
|||
}
|
||||
|
||||
getState(): State {
|
||||
// TODO(flan): This does not accurately save state. It's missing
|
||||
// all of the state for WOZ disks and the current status of the
|
||||
// bus.
|
||||
const result = {
|
||||
drives: [] as DriveState[],
|
||||
skip: this.skip,
|
||||
|
@ -868,6 +888,10 @@ export default class DiskII implements Card<State> {
|
|||
}
|
||||
|
||||
initWorker() {
|
||||
if (!window.Worker) {
|
||||
return;
|
||||
}
|
||||
|
||||
this.worker = new Worker('dist/format_worker.bundle.js');
|
||||
|
||||
this.worker.addEventListener('message', (message: MessageEvent<FormatWorkerResponse>) => {
|
||||
|
|
|
@ -0,0 +1,503 @@
|
|||
/** @jest-environment jsdom */
|
||||
import Apple2IO from 'js/apple2io';
|
||||
import DiskII, { Callbacks } from 'js/cards/disk2';
|
||||
import CPU6502 from 'js/cpu6502';
|
||||
import { VideoModes } from 'js/videomodes';
|
||||
import { mocked } from 'ts-jest/utils';
|
||||
import { BYTES_BY_SECTOR_IMAGE, BYTES_BY_TRACK_IMAGE } from '../formats/testdata/16sector';
|
||||
|
||||
jest.mock('js/apple2io');
|
||||
jest.mock('js/videomodes');
|
||||
|
||||
type Phase = 0 | 1 | 2 | 3; // not exported from DiskII
|
||||
|
||||
const STEPS_PER_TRACK = 4;
|
||||
const PHASES_PER_TRACK = 2;
|
||||
|
||||
function setTrack(diskII: DiskII, track: number) {
|
||||
const initialState = diskII.getState();
|
||||
initialState.drives[0].track = track * STEPS_PER_TRACK;
|
||||
initialState.drives[0].phase = (track * PHASES_PER_TRACK) % 4 as Phase;
|
||||
diskII.setState(initialState);
|
||||
}
|
||||
|
||||
function setWriteProtected(diskII: DiskII, isWriteProtected: boolean) {
|
||||
const initialState = diskII.getState();
|
||||
initialState.drives[0].readOnly = isWriteProtected;
|
||||
diskII.setState(initialState);
|
||||
}
|
||||
|
||||
describe('DiskII', () => {
|
||||
const mockApple2IO = new Apple2IO({} as unknown as CPU6502, {} as unknown as VideoModes);
|
||||
const callbacks: Callbacks = {
|
||||
driveLight: jest.fn(),
|
||||
dirty: jest.fn(),
|
||||
label: jest.fn(),
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
jest.resetAllMocks();
|
||||
});
|
||||
|
||||
it('is constructable', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
expect(diskII).not.toBeNull();
|
||||
});
|
||||
|
||||
it('round-trips the state when there are no changes', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
const state = diskII.getState();
|
||||
diskII.setState(state);
|
||||
|
||||
expect(diskII.getState()).toEqual(state);
|
||||
});
|
||||
|
||||
it('round-trips the state when there are changes', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
diskII.setBinary(2, 'BYTES_BY_SECTOR', 'po', BYTES_BY_SECTOR_IMAGE);
|
||||
|
||||
const state = diskII.getState();
|
||||
// These are just arbitrary changes, not an exhaustive list of fields.
|
||||
state.drive = 2;
|
||||
state.skip = 1;
|
||||
state.latch = 0x42;
|
||||
state.on = true;
|
||||
state.writeMode = true;
|
||||
state.drives[1].tracks[14][12] = 0x80;
|
||||
state.drives[1].head = 1000;
|
||||
state.drives[1].phase = 3;
|
||||
diskII.setState(state);
|
||||
|
||||
expect(diskII.getState()).toEqual(state);
|
||||
});
|
||||
|
||||
it('calls all of the callbacks when state is restored', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
jest.resetAllMocks();
|
||||
|
||||
const state = diskII.getState();
|
||||
diskII.setState(state);
|
||||
|
||||
expect(callbacks.driveLight).toHaveBeenCalledTimes(2);
|
||||
expect(callbacks.driveLight).toHaveBeenCalledWith(1, false);
|
||||
expect(callbacks.driveLight).toHaveBeenCalledWith(2, false);
|
||||
|
||||
expect(callbacks.label).toHaveBeenCalledTimes(2);
|
||||
expect(callbacks.label).toHaveBeenCalledWith(1, 'BYTES_BY_TRACK', undefined);
|
||||
expect(callbacks.label).toHaveBeenCalledWith(2, 'Disk 2', undefined);
|
||||
|
||||
expect(callbacks.dirty).toHaveBeenCalledTimes(2);
|
||||
expect(callbacks.dirty).toHaveBeenCalledWith(1, true);
|
||||
expect(callbacks.dirty).toHaveBeenCalledWith(2, false);
|
||||
});
|
||||
|
||||
describe('drive lights', () => {
|
||||
it('turns on drive light 1 when the motor is turned on', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
|
||||
expect(callbacks.driveLight).toBeCalledTimes(1);
|
||||
expect(callbacks.driveLight).toBeCalledWith(1, true);
|
||||
});
|
||||
|
||||
it('turns off drive light 1 when the motor is turned off', () => {
|
||||
jest.useFakeTimers();
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
mocked(callbacks.driveLight).mockReset();
|
||||
|
||||
diskII.ioSwitch(0x88); // turn off the motor
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(callbacks.driveLight).toBeCalledTimes(1);
|
||||
expect(callbacks.driveLight).toBeCalledWith(1, false);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('turns on drive light 2 when drive 2 is selected and the motor is turned on', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
|
||||
diskII.ioSwitch(0x8B); // select drive 2
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
|
||||
expect(callbacks.driveLight).toBeCalledTimes(1);
|
||||
expect(callbacks.driveLight).toBeCalledWith(2, true);
|
||||
});
|
||||
|
||||
it('turns off drive light 2 when drive 2 is selected and the motor is turned off', () => {
|
||||
jest.useFakeTimers();
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.ioSwitch(0x8B); // select drive 2
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
mocked(callbacks.driveLight).mockReset();
|
||||
|
||||
diskII.ioSwitch(0x88); // turn off the motor
|
||||
|
||||
jest.runAllTimers();
|
||||
expect(callbacks.driveLight).toBeCalledTimes(1);
|
||||
expect(callbacks.driveLight).toBeCalledWith(2, false);
|
||||
jest.useRealTimers();
|
||||
});
|
||||
|
||||
it('turns off drive light 1 and turns on drive light two when drive 2 is selected', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8B); // select drive 2
|
||||
|
||||
expect(callbacks.driveLight).toBeCalledTimes(3);
|
||||
expect(callbacks.driveLight).toHaveBeenNthCalledWith(1, 1, true);
|
||||
expect(callbacks.driveLight).toHaveBeenNthCalledWith(2, 1, false);
|
||||
expect(callbacks.driveLight).toHaveBeenNthCalledWith(3, 2, true);
|
||||
});
|
||||
});
|
||||
|
||||
describe('head positioning', () => {
|
||||
it('does not allow head positioning when the drive is off', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(0);
|
||||
expect(state.drives[0].track).toBe(0);
|
||||
});
|
||||
|
||||
it('allows head positioning when the drive is on', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(2);
|
||||
expect(state.drives[0].track).toBe(4);
|
||||
});
|
||||
|
||||
it('moves the head to track 2 from track 0 when all phases are cycled', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(0);
|
||||
expect(state.drives[0].track).toBe(2 * STEPS_PER_TRACK);
|
||||
});
|
||||
|
||||
it('moves the head to track 10 from track 8 when all phases are cycled', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 8);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(0);
|
||||
expect(state.drives[0].track).toBe(10 * STEPS_PER_TRACK);
|
||||
});
|
||||
|
||||
it('stops the head at track 34', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 33);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(0);
|
||||
// The emulated Disk II puts data for track n on the
|
||||
// 4 quarter-tracks starting with n * STEPS_PER_TRACK.
|
||||
// On a real Disk II, the data would likely be on 3
|
||||
// quarter-tracks starting with n * STEPS_PER_TRACK - 1,
|
||||
// leaving 1 essentially blank quarter track at the
|
||||
// half-track.
|
||||
expect(state.drives[0].track).toBe(35 * STEPS_PER_TRACK - 1);
|
||||
});
|
||||
|
||||
it('moves a half track when only one phase is activated', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(3);
|
||||
expect(state.drives[0].track).toBe(15 * STEPS_PER_TRACK + 2);
|
||||
});
|
||||
|
||||
it('moves backward one track when phases are cycled in reverse', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(0);
|
||||
expect(state.drives[0].track).toBe(14 * STEPS_PER_TRACK);
|
||||
});
|
||||
|
||||
it('moves backward two tracks when all phases are cycled in reverse', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(2);
|
||||
expect(state.drives[0].track).toBe(13 * STEPS_PER_TRACK);
|
||||
});
|
||||
|
||||
it('does not move backwards past track 0', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 1);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x81); // coil 0 on
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
diskII.ioSwitch(0x80); // coil 0 off
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x86); // coil 3 off
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(2);
|
||||
expect(state.drives[0].track).toBe(0);
|
||||
});
|
||||
|
||||
it('moves backward one half track', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
diskII.ioSwitch(0x84); // coil 2 off
|
||||
diskII.ioSwitch(0x82); // coil 1 off
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(1);
|
||||
expect(state.drives[0].track).toBe(14.5 * STEPS_PER_TRACK);
|
||||
});
|
||||
|
||||
// The emulated Disk II is not able to step quarter tracks because
|
||||
// it does not track when phases are turned off.
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('moves a quarter track when two neighboring phases are activated and held', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x87); // coil 3 on
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(3);
|
||||
expect(state.drives[0].track).toBe(15 * STEPS_PER_TRACK + 1);
|
||||
});
|
||||
|
||||
// The emulated Disk II is not able to step quarter tracks because
|
||||
// it does not track when phases are turned off.
|
||||
// eslint-disable-next-line jest/no-disabled-tests
|
||||
it.skip('moves backward one quarter track', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setTrack(diskII, 15);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x85); // coil 2 on
|
||||
diskII.ioSwitch(0x83); // coil 1 on
|
||||
|
||||
const state = diskII.getState();
|
||||
expect(state.drives[0].phase).toBe(1);
|
||||
expect(state.drives[0].track).toBe(14.25 * STEPS_PER_TRACK);
|
||||
});
|
||||
});
|
||||
|
||||
describe('reading nibble-based disks', () => {
|
||||
it('spins the disk', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8e); // read mode
|
||||
|
||||
// Just check for changing nibbles
|
||||
let spinning = false;
|
||||
const firstNibble = diskII.ioSwitch(0x8c); // read data
|
||||
for (let i = 0; i < 512; i++) {
|
||||
const thisNibble = diskII.ioSwitch(0x8c); // read data
|
||||
if (thisNibble >= 0x80 && firstNibble !== thisNibble) {
|
||||
spinning = true;
|
||||
}
|
||||
}
|
||||
expect(spinning).toBeTruthy();
|
||||
});
|
||||
|
||||
it('after reading the data, the data register is set to zero', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8e); // read mode
|
||||
|
||||
// Find address field prolog
|
||||
let nibble = diskII.ioSwitch(0x8c); // read data
|
||||
for (let i = 0; i < 512 && nibble !== 0xD5; i++) {
|
||||
nibble = diskII.ioSwitch(0x8c); // read data
|
||||
}
|
||||
expect(nibble).toBe(0xD5);
|
||||
nibble = diskII.ioSwitch(0x8c); // read data
|
||||
// expect next read to be a zero because the sequencer is waiting
|
||||
// for data
|
||||
expect(nibble).toBe(0x00);
|
||||
});
|
||||
|
||||
it('after reading the data, then zero, there is new data', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8e); // read mode
|
||||
|
||||
// Find address field prolog
|
||||
let nibble = diskII.ioSwitch(0x8c); // read data
|
||||
for (let i = 0; i < 512 && nibble !== 0xD5; i++) {
|
||||
nibble = diskII.ioSwitch(0x8c); // read data
|
||||
}
|
||||
expect(nibble).toBe(0xD5);
|
||||
nibble = diskII.ioSwitch(0x8c); // read data
|
||||
// expect next read to be a zero
|
||||
expect(nibble).toBe(0x00);
|
||||
// expect next read to be new data
|
||||
nibble = diskII.ioSwitch(0x8c); // read data
|
||||
expect(nibble).toBe(0xAA);
|
||||
});
|
||||
|
||||
it('read write protect status', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
setWriteProtected(diskII, true);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8E); // read mode
|
||||
diskII.ioSwitch(0x8D); // read write protect if read
|
||||
const isWriteProtected = diskII.ioSwitch(0x8E); // read data
|
||||
|
||||
expect(isWriteProtected).toBe(0xff);
|
||||
});
|
||||
});
|
||||
|
||||
describe('writing nibble-based disks', () => {
|
||||
it('writes a nibble to the disk', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
let track0 = diskII.getState().drives[0].tracks[0];
|
||||
expect(track0[0]).toBe(0xFF);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8F, 0x80); // write
|
||||
diskII.ioSwitch(0x8C); // shift
|
||||
|
||||
track0 = diskII.getState().drives[0].tracks[0];
|
||||
expect(track0[0]).toBe(0x80);
|
||||
});
|
||||
|
||||
it('writes two nibbles to the disk', () => {
|
||||
const diskII = new DiskII(mockApple2IO, callbacks);
|
||||
diskII.setBinary(1, 'BYTES_BY_TRACK', 'po', BYTES_BY_TRACK_IMAGE);
|
||||
let track0 = diskII.getState().drives[0].tracks[0];
|
||||
expect(track0[0]).toBe(0xFF);
|
||||
|
||||
diskII.ioSwitch(0x89); // turn on the motor
|
||||
diskII.ioSwitch(0x8F, 0x80); // write
|
||||
diskII.ioSwitch(0x8C); // shift
|
||||
diskII.ioSwitch(0x8F, 0x81); // write
|
||||
diskII.ioSwitch(0x8C); // shift
|
||||
|
||||
track0 = diskII.getState().drives[0].tracks[0];
|
||||
expect(track0[0]).toBe(0x80);
|
||||
expect(track0[1]).toBe(0x81);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in New Issue