`timescale 1ns/1ps module max2771_spi_engine #( parameter integer CLOCK_HZ = 50_000_000, parameter integer MAX_SCLK_HZ = 4_000_000 ) ( input wire clk, input wire rst_n, input wire start, input wire read_not_write, input wire [3:0] address, input wire [31:0] write_data, output reg [31:0] read_data, output reg busy, output wire ready, output reg sclk, output reg csn, input wire sdata_i, output reg sdata_o, output reg sdata_oe ); // Ceiling division keeps SCLK at or below the MAX2771 4 MHz limit. localparam integer HALF_PERIOD_CYCLES = (CLOCK_HZ + (2 * MAX_SCLK_HZ) - 1) / (2 * MAX_SCLK_HZ); localparam integer CS_INACTIVE_CYCLES = (CLOCK_HZ + 10_000_000 - 1) / 10_000_000; reg [47:0] tx_shift; reg [31:0] rx_shift; reg [5:0] bit_index; integer half_count; integer inactive_count; reg transaction_is_read; assign ready = !busy && (inactive_count == 0); initial begin if (CLOCK_HZ <= 0) $error("CLOCK_HZ must be positive"); if (MAX_SCLK_HZ <= 0) $error("MAX_SCLK_HZ must be positive"); if (HALF_PERIOD_CYCLES < 1) $error("HALF_PERIOD_CYCLES must be at least one"); end always @(posedge clk) begin if (!rst_n) begin read_data <= 32'd0; busy <= 1'b0; sclk <= 1'b0; csn <= 1'b1; sdata_o <= 1'b0; sdata_oe <= 1'b0; tx_shift <= 48'd0; rx_shift <= 32'd0; bit_index <= 6'd0; half_count <= 0; inactive_count <= 0; transaction_is_read <= 1'b0; end else if (busy) begin if (half_count != 0) begin half_count <= half_count - 1; end else begin half_count <= HALF_PERIOD_CYCLES - 1; if (!sclk) begin // MAX2771 samples input and the host samples read data on rising SCLK. sclk <= 1'b1; if (transaction_is_read && (bit_index >= 6'd16)) rx_shift <= {rx_shift[30:0], sdata_i}; end else begin // Both sides change or release SDATA on falling SCLK. sclk <= 1'b0; if (bit_index == 6'd47) begin read_data <= rx_shift; busy <= 1'b0; csn <= 1'b1; sdata_oe <= 1'b0; inactive_count <= CS_INACTIVE_CYCLES; end else begin bit_index <= bit_index + 1'b1; tx_shift <= {tx_shift[46:0], 1'b0}; sdata_o <= tx_shift[46]; if (transaction_is_read && (bit_index == 6'd12)) sdata_oe <= 1'b0; end end end end else if (inactive_count != 0) begin inactive_count <= inactive_count - 1; end else if (start) begin // Frame: A[11:0], R/W, three turnaround bits, D[31:0]. A[11:4] are zero. tx_shift <= {8'd0, address, read_not_write, 3'd0, write_data}; rx_shift <= 32'd0; bit_index <= 6'd0; transaction_is_read <= read_not_write; busy <= 1'b1; csn <= 1'b0; sclk <= 1'b0; sdata_o <= 1'b0; sdata_oe <= 1'b1; half_count <= HALF_PERIOD_CYCLES - 1; end end endmodule