aboutsummaryrefslogtreecommitdiffstats
path: root/fpga
diff options
context:
space:
mode:
authorMax Köhler <max.koehler@ni.com>2020-06-17 11:07:45 +0200
committerWade Fife <wade.fife@ettus.com>2020-06-17 07:13:20 -0500
commit366cab1fef44f1132a2acf8046ecaeac80793ccc (patch)
treefafe229be293db5c8090e703479d5cbd4284116a /fpga
parentf17a5113e228ac1bc5312193eb8f687e127184a3 (diff)
downloaduhd-366cab1fef44f1132a2acf8046ecaeac80793ccc.tar.gz
uhd-366cab1fef44f1132a2acf8046ecaeac80793ccc.tar.bz2
uhd-366cab1fef44f1132a2acf8046ecaeac80793ccc.zip
fpga: lib: add extended spi core for 64bit
The existing SPI core (simple_spi_slave.v) was limited to 32 bit. This commit adds a second spi core with capability to transfer up to 64 bits while keeping the same amount of resources when using generic setting MAX_BITS = 32. Furthermore, the new module aligns mosi and miso with the edges of sclk. The register stages were not aligned in the existing version.
Diffstat (limited to 'fpga')
-rw-r--r--fpga/usrp3/lib/control/Makefile.srcs1
-rw-r--r--fpga/usrp3/lib/control/simple_spi_core_64bit.v286
2 files changed, 287 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/control/Makefile.srcs b/fpga/usrp3/lib/control/Makefile.srcs
index 578f19ea5..21f986a50 100644
--- a/fpga/usrp3/lib/control/Makefile.srcs
+++ b/fpga/usrp3/lib/control/Makefile.srcs
@@ -39,6 +39,7 @@ settings_bus_mux.v \
settings_bus_timed_2clk.v \
simple_i2c_core.v \
simple_spi_core.v \
+simple_spi_core_64bit.v \
synchronizer_impl.v \
synchronizer.v \
pulse_synchronizer.v \
diff --git a/fpga/usrp3/lib/control/simple_spi_core_64bit.v b/fpga/usrp3/lib/control/simple_spi_core_64bit.v
new file mode 100644
index 000000000..fdacd8ea1
--- /dev/null
+++ b/fpga/usrp3/lib/control/simple_spi_core_64bit.v
@@ -0,0 +1,286 @@
+//
+// Copyright 2020 Ettus Research, A National Instruments Company
+//
+// SPDX-License-Identifier: LGPL-3.0-or-later
+//
+// Module: simple_spi_core_64bit
+// Description:
+// Simple SPI core based on simple_spi_core.v. Extended to 64 bit transmissions
+// while preserving the same control interface for 32 bit transmissions.
+
+// Settings register controlled.
+// 4 settings regs, control and data
+// 1 up to 64-bit readback and status signal
+
+// Settings reg map:
+//
+// BASE+0 divider setting
+// bits [15:0] spi clock divider
+//
+// BASE+1 configuration input
+// bits [23:0] slave select, bit0 = slave0 enabled
+// bits [29:24] num bits (0 through 63; value of 0 transmits 64 bits)
+// bit [30] data input edge = in data bit latched on rising edge of clock
+// bit [31] data output edge = out data bit latched on rising edge of clock
+//
+// BASE+2 input data (bits 63...32)
+// Writing this register begins a spi transaction. If up to 32 bits (MAX_BITS <=
+// 32) are required only this input data register needs to be written. Bits are
+// latched out from bit 63. Therefore, load this register aligning with MSBs.
+//
+// BASE+3 input data (bits 31...0, present if MAX_BITS>32)
+// This register needs to be written before accessing BASE+2 input data
+// register when MAX_BITS > 32.
+//
+// Readback
+// Bits are latched into bit 0.
+
+module simple_spi_core_64bit
+ #(
+ //settings register base address
+ parameter BASE = 0,
+
+ //width of serial enables (up to 24 is possible)
+ parameter WIDTH = 8,
+
+ //idle state of the spi clock
+ parameter CLK_IDLE = 0,
+
+ //idle state of the serial enables
+ parameter SEN_IDLE = 24'hffffff,
+
+ //maximum number of bits for single transmission (<=64)
+ parameter MAX_BITS = 32
+ )
+ (
+ //clock and synchronous reset
+ input clock, input reset,
+
+ //32-bit settings bus inputs
+ input set_stb, input [7:0] set_addr, input [31:0] set_data,
+
+ //up to 64-bit data readback
+ output [MAX_BITS-1:0] readback,
+ output reg readback_stb,
+
+ //read is high when spi core can begin another transaction
+ output ready,
+
+ //spi interface, slave selects, clock, data in, data out
+ output [WIDTH-1:0] sen,
+ output reg sclk,
+ output reg mosi,
+ input miso,
+
+ //optional debug output
+ output [23:0] debug
+ );
+
+ // assert for MAX_BITS
+ generate
+ if (MAX_BITS > 64 || MAX_BITS < 1) begin
+ MAX_BITS_must_be_between_1_and_64();
+ end
+ endgenerate
+
+ wire [15:0] sclk_divider;
+ setting_reg #(.my_addr(BASE+0),.width(16)) divider_sr(
+ .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
+ .out(sclk_divider),.changed());
+
+ wire [23:0] slave_select;
+ wire [5:0] num_bits;
+ wire datain_edge, dataout_edge;
+ setting_reg #(.my_addr(BASE+1),.width(32)) ctrl_sr(
+ .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
+ .out({dataout_edge, datain_edge, num_bits, slave_select}),.changed());
+
+ wire [63:0] mosi_data;
+ wire trigger_spi;
+ setting_reg #(.my_addr(BASE+2),.width(32)) data_upper_sr(
+ .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
+ .out(mosi_data[63:32]),.changed(trigger_spi));
+ setting_reg #(.my_addr(BASE+3),.width(32)) data_lower_sr(
+ .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data),
+ .out(mosi_data[31:0]),.changed());
+
+ localparam WAIT_TRIG = 0;
+ localparam PRE_IDLE = 1;
+ localparam CLK_REG = 2;
+ localparam CLK_INV = 3;
+ localparam POST_IDLE = 4;
+ localparam IDLE_SEN = 5;
+
+ reg [2:0] state;
+
+ reg ready_reg;
+ assign ready = ready_reg && ~trigger_spi;
+
+ //serial clock either idles or is in one of two clock states
+ //One pipeline stage to align output data with clock edge.
+ reg sclk_reg;
+ always @(posedge clock) begin
+ sclk <= sclk_reg;
+ end
+
+ //serial enables either idle or enabled based on state
+ // IJB. One pipeline stage to break critical path from register in I/O pads.
+ wire sen_is_idle = (state == WAIT_TRIG) || (state == IDLE_SEN);
+ wire [23:0] sen24 = (sen_is_idle) ? SEN_IDLE : (SEN_IDLE ^ slave_select);
+ reg [WIDTH-1:0] sen_reg = SEN_IDLE[WIDTH-1:0];
+ always @(posedge clock) begin
+ if (reset) begin
+ sen_reg <= SEN_IDLE[WIDTH-1:0];
+ end else begin
+ sen_reg <= sen24[WIDTH-1:0];
+ end
+ end
+ assign sen = sen_reg;
+
+ //data output shift register
+ // IJB. One pipeline stage to break critical path from register in I/O pads.
+ reg [MAX_BITS-1:0] dataout_reg = {MAX_BITS {1'b0}};
+ wire [MAX_BITS-1:0] dataout_next = {dataout_reg[MAX_BITS-2:0], 1'b0};
+
+ always @(posedge clock) begin
+ mosi <= dataout_reg[MAX_BITS-1];
+ end
+
+ //data input shift register
+ // IJB. Two pipeline stages to break critical path from register in I/O pads.
+ reg miso_pipe, miso_pipe2;
+ always @(posedge clock) begin
+ miso_pipe2 <= miso;
+ miso_pipe <= miso_pipe2;
+ end
+
+ // Register to control input data capturing, compensating 2 miso_pipe
+ // registers and the output register on mosi/sclk.
+ //
+ // When sclk_counter_done is asserted the FSM below updates sclk_reg and
+ // dataout_reg (compensated by datain_capture_reg).
+ // One clock cycle later those values get propagated to sclk and mosi. On
+ // the active datain_edge miso would be capture into miso_pipe2. This clock
+ // cycle is compensated by datain_capture_pipe[0].
+ // Propagation of miso data to miso_pipe is compensated by
+ // datain_capture_pipe[1].
+ // On the next clock edge datain_capture_pipe[1] serves as enable signal for
+ // datain_reg which then consumes the aligned data from miso_pipe.
+ reg datain_capture_reg = 1'b0;
+ reg [1:0] datain_capture_pipe;
+ wire capturing_done = ~| {datain_capture_reg, datain_capture_pipe};
+
+ reg [MAX_BITS-1:0] datain_reg;
+ wire [MAX_BITS-1:0] datain_next = {datain_reg[MAX_BITS-2:0], miso_pipe};
+ assign readback = datain_reg;
+
+ always @(posedge clock) begin
+ datain_capture_pipe <= {datain_capture_pipe[0], datain_capture_reg};
+ if (datain_capture_pipe[1]) begin
+ datain_reg <= datain_next;
+ end
+ end
+
+ //counter for spi clock
+ reg [15:0] sclk_counter = 16'b0;
+ wire sclk_counter_done = (sclk_counter == sclk_divider);
+ wire [15:0] sclk_counter_next = (sclk_counter_done)? 0 : sclk_counter + 1;
+
+ //counter for latching bits miso/mosi
+ reg [5:0] bit_counter = 6'b0;
+ wire [5:0] bit_counter_next = bit_counter + 1;
+ wire bit_counter_done = (bit_counter_next == num_bits);
+
+ always @(posedge clock) begin
+ if (reset) begin
+ state <= WAIT_TRIG;
+ sclk_reg <= CLK_IDLE;
+ ready_reg <= 0;
+ readback_stb <= 1'b0;
+ end else begin
+ datain_capture_reg <= 1'b0;
+
+ case (state)
+ WAIT_TRIG: begin
+ if (trigger_spi) begin
+ state <= PRE_IDLE;
+ end
+ readback_stb <= 1'b0;
+ ready_reg <= ~trigger_spi;
+ dataout_reg <= mosi_data[63:64-MAX_BITS];
+ sclk_counter <= 0;
+ bit_counter <= 0;
+ sclk_reg <= CLK_IDLE;
+ end
+
+ PRE_IDLE: begin
+ if (sclk_counter_done) begin
+ state <= CLK_REG;
+ end
+ sclk_counter <= sclk_counter_next;
+ sclk_reg <= CLK_IDLE;
+ end
+
+ CLK_REG: begin
+ if (sclk_counter_done) begin
+ state <= CLK_INV;
+ if (datain_edge != CLK_IDLE) begin
+ datain_capture_reg <= 1'b1;
+ end
+ if (dataout_edge != CLK_IDLE && bit_counter != 0) begin
+ dataout_reg <= dataout_next;
+ end
+ sclk_reg <= ~CLK_IDLE; //transition to rising when CLK_IDLE == 0
+ end
+ sclk_counter <= sclk_counter_next;
+ end
+
+ CLK_INV: begin
+ if (sclk_counter_done) begin
+ state <= (bit_counter_done) ? POST_IDLE : CLK_REG;
+ bit_counter <= bit_counter_next;
+ if (datain_edge == CLK_IDLE) begin
+ datain_capture_reg <= 1'b1;
+ end
+ if (dataout_edge == CLK_IDLE && ~bit_counter_done) begin
+ dataout_reg <= dataout_next;
+ end
+ sclk_reg <= CLK_IDLE; //transition to falling when CLK_IDLE == 0
+ end
+ sclk_counter <= sclk_counter_next;
+ end
+
+ POST_IDLE: begin
+ if (sclk_counter_done) begin
+ state <= IDLE_SEN;
+ end
+ sclk_counter <= sclk_counter_next;
+ sclk_reg <= CLK_IDLE;
+ end
+
+ IDLE_SEN: begin
+ if (sclk_counter_done && capturing_done) begin
+ ready_reg <= 1'b1;
+ readback_stb <= 1'b1;
+ state <= WAIT_TRIG;
+ end
+ sclk_counter <= sclk_counter_next;
+ sclk_reg <= CLK_IDLE;
+ end
+
+ default: begin
+ state <= WAIT_TRIG;
+ end
+ endcase //state
+ end
+ end
+
+ assign debug = {
+ trigger_spi, state, //4
+ sclk, mosi, miso, ready, //4
+ 2'b0, bit_counter[5:0], //8
+ sclk_counter_done, bit_counter_done, //2
+ sclk_counter[5:0] //6
+ };
+
+endmodule //simple_spi_core