@fugloy/emu 0.0.0-stage → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +21 -0
- package/NOTICE.md +27 -0
- package/README.md +235 -2
- package/package.json +30 -3
- package/src/cpu/z80cpu.js +3350 -0
- package/src/debug/debugger.js +788 -0
- package/src/debug/instr.js +90 -0
- package/src/debug/label-file.js +429 -0
- package/src/debug/state.js +273 -0
- package/src/devices/cassette-tape.js +254 -0
- package/src/devices/cassette.js +263 -0
- package/src/devices/disk-image.js +965 -0
- package/src/devices/fdc-wd1771.js +180 -0
- package/src/devices/fdc-wd1793.js +1065 -0
- package/src/devices/interrupt-latch.js +76 -0
- package/src/devices/key-queue.js +278 -0
- package/src/devices/keyboard.js +176 -0
- package/src/devices/printer.js +44 -0
- package/src/devices/sound.js +53 -0
- package/src/devices/video.js +69 -0
- package/src/index.js +23 -0
- package/src/loaders/bas.js +107 -0
- package/src/loaders/cas-format.js +213 -0
- package/src/loaders/cmd-format.js +88 -0
- package/src/loaders/handoff.js +29 -0
- package/src/loaders/load.js +299 -0
- package/src/machine/drive-select.js +42 -0
- package/src/machine/io.js +513 -0
- package/src/machine/machine.js +333 -0
- package/src/machine/memory.js +290 -0
- package/src/machine/models.js +123 -0
- package/src/machine/rom-hashes.js +21 -0
- package/src/machine/sha256.js +74 -0
- package/src/machine/trs80-system.js +289 -0
|
@@ -0,0 +1,180 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* WD1771 floppy disk controller: the Model I Expansion Interface's (emu phase 6, Ruling 5).
|
|
3
|
+
*
|
|
4
|
+
* Memory-mapped by the Expansion Interface (machine/io.js, readExpansion and writeExpansion):
|
|
5
|
+
* $37EC status (R) / command (W) $37EE sector register
|
|
6
|
+
* $37ED track register $37EF data register
|
|
7
|
+
* $37E1 (W) drive select: bits 0-3 drives 0-3 (machine/io.js, decodeModel1Select)
|
|
8
|
+
* The addresses are CONSTANTS.Z80's "Memory Mapped Hardware, Model 1" (kiwisincebirth/TRS-80-ROMS)
|
|
9
|
+
* and the Level II ROM's boot (MDL1LEV2.Z80, BOOT at $069F: drive 0 selected by writing 1 to $37E1,
|
|
10
|
+
* Restore written to $37EC, the sector read through $37EF).
|
|
11
|
+
*
|
|
12
|
+
* The authority is the FD1771-01 data sheet (Western Digital), as reprinted in George Blank,
|
|
13
|
+
* "Pathways Through the ROM" (1980), chapter 14, pp. 93-111 (OCR in
|
|
14
|
+
* docs/reference/web-sources/archive-org/pathways_djvu.txt); comments name its sections in
|
|
15
|
+
* quotes. The 1771 is the WD1793's predecessor, and this class is the phase 5 controller
|
|
16
|
+
* (fdc-wd1793.js) with the 1771's differences:
|
|
17
|
+
*
|
|
18
|
+
* - Single density only: no density latch, every ID recorded in MFM is invisible to it ("The
|
|
19
|
+
* FD1771 is compatible with the IBM 3740 data entry system format"). The stock Expansion
|
|
20
|
+
* Interface drives one side; `select` takes no side.
|
|
21
|
+
* - Read and Write Sector: 1 0 0 m b E 0 0 and 1 0 1 m b E a1 a0 (Table 2). No side compare (the
|
|
22
|
+
* WD1793's C and S flags do not exist). `b` = 1 is the IBM length, 128 x 2^n by the ID's length
|
|
23
|
+
* code; `b` = 0 is 16 x N bytes ("an indicator of all zeroes is interpreted as 256 sixteen byte
|
|
24
|
+
* groups"). A length other than the sector's own leaves the data CRC unmatched, which the
|
|
25
|
+
* command reports as a CRC error.
|
|
26
|
+
* - The data address mark: Read Sector reports its type in status bits 6 and 5 (FB 00, FA 01,
|
|
27
|
+
* F9 10, F8 11: "At the end of the Read operation, the type of Data Address Mark encountered in
|
|
28
|
+
* the data field is recorded in the Status Register (Bits 5 and 6)"); Write Sector writes the
|
|
29
|
+
* mark a1 a0 choose (00 FB, 01 FA, 10 F9, 11 F8: Table 4). Write Track takes FA and F9 as data
|
|
30
|
+
* marks too ("Control Bytes for Initialization").
|
|
31
|
+
* - Times: CLK is 1 MHz (the sheet's "may be 1.0 MHz for mini floppy": 1 MHz / 4 / 2 gives the
|
|
32
|
+
* 125 kbit/s of a single-density 5.25" disk; unverified against the Expansion Interface
|
|
33
|
+
* schematic, spec §5). So the step rates are the 1771-X1, CLK = 1 MHz, TEST = 1 column of
|
|
34
|
+
* Table 1, 12, 12, 20, 40 ms for r1 r0 = 00, 01, 10, 11, and the head settles 20 ms ("IF TEST =
|
|
35
|
+
* 1 AND CLK 1 MHz THERE IS A 20MS DELAY", the Type I and II flow charts). An ID search ends
|
|
36
|
+
* after two revolutions ("within two revolutions of the disk; otherwise, the Record Not Found
|
|
37
|
+
* status bit is set"; a verify likewise): at the third index pulse, the first at which two
|
|
38
|
+
* whole revolutions have passed. The head unloads at the third index pulse after the last
|
|
39
|
+
* command ("remains activated until the third index pulse following the last operation").
|
|
40
|
+
* - Read Address: "The Sector Address of the ID field is written into the Sector Register" (the
|
|
41
|
+
* WD1793 writes the track address).
|
|
42
|
+
* - INTRQ goes to the Expansion Interface's interrupt latch at $37E0, bit 6 (DISK_BIT), not to an
|
|
43
|
+
* NMI: the latch's bit follows INTRQ, so it clears when the status is read or a command is
|
|
44
|
+
* written, as INTRQ does ("The INTRQ signal remains active until reset by reading the Status
|
|
45
|
+
* Register to the processor or by the loading of the Command Register"; Kitsz, The Custom TRS-80
|
|
46
|
+
* and Other Mysteries, p. 137: reading 37EC resets the disk flip-flop).
|
|
47
|
+
* - Master reset runs a Restore ("Note that the RESTORE command is executed when MR goes from an
|
|
48
|
+
* active to an inactive state"); the Model I's reset is taken to pulse MR (unverified, spec §5),
|
|
49
|
+
* which also returns the head to track 0 before the ROM's first status read.
|
|
50
|
+
*
|
|
51
|
+
* Kept from the WD1793 model and unverified for the 1771 (spec §5): the Force Interrupt
|
|
52
|
+
* conditions and DRQ handling, READY true when the selected drive holds a disk (no motor
|
|
53
|
+
* time-out), Lost Data timing, and the data mark window of a DMK (30 bytes in FM; the 1771's sheet
|
|
54
|
+
* says 28).
|
|
55
|
+
*/
|
|
56
|
+
|
|
57
|
+
import { FDC1793 } from "./fdc-wd1793.js";
|
|
58
|
+
import { DISK_BIT } from "./interrupt-latch.js";
|
|
59
|
+
|
|
60
|
+
/** Write Sector's a1 a0 (bits 1-0): the data address mark it writes (Table 4). */
|
|
61
|
+
const WRITE_MARKS = [0xfb, 0xfa, 0xf9, 0xf8];
|
|
62
|
+
const ST_RECORD_TYPE = 0x60; // Read Sector: bits 6 and 5
|
|
63
|
+
|
|
64
|
+
export class FDC1771 extends FDC1793 {
|
|
65
|
+
/**
|
|
66
|
+
* @param {{ cpuHz: number, latch?: import('./interrupt-latch.js').InterruptLatch | null }} opts the
|
|
67
|
+
* CPU clock the times count in, and the Expansion Interface's latch INTRQ drives (null: none)
|
|
68
|
+
*/
|
|
69
|
+
constructor({ cpuHz, latch = null }) {
|
|
70
|
+
super({ cpuHz });
|
|
71
|
+
this.latch = latch;
|
|
72
|
+
this.mfm = false;
|
|
73
|
+
const ms = (/** @type {number} */ n) => Math.round((n * cpuHz) / 1000);
|
|
74
|
+
this.stepTStates = [12, 12, 20, 40].map(ms);
|
|
75
|
+
this.settleTStates = ms(20);
|
|
76
|
+
this.searchPulses = 3;
|
|
77
|
+
this.headUnloadPulses = 3;
|
|
78
|
+
this.readAddressField = 2;
|
|
79
|
+
this.fmMarks = true;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
/** INTRQ, mirrored into the Expansion Interface latch's disk bit. */
|
|
83
|
+
get intrq() {
|
|
84
|
+
return this._intrq;
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
set intrq(v) {
|
|
88
|
+
this._intrq = v;
|
|
89
|
+
// The base constructor sets INTRQ before `latch` exists
|
|
90
|
+
if (this.latch) {
|
|
91
|
+
if (v) this.latch.raise(DISK_BIT);
|
|
92
|
+
else this.latch.clear(DISK_BIT);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
/**
|
|
97
|
+
* The drive-select latch at $37E1, decoded: a drive 0-3 or -1 for none. One side, single
|
|
98
|
+
* density, whatever the caller passes.
|
|
99
|
+
* @param {number} drive
|
|
100
|
+
*/
|
|
101
|
+
select(drive) {
|
|
102
|
+
super.select(drive, 0, false);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Master reset with the machine's: the WD1793 model's reset, then the Restore MR starts (03,
|
|
107
|
+
* as the FD179X loads it; the 1771's sheet names no rate), when a disk is mounted (with none the
|
|
108
|
+
* controller is taken as absent, machine/io.js).
|
|
109
|
+
*/
|
|
110
|
+
reset() {
|
|
111
|
+
super.reset();
|
|
112
|
+
if (this.anyDiskMounted()) this.writeCommand(0x03);
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* A command written while Busy overrides the one running: "This register should not be loaded
|
|
117
|
+
* when the device is busy unless the execution of the current command is to be overridden.
|
|
118
|
+
* This latter action results in an interrupt" ("Command Register"; the WD1793's sheet allows
|
|
119
|
+
* only Force Interrupt then, and the WD1793 model ignores the write). The running command stops
|
|
120
|
+
* as Force Interrupt stops one (Busy and DRQ reset, a sector not yet written is not written),
|
|
121
|
+
* the new one starts, and INTRQ is raised for the override and left up until a status read
|
|
122
|
+
* (whether the new command's load resets it first is not said; unverified, spec §5). Model I
|
|
123
|
+
* boot sectors rely on it: the dual-boot disks' loader writes its Seek about 190 T-states after
|
|
124
|
+
* the ROM took the boot sector's last byte, while the controller is still reading the CRC.
|
|
125
|
+
* @param {number} value
|
|
126
|
+
*/
|
|
127
|
+
writeCommand(value) {
|
|
128
|
+
value &= 0xff;
|
|
129
|
+
if (value >> 4 === 0x0d || !(this.status & 0x01)) {
|
|
130
|
+
super.writeCommand(value);
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
this.pending = null;
|
|
134
|
+
this.buffer = null;
|
|
135
|
+
this.trackWrite = null;
|
|
136
|
+
this.status &= ~(0x01 | (this.commandType === 1 ? 0 : 0x02));
|
|
137
|
+
// With Busy now clear the base loads the command, which resets INTRQ as any command load does
|
|
138
|
+
// (unless Force Interrupt I3 holds it) and may finish at once (not ready: INTRQ raised); then
|
|
139
|
+
// the override's own interrupt is raised over whatever that left. The order matters: raised
|
|
140
|
+
// first, the base's load would clear it.
|
|
141
|
+
super.writeCommand(value);
|
|
142
|
+
this.intrq = true;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
_startType2(value) {
|
|
146
|
+
super._startType2(value);
|
|
147
|
+
const p = this.pending;
|
|
148
|
+
this.writeDeleted = false;
|
|
149
|
+
if (p === null) return;
|
|
150
|
+
p.compareSide = false;
|
|
151
|
+
p.ibm = (value & 0x08) !== 0;
|
|
152
|
+
if (p.kind === "write") p.dam = WRITE_MARKS[value & 3];
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
/** The data field's length the command's b flag and the ID's length code give. */
|
|
156
|
+
_length(id, p) {
|
|
157
|
+
if (p.ibm) return 128 << (id.sizeCode & 3);
|
|
158
|
+
return 16 * (id.sizeCode === 0 ? 256 : id.sizeCode);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
_type2Event(p) {
|
|
162
|
+
const searching = p.phase === "search";
|
|
163
|
+
super._type2Event(p);
|
|
164
|
+
if (!searching || this.pending !== p || (p.phase !== "data" && p.phase !== "drq")) return;
|
|
165
|
+
// The ID was found: the 1771's record type and length
|
|
166
|
+
const id = this.currentDisk().trackIds(this._track, this.side)[p.index];
|
|
167
|
+
const len = this._length(id, p);
|
|
168
|
+
if (p.phase === "data") {
|
|
169
|
+
this.status = (this.status & ~ST_RECORD_TYPE) | ((~(id.dam ?? 0xfb) & 3) << 5);
|
|
170
|
+
if (len !== this.buffer.length) {
|
|
171
|
+
const field = new Uint8Array(len);
|
|
172
|
+
field.set(this.buffer.subarray(0, len));
|
|
173
|
+
this.buffer = field;
|
|
174
|
+
p.crcOk = false;
|
|
175
|
+
}
|
|
176
|
+
} else if (len !== this.buffer.length) {
|
|
177
|
+
this.buffer = new Uint8Array(len);
|
|
178
|
+
}
|
|
179
|
+
}
|
|
180
|
+
}
|