//--------------------------------------------------------------------------------------- // filename: ssp_rx.sv // description: synchronous serial peripheral interface receiver // author: leguoqing@paisat.cn //--------------------------------------------------------------------------------------- module ssp_rx #( parameter integer FREQ_HZ = 100e6, parameter integer SSP_HZ = 10e6, parameter integer STREAM_WIDTH = 8 ) ( input logic clk, input logic aresetn, input logic enable, output logic [STREAM_WIDTH-1:0] tdata, output logic tvalid, output logic tkeep, output logic tstrb, output logic tlast, input logic tready, input logic ssp_clk, input logic ssp_csn, input logic ssp_data, input logic [31:0] config_00, input logic [31:0] config_01, input logic [31:0] config_02, output logic [31:0] status_00, output logic [31:0] status_01, output logic [31:0] status_02, output logic [31:0] status_03, output logic [31:0] status_04, output logic [31:0] status_05, output logic [31:0] status_06, output logic [31:0] status_07 ); if ($ceil(FREQ_HZ/SSP_HZ) != $floor(FREQ_HZ/SSP_HZ)) begin $fatal("%m: FREQ_HZ must be an integer multiple of SSP_HZ!"); end else if (FREQ_HZ/SSP_HZ < 6) begin $fatal("%m: FREQ_HZ/SSP_HZ < 6!"); end else if ((FREQ_HZ/SSP_HZ)%2 != 0) begin $warning("%m: FREQ_HZ/SSP_HZ is not an even number, which means the SSP clock will be asymmetric!"); end localparam integer CLK_HI = $ceil(FREQ_HZ/(2*SSP_HZ)); localparam integer CLK_LO = $floor(FREQ_HZ/(2*SSP_HZ)); // State encoding typedef enum logic [1:0] { IDLE, RECEIVE, LAST } state_t; state_t state, state_next; logic [ 7:0] fsm_hardcode; logic [31:0] counter_rx_data; logic [31:0] counter_rx_last; logic [31:0] counter_half_beats; logic [31:0] counter_clk_falling; logic [31:0] counter_data_rising; logic [31:0] counter_csn_falling; logic [31:0] counter_csn_rising; logic mode_wire3; logic mode_wire2; logic [3:0] header_size; logic [15:0] frame_size; logic [31:0] frame_header; logic [31:0] frame_header_mask; logic cfg_valid; logic [31:0] frame_header_candidate; logic header_matched; logic [31:0] header_mask_shifted; logic [15:0] beats_cnt; logic [STREAM_WIDTH-1:0] rx_shift_reg; logic [$clog2(STREAM_WIDTH)-1:0] rx_bit_cnt; logic data_valid; logic [STREAM_WIDTH-1:0] data; logic [2:0] ssp_csn_sync; logic [2:0] ssp_data_sync; logic [2:0] ssp_clk_sync; logic ssp_clk_falling; assign mode_wire3 = config_00[0]; assign mode_wire2 = config_00[1]; assign header_size = config_00[8 +: 4]; assign frame_size = config_00[16 +: 16]; assign frame_header = config_01; assign frame_header_mask = config_02; assign cfg_valid = (mode_wire3 && !mode_wire2) || (!mode_wire3 && mode_wire2 && header_size != 0 && frame_size != 0 && frame_header_mask != 0 && frame_header != 0); assign status_00 = {8'(cfg_valid), 5'h0, ssp_clk_sync[2], ssp_csn_sync[2], ssp_data_sync[2], fsm_hardcode, 8'(enable)}; assign status_01 = counter_rx_data; assign status_02 = counter_rx_last; assign status_03 = counter_half_beats; assign status_04 = counter_clk_falling; assign status_05 = counter_data_rising; assign status_06 = counter_csn_falling; assign status_07 = counter_csn_rising; // synchronize ssp_clk/ssp_csn/ssp_data to clk domain always_ff @(posedge clk) begin if (!aresetn) begin ssp_csn_sync <= 3'b111; ssp_data_sync <= 3'b000; ssp_clk_sync <= 3'b000; end else begin ssp_csn_sync <= {ssp_csn_sync[1:0], ssp_csn}; ssp_data_sync <= {ssp_data_sync[1:0], ssp_data}; ssp_clk_sync <= {ssp_clk_sync[1:0], ssp_clk}; end end assign ssp_clk_falling = (ssp_clk_sync[2:1] == 2'b10); always_ff @(posedge clk) begin if (!aresetn) begin counter_clk_falling <= 32'h0; counter_data_rising <= 32'h0; counter_csn_falling <= 32'h0; counter_csn_rising <= 32'h0; end else begin if (ssp_clk_falling) begin counter_clk_falling <= counter_clk_falling + 32'h1; end if (ssp_data_sync[2:1] == 2'b01) begin counter_data_rising <= counter_data_rising + 32'h1; end if (ssp_csn_sync[2:1] == 2'b10) begin counter_csn_falling <= counter_csn_falling + 32'h1; end if (ssp_csn_sync[2:1] == 2'b01) begin counter_csn_rising <= counter_csn_rising + 32'h1; end end end always_ff @(posedge clk) begin if (!aresetn) begin fsm_hardcode <= 8'h0; end else begin case (state) IDLE: fsm_hardcode <= 8'h0; RECEIVE: fsm_hardcode <= 8'h1; LAST: fsm_hardcode <= 8'h2; default: fsm_hardcode <= 8'hF; endcase end end // FSM: State register always_ff @(posedge clk) begin if (!aresetn) begin state <= IDLE; end else begin state <= state_next; end end // FSM: Next state logic always_comb begin state_next = state; case (state) IDLE: begin if (cfg_valid) if (mode_wire3) begin if (!ssp_csn_sync[1]) begin state_next = RECEIVE; end end else if (mode_wire2) begin state_next = RECEIVE; end end RECEIVE: begin if (mode_wire3) begin if (ssp_csn_sync[1]) begin state_next = LAST; end end else if (mode_wire2) begin if (header_matched && |frame_size != 0 && beats_cnt == frame_size) begin state_next = LAST; end end if (!cfg_valid) begin state_next = IDLE; end end LAST: begin state_next = IDLE; end endcase end // FSM: Output logic always_ff @(posedge clk) begin if (!aresetn) begin rx_shift_reg <= 0; rx_bit_cnt <= 0; data_valid <= 0; data <= 0; tvalid <= 0; tstrb <= 0; tkeep <= 0; tlast <= 0; tdata <= 0; header_matched <= 0; frame_header_candidate <= 0; beats_cnt <= 0; header_mask_shifted <= 0; end else begin if (tready) begin tvalid <= 1'b0; tstrb <= 1'b0; tkeep <= 1'b0; tlast <= 1'b0; end case (state) IDLE: begin rx_shift_reg <= 0; rx_bit_cnt <= 0; data_valid <= 0; data <= 0; header_matched <= 0; frame_header_candidate <= 0; beats_cnt <= 0; header_mask_shifted <= 0; end RECEIVE: begin if (ssp_clk_falling) begin rx_shift_reg <= {rx_shift_reg[STREAM_WIDTH-2:0], ssp_data_sync[1]}; rx_bit_cnt <= rx_bit_cnt + 1'b1; if (mode_wire3 == 1 || (mode_wire2 == 1 && header_matched)) begin if (rx_bit_cnt == STREAM_WIDTH-1) begin rx_bit_cnt <= 0; if (mode_wire3 == 0) beats_cnt <= beats_cnt + 1'b1; data <= {rx_shift_reg[STREAM_WIDTH-2:0], ssp_data_sync[1]}; data_valid <= 1'b1; // defer one beat if (data_valid) begin tvalid <= 1'b1; tstrb <= 1'b1; tkeep <= 1'b1; tlast <= 1'b0; tdata <= data; end end end if (mode_wire3 == 0 && mode_wire2 == 1 && header_matched == 0) begin frame_header_candidate <= {frame_header_candidate[30:0], ssp_data_sync[1]}; end end if (mode_wire3 == 0 && mode_wire2 == 1 && frame_header_candidate == (frame_header & frame_header_mask)) begin header_matched <= 1'b1; rx_bit_cnt <= 0; header_mask_shifted <= frame_header_mask; end // implicitly condition: has enough cycle to accomplish before next beat arrives if (header_matched && beats_cnt < header_size) begin tvalid <= 1'b1; tstrb <= 1'b1; tkeep <= |header_mask_shifted[32-STREAM_WIDTH+:STREAM_WIDTH]; tlast <= 1'b0; tdata <= frame_header_candidate[32-STREAM_WIDTH+:STREAM_WIDTH]; if (tvalid && tready) begin tvalid <= 1'b0; tstrb <= 1'b0; tkeep <= 1'b0; tlast <= 1'b0; frame_header_candidate <= {frame_header_candidate[0+:32-STREAM_WIDTH], {STREAM_WIDTH{1'b0}}}; header_mask_shifted <= {header_mask_shifted[0+:32-STREAM_WIDTH], {STREAM_WIDTH{1'b0}}}; beats_cnt <= beats_cnt + |header_mask_shifted[32-STREAM_WIDTH+:STREAM_WIDTH]; end end end LAST: begin tvalid <= 1'b1; tstrb <= 1'b1; tkeep <= 1'b1; tlast <= 1'b1; tdata <= data; end endcase end end always_ff @(posedge clk) begin if (!aresetn) begin counter_rx_data <= 32'h0; counter_rx_last <= 32'h0; counter_half_beats <= 0; end else begin if (tvalid && tready && tkeep) begin counter_rx_data <= counter_rx_data + 32'h1; end if (tvalid && tready && tlast) begin counter_rx_last <= counter_rx_last + 32'h1; end if (mode_wire3 == 1 && ssp_csn_sync[1] && state == RECEIVE && rx_bit_cnt != 3'h0) begin counter_half_beats <= counter_half_beats + 32'h1; end end end endmodule