123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320 |
- //
- // 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 three interrupt flags (3-1); the FPGA CPU additionally
- // has a fourth interrupt condition (0) which indicates DRAM timing
- // overrun/underrun.
- //
- // The SPI command byte is:
- // Bit [7:5] - reserved, must be 0
- // Bit 4 - read/write#
- // Bit [3:2] - clear upstream (FPGA->ESP) interrupt flag if nonzero
- // Bit [1: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:12] = 4'b1001
- // Bit [11: 8] = 0 reserved
- // Bit [ 7: 5] = upstream interrupts pending
- // Bit 4 = downstream writes enabled
- // Bit [ 3: 1] = downstream interrupts pending
- // Bit 0 = underrun error
- //
- // The following CPU registers are defined:
- //
- // 0 = status bits [3:0] (downstream)
- // 1 = write-1-clear of status bits [3:0]
- // 2 = status bits [7:4] (upstream)
- // 3 = write-1-set of status bits [7:4]
- //
- 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 [ 4:0] spi_cmd;
- reg [31:0] spi_shr;
- reg [ 3:0] spi_ctr;
- reg [ 3:0] cpu_irq;
- reg [ 3:0] cpu_set_irq; // CPU IRQs to set once idle
- reg cpu_send_irq; // Ready to set CPU IRQs
- reg [ 3:1] spi_irq;
- reg [ 3:1] latched_spi_irq; // SPI IRQ as of transition start
- 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[4];
- 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)
- begin
- // Memory output
- spi_shr[31:24] <= mem_rdata[ 7: 0];
- spi_shr[23:16] <= mem_rdata[15: 8];
- spi_shr[15: 8] <= mem_rdata[23:16];
- spi_shr[ 7: 0] <= mem_rdata[31:24];
- end
- else
- begin
- // Status output
- spi_shr[31:28] <= { latched_spi_irq, spi_wr_en };
- spi_shr[27:24] <= cpu_irq;
- spi_shr[23:16] <= 8'b1001_0000;
- spi_shr[15: 8] <= rtc_ctr[ 7: 0];
- spi_shr[ 7: 0] <= rtc_ctr[15: 8];
- end // else: !if(spi_state == st_io)
- 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[5:0];
- spi_state <= st_addr;
- latched_spi_irq <= spi_irq;
- spi_mem_en <= ~mem_write | spi_wr_en;
- for (int i = 1; i < 4; i++)
- begin
- if (spi_indata[3:2] == i)
- spi_irq[i] <= 1'b0;
- if (spi_indata[1:0] == i)
- cpu_set_irq[i] <= 1'b1;
- end
- end
- st_addr: begin
- mem_addr <= spi_indata & mem_addr_mask;
- spi_state <= st_io;
- mem_valid <= ~mem_write;
- 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[1:0])
- 2'b00:
- cpu_irq <= cpu_wdata[3:0];
- 2'b01:
- for (int i = 0; i < 4; i++)
- if (cpu_wdata[i])
- cpu_irq[i] <= cpu_send_irq & cpu_set_irq[i];
- 2'b10:
- { spi_irq, spi_wr_en } <= cpu_wdata[3:0];
- 2'b11: begin
- if (cpu_wdata[0])
- spi_wr_en <= 1'b1;
- for (int i = 1; i < 4; i++)
- if (cpu_wdata[i])
- spi_irq[i] <= 1'b1;
- end
- endcase // case (cpu_addr[1:0])
- end // else: !if(~rst_n)
- always @(posedge sys_clk)
- irq <= |cpu_irq;
- always @(*)
- casez (cpu_addr[1:0])
- 2'b0?:
- cpu_rdata = { 28'b0, cpu_irq };
- 2'b1?:
- cpu_rdata = { 28'b0, spi_irq, spi_wr_en };
- endcase // casez (cpu_addr[1:0])
- endmodule // esp
|