diff options
Diffstat (limited to 'hdl/spi_controller.v')
| -rw-r--r-- | hdl/spi_controller.v | 290 |
1 files changed, 290 insertions, 0 deletions
diff --git a/hdl/spi_controller.v b/hdl/spi_controller.v new file mode 100644 index 0000000..5bc71f6 --- /dev/null +++ b/hdl/spi_controller.v @@ -0,0 +1,290 @@ +`timescale 1ns / 1ps + +module spi_controller ( + input wire clk, + input wire rst_n, + + input wire [7:0] cmd_opcode, + input wire [31:0] cmd_address, + input wire [15:0] cmd_length, + input wire cmd_valid, + output wire cmd_ready, + + input wire [7:0] data_in, + input wire data_in_valid, + output wire data_in_ready, + + output wire [7:0] data_out, + output wire data_out_valid, + input wire data_out_ready, + + output wire spi0_sck, + output wire spi0_mosi, + input wire spi0_miso, + output wire spi0_cs, + + output wire spi1_sck, + output wire spi1_mosi, + input wire spi1_miso, + output wire spi1_cs, + + output reg [3:0] flash_select, + output reg [3:0] spi_state, + output reg [3:0] status_reg +); + + localparam STATE_IDLE = 4'h0; + localparam STATE_CMD = 4'h1; + localparam STATE_ADDR = 4'h2; + localparam STATE_DUMMY = 4'h3; + localparam STATE_DATA_RX = 4'h4; + localparam STATE_DATA_TX = 4'h5; + localparam STATE_WAIT = 4'h6; + localparam STATE_ERROR = 4'h7; + + localparam MODE_STANDARD = 2'b00; + localparam MODE_FAST = 2'b01; + localparam MODE_DUAL = 2'b10; + localparam MODE_QUAD = 2'b11; + + localparam CMD_WREN = 8'h06; + localparam CMD_WRDI = 8'h04; + localparam CMD_RDSR = 8'h05; + localparam CMD_WRSR = 8'h01; + localparam CMD_READ = 8'h03; + localparam CMD_FAST_READ = 8'h0B; + localparam CMD_DREAD = 8'h3B; + localparam CMD_QREAD = 8'h6B; + localparam CMD_PP = 8'h02; + localparam CMD_SE = 8'h20; + localparam CMD_BE32 = 8'h52; + localparam CMD_BE64 = 8'hD8; + localparam CMD_CE = 8'hC7; + localparam CMD_RDID = 8'h9F; + localparam CMD_RDFR = 8'h48; + localparam CMD_WRFR = 8'h42; + + reg [7:0] spi_mode; + reg [2:0] spi_divider; + reg [31:0] current_addr; + reg [15:0] bytes_remaining; + reg [7:0] command_reg; + reg [3:0] state; + reg [3:0] next_state; + + reg [7:0] tx_shift_reg; + reg [7:0] rx_shift_reg; + reg [3:0] bit_counter; + reg tx_active; + reg rx_active; + + reg cs0_active; + reg cs1_active; + reg sck_active; + + reg [23:0] delay_counter; + + reg [7:0] clk_div_counter; + wire sck_posedge; + wire sck_negedge; + + assign sck_posedge = (clk_div_counter == spi_divider); + assign sck_negedge = (clk_div_counter == (spi_divider >> 1)); + + assign spi0_sck = sck_active & cs0_active & ~spi0_cs; + assign spi1_sck = sck_active & cs1_active & ~spi1_cs; + assign spi0_cs = ~cs0_active; + assign spi1_cs = ~cs1_active; + + assign spi0_mosi = tx_active ? tx_shift_reg[7] : 1'bz; + assign spi1_mosi = tx_active ? tx_shift_reg[7] : 1'bz; + + assign cmd_ready = (state == STATE_IDLE); + + assign data_in_ready = (state == STATE_DATA_TX) && tx_active && sck_negedge; + assign data_out_valid = (state == STATE_DATA_RX) && rx_active && sck_posedge; + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + state <= STATE_IDLE; + spi_state <= STATE_IDLE; + cs0_active <= 1'b0; + cs1_active <= 1'b0; + sck_active <= 1'b0; + tx_active <= 1'b0; + rx_active <= 1'b0; + spi_mode <= MODE_STANDARD; + spi_divider <= 3'd1; + flash_select <= 2'b00; + status_reg <= 4'b0000; + end else begin + state <= next_state; + spi_state <= state; + + case (state) + STATE_IDLE: begin + cs0_active <= 1'b0; + cs1_active <= 1'b0; + sck_active <= 1'b0; + tx_active <= 1'b0; + rx_active <= 1'b0; + + if (cmd_valid && cmd_ready) begin + if (cmd_address[24] == 1'b0) begin + cs0_active <= 1'b1; + flash_select <= 2'b01; + end else begin + cs1_active <= 1'b1; + flash_select <= 2'b10; + end + + command_reg <= cmd_opcode; + current_addr <= cmd_address; + bytes_remaining <= cmd_length; + next_state <= STATE_CMD; + end + end + + STATE_CMD: begin + if (sck_negedge) begin + if (bit_counter == 4'd7) begin + bit_counter <= 4'd0; + if (command_reg == CMD_READ || + command_reg == CMD_FAST_READ || + command_reg == CMD_PP) begin + next_state <= STATE_ADDR; + end else if (command_reg == CMD_RDSR || + command_reg == CMD_RDID) begin + next_state <= STATE_DATA_RX; + end else begin + next_state <= STATE_WAIT; + end + end else begin + tx_shift_reg <= tx_shift_reg << 1; + bit_counter <= bit_counter + 1; + end + end + end + + STATE_ADDR: begin + if (sck_negedge) begin + if (bit_counter == 4'd7) begin + bit_counter <= 4'd0; + if (addr_byte_counter == 3'd2) begin + if (command_reg == CMD_FAST_READ || + command_reg == CMD_DREAD || + command_reg == CMD_QREAD) begin + next_state <= STATE_DUMMY; + end else if (command_reg == CMD_READ) begin + next_state <= STATE_DATA_RX; + end else if (command_reg == CMD_PP) begin + next_state <= STATE_DATA_TX; + end else begin + next_state <= STATE_WAIT; + end + end else begin + addr_byte_counter <= addr_byte_counter + 1; + end + end else begin + tx_shift_reg <= tx_shift_reg << 1; + bit_counter <= bit_counter + 1; + end + end + end + + STATE_DATA_RX: begin + rx_active <= 1'b1; + if (sck_posedge) begin + rx_shift_reg <= {rx_shift_reg[6:0], (cs0_active ? spi0_miso : spi1_miso)}; + if (bit_counter == 4'd7) begin + bit_counter <= 4'd0; + data_out <= rx_shift_reg; + if (bytes_remaining > 0) begin + bytes_remaining <= bytes_remaining - 1; + end else begin + next_state <= STATE_IDLE; + rx_active <= 1'b0; + end + end else begin + bit_counter <= bit_counter + 1; + end + end + end + + STATE_DATA_TX: begin + tx_active <= 1'b1; + if (sck_negedge) begin + if (bit_counter == 4'd7) begin + bit_counter <= 4'd0; + if (data_in_valid && data_in_ready) begin + tx_shift_reg <= data_in; + if (bytes_remaining > 0) begin + bytes_remaining <= bytes_remaining - 1; + end else begin + next_state <= STATE_WAIT; + tx_active <= 1'b0; + end + end + end else begin + tx_shift_reg <= tx_shift_reg << 1; + bit_counter <= bit_counter + 1; + end + end + end + + STATE_WAIT: begin + if (delay_counter == 24'hFFFFFF) begin + next_state <= STATE_IDLE; + end else begin + delay_counter <= delay_counter + 1; + end + end + + STATE_ERROR: begin + cs0_active <= 1'b0; + cs1_active <= 1'b0; + sck_active <= 1'b0; + next_state <= STATE_IDLE; + end + endcase + end + end + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + clk_div_counter <= 8'd0; + sck_active <= 1'b0; + end else begin + if (cs0_active || cs1_active) begin + if (clk_div_counter >= spi_divider) begin + clk_div_counter <= 8'd0; + sck_active <= ~sck_active; + end else begin + clk_div_counter <= clk_div_counter + 1; + end + end else begin + clk_div_counter <= 8'd0; + sck_active <= 1'b0; + end + end + end + + reg [2:0] addr_byte_counter; + + always @(posedge clk or negedge rst_n) begin + if (!rst_n) begin + addr_byte_counter <= 3'd0; + end else if (state == STATE_ADDR) begin + end else begin + addr_byte_counter <= 3'd0; + end + end + + initial begin + tx_shift_reg = 8'h00; + rx_shift_reg = 8'h00; + bit_counter = 4'd0; + delay_counter = 24'h0; + end + +endmodule
\ No newline at end of file |
