// // 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, and for a read, 32 dummy bits (16 cycles) // All data is processed as 32-bit words only. // module esp ( 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 ); reg [24:2] mem_addr; reg mem_valid; reg [31:0] mem_wdata; wire mem_write; wire mem_ready; wire [31:0] mem_rdata; dram_port #(32) mem ( .bus ( dram ), .prio ( 2'd2 ), .addr ( {mem_addr, 2'b00} ), .valid ( mem_valid ), .wd ( mem_wdata ), .wstrb ( {4{mem_wrq}} ), .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_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:1] spi_irq; reg [ 1:0] spi_out; reg spi_oe; 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_cmd; spi_cmd <= 'b0; spi_ctr <= 4'd3; // 8 bits needed for this state cpu_irq <= 'b0; spi_irq <= 'b0; spi_oe <= 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; spi_cmd <= 'b0; for (int i = 1; i < 4; i++) if (spi_cmd[1:0] == i) cpu_irq[i] <= 1'b1; end else if (spi_clk_q == 2'b01) begin spi_ctr <= spi_ctr - 1'b1; if (|spi_ctr) begin spi_shr <= spi_indata; end else begin // Transfer data to/from memory controller, but // we have to shuffle endianness... 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]; mem_wdata[31:24] <= spi_indata[ 7: 0]; mem_wdata[23:16] <= spi_indata[15: 8]; mem_wdata[15: 8] <= spi_indata[23:16]; mem_wdata[ 7: 0] <= spi_indata[31:24]; if (mem_valid) cpu_irq[0] <= 1'b1; // Overrun/underrun case (spi_state) st_cmd: begin spi_cmd <= spi_indata[5:0]; spi_state <= st_addr; for (int i = 1; i < 4; i++) if (spi_indata[3:2] == i) spi_irq[i] <= 1'b0; end st_addr: begin mem_addr <= spi_indata[25:2]; spi_state <= st_io; mem_valid <= ~mem_write; end st_io: begin mem_valid <= 1'b1; end endcase end end else if (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_valid <= 1'b0; mem_addr <= mem_addr + 1'b1; 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] <= 1'b0; 2'b10: spi_irq <= cpu_wdata[3:1]; 2'b11: for (int i = 1; i < 4; i++) if (cpu_wdata[i]) spi_irq[i] <= 1'b1; 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, 1'b0 }; endcase // casez (cpu_addr[1:0]) endmodule // esp