322 lines
12 KiB
Systemverilog
322 lines
12 KiB
Systemverilog
//---------------------------------------------------------------------------------------
|
|
// 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 |