mirror of
https://github.com/steve-chamberlin/fpga-disk-controller.git
synced 2025-04-05 07:37:29 +00:00
296 lines
12 KiB
Verilog
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 |