//
// Fast data download from 2-bit SPI flash.
//
// Feed a FIFO that then writes to SDRAM.
// This unit is designed to write 8-byte chunks.
//
// This unit does *not* require a 2x SPI clock;
// it uses a DDR buffer for clock out.
//

module spirom (
	       input 		 rst_n,
	       input 		 rom_clk,
	       input 		 ram_clk,

	       output 		 spi_sck,
	       inout [1:0] 	 spi_io,
	       output reg 	 spi_cs_n,

	       output [15:0] 	 wd, // Data to RAM
	       (* syn_preserve = 1 *)	// Don't merge into FIFO
	       output  [24:1] waddr, // RAM address
	       output reg [1:0]  wrq, // Write request (min 8/16 bytes)
	       input 		 wacc, // Data accepted (ready for next data)

	       output reg 	 done
	       );

   //
   // XXX: make these CPU programmable
   //
   parameter [24:0] ramstart = 25'h000_0000;
   parameter [23:0] romstart = 24'h10_0000; // 1 MB
   parameter [23:0] datalen  = 24'h08_0000; // 512K

   //
   // FIFO and input latches
   //
   reg [1:0] 		  spi_in_q;
   reg 			  spi_in_req;
   reg 			  spi_in_req_q;
   wire [11:0] 		  wrusedw;
   wire [8:0] 		  rdusedw;
   wire [15:0] 		  fifo_out;
   wire 		  rdempty;
   
   ddufifo spirom_fifo (
			.aclr ( ~rst_n ),

			.wrclk ( rom_clk ),
			.data ( spi_in_q ),
			.wrreq ( spi_in_req_q ),
			.wrfull ( ),
			.wrusedw ( wrusedw ),

			.rdclk ( ram_clk ),
			.q ( fifo_out ),
			.rdreq ( wacc ),
			.rdempty ( rdempty ),
			.rdusedw ( rdusedw )
			);

   //
   // Interfacing between FIFO and output signals
   //
   // Shuffle fifo_out because SPI brings in data in bigendian bit
   // order within bytes, but the FIFO IP assumes littleendian
   //
   assign wd[ 7: 6] = fifo_out[ 1: 0];
   assign wd[ 5: 4] = fifo_out[ 3: 2];
   assign wd[ 3: 2] = fifo_out[ 5: 4];
   assign wd[ 1: 0] = fifo_out[ 7: 6];

   assign wd[15:14] = fifo_out[ 9: 8];
   assign wd[13:12] = fifo_out[11:10];
   assign wd[11:10] = fifo_out[13:12];
   assign wd[ 9: 8] = fifo_out[15:14];

   always @(negedge rst_n or posedge ram_clk)
     if (~rst_n)
       begin
	  wrq <= 2'b00;
       end
     else
       begin
	  wrq[0] <= rdusedw >= 9'd4; // 4*2 =  8 bytes min available
	  wrq[1] <= rdusedw >= 9'd8; // 8*2 = 16 bytes min available
       end

   reg [24:1] waddr_q;
   reg 	      wacc_q;

   assign waddr = waddr_q;

   always @(negedge rst_n or posedge ram_clk)
     if (~rst_n)
       begin
	  waddr_q  <= ramstart >> 1;
	  wacc_q   <= 1'b0;
	  done     <= 1'b0;
       end
     else
       begin
	  wacc_q   <= wacc;
	  waddr_q  <= waddr_q + wacc_q;
	  done  <= done |
		   (wacc_q & (waddr_q == (((ramstart + datalen) >> 1) - 1'b1)));
       end // else: !if(~rst_n)

   reg [5:0]  spi_cmd_ctr;
   reg [26:0] spi_data_ctr;
   reg 	      spi_clk_en    = 1'b0;
   reg 	      spi_mosi_en = 1'b1;

   ddio_out spi_clk_buf (
			 .aclr ( ~rst_n ),
			 .datain_h ( spi_clk_en ),
			 .datain_l ( 1'b0 ),
			 .outclock ( rom_clk ),
			 .dataout ( spi_sck )
			 );

   always @(negedge rst_n or posedge rom_clk)
     if (~rst_n)
       begin
	  spi_cmd_ctr  <= 6'b0;
	  spi_clk_en   <= 1'b0;
	  spi_data_ctr <= datalen << 2;
	  spi_cs_n     <= 1'b1;
	  spi_in_req   <= 1'b0;
	  spi_in_req_q <= 1'b0;
       end
     else
       begin
	  spi_in_q     <= spi_io;
	  spi_in_req   <= 1'b0;
	  spi_in_req_q <= spi_in_req;
	  spi_clk_en   <= 1'b0;

	  if ( ~|spi_data_ctr )
	    begin
	       spi_cs_n   <= 1'b1;
	       spi_clk_en <= 1'b0;
	    end
	  else
	    begin
	       spi_cs_n   <= 1'b0;
	       if ( ~spi_cs_n )
		 begin
		    // 64/4 = 16 bytes min space
		    spi_clk_en <= (~wrusedw) >= 12'd128;
		    if ( spi_clk_en )
		      begin
			 if ( spi_cmd_ctr[5] & spi_cmd_ctr[3] )
			   begin
			      spi_in_req <= 1'b1;
			      spi_data_ctr <= spi_data_ctr - 1'b1;
			   end
			 else
			   begin
			      spi_cmd_ctr <= spi_cmd_ctr + 1'b1;
			   end
		      end // if ( spi_clk_en )
		 end // if ( ~spi_cs_n )
	    end // else: !if( ~|spi_data_ctr )
       end // else: !if(~rst_n)

   // SPI output data is shifted on the negative edge
   reg [31:0] spi_cmd;
   reg 	      spi_clk_en_q;

   assign spi_io[0] = spi_mosi_en ? spi_cmd[31] : 1'bz;
   assign spi_io[1] = 1'bz;

   always @(negedge rst_n or negedge rom_clk)
     if (~rst_n)
       begin
	  spi_cmd      <= { 8'h3b, romstart }; // Fast Read Dual Output
	  spi_mosi_en  <= 1'b1;
	  spi_clk_en_q <= 1'b0;
       end
     else
       begin
	  spi_clk_en_q <= spi_clk_en;
	  if ( spi_clk_en_q )
	    spi_cmd <= (spi_cmd << 1) | 1'b1;

	  spi_mosi_en <= spi_cs_n | (spi_cmd_ctr < 6'd36);
       end

endmodule // spirom
