/* 
  Floppy Emu, copyright 2013 Steve Chamberlin, "Big Mess o' Wires". All rights reserved.
	
  Floppy Emu is licensed under a Creative Commons Attribution-NonCommercial 3.0 Unported 
  license. (CC BY-NC 3.0) The terms of the license may be viewed at 	
  http://creativecommons.org/licenses/by-nc/3.0/
	
  Based on a work at http://www.bigmessowires.com/macintosh-floppy-emu/
	
  Permissions beyond the scope of this license may be available at www.bigmessowires.com
  or from mailto:steve@bigmessowires.com.

  --------------------------------------------------------------------------------------
  
  Disk registers (read):	
	    State-control lines    Register
  CA2    CA1    CA0    SEL    addressed    Information in register

  0      0      0      0      DIRTN        Head step direction (0 = toward track 79, 1 = toward track 0)
  0      0      0      1      CSTIN        Disk in place (0 = disk is inserted)
  0      0      1      0      STEP         Drive head stepping (setting to 0 performs a step, returns to 1 when step is complete)
  0      0      1      1      WRTPRT       Disk locked (0 = locked)
  0      1      0      0      MOTORON      Drive motor running (0 = on, 1 = off)
  0      1      0      1      TK0          Head at track 0 (0 = at track 0)
  0		1		 1		  0		SWITCHED   	 Disk switched (1 = yes?) SWIM3: relax, also eject in progress
  0      1      1      1      TACH         GCR: Tachometer (produces 60 pulses for each rotation of the drive motor), MFM: Index pulse
  1      0      0      0      RDDATA0      Read data, lower head, side 0
  1      0      0      1      RDDATA1      Read data, upper head, side 1 
  1      0      1      0      SUPERDR      Drive is a Superdrive (0 = no, 1 = yes) SWIM3: two meg drive. 
  1      0      1      1      MFM_MODE		 SWIM3: MFM_MODE, 1 = yes (opposite of writing?)
  1      1      0      0      SIDES        Single- or double-sided drive (0 = single side, 1 = double side), SWIM: 0 = 4MB, 1 = not 4MB
  1      1      0      1      READY        0 = yes, SWIM3: SEEK_COMPLETE
  1      1      1      0      INSTALLED	 0 = yes, only used by SWIM, not IWM? SWIM3: drive present. 
  1      1      1      1      HSHK_HD      400K/800K: implements ready handshake if 1, Superdrive: Inserted disk capacity (0 = HD, 1 = DD), SWIM3: 1 = ONE_MEG_MEDIA 


  Disk registers (write):
    Control lines      Register
  CA1    CA0    SEL    addressed    Register function

  0      0      0      DIRTN        Set stepping direction (0 = toward track 79, 1 = toward track 0), SWIM3: SEEK_POSITIVE
  0      0      1      SWITCHED		Reset disk switched flag (writing 1 sets switch flag to 0)
  0      1      0      STEP         Step the drive head one track (setting to 0 performs a step, returns to 1 when step is complete)
  1      0      0      MOTORON      Turn drive motor on/off (0 = on, 1 = off)
  1      0      0      TWOMEGMEDIA_CHECK The first time zero is written, changes the behavior when reading SIDES
  0      1      1      MFM_MODE     0 = MFM, 1 = GCR
  1      1      0      EJECT        Eject the disk (writing 1 ejects the disk)
  1      1      0		  INDEX			if writing 0
*/

`define DRIVE_REG_DIRTN		0  
`define DRIVE_REG_CSTIN		1  	                           
`define DRIVE_REG_STEP		2                          
`define DRIVE_REG_WRTPRT	3  
`define DRIVE_REG_MOTORON	4  
`define DRIVE_REG_TK0		5  
`define DRIVE_REG_EJECT		6  	                           
`define DRIVE_REG_TACH		7  
`define DRIVE_REG_RDDATA0	8  
`define DRIVE_REG_RDDATA1	9  
`define DRIVE_REG_SUPERDR	10 
`define DRIVE_REG_UNUSED   11
`define DRIVE_REG_SIDES		12 
`define DRIVE_REG_READY		13 
`define DRIVE_REG_INSTALLED	14 
`define DRIVE_REG_HSHK_HD		15 

