aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/x4xx_gpio_spi.v
diff options
context:
space:
mode:
Diffstat (limited to 'fpga/usrp3/top/x400/x4xx_gpio_spi.v')
-rw-r--r--fpga/usrp3/top/x400/x4xx_gpio_spi.v534
1 files changed, 534 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/x4xx_gpio_spi.v b/fpga/usrp3/top/x400/x4xx_gpio_spi.v
new file mode 100644
index 000000000..b5866f867
--- /dev/null
+++ b/fpga/usrp3/top/x400/x4xx_gpio_spi.v
@@ -0,0 +1,534 @@
+//
+// Copyright 2021 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: x4xx_gpio_spi
+//
+// Description:
+//
+// This block enables control of a SPI master engine via CtrlPort
+// transactions.
+// It also enables customization on how signals from the SPI buses
+// connected to the master are mapped to the GPIO Ports.
+// This block supports configuring communication to up to 4 SPI slaves.
+//
+// Parameters:
+//
+// NUM_SLAVES : Number of SPI slaves to be supported. Values from 1 to 4
+// are supported. SPI transfers can only target one slave
+// at a time.
+// BASE_ADDRESS : Start address for this register block.
+// SIZE_ADDRESS : Size of the CtrlPort window to consider in this block.
+//
+
+`default_nettype none
+
+
+module x4xx_gpio_spi #(
+ parameter NUM_SLAVES = 4,
+ parameter [19:0] BASE_ADDRESS = 0,
+ parameter [19:0] SIZE_ADDRESS = 19'h20
+) (
+ input wire ctrlport_clk,
+ input wire ctrlport_clk_2x,
+ input wire ctrlport_rst,
+
+ // Request
+ input wire s_ctrlport_req_wr,
+ input wire s_ctrlport_req_rd,
+ input wire [19:0] s_ctrlport_req_addr,
+ input wire [31:0] s_ctrlport_req_data,
+
+ // Response
+ output reg s_ctrlport_resp_ack = 1'b0,
+ output reg [ 1:0] s_ctrlport_resp_status = 2'b0,
+ output reg [31:0] s_ctrlport_resp_data = 32'b0,
+
+ // GPIO control/status
+ output wire [31:0] gpio_out,
+ output wire [31:0] gpio_ddr,
+ input wire [31:0] gpio_in
+);
+
+ `include "../../lib/rfnoc/core/ctrlport.vh"
+ `include "regmap/dig_ifc_regmap_utils.vh"
+
+ // Registers / wires for SPI core communication
+ reg [31:0] set_data = 0;
+ reg [ 7:0] set_addr = 0;
+ reg set_stb = 1'b0;
+
+ wire [31:0] readback;
+ wire readback_stb;
+ wire readback_stb_extended;
+
+ wire sclk;
+ wire mosi;
+ wire miso;
+ // This array is set to the maximum supported SPI slaves instead of
+ // the provided NUM_SLAVES to facilitate concurrent re-mapping.
+ // See section(GPIO Mapping) of this file.
+ wire [3:0] ss;
+
+ // Auxiliary signals to compute which GPIO lines are outputs
+ reg [31:0] gpio_is_mosi = 32'b0;
+ reg [31:0] gpio_is_sclk = 32'b0;
+ reg [31:0] gpio_is_cs = 32'b0;
+
+ // SPI-to-GPIO mapping signals.
+ // These arrays are set to the maximum supported SPI slaves instead of
+ // the provided NUM_SLAVES to facilitate concurrent re-mapping.
+ // See section(GPIO Mapping) of this file.
+ reg [ SLAVE_CLK_SIZE-1:0] sclk_mapping [3:0];
+ reg [SLAVE_MOSI_SIZE-1:0] mosi_mapping [3:0];
+ reg [SLAVE_MISO_SIZE-1:0] miso_mapping [3:0];
+ reg [ SLAVE_CS_SIZE-1:0] ss_mapping [3:0];
+
+ //---------------------------------------------------------------------------
+ // Address calculation
+ //---------------------------------------------------------------------------
+
+ wire address_in_range =
+ (s_ctrlport_req_addr >= BASE_ADDRESS) &&
+ (s_ctrlport_req_addr < BASE_ADDRESS + SIZE_ADDRESS);
+
+ // Check that address is targeting slave configuration.
+ wire address_is_slave =
+ (s_ctrlport_req_addr >= BASE_ADDRESS + SPI_SLAVE_CONFIG(0)) &&
+ (s_ctrlport_req_addr <= BASE_ADDRESS + SPI_SLAVE_CONFIG(3));
+
+ // Decode the slave being addressed.
+ wire [1:0]slave_address = s_ctrlport_req_addr[3:2];
+
+ //---------------------------------------------------------------------------
+ // Slave configuration signals
+ //---------------------------------------------------------------------------
+
+ // These settings are registered individually for each slave
+ reg [ NUM_SLAVES-1:0] data_in_edge = {NUM_SLAVES{1'b0}};
+ reg [ NUM_SLAVES-1:0] data_out_edge = {NUM_SLAVES{1'b0}};
+ reg [SPI_LENGTH_SIZE-1:0] slave_spi_length [NUM_SLAVES-1:0];
+
+ // One-hot encoding to indicate active slave
+ reg [NUM_SLAVES-1:0] slave_select = {NUM_SLAVES{1'b0}};
+
+ //---------------------------------------------------------------------------
+ // FSM to handle transfers
+ //---------------------------------------------------------------------------
+
+ localparam IDLE = 3'd0;
+ localparam SET_DIVIDER = 3'd1;
+ localparam WRITE_SPI = 3'd2;
+ localparam CONFIG_TRANSFER = 3'd3;
+ localparam WAIT_SPI = 3'd4;
+
+ localparam DIVIDER_ADDRESS = 8'd0;
+ localparam CTRL_ADDRESS = 8'd1;
+ localparam DATA_ADDRESS = 8'd2;
+
+ reg [ 2:0] state = IDLE;
+ reg [ 31:0] data_cache;
+ reg [SPI_CLK_DIV_SIZE-1:0] divider;
+ reg [ 1:0] cs;
+ reg spi_go = 1'b0;
+ reg spi_ready = 1'b0;
+
+ integer slave_i;
+
+ //---------------------------------------------------------------------------
+ // CtrlPort Register endpoints
+ //---------------------------------------------------------------------------
+
+ always @ (posedge ctrlport_clk) begin
+
+ if (ctrlport_rst) begin
+ s_ctrlport_resp_ack <= 1'b0;
+ spi_go <= 1'b0;
+ spi_ready <= 1'b0;
+ divider <= {SPI_CLK_DIV_SIZE{1'b0}};
+ cs <= 2'b0;
+
+ // Assigned to unassigned mapping. This avoids overwriting
+ // signals with those from uninitialized slaves.
+ for (slave_i=0; slave_i<4; slave_i=slave_i+1) begin
+ sclk_mapping[slave_i] <= 5'h31;
+ mosi_mapping[slave_i] <= 5'h31;
+ miso_mapping[slave_i] <= 5'h31;
+ ss_mapping [slave_i] <= 5'h31;
+ end
+
+ for (slave_i=0; slave_i<NUM_SLAVES; slave_i=slave_i+1) begin
+ slave_spi_length[slave_i] <= {SPI_LENGTH_SIZE{1'b0}};
+ end
+ end else begin
+
+ // Default assignments
+ s_ctrlport_resp_ack <= 1'b0;
+ spi_go <= 1'b0;
+
+ // Requests appear
+ if (s_ctrlport_req_wr) begin
+ s_ctrlport_resp_ack <= 1'b1;
+ s_ctrlport_resp_data <= {32{1'bx}};
+ s_ctrlport_resp_status <= CTRL_STS_OKAY;
+
+ // Address spi configuration writes
+ if(address_is_slave) begin
+ // GPIO mapping
+ sclk_mapping [slave_address] <= s_ctrlport_req_data[SLAVE_CLK_MSB:SLAVE_CLK];
+ mosi_mapping [slave_address] <= s_ctrlport_req_data[SLAVE_MOSI_MSB:SLAVE_MOSI];
+ miso_mapping [slave_address] <= s_ctrlport_req_data[SLAVE_MISO_MSB:SLAVE_MISO];
+ ss_mapping [slave_address] <= s_ctrlport_req_data[SLAVE_CS_MSB:SLAVE_CS];
+ // Transfer Configuration
+ slave_spi_length [slave_address] <= s_ctrlport_req_data[SPI_LENGTH_MSB:SPI_LENGTH];
+ data_in_edge [slave_address] <= s_ctrlport_req_data[MISO_EDGE];
+ data_out_edge [slave_address] <= s_ctrlport_req_data[MOSI_EDGE];
+ end else begin
+
+ case (s_ctrlport_req_addr)
+ BASE_ADDRESS + SPI_TRANSACTION_CONFIG: begin
+ divider <= s_ctrlport_req_data[SPI_CLK_DIV_MSB:SPI_CLK_DIV];
+ cs <= s_ctrlport_req_data[SPI_SLAVE_SELECT_MSB:SPI_SLAVE_SELECT];
+ end
+
+ BASE_ADDRESS + SPI_TRANSACTION_GO: begin
+ spi_ready <= 1'b0;
+ spi_go <= 1'b1;
+ end
+
+ // No register implementation for provided address
+ default: begin
+ // Acknowledge and provide error status if address is in range
+ if (address_in_range) begin
+ s_ctrlport_resp_status <= CTRL_STS_CMDERR;
+
+ // No response if out of range
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+ end
+
+ endcase
+ end
+
+ end else if(s_ctrlport_req_rd) begin
+ // default assumption: valid request
+ s_ctrlport_resp_ack <= 1'b1;
+ s_ctrlport_resp_status <= CTRL_STS_OKAY;
+ s_ctrlport_resp_data <= {32{1'b0}};
+
+ case (s_ctrlport_req_addr)
+ BASE_ADDRESS + SPI_TRANSACTION_CONFIG: begin
+ s_ctrlport_resp_data[SPI_CLK_DIV_MSB:SPI_CLK_DIV] <= divider;
+ end
+
+ BASE_ADDRESS + SPI_STATUS: begin
+ s_ctrlport_resp_data[SPI_READY] <= spi_ready;
+ s_ctrlport_resp_data[SPI_RESPONSE_MSB:SPI_RESPONSE] <= readback[SPI_RESPONSE_MSB:SPI_RESPONSE];
+ end
+
+ // No register implementation for provided address
+ default: begin
+ // Acknowledge and provide error status if address is in range
+ if (address_in_range) begin
+ s_ctrlport_resp_status <= CTRL_STS_CMDERR;
+
+ // No response if out of range
+ end else begin
+ s_ctrlport_resp_ack <= 1'b0;
+ end
+ end
+
+ endcase
+ end
+
+ if (readback_stb_extended) begin
+ spi_ready <= 1'b1;
+ end
+ end
+
+ end
+
+ //---------------------------------------------------------------------------
+ // SPI Control FSM
+ //---------------------------------------------------------------------------
+
+ always @ (posedge ctrlport_clk) begin
+ if (ctrlport_rst) begin
+ state <= IDLE;
+ set_stb <= 1'b0;
+ data_cache <= 32'h0;
+ end else begin
+
+ // Default Assignments
+ set_stb <= 1'b0;
+
+ case (state)
+ IDLE: begin
+ // Save data and address for further steps
+ data_cache <= s_ctrlport_req_data;
+ if (spi_go) begin
+ state <= CONFIG_TRANSFER;
+
+ slave_select <= {NUM_SLAVES{1'b0}};
+ slave_select[cs] <= 1'b1;
+ end
+ end
+
+ // Set slave select
+ CONFIG_TRANSFER: begin
+ state <= SET_DIVIDER;
+
+ set_stb <= 1'b1;
+ set_addr <= CTRL_ADDRESS;
+ set_data <= { data_out_edge[cs], // 1 bit
+ data_in_edge[cs], // 1 bit
+ slave_spi_length[cs], // 6 bits
+ {24-NUM_SLAVES{1'b0}}, // Padding for slaves
+ slave_select // NUM_SLAVES bits
+ };
+ end
+
+ // Write divider to SPI core
+ SET_DIVIDER: begin
+ state <= WRITE_SPI;
+
+ set_stb <= 1'b1;
+ set_addr <= DIVIDER_ADDRESS;
+ set_data <= {16'b0, divider};
+ end
+
+ // Write data bits to SPI core (aligned to MSB)
+ WRITE_SPI: begin
+ state <= WAIT_SPI;
+
+ set_stb <= 1'b1;
+ set_addr <= DATA_ADDRESS;
+ set_data <= data_cache;
+ end
+
+ // Wait for transaction to complete and translate to ctrlport response
+ WAIT_SPI: begin
+
+ if (readback_stb_extended) begin
+ state <= IDLE;
+ end
+ end
+
+ default: begin
+ state <= IDLE;
+ end
+ endcase
+
+ // Update Auxiliary signals
+ gpio_is_mosi <= 32'h0;
+ gpio_is_sclk <= 32'h0;
+ gpio_is_cs <= 32'h0;
+
+ for (slave_i=0; slave_i<NUM_SLAVES; slave_i=slave_i+1) begin
+ gpio_is_mosi [mosi_mapping[slave_i]] <= 1'b1;
+ gpio_is_sclk [sclk_mapping[slave_i]] <= 1'b1;
+ gpio_is_cs [ ss_mapping[slave_i]] <= 1'b1;
+ end
+
+ end
+ end
+
+
+ //---------------------------------------------------------------------------
+ // SPI master
+ //---------------------------------------------------------------------------
+
+ // Register set_stb for use in 2x domain.
+ reg set_stb_2x = 1'b0;
+ reg ctrlport_clk_phase = 1'b0;
+
+ always @ (posedge ctrlport_clk_2x) begin
+ // Assert strobe only during a single 2x cycle of the
+ // 1x pulse.
+ set_stb_2x <= ctrlport_clk_phase & set_stb;
+ ctrlport_clk_phase <= ~ctrlport_clk_phase;
+ end
+
+ simple_spi_core #(
+ .BASE (0),
+ .WIDTH (NUM_SLAVES),
+ .CLK_IDLE (0),
+ .SEN_IDLE ({NUM_SLAVES{1'b1}})
+ ) simple_spi_core_i (
+ .clock (ctrlport_clk_2x),
+ .reset (ctrlport_rst),
+ .set_stb (set_stb_2x),
+ .set_addr (set_addr),
+ .set_data (set_data),
+ .readback (readback),
+ .readback_stb (readback_stb),
+ .ready (),
+ .sen (ss[NUM_SLAVES-1:0]),
+ .sclk (sclk),
+ .mosi (mosi),
+ .miso (miso),
+ .debug ()
+ );
+
+ // Delay and extend signal for use in 1x domain.
+ reg readback_stb_dly = 1'b0;
+
+ always @ (posedge ctrlport_clk_2x) begin
+ readback_stb_dly <= readback_stb;
+ end
+
+ assign readback_stb_extended = readback_stb_dly | readback_stb;
+
+ //---------------------------------------------------------------------------
+ // GPIO Mapping
+ //---------------------------------------------------------------------------
+
+ wire [31:0] mosi_mux_out;
+ reg [31:0] mosi_mux_out_dlyd;
+ wire [31:0] ss_mux_out;
+ wire [31:0] gated_sclk;
+ reg [31:0] gated_sclk_dlyd;
+ reg [31:0] gpio_is_sclk_dlyd;
+
+ genvar i;
+ generate
+
+ for (i = 0; i < 32; i = i + 1) begin: dio_output_gen
+
+ // Indicate which GPIO lines are outputs
+ assign gpio_ddr[i] = gpio_is_mosi[i] | gpio_is_sclk[i] | gpio_is_cs[i];
+
+ // CS re-mapping
+ assign ss_mux_out[i] = ( i == ss_mapping[0] ) ? ss[0] :
+ ( i == ss_mapping[1] ) ? ss[1] :
+ ( i == ss_mapping[2] ) ? ss[2] :
+ ( i == ss_mapping[3] ) ? ss[3] :
+ 1'b0;
+
+ assign mosi_mux_out[i] = gpio_is_mosi[i] ? mosi : ss_mux_out[i];
+ assign gated_sclk[i] = gpio_is_sclk[i] ? sclk : 1'b0;
+
+ // register signals once remapping logic is resolved
+ always @ (posedge ctrlport_clk_2x) begin
+ mosi_mux_out_dlyd[i] <= mosi_mux_out[i];
+ gpio_is_sclk_dlyd[i] <= gpio_is_sclk[i];
+ gated_sclk_dlyd[i] <= gated_sclk[i];
+ end
+
+ // Choose between SCLK and MOSI/SS mux.
+ glitch_free_mux glitch_free_gpio_out (
+ .select (gpio_is_sclk_dlyd[i]),
+ .signal0 (mosi_mux_out_dlyd[i]),
+ .signal1 (gated_sclk_dlyd[i]),
+ .muxed_signal (gpio_out[i])
+ );
+
+ end
+ endgenerate
+
+ assign miso = ( ~ss[0] ) ? gpio_in[miso_mapping[0]] :
+ ( ~ss[1] ) ? gpio_in[miso_mapping[1]] :
+ ( ~ss[2] ) ? gpio_in[miso_mapping[2]] :
+ ( ~ss[3] ) ? gpio_in[miso_mapping[3]] :
+ 1'b0;
+
+
+endmodule
+
+
+`default_nettype wire
+
+//XmlParse xml_on
+//<regmap name="DIG_IFC_REGMAP" readablestrobes="false" generatevhdl="true" ettusguidelines="true">
+// <group name="SPI_OVER_GPIO_REGS">
+// <info>
+// </info>
+// <regtype name="SPI_SETUP" size="32">
+// <info>
+// Controls SPI Transaction
+// </info>
+// <bitfield name="SLAVE_CLK" range="0..4" initialvalue="0">
+// <info>
+// Indicates which GPIO line to use for the SCLK signal.</br>
+// 0-11 : Port A GPIO</br>
+// 16-27: Port B GPIO
+// </info>
+// </bitfield>
+// <bitfield name="SLAVE_MOSI" range="5..9" initialvalue="0">
+// <info>
+// Indicates which GPIO line to use for the MOSI signal.</br>
+// 0-11 : Port A GPIO</br>
+// 16-27: Port B GPIO
+// </info>
+// </bitfield>
+// <bitfield name="SLAVE_MISO" range="10..14" initialvalue="0">
+// <info>
+// Indicates which GPIO line to use for the MISO signal.</br>
+// 0-11 : Port A GPIO</br>
+// 16-27: Port B GPIO
+// </info>
+// </bitfield>
+// <bitfield name="SLAVE_CS" range="15..19" initialvalue="0">
+// <info>
+// Indicates which GPIO line to use for the CS signal.</br>
+// 0-11 : Port A GPIO</br>
+// 16-27: Port B GPIO
+// </info>
+// </bitfield>
+// <bitfield name="SPI_LENGTH" range="20..25" initialvalue="0">
+// <info>
+// Indicates the length of SPI transactions to this slave.
+// </info>
+// </bitfield>
+// <bitfield name="MISO_EDGE" range="26" initialvalue="0">
+// <info>
+// Controls the edge in which the MISO line is latched.</br>
+// 0 = falling edge of SCLK.</br>
+// 1 = rising edge of SCLK.
+// </info>
+// </bitfield>
+// <bitfield name="MOSI_EDGE" range="27" initialvalue="0">
+// <info>
+// Controls the edge in which the MOSI line is updated.</br>
+// 0 = falling edge of SCLK.</br>
+// 1 = rising edge of SCLK.
+// </info>
+// </bitfield>
+// </regtype>
+// <register name="SPI_SLAVE_CONFIG" typename="SPI_SETUP" offset="0x00" count="4" options="--step 4">
+// <info> Set of configuration registers for the supported slaves. </info>
+// </register>
+// <register name="SPI_TRANSACTION_CONFIG" offset="0x10" size="32">
+// <info>
+// Controls clock rate and target for subsequent SPI transactions.
+// </info>
+// <bitfield name="SPI_CLK_DIV" range="0..15" initialvalue="0">
+// <info> Controls the rate for subsequent SPI transactions. SCLK = DataClk/[(SPI_CLK_DIV+1)]</info>
+// </bitfield>
+// <bitfield name="SPI_SLAVE_SELECT" range="16..17" initialvalue="0"/>
+// </register>
+// <register name="SPI_TRANSACTION_GO" offset="0x14" size="32" readable="false">
+// <info>
+// Starts a SPI transaction
+// </info>
+// <bitfield name="SPI_DATA" range="0..31" initialvalue="0">
+// <info> Payload to be sent for the SPI transaction. If the payload is shorter than 32 bits,
+// it must be aligned to the MSbs in this field. LSbs are ignored in this scenario.</info>
+// </bitfield>
+// </register>
+// <register name="SPI_STATUS" offset="0x18" size="32" writable="false">
+// <info>
+// Contains the status of the SPI engine.
+// </info>
+// <bitfield name="SPI_READY" range="24" initialvalue="0">
+// <info> Indicates the SPI engine is ready to start a new SPI transaction. </info>
+// </bitfield>
+// <bitfield name="SPI_RESPONSE" range="0..23" initialvalue="0">
+// <info> Records the response of the last completed SPI transaction. </info>
+// </bitfield>
+// </register>
+// </group>
+//</regmap>
+//XmlParse xml_off