aboutsummaryrefslogtreecommitdiffstats
path: root/fpga/usrp3/top/x400/cpld/spi_slave.v
diff options
context:
space:
mode:
authorMax Köhler <max.koehler@ni.com>2021-02-05 13:14:41 -0600
committerAaron Rossetto <aaron.rossetto@ni.com>2021-06-10 11:56:58 -0500
commit7015f5ed2d495f3908773b7c7d74864d0cc3871a (patch)
treee9c9f7d95f5c35c089bfc9534707934bfe41344a /fpga/usrp3/top/x400/cpld/spi_slave.v
parent6d3765605262016a80f71e36357f749ea35cbe5a (diff)
downloaduhd-7015f5ed2d495f3908773b7c7d74864d0cc3871a.tar.gz
uhd-7015f5ed2d495f3908773b7c7d74864d0cc3871a.tar.bz2
uhd-7015f5ed2d495f3908773b7c7d74864d0cc3871a.zip
fpga: x400: cpld: Add support for X410 motherboard CPLD
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com> Co-authored-by: Javier Valenzuela <javier.valenzuela@ni.com>
Diffstat (limited to 'fpga/usrp3/top/x400/cpld/spi_slave.v')
-rw-r--r--fpga/usrp3/top/x400/cpld/spi_slave.v288
1 files changed, 288 insertions, 0 deletions
diff --git a/fpga/usrp3/top/x400/cpld/spi_slave.v b/fpga/usrp3/top/x400/cpld/spi_slave.v
new file mode 100644
index 000000000..ab2f16fea
--- /dev/null
+++ b/fpga/usrp3/top/x400/cpld/spi_slave.v
@@ -0,0 +1,288 @@
+//
+// Copyright 2021 Ettus Research, A National Instruments Brand
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: spi_slave
+//
+// Description:
+//
+// SPI slave for configuration CPOL = CPHA = 0.
+// Transfers 8 bit = 1 byte MSB first. Parallel data has to be
+// provided and consumed immediately when flags are asserted.
+//
+// Limitation: clk frequency <= 2*sclk frequency
+//
+// Data request from sclk domain is triggered towards the clk domain ahead of
+// time. This is due to the clock domain crossing using the synchronizer and
+// processing pipeline stages.
+//
+// The worst case propagation delay of the used synchronizer is:
+//
+// 4 'clk' clock cycles:
+// 1 clock cycle of signal propagation to synchronizer
+// (data_request_sclk assertion)
+// 1 clock cycle to capture data with instability in first stage
+// 1 clock cycle to stabilize first stage
+// 1 clock cycle to capture data in second stage
+// (data_request_clk available in 'clk' domain)
+//
+// Once synchronized in 'clk' domain, there is one additional clock cycle to
+// derive data_out_valid and data_in_required. To ensure that transmit data
+// is registered a 'clk' cycle ahead of the actual transmission we need 2
+// more 'clk' clock cycles. This ensures that transmit_word has changed and
+// is stable for at least one 'clk' cycle before 'sclk' asserts again. Any
+// additional time required externally to respond to the control port
+// requests should be considered in this crossing as well. This is a total of
+// 7 clock cycles (+ctrlport response margin) @ clk domain. The minimum
+// required time in sclk domain to issue the request is calculated based on
+// the clock frequencies.
+//
+// Parameters:
+//
+// CLK_FREQUENCY : Frequency of "clk"
+// SPI_FREQUENCY : Frequency of "sclk"
+//
+
+`default_nettype none
+
+
+module spi_slave #(
+ parameter CLK_FREQUENCY = 50000000,
+ parameter SPI_FREQUENCY = 10000000
+) (
+ //---------------------------------------------------------------
+ // SPI Interface
+ //---------------------------------------------------------------
+
+ input wire sclk,
+ input wire cs_n,
+ input wire mosi,
+ output wire miso,
+
+ //---------------------------------------------------------------
+ // Parallel Interface
+ //---------------------------------------------------------------
+
+ input wire clk,
+ input wire rst,
+
+ output reg data_in_required,
+ input wire data_in_valid,
+ input wire [7:0] data_in,
+
+ output reg data_out_valid,
+ output reg [7:0] data_out,
+
+ output wire active
+);
+
+ wire [0:0] data_request_clk;
+ wire [0:0] reception_complete_clk;
+
+ //---------------------------------------------------------------
+ // SPI Receiver @ sclk
+ //---------------------------------------------------------------
+
+ reg [7:0] receiver_reg;
+ reg [2:0] current_bit_index;
+ reg reception_complete_sclk = 1'b0;
+ reg [7:0] received_word;
+
+ always @(posedge sclk or posedge cs_n) begin
+ // Reset logic on positive cs_n edge = slave idle
+ if (cs_n) begin
+ receiver_reg <= 8'b0;
+ end
+ // Rising edge of sclk
+ else begin
+ // Capture bits into shift register MSBs first
+ receiver_reg <= {receiver_reg[6:0], mosi};
+ end
+ end
+
+ // Reset with cs_n might occur too early during clk sync.
+ // Reset half way through the reception.
+ always @(posedge sclk) begin
+ // Complete word was received
+ if (current_bit_index == 7) begin
+ reception_complete_sclk <= 1'b1;
+ received_word <= {receiver_reg[6:0], mosi};
+
+ // Reset after half transaction
+ end else if (current_bit_index == 3) begin
+ reception_complete_sclk <= 1'b0;
+ end
+ end
+
+ //---------------------------------------------------------------
+ // Handover of data sclk -> clk
+ //---------------------------------------------------------------
+
+ synchronizer #(
+ .WIDTH (1),
+ .STAGES (2),
+ .INITIAL_VAL (1'b0),
+ .FALSE_PATH_TO_IN (1)
+ ) data_sync_inst (
+ .clk (clk),
+ .rst (1'b0),
+ .in (reception_complete_sclk),
+ .out (reception_complete_clk)
+ );
+
+ //---------------------------------------------------------------
+ // Parallel interface data output @ clk
+ //---------------------------------------------------------------
+
+ reg reception_complete_clk_delayed = 1'b0;
+
+ // Propagate toggling signal without reset to ensure stability on reset
+ always @(posedge clk) begin
+ // Capture last state of reception
+ reception_complete_clk_delayed <= reception_complete_clk;
+ end
+
+ // Derive data and control signal
+ always @(posedge clk) begin
+ if (rst) begin
+ data_out_valid <= 1'b0;
+ data_out <= 8'b0;
+ end
+ else begin
+ // Default assignment
+ data_out_valid <= 1'b0;
+
+ // Provide data to output on rising_edge
+ if (reception_complete_clk & ~reception_complete_clk_delayed) begin
+ // Data can simply be captured as the reception complete signal
+ // indicates stable values in received_word.
+ data_out <= received_word;
+ data_out_valid <= 1'b1;
+ end
+ end
+ end
+
+ //---------------------------------------------------------------
+ // SPI Transmitter @ sclk
+ //---------------------------------------------------------------
+
+ // Data request calculation:
+ // SCLK_CYCLES_DURING_DATA_REQ = 8 clk period / sclk period
+ // Clock periods are expressed by reciprocal of frequencies.
+ // Term "+CLK_FREQUENCY-1" is used to round up the result in integer logic.
+ localparam SCLK_CYCLES_DURING_DATA_REQ = (8*SPI_FREQUENCY + CLK_FREQUENCY-1)/CLK_FREQUENCY;
+ // subtract from 8 bits per transfer to get target index
+ localparam DATA_REQ_BIT_INDEX = 8 - SCLK_CYCLES_DURING_DATA_REQ;
+
+ reg [7:0] transmit_bits;
+ reg [7:0] transmit_word;
+ reg data_request_sclk = 1'b0;
+
+ always @(negedge sclk or posedge cs_n) begin
+ // Reset logic on positive cs_n edge = slave idle
+ if (cs_n) begin
+ current_bit_index <= 3'b0;
+ data_request_sclk <= 1'b0;
+ transmit_bits <= 8'b0;
+ end
+ // Falling edge of sclk
+ else begin
+ // Fill or move shift register for byte transmissions
+ if (current_bit_index == 7) begin
+ transmit_bits <= transmit_word;
+ end else begin
+ transmit_bits <= {transmit_bits[6:0], 1'b0};
+ end
+
+ // Update bit index
+ current_bit_index <= current_bit_index + 1'b1;
+
+ // Trigger request for new word at start of calculated index
+ if (current_bit_index == DATA_REQ_BIT_INDEX-1) begin
+ data_request_sclk <= 1'b1;
+ // Reset after half the reception in case cs_n is not changed in between
+ // two transactions.
+ end else if (current_bit_index == (DATA_REQ_BIT_INDEX+4-1)%8) begin
+ data_request_sclk <= 1'b0;
+ end
+ end
+ end
+
+ // Drive miso output with data when cs_n low
+ assign miso = cs_n ? 1'bz : transmit_bits[7];
+
+ //---------------------------------------------------------------
+ // Handover of Data Request sclk -> clk
+ //---------------------------------------------------------------
+
+ synchronizer #(
+ .WIDTH (1),
+ .STAGES (2),
+ .INITIAL_VAL (1'b0),
+ .FALSE_PATH_TO_IN (1)
+ ) request_sync_inst (
+ .clk (clk),
+ .rst (rst),
+ .in (data_request_sclk),
+ .out (data_request_clk)
+ );
+
+ //---------------------------------------------------------------
+ // Parallel Interface Data Input Control
+ //---------------------------------------------------------------
+
+ reg data_request_clk_delayed;
+
+ always @(posedge clk) begin
+ if (rst) begin
+ data_request_clk_delayed <= 1'b0;
+ data_in_required <= 1'b0;
+ transmit_word <= 8'b0;
+ end
+ else begin
+ // Default assignment
+ data_in_required <= 1'b0;
+
+ // Capture last state of data request
+ data_request_clk_delayed <= data_request_clk;
+
+ // Request data from input
+ if (~data_request_clk_delayed & data_request_clk) begin
+ data_in_required <= 1'b1;
+ end
+
+ // Capture new data if valid data available, 0 otherwise.
+ if (data_in_required) begin
+ if (data_in_valid) begin
+ transmit_word <= data_in;
+ end else begin
+ transmit_word <= 8'b0;
+ end
+ end
+ end
+ end
+
+ //---------------------------------------------------------------
+ // Chip Select
+ //---------------------------------------------------------------
+ // Driven as active signal in parallel clock domain
+
+ wire cs_n_clk;
+ assign active = ~cs_n_clk;
+ synchronizer #(
+ .WIDTH (1),
+ .STAGES (2),
+ .INITIAL_VAL (1'b1),
+ .FALSE_PATH_TO_IN (1)
+ ) active_sync_inst (
+ .clk (clk),
+ .rst (rst),
+ .in (cs_n),
+ .out (cs_n_clk)
+ );
+
+endmodule
+
+
+`default_nettype wire