`timescale 1ns / 100ps
//////////////////////////////////////////////////////////////////////////////////
//
//
// Filename: psram.v
// Description: Pseudo Static Cellular RAM controller, synchronous burst mode
// Version 1.0
// Creation date: 11Nov2015
//
// Author: Nicolae Dumitrache 
// e-mail: ndumitrache@opencores.org
//
/////////////////////////////////////////////////////////////////////////////////
// 
// Copyright (C) 2015 Nicolae Dumitrache
// 
// This source file may be used and distributed without 
// restriction provided that this copyright statement is not 
// removed from the file and that any derivative work contains 
// the original copyright notice and the associated disclaimer.
// 
// This source file is free software; you can redistribute it 
// and/or modify it under the terms of the GNU Lesser General 
// Public License as published by the Free Software Foundation;
// either version 2.1 of the License, or (at your option) any 
// later version. 
// 
// This source is distributed in the hope that it will be 
// useful, but WITHOUT ANY WARRANTY; without even the implied 
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR 
// PURPOSE. See the GNU Lesser General Public License for more 
// details. 
// 
// You should have received a copy of the GNU Lesser General 
// Public License along with this source; if not, download it 
// from http://www.opencores.org/lgpl.shtml 
// 
///////////////////////////////////////////////////////////////////////////////////
// Additional Comments: 
// The PSRAM clock should be advanced with ~2ns (phased ~280deg at an 100Mhz reference clock) from sys_CLK
// Between bursts, a single CE# = 1 is enough (for refresh), but after a read at the end of PSRAM 128 words row,
//		an extra CE#=1 is required (why?)
// Read/Write burst operation can overlap the row bound with any value except 1 (why?)
// Tested at 108Mhz
// 95% clock efficient for 128(max) length bursts
//////////////////////////////////////////////////////////////////////////////////

`define AddrBits	23	// address bits
`define SysAddrBits 7 

module PSRAM_16bit(
		input sys_CLK,						// clock
		input [1:0]sys_CMD,				// 00=nop, 10 = write sys_words words, 11=read sys_words words
		input [`SysAddrBits-1:0]sys_words,	// words(max 2^SysAddrBits) - 1
		input [`AddrBits-1:0]sys_ADDR,// word address
		input [15:0]sys_DIN,				// data input
		output reg [15:0]sys_DOUT,
		output reg sys_rd_data_valid = 1'b0,	// data valid from mem
		output sys_wr_data_request, 				// data request from sys
		output reg [1:0]sys_cmd_ack = 2'b00,	// command acknowledged - becomes 00 after completing a command
		output reg mem_clk_on = 1'b0,
		
		output reg [22:0]MemAdr,
		inout [15:0]MemDB,
		output reg RamADVn = 1'b0,
		output reg RamCEn = 1'b0,
		output reg RamCRE = 1'b0,
		output reg RamOEn = 1'b0,
		output reg RamWEn = 1'b0,
		output RamLBn,
		output RamUBn,
		input RamWait
	);

	reg [1:0]STATE = 0;
	reg [15:0]counter = 0;
	reg rWAIT = 1'b0;
	reg wrDoutOn = 1'b0;
	reg wdataready = 1'b0;

	assign RamLBn = 1'b0;
	assign RamUBn = 1'b0;
	assign MemDB = wrDoutOn ? sys_DIN : 16'hzzzz;
	assign sys_wr_data_request = !RamWait && !counter[`SysAddrBits] && !sys_cmd_ack[0] && (STATE == 2'b11);

	always @(posedge sys_CLK) begin
		sys_DOUT <= MemDB;
		wdataready <= sys_wr_data_request;
		
		case(STATE)
			0: begin
					counter <= counter + 1'b1;
					MemAdr <= 23'b000_10_00_0_0_011_1_0_1_0_0_01_1_111; // 000_BCR_00_syncburst_vlatency_3latency_WAIThigh_0_WAITbeforedelay_0_0_str1/2_nowrap_continuousburst
					RamOEn <= 1'b1;
					if(counter[15]) begin	// initialization, wait for >150uS
						RamCRE <= !counter[4];
						RamADVn <= counter[4];
						RamCEn <= counter[4];
						RamWEn <= counter[4];
						if(counter[4]) STATE <= 1; // wait 16T for CRE write
					end else RamCEn <= 1'b1;
				end
			1: begin
					rWAIT <= 1'b0;
					sys_rd_data_valid <= 1'b0;
					mem_clk_on <= 1'b1;
					wrDoutOn <= 1'b0;
					if(!rWAIT) begin
						sys_cmd_ack <= sys_CMD;
						if(sys_CMD[1]) begin	// r/w command
							MemAdr <= sys_ADDR;
							RamADVn <= 1'b0;
							RamCEn <= 1'b0;
							RamOEn <= !sys_CMD[0];
							RamWEn <= sys_CMD[0];
							counter[`SysAddrBits:0] <= {1'b0, sys_words};	
							STATE <= 2;
						end
					end
				end
			2: begin
					RamADVn <= 1'b1;
					RamWEn <= 1'b1;
					rWAIT <= 1'b1;
					STATE <= 3;
				end
			3: begin
					rWAIT <= RamWait;
					if(!RamWait) begin
						counter <= counter - 1'b1;
						wrDoutOn <= !sys_cmd_ack[0];
					end
					if(!rWAIT) begin
						sys_rd_data_valid <= sys_cmd_ack[0];
						if(counter[`SysAddrBits]) begin
							RamCEn <= 1'b1;
							RamOEn <= 1'b1;
							sys_cmd_ack <= 2'b00;
							if(!sys_cmd_ack[0]) rWAIT <= 1'b0; // extra CE# = 1 for read at the end of 128 words row (why needed?)
							STATE <= 1;
						end
					end else sys_rd_data_valid <= 1'b0;
				end
		endcase
	end
	
endmodule
