//
// Communication interface with ESP32-S2
//
// This is a DIO (2-bit, including command) SPI slave interface which
// allows direct access to content in SDRAM. Additionally, each
// direction has 7 interrupt condition flags (7-1); the FPGA CPU
// additionally has an internal interrupt condition (0) which
// indicates DRAM timing overrun/underrun, also visible in the status
// word as bit 0.
//
// The SPI command byte is:
// Bit    7   - save command timestamp
// Bit    6   - read/write#
// Bit [5:3]  - clear upstream (FPGA->ESP) interrupt flag if nonzero
// Bit [2:0]  - set downstream (ESP->FPGA) interrupt flag if nonzero
//
// CPU downstream interrupts are set after the transaction completes
// (CS# goes high.)
//
// A 32-bit address follows; for a read, the following 16 cycles
// contains dummy/status data:
//
// Bit [31:16] = 16 LSB of 32 kHz RTC counter
// Bit [15: 9] = upstream interrupts pending
// Bit      8  = downstream writes enabled
// Bit [ 7: 1] = downstream interrupts pending
// Bit      0  = underrun error
//
// The following CPU registers are defined:
//
// 0	       = status bits [7:0] (downstream)
// 1	       = write-1-clear of status bits [7:0]
// 2           = status bits [15:8] (upstream)
// 3           = write-1-set of status bits [15:8]
// 4           = timestamp (16 LSB of 32 kHz RTC counter) saved by cmd bit 7
//
module esp #(
	     parameter        dram_bits = 25,
	     parameter [31:0] dram_base = 32'h40000000
	     ) (
		input	      rst_n,
		input	      sys_clk,
		input	      sdram_clk,

		input	      cpu_valid,
		input [4:0]   cpu_addr,
		input [3:0]   cpu_wstrb,
		input [31:0]  cpu_wdata,
		output [31:0] cpu_rdata,
		output reg    irq,

		dram_bus.dstr dram,

		output reg    esp_int,
		input	      spi_clk,
		inout [1:0]   spi_io,
		input	      spi_cs_n,

		input [15:0]  rtc_ctr
		);

   reg  [31:0]		      mem_addr = 'b0;
   wire [31:0]		      mem_addr_mask = (1'b1 << dram_bits) - 3'd4;
   wire [31:0]		      mem_addr_out = (mem_addr & mem_addr_mask)
			      | dram_base;

   reg			      mem_valid;
   reg [31:0]		      mem_wdata;
   wire			      mem_write;
   reg [ 3:0]		      mem_wstrb;
   wire			      mem_ready;
   wire [31:0]		      mem_rdata;

   dram_port #(32) mem
     (
      .bus   ( dram ),
      .prio  ( 2'd2 ),
      .addr  ( mem_addr[dram_bits-1:0] ),
      .valid ( mem_valid ),
      .wd    ( mem_wdata ),
      .wstrb ( mem_wstrb ),
      .ready ( mem_ready ),
      .rd    ( mem_rdata )
      );

   reg [1:0]		  spi_clk_q;
   reg			  spi_cs_n_q;
   reg [1:0]		  spi_io_q;

   always @(posedge sdram_clk)
     begin
	spi_clk_q  <= { spi_clk_q[0], spi_clk };
	spi_cs_n_q <= spi_cs_n;
	spi_io_q   <= spi_io;
     end

   typedef enum logic [1:0] {
	st_dead,		// Do nothing until CS# deasserted
	st_cmd,			// Reading command
	st_addr,		// Reading address
	st_io			// I/O (including read dummy bits)
   } state_t;

   state_t    spi_state;

   reg [ 7:0] spi_cmd;
   reg [31:0] spi_shr;
   reg [ 3:0] spi_ctr;
   reg [ 7:0] cpu_irq;
   reg [ 7:0] cpu_set_irq;	// CPU IRQs to set once idle
   reg        cpu_send_irq;	// Ready to set CPU IRQs
   reg [ 7:1] spi_irq;
   reg [ 7:1] latched_spi_irq;	// SPI IRQ as of transition start
   reg [15:0] timestamp_q;
   reg [ 1:0] spi_out;
   reg	      spi_oe;
   reg [ 2:0] spi_wbe;		// Partial word write byte enables
   reg [23:0] spi_wdata;	// Partial word write data
   reg	      spi_wr_en;	// SPI writes enabled by CPU
   reg	      spi_mem_en;	// Read, or write enabled at start of trans

   assign spi_io = spi_oe ? spi_out : 2'bzz;

   assign mem_write = ~spi_cmd[6];

   wire [31:0] spi_indata = { spi_shr[29:0], spi_io_q };

   reg	       cpu_valid_q;

   always @(negedge rst_n or posedge sdram_clk)
     if (~rst_n)
       begin
	  spi_state       <= st_dead;
	  spi_cmd         <= 'b0;
	  spi_ctr         <= 4'd3;	// 8 bits needed for this state
	  cpu_irq         <= 'b0;
	  cpu_set_irq     <= 'b0;
	  cpu_send_irq    <= 1'b0;
	  spi_irq         <= 'b0;
	  latched_spi_irq <= 'b0;
	  spi_oe          <= 1'b0;
	  spi_wbe         <= 3'b0;
	  mem_addr        <= 'b0;
	  mem_wstrb       <= 4'b0;
	  mem_valid       <= 1'b0;
	  spi_wr_en       <= 1'b0;
	  spi_mem_en      <= 1'b0;
       end
     else
       begin
	  esp_int <= ~|spi_irq;

	  if (spi_cs_n_q)
	    begin
	       spi_state    <= st_cmd;
	       spi_ctr      <= 4'd3;
	       spi_oe       <= 1'b0;
	       cpu_send_irq <= |cpu_set_irq;
	    end

	  if (cpu_send_irq & ~mem_valid & ~|spi_wbe)
	    begin
	       cpu_irq      <= cpu_irq | cpu_set_irq;
	       cpu_set_irq  <= 'b0;
	       cpu_send_irq <= 1'b0;
	    end

	  if (~spi_cs_n_q && spi_clk_q == 2'b01)
	    begin
	       spi_ctr <= spi_ctr - 1'b1;
	       spi_shr <= spi_indata;

	       case (spi_ctr)
		 4'b1100:
		   if (spi_state == st_io && mem_write)
		     begin
			spi_wbe[0]      <= 1'b1;
			spi_wdata[7:0]  <= spi_indata[7:0];
		     end
		 4'b1000:
		   if (spi_state == st_io && mem_write)
		     begin
			spi_wbe[1]      <= 1'b1;
			spi_wdata[15:8] <= spi_indata[7:0];
		     end
		 4'b0100:
		   if (spi_state == st_io && mem_write)
		     begin
			spi_wbe[2]       <= 1'b1;
			spi_wdata[23:16] <= spi_indata[7:0];
		     end
		 4'b0000: begin
		       // Load spi_shr, but account for endianness here...
		       if (spi_state == st_io)
			 spi_shr <= bswap32(mem_rdata);
		       else
			 // Status output
			 spi_shr <= bswap32({
					     rtc_ctr,
					     latched_spi_irq, spi_wr_en,
					     cpu_irq
					     });

		       if (mem_valid && spi_state != st_cmd)
			 begin
			    cpu_irq[0] <= 1'b1; // Overrun/underrun
			    spi_wr_en  <= 1'b0; // Block further memory writes
			    spi_mem_en <= ~mem_write;
			 end

		       case (spi_state)
			 st_dead: begin
			    // Do nothing
			 end
			 st_cmd: begin
			    spi_cmd         <= spi_indata[7:0];
			    spi_state       <= st_addr;
			    latched_spi_irq <= spi_irq;
			    for (int i = 1; i < 8; i++)
			      begin
				 if (spi_indata[5:3] == i)
				   spi_irq[i] <= 1'b0;
				 if (spi_indata[2:0] == i)
				   cpu_set_irq[i] <= 1'b1;
			      end
			    if (spi_indata[7])
			      timestamp_q <= rtc_ctr;
			 end
			 st_addr: begin
			    mem_addr   <= spi_indata & mem_addr_mask;
			    spi_state  <= st_io;
			    mem_valid  <= ~mem_write;
			    spi_mem_en <= ~mem_write | spi_wr_en;
			    mem_wstrb  <= 4'b0;
			    spi_wbe    <= 3'b000;
			    // If the first word is partial, skip ahead
			    if (mem_write)
			      spi_ctr[3:2] <= ~spi_indata[1:0];
			 end
			 st_io: begin
			    if (mem_write)
			      begin
				 mem_wdata[23: 0] <= spi_wdata[23:0];
				 mem_wdata[31:24] <= spi_indata[7:0];
				 mem_wstrb <= { 1'b1, spi_wbe };
			      end
			    else
			      begin
				 mem_wstrb <= 4'b0000;
			      end // else: !if(mem_write)
			    mem_valid <= spi_mem_en;
			    spi_wbe   <= 3'b000;
			 end
		       endcase
		    end // case: 4'b0000
		 default: begin
		    // Do nothing
		 end
	       endcase // case (spi_ctr)
	    end // if (spi_clk_q == 2'b01)
	  else if (~spi_cs_n_q && spi_clk_q == 2'b10)
	    begin
	       spi_out <= spi_shr[31:30];
	       spi_oe  <= (spi_state == st_io) & ~mem_write;
	    end

	  if (mem_valid & mem_ready)
	    begin
	       mem_addr  <= mem_addr + 3'd4;
	       mem_valid <= 1'b0;
	    end

	  if (spi_state != st_io & ~mem_valid & |spi_wbe)
	    begin
	       // Complete a partial write terminated by CS#
	       mem_valid        <= spi_mem_en;
	       mem_wstrb        <= { 1'b0, spi_wbe };
	       mem_wdata[23:0]  <= spi_wdata[23:0];
	       mem_wdata[31:24] <= 8'hxx;
	       spi_wbe          <= 3'b000;
	    end

	  cpu_valid_q <= cpu_valid;
	  if (cpu_valid & ~cpu_valid_q & cpu_wstrb[0])
	    case (cpu_addr[2:0])
	      3'b000:
		 cpu_irq <= cpu_wdata[7:0];
	      3'b001:
		for (int i = 0; i < 8; i++)
		  if (cpu_wdata[i])
		    cpu_irq[i] <= cpu_send_irq & cpu_set_irq[i];
	      3'b010:
		 { spi_irq, spi_wr_en } <= cpu_wdata[7:0];
	      3'b011: begin
		if (cpu_wdata[0])
		  spi_wr_en <= 1'b1;
		for (int i = 1; i < 8; i++)
		  if (cpu_wdata[i])
		    spi_irq[i] <= 1'b1;
	      end
	      default: begin
		 // Do nothing
	      end
	    endcase // case (cpu_addr[2:0])
       end // else: !if(~rst_n)

   always @(posedge sys_clk)
     irq <= |cpu_irq;

   always @(*)
     casez (cpu_addr[2:0])
       3'b000:
	 cpu_rdata = { 28'b0, cpu_irq };
       3'b010:
	 cpu_rdata = { 28'b0, spi_irq, spi_wr_en };
       3'b100:
	 cpu_rdata = { 16'b0, timestamp_q };
       default:
	 cpu_rdata = 32'bx;
     endcase // casez (cpu_addr[2:0])

endmodule // esp
