module RAM4GS(PHI2, MAin, CROW, Din, Dout,
			  nCCAS, nCRAS, nFWE,
			  RBA, RA, RD, nRCS, RCLK, RCKE,
			  nRWE, nRRAS, nRCAS, RDQMH, RDQML);

	/* 65816 Phase 2 Clock */
	input PHI2;

	/* Async. DRAM Control Inputs */
	input nCCAS, nCRAS;

	/* Synchronized PHI2 and DRAM signals */
	reg PHI2r, PHI2r2, PHI2r3;
	reg RASr, RASr2, RASr3;
	reg CASr, CASr2, CASr3;
	reg FWEr;
	reg CBR;

	/* 65816 Data */
	input [7:0] Din;
	output [7:0] Dout = RD[7:0];

	/* Latched 65816 Bank Address */
	reg [7:0] Bank;

	/* Async. DRAM Address Bus */
	input [1:0] CROW;
	input [9:0] MAin;
	input nFWE;
	reg n8MEGEN = 0;
	reg XOR8MEG = 0;
	
	/* SDRAM Clock */
	input RCLK;
	
	/* SDRAM */
	reg RCKEEN;
	output reg RCKE = 0;
	output reg nRCS = 1, nRRAS = 1, nRCAS = 1, nRWE = 1;
	output reg [1:0] RBA;
	reg nRowColSel;
	reg RA11;
	reg RA10;
	reg [9:0] RowA;
	output [11:0] RA;
	assign RA[11] = RA11;
	assign RA[10] = RA10;
	assign RA[9:0] = ~nRowColSel ? RowA[9:0] : MAin[9:0];
	output RDQML = ~nRowColSel ? 1'b1 : ~MAin[9];
	output RDQMH = ~nRowColSel ? 1'b1 : MAin[9];
	reg [7:0] WRD;
	inout [7:0] RD = (~nCCAS & ~nFWE) ? WRD[7:0] : 8'bZ;

	/* UFM Interface */
	reg UFMD = 0; // UFM data register bit 15
	reg ARCLK = 0; // UFM address register clock
	// UFM address register data input tied to 0
	reg ARShift = 0; // 1 to Shift UFM address in, 0 to increment
	reg DRCLK = 0; // UFM data register clock
	reg DRDIn = 0; // UFM data register input
	reg DRShift = 0; // 1 to shift UFM out, 0 to load from current address
	reg UFMErase = 0; // Rising edge starts erase. UFM+RTP must not be busy
	reg UFMProgram = 0; // Rising edge starts program. UFM+RTP must not be busy
	reg UFMOscEN = 0; // UFM oscillator enable
	wire UFMBusy; // 1 if UFM is doing user operation. Asynchronous
	wire RTPBusy; // 1 if real-time programming in progress. Asynchronous
	wire DRDOut; // UFM data output
	// UFM oscillator always enabled
	wire UFMOsc; // UFM oscillator output (3.3-5.5 MHz)
	UFM UFM_inst ( // UFM IP block (for Altera MAX II and MAX V)
		.arclk (ARCLK),
		.ardin (1'b0),
		.arshft (ARShift),
		.drclk (DRCLK),
		.drdin (DRDIn),
		.drshft (DRShift),
		.erase (UFMErase),
		.oscena (UFMOscEN),
		.program (UFMProgram),
		.busy (UFMBusy),
		.drdout (DRDOut),
		.osc (UFMOsc),
		.rtpbusy (RTPBusy));
	reg UFMBusyReg = 0; // UFMBusy registered to sync with RCLK
	reg RTPBusyReg = 0; // RTPBusy registered to sync with RCLK

	/* UFM State */
	reg UFMInitDone = 0; // 1 if UFM initialization finished
	reg UFMReqErase = 0; // 1 if UFM requires erase

	/* UFM Command Interface */
	reg C1Submitted = 0;
	reg ADSubmitted = 0;
	reg CmdEnable = 0;
	reg CmdSubmitted = 0;
	reg Cmdn8MEGEN = 0;
	reg CmdDRCLK = 0;
	reg CmdDRDIn = 0;
	reg CmdUFMErase = 0; // Set by user command. Programs UFM
	reg CmdUFMPrgm = 0; // Set by user command. Erases UFM
	wire ADWR = Bank[7:0]==8'hFB & MAin[7:0]==8'hFF & ~nFWE;
	wire C1WR = Bank[7:0]==8'hFB & MAin[7:0]==8'hFE & ~nFWE;
	wire CMDWR = Bank[7:0]==8'hFB & MAin[7:0]==8'hFD & ~nFWE;
	
	/* State Counters */
	reg InitReady = 0; // 1 if ready for init sequence
	reg Ready = 0; // 1 if done with init sequence
	reg [1:0] S = 0; // post-RAS State counter
	reg [17:0] FS = 0; // Fast init state counter
	reg [3:0] IS = 0; // Init state counter
	reg WriteDone;
	
	/* Synchronize PHI2, RAS, CAS */
	always @(posedge RCLK) begin
		PHI2r <= PHI2; PHI2r2 <= PHI2r; PHI2r3 <= PHI2r2;
		RASr <= ~nCRAS; RASr2 <= RASr; RASr3 <= RASr2;
		CASr <= ~nCCAS; CASr2 <= CASr; CASr3 <= CASr2;
	end
	
	/* Latch 65816 bank when PHI2 rises */
	always @(posedge PHI2) begin
		if (Ready) RA11 <= (Din[6] & ~n8MEGEN) ^ XOR8MEG; // Set RA11
		else RA11 <= 1'b0; // Reserved in mode register
		Bank[7:0] <= Din[7:0]; // Latch bank
	end
	
	/* Latch bank address, row address, WE, and CAS when RAS falls */
	always @(negedge nCRAS) begin
		if (Ready) begin
			RBA[1:0] <= CROW[1:0];
			RowA[9:0] <= MAin[9:0];
		end else begin
			RBA[1:0] <= 2'b00;		// Reserved in mode register
			RowA[9] <= 1'b1;		// "1" for single write mode
			RowA[8] <= 1'b0;		// Reserved
			RowA[7] <= 1'b0;		// "0" for not test mode
			RowA[6:4] <= 3'b010;	// "2" for CAS latency 2
			RowA[3] <= 1'b0;		// "0" for sequential burst (not used)
			RowA[2:0] <= 3'b000;	// "0" for burst length 1 (no burst)
		end
		FWEr <= ~nFWE;
		CBR <= ~nCCAS;
	end

	/* Latch write data when CAS falls */
	always @(negedge nCCAS) begin
		WRD[7:0] <= Din[7:0];
	end

	/* State counter from RAS */
	always @(posedge RCLK) begin
		if (~RASr2) S <= 0;
		else if (S==2'h3) S <= 2'h3;
		else S <= S+1;
	end
	/* Init state counter */
	always @(posedge RCLK) begin
		// Wait ~4.178ms (at 62.5 MHz) before starting init sequence
		FS <= FS+1;
		if (FS[17:10] == 8'hFF) InitReady <= 1'b1;
	end

	/* SDRAM CKE */
	always @(posedge RCLK) begin
		// Only 1 LUT4 allowed for this function!
		RCKE <= ((RASr | RASr2) & RCKEEN) | (~RASr2 & RASr3);
	end

	/* SDRAM command */
	always @(posedge RCLK) begin
		if (Ready) begin
			if (S==0) begin
				if (RASr2) begin
					if (CBR) begin
						// AREF
						nRCS <= 1'b0;
						nRRAS <= 1'b0;
						nRCAS <= 1'b0;
						nRWE <= 1'b1;
						RA10 <= 1'b1; // RA10 is don't care
					end else begin
						// ACT
						nRCS <= 1'b0;
						nRRAS <= 1'b0;
						nRCAS <= 1'b1;
						nRWE <= 1'b1;
						RA10 <= 1'b1; // Bank RA10 consistently "1"
					end
					// Enable clock only for reads
					RCKEEN <= ~CBR & ~FWEr;
				end else if (RCKE) begin
					// PCall
					nRCS <= 1'b0;
					nRRAS <= 1'b0;
					nRCAS <= 1'b1;
					nRWE <= 1'b0;
					RA10 <= 1'b1; // "all"
					RCKEEN <= 1'b1;
				end else begin
					// NOP
					nRCS <= 1'b1;
					nRRAS <= 1'b1;
					nRCAS <= 1'b1;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // RA10 is don't care
					RCKEEN <= 1'b1;
				end
				nRowColSel <= 1'b0; // Select registered row addres
			end else if (S==1) begin
				// NOP
				nRCS <= 1'b1;
				nRRAS <= 1'b1;
				nRCAS <= 1'b1;
				nRWE <= 1'b1;
				RA10 <= 1'b1; // RA10 is don't care
				nRowColSel <= 1'b1; // Select asynchronous column address
				RCKEEN <= ~CBR; // Disable clock if refresh cycle
			end else if (S==2) begin
				if (~FWEr & ~CBR) begin
					// RD
					nRCS <= 1'b0;
					nRRAS <= 1'b1;
					nRCAS <= 1'b0;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // Auto-precharge
				end else begin
					// NOP
					nRCS <= 1'b1;
					nRRAS <= 1'b1;
					nRCAS <= 1'b1;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // RA10 is don't care
				end
				nRowColSel <= 1'b1; // Select asynchronous column address
				RCKEEN <= ~CBR & FWEr; // Enable clock only for writes
			end else if (S==3) begin
				if (CASr2 & ~CASr3 & ~CBR & FWEr) begin
					// WR
					nRCS <= 1'b0;
					nRRAS <= 1'b1;
					nRCAS <= 1'b0;
					nRWE <= 1'b0;
					RA10 <= 1'b1; // Auto-precharge
				end else begin
					// NOP
					nRCS <= 1'b1;
					nRRAS <= 1'b1;
					nRCAS <= 1'b1;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // RA10 is don't care
				end
				nRowColSel <= ~(~FWEr | CASr3 | CBR);
				RCKEEN <= ~(~FWEr | CASr2 | CBR);
			end
		end else if (InitReady) begin
			if (S==0 & RASr2) begin
				if (IS==0) begin
					// NOP
					nRCS <= 1'b1;
					nRRAS <= 1'b1;
					nRCAS <= 1'b1;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // RA10 is don't care
				end else if (IS==1) begin
					// PC all
					nRCS <= 1'b0;
					nRRAS <= 1'b0;
					nRCAS <= 1'b1;
					nRWE <= 1'b0;
					RA10 <= 1'b1; // "all"
				end else if (IS==9) begin
					// Load mode register
					nRCS <= 1'b0;
					nRRAS <= 1'b0;
					nRCAS <= 1'b0;
					nRWE <= 1'b0;
					RA10 <= 1'b0; // Reserved in mode register
				end else begin
					// AREF
					nRCS <= 1'b0;
					nRRAS <= 1'b0;
					nRCAS <= 1'b0;
					nRWE <= 1'b1;
					RA10 <= 1'b1; // RA10 is don't care
				end
				IS <= IS+1;
			end else begin
				// NOP
				nRCS <= 1'b1;
				nRRAS <= 1'b1;
				nRCAS <= 1'b1;
				nRWE <= 1'b1;
				RA10 <= 1'b1; // RA10 is don't care
			end
			if (S==3 & ~RASr2 & IS==15) Ready <= 1'b1;
			nRowColSel <= 1'b0; // Select registered row address
			RCKEEN <= 1'b1;
		end else begin
			// NOP
			nRCS <= 1'b1;
			nRRAS <= 1'b1;
			nRCAS <= 1'b1;
			nRWE <= 1'b1;
			RA10 <= 1'b1; // RA10 is don't care
			nRowColSel <= 1'b0; // Select registered row address
			RCKEEN <= 1'b0;
		end
	end
	
	/* Submit command when PHI2 falls */
	always @(negedge PHI2) begin
		// Magic number check
		if (C1WR & Din[7:0]==8'hC1) begin // "C1" magic number
			if (ADSubmitted) begin
				CmdEnable <= 1'b1;
				UFMOscEN <= 1'b1;
			end
			C1Submitted <= 1'b1;
			ADSubmitted <= 1'b0;
		end else if (ADWR & Din[7:0]==8'hAD) begin // "AD" magic number
			if (C1Submitted) begin
				CmdEnable <= 1'b1;
				UFMOscEN <= 1'b1;
			end
			ADSubmitted <= 1'b1;
			C1Submitted <= 1'b0;
		end else if (C1WR | ADWR) begin // wrong magic number submitted
			CmdEnable <= 1'b0;
			C1Submitted <= 1'b0;
			ADSubmitted <= 1'b0;
		end else if (CMDWR) CmdEnable <= 1'b0;

		// Submit command
		if (CMDWR & CmdEnable) begin
			if (Din[7:4]==4'h0) begin
				XOR8MEG <= Din[0];
			end else if (Din[7:4]==4'h1) begin
				Cmdn8MEGEN <= ~Din[0];
				CmdSubmitted <= 1'b1;
			end else if (Din[7:4]==4'h2) begin
				Cmdn8MEGEN <= n8MEGEN;
				CmdUFMErase <= Din[3];
				CmdUFMPrgm <= Din[2];
				CmdDRCLK <= Din[1];
				CmdDRDIn <= Din[0];
				CmdSubmitted <= 1'b1;
			end
		end
	end

	/* UFM Control */
	always @(posedge RCLK) begin
		if (~Ready) begin
			if (~UFMInitDone & FS[17:16]==2'b00) begin
				// Shift 0 into address register
				ARCLK <= FS[3]; // Clock address register
				ARShift <= 1'b1; // Shift 0 into address register
				DRCLK <= 1'b0; // Don't clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b0; // DRShift is don't care
			end else if (~UFMInitDone & FS[17:16]==2'b01 & FS[7:4]==4'h0) begin
				// Parallel transfer UFM data to shift register
				ARCLK <= 1'b0; // Don't clock address register
				ARShift <= 1'b0; // ARShift is don't care
				DRCLK <= FS[3]; // Clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b0; // Parallel transfer to data register
			end else if (~UFMInitDone & FS[17:16]==2'b01 & FS[7:4]==4'h4) begin
				// Shift UFM
				ARCLK <= 1'b0; // Don't clock address register
				ARShift <= 1'b0; // ARShift is don't care
				DRCLK <= FS[3]; // Clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b1; // Shift data register
				// Capture bit 15 of this UFM word in UFMD register
				if (FS[3:0]==4'h7) UFMD <= DRDOut;
			end else if (~UFMInitDone & FS[17:16]==2'b01 & FS[7:4]==4'h5) begin
				// Check saved capacity entry
				if (UFMD) UFMInitDone <= 1'b1; // If erased, quit iterating
				else begin // If valid setting here
					n8MEGEN <= ~DRDOut; // Set capacity setting
					// If last byte in sector, mark need to erase
					if (FS[15:8]==8'hFF) begin
						UFMReqErase <= 1'b1; // Mark need to wrap around
						UFMInitDone <= 1'b1; // Quit iterating
					end
				end
			end else if (~UFMInitDone & FS[17:16]==2'b01 & FS[7:4]==4'h6) begin
				// Increment UFM address
				ARCLK <= FS[3]; // Clock address register
				ARShift <= 1'b0; // Increment UFM address
				DRCLK <= 1'b0; // Don't clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b0; // DRShift is don't care
			end else if (FS[17:16]==2'b10 & UFMReqErase) begin
				// Shift 0 into address register
				ARCLK <= FS[3]; // Clock address register
				ARShift <= 1'b1; // Shift 0 into address register
				DRCLK <= 1'b0; // Don't clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b0; // DRShift is don't care
			end else begin
				// Don't do anything with UFM
				ARCLK <= 1'b0; // Don't clock address register
				ARShift <= 1'b0; // ARShift is don't care
				DRCLK <= 1'b0; // Don't clock data register
				DRDIn <= 1'b0; // DRDIn is don't care
				DRShift <= 1'b0; // DRShift is don't care
			end

			// Don't erase or program UFM during initialization 
			UFMErase <= 1'b0;
			UFMProgram <= 1'b0;
		end else begin
			// Can only shift UFM data register now
			ARCLK <= 1'b0;
			ARShift <= 1'b0;
			DRShift <= 1'b1;

			// Set user command signals after PHI2 falls
			if (~PHI2r2 & PHI2r3 & CmdSubmitted) begin
				n8MEGEN <= Cmdn8MEGEN;
				DRCLK <= CmdDRCLK;
				DRDIn <= CmdDRDIn;
			end

			// UFM programming sequence
			if (CmdUFMPrgm | CmdUFMErase) begin
				if (~UFMBusyReg & ~RTPBusyReg) begin
					if (UFMReqErase | CmdUFMErase) UFMErase <= 1'b1;
					else if (CmdUFMPrgm) UFMProgram <= 1'b1;
				end else if (UFMBusyReg) UFMReqErase <= 1'b0;
			end
		end
	end
endmodule
