//
// Fast data download from 2-bit SPI flash, or zero SDRAM.
//
// Feed a FIFO that then writes to SDRAM.
// Requires writes in aligned 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,
	       input		 sys_clk,

	       /* SPI ROM interface */
	       output		 spi_sck,
	       inout [1:0]	 spi_io,
	       output reg	 spi_cs_n,

	       /* SDRAM interface */
	       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 4/8 bytes)
	       input		 wacc, // Data accepted (ready for next data)

	       /* CPU control interface */
	       output reg [31:0] cpu_rdata,
	       input [31:0]	 cpu_wdata,
	       input		 cpu_valid,
	       input [3:0]	 cpu_wstrb,
	       input [1:0]	 cpu_addr,
	       output reg	 irq
	       );

   reg [24:3] ramstart;
   reg [23:3] romstart;
   reg [23:3] datalen;
   reg	      is_spi;
   reg	      go_zero;
   reg	      go_spi;
   reg	      done;
   reg [1:0]  done_q;

   always @(negedge rst_n or posedge sys_clk)
     if (~rst_n)
       begin
	  ramstart <= 23'bx;
	  romstart <= 23'bx;
	  datalen  <= 21'bx;
	  is_spi   <= 1'b0;
	  go_zero  <= 1'b0;
	  go_spi   <= 1'b0;
	  done_q   <= 2'b11;
	  irq      <= 1'b1;
       end
     else
       begin
	  done_q <= { done_q[0], done };

	  if (cpu_valid & cpu_wstrb[0])
	    begin
	       // Only full word writes supported!!
	       case (cpu_addr)
		 2'b00: begin
		    ramstart <= cpu_wdata[24:3];
		 end
		 2'b01: begin
		    romstart <= cpu_wdata[23:3];
		    is_spi <= |cpu_wdata[23:3];
		 end
		 2'b10: begin
		    datalen <= cpu_wdata[23:3];
		    if (|cpu_wdata[23:3])
		      begin
			 go_zero <= ~is_spi;
			 go_spi  <=  is_spi;
			 irq     <=  1'b0;
		      end
		 end
		 default: begin
		    // Do nothing
		 end
	       endcase // case (cpu_addr)
	    end // if (cpu_valid & cpu_wstrb[0])
	  else if (done_q == 2'b01)
	    begin
	       go_zero <= 1'b0;
	       go_spi  <= 1'b0;
	       irq     <= 1'b1;
	    end
       end // else: !if(~rst_n)

   always_comb
     case (cpu_addr)
       2'b00: cpu_rdata = { 7'b0, ramstart, 3'b0 };
       2'b01: cpu_rdata = { 8'b0, romstart, 3'b0 };
       2'b10: cpu_rdata = { 8'b0, datalen,  3'b0 };
       2'b11: cpu_rdata = { 31'b0, irq };
     endcase // case (cpu_addr)

   //
   // 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;

   ddufifo spirom_fifo (
			.aclr ( ~rst_n ),

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

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

   //
   // Interfacing between FIFO and input 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] = {2{go_spi}} & fifo_out[ 1: 0];
   assign wd[ 5: 4] = {2{go_spi}} & fifo_out[ 3: 2];
   assign wd[ 3: 2] = {2{go_spi}} & fifo_out[ 5: 4];
   assign wd[ 1: 0] = {2{go_spi}} & fifo_out[ 7: 6];

   assign wd[15:14] = {2{go_spi}} & fifo_out[ 9: 8];
   assign wd[13:12] = {2{go_spi}} & fifo_out[11:10];
   assign wd[11:10] = {2{go_spi}} & fifo_out[13:12];
   assign wd[ 9: 8] = {2{go_spi}} & fifo_out[15:14];

   reg [24:1] waddr_q;
   reg [23:1] ram_data_ctr;
   reg	      wacc_q;
   reg [1:0]  go_ram_q;

   assign waddr = waddr_q;

   always @(negedge rst_n or posedge ram_clk)
     if (~rst_n)
       begin
	  waddr_q      <= 24'bx;
	  ram_data_ctr <= 23'bx;
	  wacc_q       <= 1'b0;
	  done	       <= 1'b1;
	  go_ram_q     <= 2'b00;
	  wrq          <= 2'b00;
       end
     else
       begin
	  wrq <= 2'b00;

	  if (go_spi & ~done)
	    begin
	       wrq[0] <= rdusedw >=  9'd4; // 4*2 =  8 bytes min available
	       wrq[1] <= rdusedw >=  9'd8; // 8*2 = 16 bytes min available
	    end
	  else if (go_zero & ~done)
	    begin
	       wrq[0] <= |ram_data_ctr[23:3];
	       wrq[1] <= |ram_data_ctr[23:4];
	    end

	  wacc_q    <= wacc;
	  go_ram_q  <= { go_ram_q[0], go_spi|go_zero };

	  if (go_ram_q == 2'b01)
	    begin
	       waddr_q      <= {ramstart, 2'b00};
	       ram_data_ctr <= { datalen, 2'b00};
	       done         <= 1'b0;
	    end
	  else if (~done)
	    begin
	       waddr_q      <= waddr_q + wacc_q;
	       ram_data_ctr <= ram_data_ctr - wacc_q;
	       done         <= !(ram_data_ctr - wacc_q);
	    end
       end // else: !if(~rst_n)

   reg [5:0]   spi_cmd_ctr;
   reg [23:-2] spi_data_ctr;
   reg	       spi_clk_en = 1'b0;
   reg	       spi_mosi_en;
   reg [1:0]   go_spi_q;
   wire        go_spi_s;

   // Explicit synchronizer for go_spi
   synchronizer #(.width(1)) go_spi_synchro
     (
      .rst_n ( rst_n ),
      .clk ( rom_clk ),
      .d ( go_spi ),
      .q ( go_spi_s )
      );

   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 <= 26'b0;
	  spi_cs_n     <= 1'b1;
	  spi_in_req   <= 1'b0;
	  spi_in_req_q <= 1'b0;
	  spi_mosi_en  <= 1'b1;
	  go_spi_q     <= 4'b0000;
       end
     else
       begin
	  go_spi_q     <= { go_spi_q[0], go_spi_s };
	  spi_in_q     <= spi_io;
	  spi_in_req   <= 1'b0;
	  spi_in_req_q <= spi_in_req;
	  spi_clk_en   <= 1'b0;
	  spi_cs_n     <= 1'b1;

	  // Note: datalen <- spi_data_ctr is a 2-cycle multipath
	  if (go_spi_q == 2'b01)
	    begin
	       spi_data_ctr <= { datalen, 5'b0 };
	       spi_cmd_ctr  <= 6'b0;
	       spi_cs_n     <= 1'b0;
	    end

	  if ( ~|spi_data_ctr | ~go_spi_q[1] )
	    begin
	       spi_clk_en  <= 1'b0;
	       spi_mosi_en <= 1'b1;
	    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[2] )
			   spi_mosi_en <= 1'b0;

			 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      <= 32'bx;	// Fast Read Dual Output
	  spi_clk_en_q <= 1'b0;
       end
     else
       begin
	  if (spi_cs_n)
	    begin
	       spi_cmd[31:24] <= 8'h3b;	// Fast Read Dual Output
	       spi_cmd[23: 0] <= { romstart, 3'b000 }; // Byte address
	    end

	  spi_clk_en_q <= spi_clk_en;
	  if ( spi_clk_en_q )
	    spi_cmd <= (spi_cmd << 1) | 1'b1;
       end

   //
   // SPI_SCK output buffer
   //
   ddio_out spi_clk_buf (
			 .aclr ( ~rst_n ),
			 .datain_h ( spi_clk_en_q ),
			 .datain_l ( 1'b0 ),
			 .outclock ( rom_clk ),
			 .dataout ( spi_sck )
			 );

endmodule // spirom
