fpga-disk-controller/lattice/iwm.v

296 lines
12 KiB
Verilog

`timescale 1 ns / 1 ps
module iwm(
// bus interface
input [3:0] addr, // A3-A1 selects one of 8 bits in the state register to be updated. A0 is the new state value.
// A0 is also the /READ input, enables IWM to send the register selected by the state onto the data bus
input _devsel, // Device enable: falling edge latches A3-A0. Rising edge of (Q3 or _devsel) qualifies write register data
input fclk, // Clock for serial communication, either 7 or 8 MHz (7 MHz on Apple II)
input q3, // 2 MHz non-symmetrical clock, used to qualify the timing of the serial data being written out for synchronous mode
input _reset, // system reset
input [7:0] dataIn, // Bidirectional data bus on a real IWM. Verilog model has separate input and output data ports.
output reg [7:0] dataOut,
// disk interface
output reg wrdata, // serial data output - a transition occurs for each 1 bit
output reg [3:0] phase, // programmable output, used in different ways depending on type of disk
output _wrreq, // write request
output _enbl1, // disk 1 enable
output _enbl2, // disk 2 enable
input sense, // input from disk, used for write protect or handshake, can be polled via status register
input rddata // serial data input - falling transition of each pulse is synchronized by IWM
);
// internal private state
//reg [4:0] mode; // we're ignoring all these
reg [7:0] shifter;
reg [7:0] buffer; // when Q6Q7 is 00, functions as read register. When Q7 is 1, write register.
reg motorOn;
reg driveSelect;
reg q6, q7;
reg _underrun;
reg writeBufferEmpty;
// state pseudo-register
// the bits in this register are individually addressed by A3-A1
// The data on A0 is latched into the addressed state bit by /DEV low [the falling edge of /DEV latches information on A0-A3]
// All bits are reset to 0 by _reset low
always @(negedge _devsel or negedge _reset) begin
if (_reset == 0) begin
phase <= 4'b0000;
motorOn <= 0;
driveSelect <= 0;
q6 <= 0;
q7 <= 0;
end
else begin
case (addr[3:1])
3'h0: // ph0
phase[0] <= addr[0];
3'h1: // ph1
phase[1] <= addr[0];
3'h2: // ph2
phase[2] <= addr[0];
3'h3: // ph3
phase[3] <= addr[0];
3'h4: // motor on
motorOn <= addr[0];
3'h5: // drive select
driveSelect <= addr[0];
3'h6: // Q6
q6 <= addr[0];
3'h7: // Q7
q7 <= addr[0];
endcase
end
end
// drive enable outputs are determined from motorOn and driveSelect
// no more than one enable may be low at a time
assign _enbl1 = ~(motorOn & ~driveSelect);
assign _enbl2 = ~(motorOn & driveSelect);
// The combination of Q7 and Motor-On and /undderrun enable /wrreq low
assign _wrreq = ~(q7 & _underrun & (_enbl1 == 0 | _enbl2 == 0));
/* IWM mode register: S C M H L
(could save some macrocells here by assuming all the Apple II values)
(all mode bits are reset to 0 by _reset low)
S Clock speed:
0 = 7 MHz (Apple II)
1 = 8 MHz (Macintosh)
C Bit cell time:
0 = 4 usec/bit (for 5.25 drives and Smartport) (Apple II)
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 (Apple II)
1 = no delay (Macintosh mode)
H Handshake protocol:
0 = synchronous (software must supply proper timing for writing data) (Apple II)
1 = asynchronous (IWM supplies timing) (Macintosh Mode)
*In synchronous mode, the time of the Q7Q6 01 to 11 transition and every 8 Q3 periods (4 us)
thereafter, until Q7 is cleared, marks the beginning of a write window. The duration of the
write window is 4 periods of the Q3 input (2 us). The data written at the last write access
occurring within this write window will load the shift register with the data to be shifted
out. If the next write access has not occurred 32 us (64 Q3 period) after a load, the write
will be extended in multiples of 4 us (8 Q3 periods) until another write access, and zeroes
will be shifted out. In synchronous mode, Q3 clock input is used internally to generate the
32 and 40 us timings, which would then be 64 and 80 Q3 clock periods in duration, respectively,
and the bit cell timings, 8 Q3 periods per bit cell time in slow mode.
*In asynchronous mode, the write shift register is buffered and, when the buffer is empty, the
iwm sets the MSB of the write-handhake register to a one to indicate that the next data nibble
can be written to the buffer. The buffer register may be written at any time during the write
state. Only the data last written into the buffer register, before the contents of the buffer
register is transferred to the write shift register, is used. In asynchronous mode CLK is used
to generate the bit cell timings. In fast mode CLK is equivalent to the clock input on FCLK. In
slow mode CLK is equivalent to the clock input on FCLK
divided by two. Therefore, in 7M and slow mode the bit cell time will be 28 FCLK periods, in 8M
and slow mode the cell time will be 32 periods, and in 8M and
fast mode the cell time will be 16 periods. In asynchronous mode the write shift register is loaded
every 8 bit cell times starting seven clock periods after the write state begins.
*Normally data written to the IWM is sampled by the zero to one transition of the logial OR of
Q3 and /DEV. In asynchronous mode the Q3 input may be tied low.
*An underrun occurs when data has not been written to the buffer register between the time the
write-handshake bit indicates and empty buffer and the time the buffer is transferred to the
write shift register. If an underrun occurs in asynchronous mode /WRREQ will be disabled and
the underrun flag will be set to 0. This occurrance can be detected by reading the write-
handshake register before clearing the state bit Q7. Clearing the state bit Q7 will reset the
underrun flag.
L Latch mode (should be set in asynchronous mode):
0 = read-data stays valid for about 2 usec (Apple II)
1 = read-data stays valid for full byte time (Macintosh mode)
In latch mode, the MSB of the read data is latched internally during /DEV low. This internally
latched MSB is then used for the determination of a valid data read.
*/
/* When reading serial data, a falling transition within a bit cell window is considered to be
a 1, and no falling transition within a window is a 0. The receive data input on RDDATA is
synchronized internally with the CLK clock. The synchronized falling transition is then discriminated
to the nearest bit cell window using the 7/8 MHz FCLK signal in fast mode and the FCLK signal
divided by two in slow mode.
In read state, data is shifted into the LSB of the shift register, and it shifts from LSB to
MSB. A full data nibble is considered to be shifted in when a 1 is shifted into the MSB. When
a full nibble is shifted in, the data will be latched by the read data register and the shift
register will be cleared to all zeroes.
In synchronous mode, the shift register is readable in any intermediate state with this
exception: when a 1 is shifted into the MSB, the shift register will appear, to the data bus,
to be stalled for a period of two bit times plus four CLK periods. This is to allow the host
processor time to poll the MSB to determine when data is valid. In asynchronous mode the data
register will latch the shift register when a one is shifted into the MSB and will be cleared
14 FCLK periods (about 2 us) after a valid data read takes place (a valid data read being
defined as both /DEV being low and D7 (the MSB) outputting a 1 from the data register for at
least one FCLK period.
*/
// read registers
// Q7 Q6 Moton-On Operation
// 0 0 0 read all 1's
// 0 0 1 read data register Read
// 0 1 x read status register Write-protect sense
// 1 0 x read write-handshake reg Write
always @(*) begin
case ({q7,q6})
2'b00: // data-in register (from disk drive) - MSB is 1 when data is valid. Reads all 1's if motor is off.
dataOut <= buffer;
2'b01: // IWM status register
dataOut <= { sense, 1'b0, motorOn, 5'b00111 /*mode*/ };
default: // 2'b10 handshake register
dataOut <= { writeBufferEmpty, _underrun, 6'b000000 };
endcase
end
// Mode and data register writes, disk serial I/O
// this assumes the mode register SCMHL is 00111
reg [1:0] rddataSync;
always @(posedge fclk) begin
rddataSync <= { rddataSync[0], rddata };
end
wire q3orDev = q3 | _devsel;
reg [5:0] bitTimer; // max value 42
reg [2:0] bitCounter; // max value 7
reg [3:0] clearBufferTimer; // max value 14
always @(posedge fclk or negedge _reset) begin
if (_reset == 0) begin
_underrun <= 1'b1;
writeBufferEmpty <= 1'b1;
bitCounter <= 0;
buffer <= 0;
clearBufferTimer <= 0;
wrdata <= 0;
//mode <= 5'b00000;
end
else begin
// READ FROM DISK
if (q7 == 0 && q6 == 0) begin
if (clearBufferTimer == 0) begin
// if the timer isn't already running and there is a valid read from the data buffer?
if (_devsel == 0 && addr[0] == 0 && buffer[7] == 1'b1) begin
clearBufferTimer <= 1; // start the timer
end
end
else begin
// have about 2 us elapsed since the last valid read from the buffer?
if (clearBufferTimer == 4'hE /* 14 FCLK periods */) begin
buffer[7] <= 0; // only the MSB really needs to be cleared. Saves some CPLD logic vs clearing the whole buffer.
clearBufferTimer <= 0; // stop the timer
end
else begin
clearBufferTimer <= clearBufferTimer + 1'b1;
end
end
// was there a falling transition on rddata?
if (rddataSync[1] & ~rddataSync[0]) begin
// has at least half a bit cell time elpased since the last cell boundary?
// don't shift any bits if the clock count was less than half a bit cell time
if (bitTimer >= 14) begin
shifter <= { shifter[6:0], 1'b1 }; // shift in a 1 bit
end
bitTimer <= 0;
end
else begin
// have one and a half bit cell times elapsed?
if (bitTimer >= 42) begin
shifter <= { shifter[6:0], 1'b0 }; // shift in a 0 bit
bitTimer <= 14; // reset to half bit cell time
end
else begin
// has a complete byte been shifted in?
if (shifter[7] == 1) begin
buffer <= shifter; // latch the byte
shifter <= 0; // clear the byte from the shifter
end
bitTimer <= bitTimer + 1'b1;
end
end
end
// WRITE TO DISK
if (q7 == 1'b1) begin
// is it time for a new bit?
if (bitTimer == 28) begin
bitTimer <= 0;
// is the entire byte done?
if (bitCounter == 7) begin
bitCounter <= 0;
// is there a new byte ready?
if (writeBufferEmpty == 0) begin
// move the next byte into the shifter
shifter <= buffer;
writeBufferEmpty <= 1'b1;
end
else begin
_underrun <= 0; // error
end
end
else begin
// there are still more bits remaining, so shift the next bit
bitCounter <= bitCounter + 1'b1;
shifter <= { shifter[6:0], 1'b0 }; // left shift
end
end
else begin
bitTimer <= bitTimer + 1'b1;
end
// wrdata transition at start of a bit cell indicates a logical 1 bit
if (bitTimer == 1 && shifter[7] == 1) begin
wrdata <= ~wrdata;
end
end
else begin
_underrun <= 1; // clear error when Q7 is 0
end
// WRITE REGISTERS
// a register is written when both Q6 and Q7 are set or are being set, and A0 is 1
// Q7 Q6 Moton-On Operation
// 1 1 0 write mode register Mode Set
// 1 1 1 write data register Write Load
// The IWM spec says register writes are qualified by the rising edge of (Q3 or _devsel),
// but that's a problem for buffer and writeBufferEmpty, which would then need to be clocked
// by both FCLK and (Q3 or _devsel). Instead we'll perform a register write at any FCLK
// edge when (Q3 or _devsel) is low, treating it like a load enable. A review of timing
// diagrams looks like this will work.
if (q3orDev == 0 && q7 && q6 && addr[0]) begin
if (motorOn) begin
buffer <= dataIn; // data for disk write
writeBufferEmpty <= 0;
writeBufferEmpty <= 0;
end
else begin
//mode <= data[4:0];
end
end
end
end
// TODO: clear read buffer after a valid read
// TODO: clear shifter at start of a read operation?
// TODO: set writeBufferEmpty at start of a write?
endmodule