| // Copyright lowRISC contributors. | |
| // Licensed under the Apache License, Version 2.0, see LICENSE for details. | |
| // SPDX-License-Identifier: Apache-2.0 | |
| // | |
| // SPI Flash Read JEDEC ID handler | |
| `include "prim_assert.sv" | |
| module spid_jedec | |
| import spi_device_pkg::*; | |
| ( | |
| input clk_i, | |
| input rst_ni, | |
| input clk_out_i, // Output clock (inverted SCK) | |
| input inclk_csb_asserted_pulse_i, | |
| input jedec_cfg_t sys_jedec_i, // from CSR | |
| output io_mode_e io_mode_o, | |
| input sel_datapath_e sel_dp_i, | |
| input cmd_info_t cmd_info_i, | |
| input logic [CmdInfoIdxW-1:0] cmd_info_idx_i, | |
| output logic outclk_p2s_valid_o, | |
| output spi_byte_t outclk_p2s_byte_o, | |
| input logic outclk_p2s_sent_i | |
| ); | |
| typedef enum logic [1:0] { | |
| StIdle, | |
| // If num_cc is non-zero, State machine moves to CC state and sends CC up | |
| // to num_cc times. | |
| StCC, | |
| // In JedecId state, the logic sends manufacturer ID (jedec.jedec_id) | |
| StJedecId, | |
| // After sending a byte at StJedecId, the logic returns 2 DevId bytes to | |
| // the host system. | |
| StDevId | |
| } st_e; | |
| st_e st_q, st_d; | |
| //////////// | |
| // Signal // | |
| //////////// | |
| assign io_mode_o = SingleIO; | |
| jedec_cfg_t jedec; | |
| logic p2s_valid; | |
| spi_byte_t p2s_byte; | |
| logic next_byte; | |
| logic [1:0] byte_sel_q, byte_sel_d; | |
| // Unused | |
| logic unused_cmd_info; | |
| assign unused_cmd_info = ^{cmd_info_i , cmd_info_idx_i}; | |
| ////////////// | |
| // Datapath // | |
| ////////////// | |
| // Jedec latch | |
| always_ff @(posedge clk_i or negedge rst_ni) begin | |
| if (!rst_ni) jedec <= '{default: '0}; | |
| else if (inclk_csb_asserted_pulse_i) jedec <= sys_jedec_i; | |
| end | |
| // If num_cc is non-zero, the logic shall return CC first | |
| logic cc_needed; | |
| assign cc_needed = |jedec.num_cc; | |
| logic [7:0] cc_count; | |
| always_ff @(posedge clk_i or negedge rst_ni) begin | |
| if (!rst_ni) cc_count <= '0; | |
| else if (st_q == StCC && outclk_p2s_sent_i) begin | |
| cc_count <= cc_count + 1'b 1; | |
| end | |
| end | |
| // Output to Parallel-to-Serial | |
| always_ff @(posedge clk_out_i or negedge rst_ni) begin | |
| if (!rst_ni) begin | |
| outclk_p2s_valid_o <= 1'b 0; | |
| outclk_p2s_byte_o <= 8'h 0; | |
| end else begin | |
| outclk_p2s_valid_o <= p2s_valid; | |
| outclk_p2s_byte_o <= p2s_byte; | |
| end | |
| end | |
| always_comb begin : p2s_byte_logic | |
| p2s_byte = 8'h 0; | |
| if (st_q == StIdle) begin | |
| // Manufacturer ID always | |
| p2s_byte = (cc_needed) ? jedec.cc : jedec.jedec_id; | |
| end else if (st_q == StCC) begin | |
| p2s_byte = jedec.cc; | |
| end else if (st_q == StJedecId) begin | |
| p2s_byte = jedec.jedec_id; | |
| end else if (byte_sel_q == 2'h 2) begin | |
| // End of the transfer but host keep toggles SCK. Sending out 0 always | |
| p2s_byte = 8'h 0; | |
| end else begin | |
| // based on byte_sel_q | |
| p2s_byte = jedec.device_id[8*byte_sel_q+:8]; | |
| end | |
| end : p2s_byte_logic | |
| // Byte selection | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : byte_sel_latch | |
| if (!rst_ni) byte_sel_q <= 8'h 0; // select manufacturer id | |
| else if (next_byte) byte_sel_q <= byte_sel_d; | |
| end : byte_sel_latch | |
| assign byte_sel_d = (byte_sel_q == 2'b 10) ? 2'b 10 : byte_sel_q + 1'b 1; | |
| /////////// | |
| // State // | |
| /////////// | |
| always_ff @(posedge clk_i or negedge rst_ni) begin : state_latch | |
| if (!rst_ni) st_q <= StIdle; | |
| else st_q <= st_d; | |
| end : state_latch | |
| always_comb begin : next_state_logic | |
| st_d = st_q; | |
| p2s_valid = 1'b 0; | |
| next_byte = 1'b 0; | |
| unique case (st_q) | |
| StIdle: begin | |
| if (sel_dp_i == DpReadJEDEC) begin | |
| st_d = (cc_needed) ? StCC : StJedecId ; | |
| // Send out the dat | |
| p2s_valid = 1'b 1; | |
| end | |
| end | |
| StCC: begin | |
| // cc_count is increased by 1 when outclk_p2s_sent_i is asserted | |
| // outclk_p2s_sent_i asserts at the 7th beat, not 8th beat. When | |
| // cc_count reaches to num_cc, it is the end of a byte transaction in | |
| // SCK input clock domain. So, that time state machine moves to | |
| // JedecId | |
| if (cc_count == jedec.num_cc) begin | |
| st_d = StJedecId; | |
| end | |
| p2s_valid = 1'b 1; | |
| end | |
| StJedecId: begin | |
| if (outclk_p2s_sent_i) begin | |
| st_d = StDevId; | |
| end | |
| p2s_valid = 1'b 1; | |
| end | |
| StDevId: begin | |
| // TERMINAL_STATE | |
| // Sends data | |
| p2s_valid = 1'b 1; | |
| if (outclk_p2s_sent_i) begin | |
| next_byte = 1'b 1; | |
| end | |
| end | |
| default: begin | |
| st_d = StIdle; | |
| end | |
| endcase | |
| end : next_state_logic | |
| endmodule : spid_jedec |