mirror of
https://github.com/MiSTer-devel/MacPlus_MiSTer.git
synced 2024-11-27 02:49:32 +00:00
303 lines
8.7 KiB
Verilog
303 lines
8.7 KiB
Verilog
/* IWM
|
|
|
|
Mapped to $DFE1FF - $DFFFFF
|
|
|
|
The 16 IWM one-bit registers are {8'hDF, 8'b111xxxx1, 8'hFF}:
|
|
0 $0 ca0L CA0 off (0)
|
|
1 $200 ca0H CA0 on (1)
|
|
2 $400 ca1L CA1 off (0)
|
|
3 $600 ca1H CA1 on (1)
|
|
4 $800 ca2L CA2 off (0)
|
|
5 $A00 ca2H CA2 on (1)
|
|
6 $C00 ph3L LSTRB off (low)
|
|
7 $E00 ph3H LSTRB on (high)
|
|
8 $1000 mtrOff ENABLE disk enable off
|
|
9 $1200 mtrOn ENABLE disk enable on
|
|
10 $1400 intDrive SELECT select internal drive
|
|
11 $1600 extDrive SELECT select external drive
|
|
12 $1800 q6L Q6 off
|
|
13 $1A00 q6H Q6 on
|
|
14 $1C00 q7L Q7 off, read register
|
|
15 $1E00 q7H Q7 on, write register
|
|
|
|
Notes from IWM manual:
|
|
Serial data is shifted in/out MSB first, with a bit transferred every 2 microseconds.
|
|
When writing data, a 1 is written as a transition on writeData at a bit cell boundary time, and a 0 is written as no transition.
|
|
When reading data, a falling transition within a bit cell window is considered to be a 1, and no falling transition is considered a 0.
|
|
When reading data, the read data register will latch the shift register when a 1 is shifted into the MSB.
|
|
The read data register will be cleared 14 fclk periods (about 2 microseconds) after a valid data read takes place-- a valid data read
|
|
being defined as both /DEV being low and D7 (the MSB) outputting a one from the read data register for at least one fclk period.
|
|
*/
|
|
|
|
module iwm
|
|
(
|
|
input clk,
|
|
input cep,
|
|
input cen,
|
|
|
|
input _reset,
|
|
input selectIWM,
|
|
input _cpuRW,
|
|
input _cpuLDS,
|
|
input [15:0] dataIn,
|
|
input [3:0] cpuAddrRegHi,
|
|
input SEL, // from VIA
|
|
output [15:0] dataOut,
|
|
input [1:0] insertDisk,
|
|
output [1:0] diskEject,
|
|
input [1:0] diskSides,
|
|
|
|
output [1:0] diskMotor,
|
|
output [1:0] diskAct,
|
|
|
|
// interface to fetch data for internal drive
|
|
output [21:0] dskReadAddrInt,
|
|
input dskReadAckInt,
|
|
output [21:0] dskReadAddrExt,
|
|
input dskReadAckExt,
|
|
input [7:0] dskReadData
|
|
);
|
|
|
|
wire [7:0] dataInLo = dataIn[7:0];
|
|
reg [7:0] dataOutLo;
|
|
assign dataOut = { 8'hBE, dataOutLo };
|
|
|
|
// IWM state
|
|
reg ca0, ca1, ca2, lstrb, selectExternalDrive, q6, q7;
|
|
reg ca0Next, ca1Next, ca2Next, lstrbNext, selectExternalDriveNext, q6Next, q7Next;
|
|
wire advanceDriveHead; // prevents overrun when debugging, does not exit on a real Mac!
|
|
reg [7:0] writeData;
|
|
reg [7:0] readDataLatch;
|
|
wire _iwmBusy, _writeUnderrun;
|
|
assign _iwmBusy = 1'b1; // for writes, a value of 1 here indicates the IWM write buffer is empty
|
|
assign _writeUnderrun = 1'b1;
|
|
|
|
// floppy disk drives
|
|
reg diskEnableExt, diskEnableInt;
|
|
reg diskEnableExtNext, diskEnableIntNext;
|
|
wire newByteReadyInt;
|
|
wire [7:0] readDataInt;
|
|
wire senseInt = readDataInt[7]; // bit 7 doubles as the sense line here
|
|
wire newByteReadyExt;
|
|
wire [7:0] readDataExt;
|
|
wire senseExt = readDataExt[7]; // bit 7 doubles as the sense line here
|
|
|
|
floppy floppyInt
|
|
(
|
|
.clk(clk),
|
|
.cep(cep),
|
|
.cen(cen),
|
|
|
|
._reset(_reset),
|
|
.ca0(ca0),
|
|
.ca1(ca1),
|
|
.ca2(ca2),
|
|
.SEL(SEL),
|
|
.lstrb(lstrb),
|
|
._enable(~diskEnableInt),
|
|
.writeData(writeData),
|
|
.readData(readDataInt),
|
|
.advanceDriveHead(advanceDriveHead),
|
|
.newByteReady(newByteReadyInt),
|
|
.insertDisk(insertDisk[0]),
|
|
.diskSides(diskSides[0]),
|
|
.diskEject(diskEject[0]),
|
|
|
|
.motor(diskMotor[0]),
|
|
.act(diskAct[0]),
|
|
|
|
.dskReadAddr(dskReadAddrInt),
|
|
.dskReadAck(dskReadAckInt),
|
|
.dskReadData(dskReadData)
|
|
);
|
|
|
|
floppy floppyExt
|
|
(
|
|
.clk(clk),
|
|
.cep(cep),
|
|
.cen(cen),
|
|
|
|
._reset(_reset),
|
|
.ca0(ca0),
|
|
.ca1(ca1),
|
|
.ca2(ca2),
|
|
.SEL(SEL),
|
|
.lstrb(lstrb),
|
|
._enable(~diskEnableExt),
|
|
.writeData(writeData),
|
|
.readData(readDataExt),
|
|
.advanceDriveHead(advanceDriveHead),
|
|
.newByteReady(newByteReadyExt),
|
|
.insertDisk(insertDisk[1]),
|
|
.diskSides(diskSides[1]),
|
|
.diskEject(diskEject[1]),
|
|
|
|
.motor(diskMotor[1]),
|
|
.act(diskAct[1]),
|
|
|
|
.dskReadAddr(dskReadAddrExt),
|
|
.dskReadAck(dskReadAckExt),
|
|
.dskReadData(dskReadData)
|
|
);
|
|
|
|
wire [7:0] readData = selectExternalDrive ? readDataExt : readDataInt;
|
|
wire newByteReady = selectExternalDrive ? newByteReadyExt : newByteReadyInt;
|
|
|
|
reg [4:0] iwmMode;
|
|
/* IWM mode register: S C M H L
|
|
S Clock speed:
|
|
0 = 7 MHz
|
|
1 = 8 MHz
|
|
Should always be 1 for Macintosh.
|
|
C Bit cell time:
|
|
0 = 4 usec/bit (for 5.25 drives)
|
|
1 = 2 usec/bit (for 3.5 drives) (Macintosh mode)
|
|
M Motor-off timer:
|
|
0 = leave drive on for 1 sec after program turns
|
|
it off
|
|
1 = no delay (Macintosh mode)
|
|
Should be 0 for 5.25 and 1 for 3.5.
|
|
H Handshake protocol:
|
|
0 = synchronous (software must supply proper
|
|
timing for writing data)
|
|
1 = asynchronous (IWM supplies timing) (Macintosh Mode)
|
|
Should be 0 for 5.25 and 1 for 3.5.
|
|
L Latch mode:
|
|
0 = read-data stays valid for about 7 usec
|
|
1 = read-data stays valid for full byte time (Macintosh mode)
|
|
Should be 0 for 5.25 and 1 for 3.5.
|
|
*/
|
|
|
|
// any read/write access to IWM bit registers will change their values
|
|
always @(*) begin
|
|
ca0Next <= ca0;
|
|
ca1Next <= ca1;
|
|
ca2Next <= ca2;
|
|
lstrbNext <= lstrb;
|
|
diskEnableExtNext <= diskEnableExt;
|
|
diskEnableIntNext <= diskEnableInt;
|
|
selectExternalDriveNext <= selectExternalDrive;
|
|
q6Next <= q6;
|
|
q7Next <= q7;
|
|
|
|
if (selectIWM == 1'b1 && _cpuLDS == 1'b0) begin
|
|
case (cpuAddrRegHi[3:1])
|
|
3'h0: // ca0
|
|
ca0Next <= cpuAddrRegHi[0];
|
|
3'h1: // ca1
|
|
ca1Next <= cpuAddrRegHi[0];
|
|
3'h2: // ca2
|
|
ca2Next <= cpuAddrRegHi[0];
|
|
3'h3: // lstrb
|
|
lstrbNext <= cpuAddrRegHi[0];
|
|
3'h4: // disk enable
|
|
if (selectExternalDrive)
|
|
diskEnableExtNext <= cpuAddrRegHi[0];
|
|
else
|
|
diskEnableIntNext <= cpuAddrRegHi[0];
|
|
3'h5: // external drive
|
|
selectExternalDriveNext <= cpuAddrRegHi[0];
|
|
3'h6: // Q6
|
|
q6Next <= cpuAddrRegHi[0];
|
|
3'h7: // Q7
|
|
q7Next <= cpuAddrRegHi[0];
|
|
endcase
|
|
end
|
|
end
|
|
|
|
// update IWM bit registers
|
|
always @(posedge clk or negedge _reset) begin
|
|
if (_reset == 1'b0) begin
|
|
ca0 <= 0;
|
|
ca1 <= 0;
|
|
ca2 <= 0;
|
|
lstrb <= 0;
|
|
diskEnableExt <= 0;
|
|
diskEnableInt <= 0;
|
|
selectExternalDrive <= 0;
|
|
q6 <= 0;
|
|
q7 <= 0;
|
|
end
|
|
else begin
|
|
ca0 <= ca0Next;
|
|
ca1 <= ca1Next;
|
|
ca2 <= ca2Next;
|
|
lstrb <= lstrbNext;
|
|
diskEnableExt <= diskEnableExtNext;
|
|
diskEnableInt <= diskEnableIntNext;
|
|
selectExternalDrive <= selectExternalDriveNext;
|
|
q6 <= q6Next;
|
|
q7 <= q7Next;
|
|
end
|
|
end
|
|
|
|
// read IWM state
|
|
always @(*) begin
|
|
dataOutLo = 8'hEF;
|
|
|
|
// reading any IWM address returns state as selected by Q7 and Q6
|
|
case ({q7Next,q6Next})
|
|
2'b00: // data-in register (from disk drive) - MSB is 1 when data is valid
|
|
dataOutLo <= readDataLatch;
|
|
2'b01: // IWM status register - read only
|
|
dataOutLo <= { (selectExternalDriveNext ? senseExt : senseInt), 1'b0, diskEnableExt & diskEnableInt, iwmMode };
|
|
2'b10: // handshake - read only
|
|
dataOutLo <= { _iwmBusy, _writeUnderrun, 6'b000000 };
|
|
2'b11: // IWM mode register when not enabled (write-only), or (write?) data register when enabled
|
|
dataOutLo <= 0;
|
|
endcase
|
|
end
|
|
|
|
// write IWM state
|
|
always @(posedge clk or negedge _reset) begin
|
|
if (_reset == 1'b0) begin
|
|
iwmMode <= 0;
|
|
writeData <= 0;
|
|
end
|
|
else if(cen) begin
|
|
if (_cpuRW == 0 && selectIWM == 1'b1 && _cpuLDS == 1'b0) begin
|
|
// writing to any IWM address modifies state as selected by Q7 and Q6
|
|
case ({q7Next,q6Next})
|
|
2'b11: begin
|
|
if (diskEnableExt | diskEnableInt)
|
|
writeData <= dataInLo;
|
|
else
|
|
iwmMode <= dataInLo[4:0];
|
|
end
|
|
endcase
|
|
end
|
|
end
|
|
end
|
|
|
|
// Manage incoming bytes from the disk drive
|
|
wire iwmRead = (_cpuRW == 1'b1 && selectIWM == 1'b1 && _cpuLDS == 1'b0);
|
|
reg [3:0] readLatchClearTimer;
|
|
always @(posedge clk or negedge _reset) begin
|
|
if (_reset == 1'b0) begin
|
|
readDataLatch <= 0;
|
|
readLatchClearTimer <= 0;
|
|
end
|
|
else if(cen) begin
|
|
// a countdown timer governs how long after a data latch read before the latch is cleared
|
|
if (readLatchClearTimer != 0) begin
|
|
readLatchClearTimer <= readLatchClearTimer - 1'b1;
|
|
end
|
|
|
|
// the conclusion of a valid CPU read from the IWM will start the timer to clear the latch
|
|
if (iwmRead && readDataLatch[7]) begin
|
|
readLatchClearTimer <= 4'hD; // clear latch 14 clocks after the conclusion of a valid read
|
|
end
|
|
|
|
// when the drive indicates that a new byte is ready, latch it
|
|
// NOTE: the real IWM must self-synchronize with the incoming data to determine when to latch it
|
|
if (newByteReady) begin
|
|
readDataLatch <= readData;
|
|
end
|
|
else if (readLatchClearTimer == 1'b1) begin
|
|
readDataLatch <= 0;
|
|
end
|
|
end
|
|
end
|
|
assign advanceDriveHead = readLatchClearTimer == 1'b1; // prevents overrun when debugging, does not exist on a real Mac!
|
|
endmodule
|