`define FIRMWARE_VERSION_NUMBER 		11

module floppyemu(
	input clk,
	
   // Macintosh interface	
	input ca0,				// PH0
	input ca1,				// PH1
	input ca2,				// PH2
	input lstrb,			// PH3
	input SEL, 				// HDSEL from VIA
	input _enable, 			
	input wr,
	input _wreq,
	input pwm, 				// unused
	output rd,
	 
	// microcontroller interface
	input _rst,
	
	output stepDirectionMotorOn,
	output reg stepRequest,
	input stepAck_diskInserted,
	
	output _wreqMCU,
	output reg rdAckWrTick,
	output reg driveCurrentSide,
	output reg ejectRequest,
	
	input driveTach,
	input byteReady_tk0,
	
	input outputEnable,
	inout [6:0] data,
	
	output zero,
	
	// status display 
	output led,
	
	// debugging
	output test
);

	/********** drive state data **********/
	reg _driveRegTK0;
	reg _driveRegMotorOn;
	reg driveRegStepDirection;
	reg _driveRegWriteProtect;
	reg _driveRegDiskInserted;
	reg _driveRegMFMMode;
	
	/********** serial to parallel interface **********/	
	// GCR: One bit every 2 microseconds
	// The exact rate on the Macintosh is actually 16 clocks @ 7.8336 MHz = 2.04 microseconds.
	// MFM: One bit every 1 microsecond
	reg [7:0] shifter;
	reg [5:0] bitTimer;
	reg [3:0] bitCounter;
	reg [6:0] wrData;
	reg rdHead;
	reg [1:0] wrHistory;
	reg mfmWriteSynced;
	reg wrClear;
	
	always @(posedge clk) begin
		wrHistory <= { wrHistory[0], wr };
	end
	
	always @(posedge clk or negedge _rst) begin
		if (_rst == 0) begin
			rdAckWrTick <= 0;
			_driveRegWriteProtect <= 1;
			_driveRegDiskInserted <= 1;
			_driveRegMFMMode <= 1;
			wrData <= `FIRMWARE_VERSION_NUMBER;
			mfmWriteSynced <= 0;
			wrClear <= 0;
		end
		else begin
			// one-way switch for disk inserted register - until next reset, stepAck_diskInserted will act only as stepAck
			if (_driveRegDiskInserted == 1 && stepAck_diskInserted == 0) begin
				_driveRegDiskInserted <= 0;
			end
			// is the Macintosh currently writing to the disk?
			if (_wreq == 0) begin
				// was there a transition on the wr line?
				// GCR: any transition
				// MFM: falling edge
				if (((wrHistory[1] != wrHistory[0]) && (_driveRegMFMMode == 1)) ||
					 ((wrHistory[1] && ~wrHistory[0]) && (_driveRegMFMMode == 0))) begin
					// has at least half a bit cell time elpased since the last cell boundary?
					if ((bitTimer >= 20 && _driveRegMFMMode == 1) ||
						 (bitTimer >= 10 && _driveRegMFMMode == 0)) begin 
						shifter <= { shifter[6:0], 1'b1 }; 
						bitCounter <= bitCounter - 1'b1;					
					end
					// do nothing if the clock count was less than half a cell
					
					// reset the bit timer
					bitTimer <= 0;
				end
				else begin
					// have one and a half bit cell times elapsed?
					if ((bitTimer >= 60 && _driveRegMFMMode == 1) ||
						 (bitTimer >= 30 && _driveRegMFMMode == 0)) begin
						shifter <= { shifter[6:0], 1'b0 };
						bitCounter <= bitCounter - 1'b1;
						
						if (_driveRegMFMMode == 1)
							bitTimer <= 20;
						else
							bitTimer <= 10;
					end
					else begin
						// init shifter at the beginning of a write, so we can recognize the framing bits later
						if (wrClear == 0) begin
							shifter <= 0;
							wrClear <= 1;
						end
						// has a complete byte been shifted in?
						else if (_driveRegMFMMode == 1) begin
							// GCR
							if (shifter[7] == 1) begin
								// GCR: The complete byte is shifter[7:0], but only 7 bits are stored in wrData, since the MSB is always 1.
								wrData <= shifter[6:0]; // store the byte for the mcu
								shifter <= 0; // clear the byte from the shifter
								rdAckWrTick <= ~rdAckWrTick; // signal the mcu that a new byte is ready
							end
						end	
						else begin
							// MFM
							// If we're in write mode, but haven't yet synched (framed the bytes in the bit stream),
							// and we see 01000100 in the shifter, assume that it's the first half of an A1 sync
							if ((bitCounter == 0) ||
								 (shifter == 8'h44 && mfmWriteSynced == 0)) begin		
								// MFM: send the mcu the data nibble in the low 4 bits, and clock bit C2 in bit 4
								wrData[0] <= shifter[0];
								wrData[1] <= shifter[2];
								wrData[2] <= shifter[4];
								wrData[3] <= shifter[6];
								wrData[4] <= shifter[5]; // clock bit
								bitCounter <= 8;
								
								rdAckWrTick <= (shifter == 8'h44 && mfmWriteSynced == 0) ? 0 : ~rdAckWrTick; // signal the mcu that a new nibble is ready
								mfmWriteSynced <= mfmWriteSynced | (shifter == 8'h44);
							end
						end
						
						bitTimer <= bitTimer + 1'b1;	
					end	
				end
			end
			else begin
				mfmWriteSynced <= 0;
				wrClear <= 0;
				// is it time for a new bit?
				if ((bitTimer == 40 && _driveRegMFMMode == 1) ||
					 (bitTimer == 20 && _driveRegMFMMode == 0))
				begin
					// are all the bits done?
					if (bitCounter == 0) begin
						// is there a new byte ready to read?
						if (byteReady_tk0 == 1) begin
							// if there's a byte ready, but no disk inserted, then load config options from the MCU
							if (_driveRegDiskInserted == 1) begin
								_driveRegWriteProtect <= data[0];
								_driveRegMFMMode <= data[1];
							end
							else begin
								// load the new byte. 
								if (_driveRegMFMMode == 1) begin
									// Only 7 bits are transferred, since the MSB is always 1.
									shifter <= { 1'b1, data };
								end
								else begin
									// For MFM, the 7 bits received from the MCU are:
									// 	0 0 m d3 d2 d1 d0
									// From this we can constuct the MFM-encoded byte with clock and data bits:
									// 	c3 d3 c2 d2 c1 d1 c0 d0
									// where cN = dN+1 NOR dN.
									// If m is 1, then this is part of a mark byte, and c2 should be forced to 0.
									shifter[7] <= ~(shifter[7] | data[3]); 
									shifter[6] <= data[3];
									shifter[5] <= ~(data[3] | data[2]) & ~data[4];
									shifter[4] <= data[2];
									shifter[3] <= ~(data[2] | data[1]); 
									shifter[2] <= data[1];
									shifter[1] <= ~(data[1] | data[0]);
									shifter[0] <= data[0];
								end
								bitCounter <= 7;
								rdAckWrTick <= 1;							
							end
						end
						else begin
							// insert a sync byte
							if (_driveRegMFMMode == 1)
							begin
								shifter <= { 8'b11111111 }; 
								bitCounter <= 9; // sync "byte" sends 10 bits rather than 8
							end
							else begin
								shifter <= { 8'b10101010 }; // logical 0x0, encoded 0xA. Should sync byte be 0x4E instead?
								bitCounter <= 7;
							end							
						end		
					end
					else begin
						if (bitCounter == 7) begin
							// Clear rdAck after the first bit is done. This gives the microcontroller 2 microseconds
							// to react to rdAck before it's deasserted.
							rdAckWrTick <= 0;
						end

						// there are still more bits remaining, so shift the next bit
						shifter <= { shifter[6:0], 1'b0 }; // left shift
						bitCounter <= bitCounter - 1'b1;
					end
				end
				/* GCR: After the bit shift is completed, update the read head state, using the MSB of the shift register.
					A logical 1 is sent as a falling (high to low) transition on the read head at a bit cell boundary time,
					a logical 0 is sent as no falling transition. */
				else if (bitTimer == 2 && shifter[7] == 1 && _driveRegMFMMode == 1) begin
					rdHead <= 1'b0;
				end
				/* GCR: Half-way through the bit cell time, set the read head to 1
					to prepare for a possible falling transition for the next bit. */
				else if (bitTimer == 20 && _driveRegMFMMode == 1)
				begin
					rdHead <= 1'b1;
				end
				/* MFM: At the start of the bit cell time, set the read head to 0 if the logical value is 1. */
				else if (bitTimer == 2 && shifter[7] == 1 && _driveRegMFMMode == 0)
				begin
					rdHead <= 1'b0;
				end
				/* MFM: About a quarter-way through the bit cell time, always reset read head to 1. */
				else if (bitTimer == 7 && _driveRegMFMMode == 0)
				begin
					rdHead <= 1'b1;
				end
				
				// increment bit timer modulo 40 (20 MFM)
				if ((bitTimer == 40 && _driveRegMFMMode == 1) ||
					 (bitTimer == 20 && _driveRegMFMMode == 0))
					bitTimer <= 0;
				else
					bitTimer <= bitTimer + 1'b1;
			end
		end
	end
	
	// enable the data output only if the MCU says its data lines are Hi Z
	assign data = (outputEnable == 1) ? wrData : 7'hZZ;
	
	/********** register read **********/
	wire [3:0] driveReadRegisterSelect = {ca2,ca1,ca0,SEL};
	
	reg registerContents;
	always @* begin
		case (driveReadRegisterSelect)
			`DRIVE_REG_DIRTN:
				registerContents = driveRegStepDirection; // step direction
			`DRIVE_REG_CSTIN:
				registerContents = _driveRegDiskInserted; // disk in drive, 0 = yes
			`DRIVE_REG_STEP:
				registerContents = ~stepRequest; // STEP, 1 = complete
			`DRIVE_REG_WRTPRT:
				registerContents = _driveRegWriteProtect; // write protect, 0 = on, 1 = off
			`DRIVE_REG_MOTORON:
				registerContents = _driveRegMotorOn; // 0 = motor on
			`DRIVE_REG_TK0:
				registerContents = _driveRegTK0; // TK0: track 0 indicator
			`DRIVE_REG_EJECT:
				registerContents = 1'b0; // disk switched?
			`DRIVE_REG_TACH:
				registerContents = driveTach; // TACH: 60 pulses for each rotation of the drive motor
			`DRIVE_REG_RDDATA0:
				registerContents = rdHead; // RDDATA0
			`DRIVE_REG_RDDATA1:
				registerContents = rdHead; // RDDATA1
			`DRIVE_REG_SUPERDR:
				registerContents = 1'b1; // SUPERDR, 1 = yes
			`DRIVE_REG_UNUSED:
				registerContents = 1'b0; // UNUSED
			`DRIVE_REG_SIDES:
				registerContents = 1'b1; // SIDES = double-sided drive
			`DRIVE_REG_READY:
				registerContents = 1'b0; // READY = yes
			`DRIVE_REG_INSTALLED:
				registerContents = 1'b0; // INSTALLED = yes
			`DRIVE_REG_HSHK_HD:
				registerContents = _driveRegMFMMode; // HSHK_HD = implements ready handshake, or DD/HD media

		endcase
	end
	assign rd = _enable == 1'b1 ? 1'bZ : registerContents;
	
	always @(posedge clk or negedge _rst) begin
		if (_rst == 0) begin
			driveCurrentSide = 0;
		end
		else if (_enable == 1'b0 && lstrb == 1'b0) begin
			if (driveReadRegisterSelect == `DRIVE_REG_RDDATA0)
				driveCurrentSide = 0;
			else if (driveReadRegisterSelect == `DRIVE_REG_RDDATA1)
				driveCurrentSide = 1;
		end
	end
	
	// compute the effective _wreq state for the microcontroller
	assign _wreqMCU = ~(_wreq == 0 && _enable == 0 && _driveRegMotorOn == 0);
	
	// cheesy: during a step request, stepDirectionMotorOn is the step direction. Otherwise it's motorOn.
	assign stepDirectionMotorOn = stepRequest ? driveRegStepDirection : _driveRegMotorOn;
	
	/********** register write **********/
	wire [2:0] driveWriteRegisterSelect = {ca1,ca0,SEL};
	
	reg [4:0] lstrbHistory;
	always @(posedge clk) begin
		lstrbHistory <= { lstrbHistory[3:0], lstrb };
	end
	
	always @(posedge clk or negedge _rst) begin
		if (_rst == 1'b0) begin
			_driveRegTK0 <= 0;
			_driveRegMotorOn <= 1;
			driveRegStepDirection <= 0;
			stepRequest <= 0;
			ejectRequest <= 0;
		end
		// was there a rising edge on lstrb?
		else if (_enable == 1'b0 && lstrbHistory == 5'b01111) begin
			case (driveWriteRegisterSelect)
				`DRIVE_REG_DIRTN:
					driveRegStepDirection <= ca2;
				//`DRIVE_REG_SWITCHED: // unused
				`DRIVE_REG_STEP:
					begin
						stepRequest <= 1; // tell the microcontroller that a step was performed
					end
				`DRIVE_REG_MOTORON:
					_driveRegMotorOn <= ca2;
				`DRIVE_REG_EJECT:
					if (ca2 == 1'b1) begin
						ejectRequest <= 1; // tell the microcontroller that the disk was ejected. This stays on forever (until next reset)
					end
			endcase
		end
		else begin
			// clear step request after mcu acknowledges it, and get the new track 0 state
			if (stepRequest == 1 && stepAck_diskInserted == 1'b1) begin
				stepRequest <= 0;
				_driveRegTK0 <= byteReady_tk0;
			end
		end
	end
	
	/********** Revision 1.0 board: status LEDs and fake SD writeProtect **********/
	assign led = _driveRegMotorOn;
	assign zero = 0;
	
	
endmodule