mirror of
https://github.com/EttusResearch/uhd.git
synced 2026-10-25 17:36:10 +00:00
fpga: lib: add ctrlport endpoints for I2C, UART and high-throughput SPI
Co-authored-by: Wade Fife <wade.fife@ettus.com> Co-authored-by: Tony Liecthy <tony.liechty@ni.com>
This commit is contained in:
parent
e98bf35840
commit
dd708a46f4
@ -18,17 +18,22 @@
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// DATA_W : The bit width of tdata for all ports.
|
||||
// NUM_PORTS : The number of output ports on which to duplicate the input.
|
||||
// INPUT_REG : Set to 1 to add an input register stage to break combinatorial
|
||||
// paths. Set to 0 to allow combinatorial input paths.
|
||||
// DATA_W : The bit width of tdata for all ports.
|
||||
// NUM_PORTS : The number of output ports on which to duplicate the
|
||||
// input.
|
||||
// INPUT_REG : Set to 1 to add an input register stage to break
|
||||
// combinatorial paths. Set to 0 to allow combinatorial input
|
||||
// paths.
|
||||
// OUT_FIFO_SIZE : Configures the per-port output FIFO size. Defaults to a
|
||||
// short (32-deep) FIFO.
|
||||
//
|
||||
|
||||
|
||||
module axis_split #(
|
||||
parameter DATA_W = 32,
|
||||
parameter NUM_PORTS = 4,
|
||||
parameter INPUT_REG = 0
|
||||
parameter DATA_W = 32,
|
||||
parameter NUM_PORTS = 4,
|
||||
parameter INPUT_REG = 0,
|
||||
parameter OUT_FIFO_SIZE = 5
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
@ -106,11 +111,20 @@ module axis_split #(
|
||||
genvar i;
|
||||
for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_ports
|
||||
|
||||
// We use axi_fifo_short specifically because it can tolerate tvalid
|
||||
// de-asserting at any time. This is normally not allowed by AXI-Stream.
|
||||
axi_fifo_short #(
|
||||
.WIDTH (DATA_W)
|
||||
) axi_fifo_short_i (
|
||||
// We add a FIFO on each output port because our axi_fifo tolerates tvalid
|
||||
// de-asserting at any time, which is normally not allowed by AXI-Stream.
|
||||
// This may occur here because the output streams are independent and we
|
||||
// block all transfers when any one output stalls. To ensure we have this
|
||||
// protection built in, we do not allow bypass mode.
|
||||
|
||||
if (OUT_FIFO_SIZE < 0) begin : gen_bypass_mode
|
||||
OUT_FIFO_SIZE_must_be_non_negative();
|
||||
end
|
||||
|
||||
axi_fifo #(
|
||||
.WIDTH (DATA_W),
|
||||
.SIZE (OUT_FIFO_SIZE)
|
||||
) axi_fifo_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
|
||||
@ -18,9 +18,15 @@ axi_forwarding_cam.v \
|
||||
axi_setting_reg.v \
|
||||
axi_slave_mux.v \
|
||||
axi_test_vfifo.v \
|
||||
axis_to_ctrlport.sv \
|
||||
ctrlport_to_axis.sv \
|
||||
axis_spi.sv \
|
||||
bin2gray.v \
|
||||
binary_encoder.v \
|
||||
clock_div.v \
|
||||
../rfnoc/core/ctrlport_pkg.sv \
|
||||
ctrlport_spi_adrv_pkg.sv \
|
||||
ctrlport_spi_adrv.sv \
|
||||
db_control.v \
|
||||
fe_control.v \
|
||||
filter_bad_sid.v \
|
||||
@ -65,6 +71,9 @@ axil_ctrlport_master.v\
|
||||
handshake.v\
|
||||
handshake_latch.sv\
|
||||
ctrlport_to_regport.v \
|
||||
ctrlport_to_wb_i2c.v \
|
||||
ctrlport_to_wb_uart.v \
|
||||
ctrlport_to_simple_spi.sv \
|
||||
glitch_free_mux.v \
|
||||
regport_if.sv \
|
||||
))
|
||||
|
||||
380
fpga/usrp3/lib/control/axis_spi.sv
Normal file
380
fpga/usrp3/lib/control/axis_spi.sv
Normal file
@ -0,0 +1,380 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axis_spi
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// SPI master (CPOL=0, CPHA=0) with AXI-Stream TX and RX interfaces. Each
|
||||
// byte presented on the input stream is shifted out MSB-first on MOSI while
|
||||
// MISO bits are simultaneously assembled into bytes and presented on the
|
||||
// output stream (full-duplex).
|
||||
//
|
||||
// CS_N is asserted (driven low) and held low for the duration of the
|
||||
// AXI-Stream packet. Between words, CS_N stays asserted and SCLK simply
|
||||
// pauses if the next byte in the packet is not yet available (s_axis_tvalid
|
||||
// == 0) or if there's back-pressure on the output (m_axis_tready == 0).
|
||||
//
|
||||
// After the last SCLK falling edge, CS_N deassertion timing is controlled by
|
||||
// CS_HOLD and CS_GUARD:
|
||||
//
|
||||
// CS_HOLD=0 : CS_N deasserts on the same clock edge as the last SCLK
|
||||
// falling edge.
|
||||
// CS_HOLD=1 : CS_N stays low for one additional half-period before being
|
||||
// deasserted.
|
||||
//
|
||||
// After CS_N deasserts, CS_GUARD determines the number of half-period
|
||||
// cycles before the next CS_N assertion for the next packet:
|
||||
//
|
||||
// CS_GUARD=1 : One half SCLK period before next CS_N assertion.
|
||||
// CS_GUARD=2 : One full SCLK period before next CS_N assertion.
|
||||
//
|
||||
// SPI Clock Rate:
|
||||
//
|
||||
// The SPI clock rate is controlled by setting the desired number of clock
|
||||
// cycles per half SPI clock period, minus 1. In other words, the actual
|
||||
// SPI clock frequency for a given clock frequency will be:
|
||||
//
|
||||
// f_sclk = f_clk / (2 * (half_per + 1))
|
||||
//
|
||||
// Or, the required half period setting to approximate given SPI clock
|
||||
// frequency can be calculated as:
|
||||
//
|
||||
// half_per = round((f_clk / (2 * f_sclk)) - 1)
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// HALF_PER_W : Width of the half-period input port in bits. This determines
|
||||
// the slowest SPI clock rate that can be achieved.
|
||||
// CS_HOLD : Number of half SCLK periods to hold CS_N low after last
|
||||
// falling edge of SCLK. Can be 0 or 1.
|
||||
// CS_GUARD : Number of half SCLK periods to hold CS_N between
|
||||
// transactions. Can be 1 or 2.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// clk / rst : Clock and active-high synchronous reset.
|
||||
// half_per : Half-period value; compared against an internal counter
|
||||
// each cycle. SCLK toggles every (half_per + 1) clock cycles.
|
||||
// Minimum value is 0, which yields the fastest SCLK rate of
|
||||
// clk/2. This port must remain stable for the duration of any
|
||||
// active transaction; it is expected to be driven by a static
|
||||
// constant or an external register that is only updated while
|
||||
// the core is idle.
|
||||
// s_axis_* : AXI-Stream slave (TX data in). Each byte is serialized
|
||||
// MSB-first.
|
||||
// m_axis_* : AXI-Stream master (RX data out). Each byte is serialized
|
||||
// MSB-first.
|
||||
// sclk : SPI clock output
|
||||
// cs_n : Active-low chip select
|
||||
// mosi : Master-out / slave-in
|
||||
// miso : Master-in / slave-out input
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module axis_spi #(
|
||||
int HALF_PER_W = 8,
|
||||
bit CS_HOLD = 1,
|
||||
int CS_GUARD = 2,
|
||||
|
||||
localparam int DATA_W = 8
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// Half-period control
|
||||
input wire [HALF_PER_W-1:0] half_per,
|
||||
|
||||
// AXI-Stream Slave (TX data in)
|
||||
input wire [DATA_W-1:0] s_axis_tdata,
|
||||
input wire s_axis_tvalid,
|
||||
output logic s_axis_tready,
|
||||
input wire s_axis_tlast,
|
||||
|
||||
// AXI-Stream Master (RX data out)
|
||||
output logic [DATA_W-1:0] m_axis_tdata,
|
||||
output logic m_axis_tvalid,
|
||||
input wire m_axis_tready,
|
||||
output logic m_axis_tlast,
|
||||
|
||||
// SPI Interface
|
||||
output logic sclk = 1'b0,
|
||||
output logic cs_n = 1'b1,
|
||||
output logic mosi = 1'b0,
|
||||
input wire miso
|
||||
);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Parameter Checks
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (CS_HOLD < 0 || CS_HOLD > 1) begin : gen_hold_check
|
||||
$error("axis_spi: CS_HOLD must be 0 or 1 (setting is %0d)", CS_HOLD);
|
||||
end
|
||||
|
||||
if (CS_GUARD < 1 || CS_GUARD > 2) begin : gen_guard_check
|
||||
$error("axis_spi: CS_GUARD must be 1 or 2 (setting is %0d)", CS_GUARD);
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Local Parameters
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Enough bits to count 0 .. DATA_W-1
|
||||
localparam int BIT_CNT_W = $clog2(DATA_W);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// State Machine Type
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic [2:0] {
|
||||
ST_IDLE,
|
||||
ST_SHIFT_A,
|
||||
ST_SHIFT_B,
|
||||
ST_HOLD,
|
||||
ST_GUARD1,
|
||||
ST_GUARD2
|
||||
} state_t;
|
||||
|
||||
state_t state;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Internal Signals
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Half-period counter. Done asserts when it reaches half_per.
|
||||
logic [HALF_PER_W-1:0] half_per_cnt;
|
||||
logic half_per_done;
|
||||
|
||||
// Bit position counter (0 to DATA_W-1)
|
||||
logic [BIT_CNT_W-1:0] bit_cnt;
|
||||
|
||||
// TX / RX shift registers
|
||||
logic [DATA_W-1:0] mosi_sr;
|
||||
logic [DATA_W-1:0] miso_sr;
|
||||
|
||||
// tlast latched to indicate the end of the SPI transaction
|
||||
logic tlast_reg;
|
||||
|
||||
// True when CS_N is currently asserted (mid-packet)
|
||||
logic in_packet;
|
||||
|
||||
// One-entry registered output buffer
|
||||
logic [DATA_W-1:0] out_data;
|
||||
logic out_valid;
|
||||
logic out_last;
|
||||
|
||||
// True when the output buffer is free this cycle (empty or being drained)
|
||||
logic out_buf_free;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Combinational Assignments
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
assign half_per_done = (half_per_cnt == half_per);
|
||||
assign out_buf_free = !out_valid || m_axis_tready;
|
||||
|
||||
// AXI-Stream output driven from the registered output buffer
|
||||
assign m_axis_tdata = out_data;
|
||||
assign m_axis_tvalid = out_valid;
|
||||
assign m_axis_tlast = out_last;
|
||||
|
||||
// Accept input when idle and the output buffer has room. In ST_IDLE the SPI
|
||||
// bus is paused (SCLK stopped), so CS_N stays asserted while we wait for
|
||||
// the next word when in_packet=1.
|
||||
assign s_axis_tready = (state == ST_IDLE) && out_buf_free;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Half-Period Counter
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Counts up every cycle while a SPI transaction is occurring. Resets to zero
|
||||
// the same cycle half_per_done fires so each half-period is exactly
|
||||
// (half_per + 1) clock cycles.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
half_per_cnt <= '0;
|
||||
end else begin
|
||||
if (state == ST_IDLE) begin
|
||||
half_per_cnt <= '0;
|
||||
end else begin
|
||||
if (half_per_done) begin
|
||||
half_per_cnt <= '0;
|
||||
end else begin
|
||||
half_per_cnt <= half_per_cnt + 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
sclk <= 1'b0;
|
||||
cs_n <= 1'b1;
|
||||
mosi <= 1'b0;
|
||||
mosi_sr <= 'X;
|
||||
miso_sr <= 'X;
|
||||
bit_cnt <= 'X;
|
||||
tlast_reg <= 1'b0;
|
||||
in_packet <= 1'b0;
|
||||
out_data <= 'X;
|
||||
out_valid <= 1'b0;
|
||||
out_last <= 'X;
|
||||
end else begin
|
||||
|
||||
//-----------------------------------------------------------------------
|
||||
// Output buffer drain
|
||||
//-----------------------------------------------------------------------
|
||||
|
||||
if (m_axis_tready && out_valid) begin
|
||||
out_valid <= 1'b0;
|
||||
end
|
||||
|
||||
unique case (state)
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// IDLE: SCLK stopped. CS_N follows in_packet (stays asserted between
|
||||
// bytes of a packet, deasserted before/after).
|
||||
//
|
||||
// On input handshake: latch word and tlast, pre-drive MOSI with the
|
||||
// MSB, assert CS_N, and enter the shift loop.
|
||||
//---------------------------------------------------------------------
|
||||
ST_IDLE: begin
|
||||
sclk <= 1'b0;
|
||||
cs_n <= !in_packet;
|
||||
bit_cnt <= '0;
|
||||
|
||||
if (s_axis_tvalid && out_buf_free) begin
|
||||
mosi_sr <= s_axis_tdata;
|
||||
tlast_reg <= s_axis_tlast;
|
||||
|
||||
// Assert CS_N and pre-drive MSB onto MOSI before the first
|
||||
// SCLK rising edge (CPHA=0 requirement).
|
||||
cs_n <= 1'b0;
|
||||
mosi <= s_axis_tdata[DATA_W-1];
|
||||
|
||||
in_packet <= 1'b1;
|
||||
state <= ST_SHIFT_A;
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// SHIFT_A: First (low) half of the current SCLK bit period.
|
||||
//
|
||||
// SCLK rises at the end of this half, MISO is sampled on the rising
|
||||
// edge.
|
||||
//---------------------------------------------------------------------
|
||||
ST_SHIFT_A: begin
|
||||
if (half_per_done) begin
|
||||
sclk <= 1'b1;
|
||||
// Sample MISO into the LSB of the receive shift register
|
||||
miso_sr <= {miso_sr[DATA_W-2:0], miso};
|
||||
state <= ST_SHIFT_B;
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// SHIFT_B: Second (high) half of the current SCLK bit period.
|
||||
//
|
||||
// SCLK falls at the end of this half, MOSI advances to the next bit on
|
||||
// the falling edge.
|
||||
//
|
||||
// On the last bit, push the assembled received byte to the output
|
||||
// buffer and choose the next state.
|
||||
//---------------------------------------------------------------------
|
||||
ST_SHIFT_B: begin
|
||||
if (half_per_done) begin
|
||||
sclk <= 1'b0;
|
||||
bit_cnt <= bit_cnt + 1;
|
||||
|
||||
// Advance MOSI shift register and drive the next bit
|
||||
mosi_sr <= {mosi_sr[DATA_W-2:0], 1'b0};
|
||||
mosi <= mosi_sr[DATA_W-2];
|
||||
|
||||
if (bit_cnt == DATA_W-1) begin
|
||||
// Last bit don. Push received word to output buffer. out_valid
|
||||
// guaranteed 0 here (see block header).
|
||||
out_data <= miso_sr;
|
||||
out_valid <= 1'b1;
|
||||
out_last <= tlast_reg;
|
||||
|
||||
bit_cnt <= '0;
|
||||
|
||||
if (tlast_reg) begin
|
||||
// End of packet. Update CS_N based on CS_HOLD.
|
||||
in_packet <= 1'b0;
|
||||
if (CS_HOLD) begin
|
||||
state <= ST_HOLD;
|
||||
end else begin
|
||||
cs_n <= 1'b1;
|
||||
state <= ST_GUARD1;
|
||||
end
|
||||
end else begin
|
||||
// More words to come. Return to IDLE to accept the next word.
|
||||
// CS_N stays asserted while we wait.
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end else begin
|
||||
state <= ST_SHIFT_A;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// HOLD: Hold CS_N asserted for one extra half-period after the last
|
||||
// SCLK falling edge (only entered if CS_HOLD=1), then deassert CS_N
|
||||
// and proceed to the guard entry state.
|
||||
//---------------------------------------------------------------------
|
||||
ST_HOLD: begin
|
||||
if (half_per_done) begin
|
||||
cs_n <= 1'b1;
|
||||
state <= ST_GUARD1;
|
||||
end
|
||||
end
|
||||
|
||||
//---------------------------------------------------------------------
|
||||
// GUARD1/GUARD2: Hold CS_N deasserted for CS_GUARD half-periods after
|
||||
// the end of the transaction.
|
||||
//---------------------------------------------------------------------
|
||||
ST_GUARD1: begin
|
||||
if (half_per_done) begin
|
||||
state <= (CS_GUARD > 1) ? ST_GUARD2 : ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
ST_GUARD2: begin
|
||||
if (half_per_done) begin
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : axis_spi
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
135
fpga/usrp3/lib/control/axis_to_ctrlport.sv
Normal file
135
fpga/usrp3/lib/control/axis_to_ctrlport.sv
Normal file
@ -0,0 +1,135 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: axis_to_ctrlport
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Converts an AXI-Stream bus into a readable CtrlPort interface. Read
|
||||
// requests get responded to as soon as data becomes available on the
|
||||
// AXI-Stream input.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// WIDTH : Width of the data path in bits
|
||||
// FIFO_SIZE_LOG2 : Log base 2 of the depth of the input AXI-Stream FIFO.
|
||||
// This FIFO is optional. Depth can be set to -1 to remove
|
||||
// it entirely.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// clk : Clock
|
||||
// rst : Active-high synchronous reset
|
||||
// s_axis_tdata : AXI-Stream data input
|
||||
// s_axis_tvalid : AXI-Stream valid
|
||||
// s_axis_tready : AXI-Stream ready
|
||||
// ctrl_rd : CtrlPort read request strobe (single-cycle pulse)
|
||||
// ctrl_data : CtrlPort read data output
|
||||
// ctrl_ack : CtrlPort acknowledgement strobe (single-cycle pulse)
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module axis_to_ctrlport #(
|
||||
parameter int WIDTH = 32,
|
||||
parameter int FIFO_SIZE_LOG2 = 0
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// AXI-Stream slave
|
||||
input wire [WIDTH-1:0] s_axis_tdata,
|
||||
input wire s_axis_tvalid,
|
||||
output wire s_axis_tready,
|
||||
|
||||
// CtrlPort read interface
|
||||
input wire ctrl_rd,
|
||||
output logic [WIDTH-1:0] ctrl_data,
|
||||
output logic ctrl_ack
|
||||
);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Declarations
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic { ST_IDLE, ST_WAIT } state_t;
|
||||
state_t state;
|
||||
|
||||
// Internal stream between the input FIFO and the FSM.
|
||||
logic [WIDTH-1:0] fifo_o_tdata;
|
||||
logic fifo_o_tvalid;
|
||||
logic fifo_o_tready;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Input FIFO
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axi_fifo #(
|
||||
.WIDTH (WIDTH),
|
||||
.SIZE (FIFO_SIZE_LOG2)
|
||||
) input_fifo_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (s_axis_tdata),
|
||||
.i_tvalid (s_axis_tvalid),
|
||||
.i_tready (s_axis_tready),
|
||||
.o_tdata (fifo_o_tdata),
|
||||
.o_tvalid (fifo_o_tvalid),
|
||||
.o_tready (fifo_o_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
assign ctrl_data = fifo_o_tdata;
|
||||
assign fifo_o_tready = ctrl_ack;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
ctrl_ack <= 1'b0;
|
||||
end else begin
|
||||
// Default: deassert ACK every cycle.
|
||||
ctrl_ack <= 1'b0;
|
||||
|
||||
case (state)
|
||||
ST_IDLE: begin
|
||||
if (ctrl_rd) begin
|
||||
if (fifo_o_tvalid) begin
|
||||
// If we have data available, then ACK on next clock cycle.
|
||||
ctrl_ack <= 1'b1;
|
||||
end else begin
|
||||
// If we don't have data yet, then wait until it arrives.
|
||||
state <= ST_WAIT;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_WAIT: begin
|
||||
// ACK after we get a new data word
|
||||
if (fifo_o_tvalid) begin
|
||||
ctrl_ack <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
endmodule : axis_to_ctrlport
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
991
fpga/usrp3/lib/control/ctrlport_spi_adrv.sv
Normal file
991
fpga/usrp3/lib/control/ctrlport_spi_adrv.sv
Normal file
@ -0,0 +1,991 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_spi_adrv
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// SPI master for the ADI ADRV transceiver family. It is controlled via a
|
||||
// CtrlPort slave interface and it drives a standard 4-wire SPI bus (CPOL=0,
|
||||
// CPHA=0). It is designed to make efficient use of the CtrlPort bus by
|
||||
// eliminating the need to write redundant address bytes (e.g., repeating the
|
||||
// address for writes to the same address, or writes to sequential
|
||||
// addresses), the need to write dummy bytes in order to flush out a read
|
||||
// result, or the need to read back echo bytes in responses. Only the
|
||||
// relevant bytes need to be communicated over CtrlPort.
|
||||
//
|
||||
// Furthermore, the SPI transactions are blocking in the sense that the
|
||||
// CtrlPort interface will not ACK until the request is accepted and
|
||||
// guaranteed to complete. This allows multiple SPI transactions to be
|
||||
// enqueued in the underlying CtrlPort endpoint FIFO without the need for
|
||||
// polling the core for status.
|
||||
//
|
||||
// Two methods are available for issuing SPI transactions: REG_CONTROl and
|
||||
// REG_SI.
|
||||
//
|
||||
// REG_CONTROL Interface
|
||||
//
|
||||
// With this interface, the REG_CONTROL register controls the mode,
|
||||
// direction, and the number of data bytes in the transaction. Four
|
||||
// transaction modes are supported via the REG_CONTROL register.
|
||||
//
|
||||
// RAW : The exact byte stream in the data register is shifted out.
|
||||
// The caller is responsible for embedding any SPI address.
|
||||
//
|
||||
// SI_SEQ : Single instruction, sequential. Each data byte is preceded
|
||||
// by the 16-bit SPI address written in REG_CONTROL, which
|
||||
// auto-increments by one for every byte. Three SPI bytes are
|
||||
// sent per data byte.
|
||||
//
|
||||
// SI_REP : Single instruction, repeated. Same as SI_SEQ but the
|
||||
// address does not increment. The same address is sent
|
||||
// before every data byte.
|
||||
//
|
||||
// STREAMING : The 16-bit SPI address is sent once, followed by all data
|
||||
// bytes back-to-back. Two address bytes plus num_bytes data
|
||||
// bytes are sent total.
|
||||
//
|
||||
//
|
||||
// For write operations in all the above modes, the RX state machine
|
||||
// ignores the incoming bytes from the SPI interface without storing them.
|
||||
// So, it is not necessary to read anything when doing a write.
|
||||
//
|
||||
// For read operations in RAW mode, all MISO bytes are returned such that
|
||||
// you must read the same number of bytes that were written.
|
||||
//
|
||||
// For read operations in SI_SEQ, SI_REP, and STREAMING mode, the state
|
||||
// machine automatically drives MOSI with zeros during the data phase, so
|
||||
// that it is not necessary to write any data bytes to execute a read. And
|
||||
// it automatically captures only the data bytes from MISO; the address
|
||||
// echo bytes are discarded. So, you only need to read the number of data
|
||||
// bytes that were requested to be written (excluding the address bytes).
|
||||
//
|
||||
// REG_SI Interface
|
||||
//
|
||||
// For a single-byte read or write (referred to as "single instruction" in
|
||||
// the ADI code), there is also a REG_SI register to which a single 3-byte
|
||||
// instruction can be written (addr_hi, addr_lo, and data byte). This is
|
||||
// equivalent to using RAW mode with a length of 3 bytes. If the MSB of the
|
||||
// address (bit 15) is set, then it is treated as a read and the 3 RX data
|
||||
// bytes are inserted into the RX FIFO and can be read by reading the
|
||||
// REG_DATA register.
|
||||
//
|
||||
// The benefit of using REG_SI is that you don't need to also write to the
|
||||
// REG_CONTROL register. So, fewer CtrlPort writes are needed for three
|
||||
// instructions or less compared to RAW mode. On the downside, each
|
||||
// instruction is its own SPI transaction (i.e., CS_n deasserts between
|
||||
// each instruction). So, for 2 or more instructions, additional time may
|
||||
// be used on the SPI bus.
|
||||
//
|
||||
// RX Data FIFO
|
||||
//
|
||||
// Received bytes are packed into 32-bit words (first byte in bits [7:0],
|
||||
// second byte in bits [15:8], etc.) and stored in the RX word FIFO. Each
|
||||
// transaction ends on a word boundary; if the number of received bytes is
|
||||
// not a multiple of 4, the remaining bytes in the last word are undefined.
|
||||
// This means that the first byte of a transaction always starts with bits
|
||||
// [7:0] of a new FIFO word, regardless of how many bytes the previous
|
||||
// transaction produced.
|
||||
//
|
||||
// Each REG_SI read request always produces exactly 3 received bytes, which
|
||||
// are padded to a full 32-bit word (one FIFO entry). So each REG_SI read
|
||||
// fills one word of the FIFO, but only bytes [23:0] are valid.
|
||||
//
|
||||
// The host software must not overfill the FIFO. The default depth holds
|
||||
// one complete maximum-size transaction.
|
||||
//
|
||||
// Data Word Byte Ordering
|
||||
//
|
||||
// Words are little-endian packed. That is, byte 0 (the first byte to be
|
||||
// sent over SPI) occupies data[7:0], byte 1 occupies data[15:8], etc. Each
|
||||
// byte is serialized MSB-first. That is, bit 7 leaves the MOSI/MISO pin
|
||||
// first, bit 0 last.
|
||||
//
|
||||
// CS_N Behavior
|
||||
//
|
||||
// Asserts when REG_CONTROL is written and deasserts automatically after
|
||||
// all num_bytes have been shifted and captured. CS_N remains asserted until
|
||||
// the entire transaction has completed; the SPI clock simply pauses
|
||||
// between data bytes when the TX buffer is empty.
|
||||
//
|
||||
// For REG_SI, CS_N asserts/deasserts for each single instruction.
|
||||
//
|
||||
// CtrlPort Flow Control
|
||||
//
|
||||
// REG_CONTROL write : ACKs after the control word has been accepted.
|
||||
// REG_DATA write : ACKs after the write has been accepted and is
|
||||
// guaranteed to complete.
|
||||
// REG_DATA read : ACK'd when the data to be read becomes available.
|
||||
//
|
||||
// SPI Clock Rate
|
||||
//
|
||||
// The SPI clock rate is controlled by setting the desired number of clock
|
||||
// cycles per half SPI clock period, minus 1. In other words, the actual
|
||||
// SPI clock frequency for a given clock frequency will be:
|
||||
//
|
||||
// f_sclk = f_clk / (2 * (half_per + 1))
|
||||
//
|
||||
// Or, the required half period setting for a given SPI clock frequency can
|
||||
// be calculated as:
|
||||
//
|
||||
// half_per = (f_clk / (2 * f_sclk)) - 1
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// BASE_ADDR : CtrlPort base address.
|
||||
// NUM_BYTES_W : Log base 2 of maximum number data bytes supported per
|
||||
// transaction. Must not exceed SPI_NUM_BYTES_W.
|
||||
// HALF_PER : Default sclk half-period length in clk cycles
|
||||
// HALF_PER_W : Width of the half-period register in bits.
|
||||
// HALF_PER_EN : When 1, the half-period register (REG_HALF_PER) exists and
|
||||
// is software-readable and writable. When 0, the register
|
||||
// does not exist; the SCLK rate is fixed at the HALF_PER
|
||||
// parameter value.
|
||||
// CS_HOLD : 1 = hold CS_N low for one extra half-period after the last
|
||||
// SCLK falling edge. 0 = deassert CS_N immediately on the
|
||||
// last SCLK falling edge.
|
||||
// CS_GUARD : Number of CS_N-high half-periods between transactions. Must
|
||||
// be 1 or 2.
|
||||
// RX_FIFO_SIZE : Log base 2 of the depth of the 32-bit wide RX word FIFO. By
|
||||
// default, it holds one complete maximum-size transaction
|
||||
// (2**NUM_BYTES_W bytes / 4 bytes per word). The host
|
||||
// software must NOT overfill the FIFO.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// clk / rst : Clock and active-high synchronous reset.
|
||||
// s_ctrlport_* : CtrlPort slave request/response signals.
|
||||
// sclk : SPI clock output (registered).
|
||||
// cs_n : Active-low chip select (registered).
|
||||
// mosi : Master-out / slave-in data (registered).
|
||||
// miso : Master-in / slave-out data input.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module ctrlport_spi_adrv
|
||||
import ctrlport_pkg::*;
|
||||
import ctrlport_spi_adrv_pkg::*;
|
||||
#(
|
||||
int BASE_ADDR = 0,
|
||||
int NUM_BYTES_W = 8,
|
||||
int HALF_PER = 9,
|
||||
int HALF_PER_W = 8,
|
||||
bit HALF_PER_EN = 1,
|
||||
bit CS_HOLD = 1,
|
||||
int CS_GUARD = 2,
|
||||
int RX_FIFO_SIZE = NUM_BYTES_W-2
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// CtrlPort Slave Request
|
||||
input wire s_ctrlport_req_wr,
|
||||
input wire s_ctrlport_req_rd,
|
||||
input wire [CTRLPORT_ADDR_W-1:0] s_ctrlport_req_addr,
|
||||
input wire [CTRLPORT_DATA_W-1:0] s_ctrlport_req_data,
|
||||
|
||||
// CtrlPort Slave Response
|
||||
output logic s_ctrlport_resp_ack,
|
||||
output ctrlport_status_t s_ctrlport_resp_status,
|
||||
output logic [CTRLPORT_DATA_W-1:0] s_ctrlport_resp_data,
|
||||
|
||||
// SPI Interface
|
||||
output logic sclk,
|
||||
output logic cs_n,
|
||||
output logic mosi,
|
||||
input wire miso
|
||||
);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Parameter Checks
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (NUM_BYTES_W > SPI_NUM_BYTES_W) begin : gen_error
|
||||
$error("NUM_BYTES_W (%0d) must not exceed SPI_NUM_BYTES_W (%0d).",
|
||||
NUM_BYTES_W, SPI_NUM_BYTES_W);
|
||||
end : gen_error
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Declarations
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// SPI clock half-period. Driven by a software-writable register when
|
||||
// HALF_PER_EN=1; wired to the parameter value when HALF_PER_EN=0.
|
||||
logic [HALF_PER_W-1:0] half_per;
|
||||
|
||||
// TX 32-bit AXI-Stream (from register block into TX width converter)
|
||||
logic [31:0] tx_word_tdata;
|
||||
logic [ 3:0] tx_word_tkeep;
|
||||
logic tx_word_tvalid;
|
||||
logic tx_word_tready;
|
||||
logic tx_word_tlast;
|
||||
|
||||
// TX 8-bit AXI-Stream (from TX width converter into axis_spi)
|
||||
logic [7:0] tx_byte_tdata;
|
||||
logic tx_byte_tvalid;
|
||||
logic tx_byte_tready;
|
||||
logic tx_byte_tlast;
|
||||
|
||||
// RX 8-bit AXI-Stream (from axis_spi into RX FSM)
|
||||
logic [7:0] spi_rx_tdata;
|
||||
logic spi_rx_tvalid;
|
||||
logic spi_rx_tready;
|
||||
logic spi_rx_tlast;
|
||||
|
||||
// RX 8-bit AXI-Stream (from RX FSM into RX width converter)
|
||||
logic [7:0] rx_byte_tdata;
|
||||
logic rx_byte_tvalid;
|
||||
logic rx_byte_tready;
|
||||
logic rx_byte_tlast;
|
||||
|
||||
// RX 32-bit AXI-Stream (from RX width converter into RX FIFO)
|
||||
logic [31:0] rx_word_tdata;
|
||||
logic rx_word_tvalid;
|
||||
logic rx_word_tready;
|
||||
logic rx_word_tlast;
|
||||
|
||||
// RX FIFO output
|
||||
logic [31:0] rx_fifo_tdata;
|
||||
logic rx_fifo_tvalid;
|
||||
logic rx_fifo_tready;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Address Decode
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic address_in_range;
|
||||
logic is_si_reg;
|
||||
logic is_ctrl_reg;
|
||||
logic is_data_win;
|
||||
|
||||
assign address_in_range =
|
||||
(s_ctrlport_req_addr[CTRLPORT_ADDR_W-1:REG_ADDR_W] ==
|
||||
BASE_ADDR[CTRLPORT_ADDR_W-1:REG_ADDR_W]);
|
||||
|
||||
assign is_si_reg = address_in_range &&
|
||||
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_SI);
|
||||
assign is_ctrl_reg = address_in_range &&
|
||||
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_CONTROL);
|
||||
assign is_data_win = address_in_range &&
|
||||
(s_ctrlport_req_addr[0+:REG_ADDR_W] >= REG_DATA) &&
|
||||
(s_ctrlport_req_addr[0+:REG_ADDR_W] < REG_DATA + 64);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Half-Period Register
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// When HALF_PER_EN=1 the register exists and is software-readable and
|
||||
// writable. When HALF_PER_EN=0 none of this logic exists and half_per is a
|
||||
// constant tied to the HALF_PER parameter.
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic half_per_ack;
|
||||
|
||||
if (HALF_PER_EN) begin : gen_half_per_reg
|
||||
logic is_half_per_reg;
|
||||
assign is_half_per_reg = address_in_range &&
|
||||
(s_ctrlport_req_addr[0+:REG_ADDR_W] == REG_HALF_PER);
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
half_per <= HALF_PER;
|
||||
half_per_ack <= 1'b0;
|
||||
end else begin
|
||||
half_per_ack <= 1'b0;
|
||||
|
||||
if (is_half_per_reg) begin
|
||||
if (s_ctrlport_req_wr) begin
|
||||
half_per <= s_ctrlport_req_data[HALF_PER_W-1:0];
|
||||
half_per_ack <= 1'b1;
|
||||
end else if (s_ctrlport_req_rd) begin
|
||||
half_per_ack <= 1'b1;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
end else begin : gen_half_per_fixed
|
||||
assign half_per = HALF_PER;
|
||||
assign half_per_ack = 1'b0;
|
||||
end : gen_half_per_fixed
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Control Register Write Handling
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// For writes to REG_SI, we must also write a control word equivalent to a
|
||||
// 3-byte RAW control word, with read or write set based on the MSB of the
|
||||
// address.
|
||||
spi_adrv_ctrl_t si_ctrl_word;
|
||||
assign si_ctrl_word = '{
|
||||
dir: s_ctrlport_req_data[7] ? SPI_READ : SPI_WRITE,
|
||||
rsvd1: 'X,
|
||||
mode: MODE_RAW,
|
||||
num_bytes: 3,
|
||||
addr: 'X
|
||||
};
|
||||
|
||||
logic [CTRLPORT_DATA_W-1:0] ctrl_data;
|
||||
logic ctrl_wr;
|
||||
logic ctrl_ack;
|
||||
logic [CTRLPORT_DATA_W-1:0] ctrl_tdata;
|
||||
logic ctrl_tvalid;
|
||||
logic ctrl_tready;
|
||||
|
||||
// Control word split to TX and RX FSMs.
|
||||
spi_adrv_ctrl_t ctrl_tx_tdata;
|
||||
logic ctrl_tx_tvalid;
|
||||
logic ctrl_tx_tready;
|
||||
spi_adrv_ctrl_t ctrl_rx_tdata;
|
||||
logic ctrl_rx_tvalid;
|
||||
logic ctrl_rx_tready;
|
||||
|
||||
// REG_CONTROL writes pass the data word through. REG_SI writes use the
|
||||
// synthesized control word above.
|
||||
assign ctrl_data = is_si_reg ? si_ctrl_word : s_ctrlport_req_data;
|
||||
assign ctrl_wr = s_ctrlport_req_wr && (is_ctrl_reg || is_si_reg);
|
||||
|
||||
ctrlport_to_axis #(
|
||||
.WIDTH (CTRLPORT_DATA_W),
|
||||
.FIFO_SIZE_LOG2 (1)
|
||||
) ctrlport_to_axis_ctrl (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.ctrl_data (ctrl_data),
|
||||
.ctrl_wr (ctrl_wr),
|
||||
.ctrl_ack (ctrl_ack),
|
||||
.m_axis_tdata (ctrl_tdata),
|
||||
.m_axis_tvalid (ctrl_tvalid),
|
||||
.m_axis_tready (ctrl_tready)
|
||||
);
|
||||
|
||||
// Duplicate the control word to the TX FSM (port 0) and RX FSM (port 1).
|
||||
axis_split #(
|
||||
.DATA_W (CTRLPORT_DATA_W),
|
||||
.NUM_PORTS (2),
|
||||
.INPUT_REG (0),
|
||||
.OUT_FIFO_SIZE (1)
|
||||
) ctrl_split_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.s_axis_tdata (ctrl_tdata),
|
||||
.s_axis_tvalid (ctrl_tvalid),
|
||||
.s_axis_tready (ctrl_tready),
|
||||
.m_axis_tdata ({ctrl_rx_tdata, ctrl_tx_tdata}),
|
||||
.m_axis_tvalid ({ctrl_rx_tvalid, ctrl_tx_tvalid}),
|
||||
.m_axis_tready ({ctrl_rx_tready, ctrl_tx_tready})
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Data Register Write Handling
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic data_wr;
|
||||
logic data_ack;
|
||||
|
||||
assign data_wr = s_ctrlport_req_wr && (is_data_win || is_si_reg);
|
||||
|
||||
ctrlport_to_axis #(
|
||||
.WIDTH (CTRLPORT_DATA_W),
|
||||
.FIFO_SIZE_LOG2 (0)
|
||||
) ctrlport_to_axis_data (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.ctrl_data (s_ctrlport_req_data),
|
||||
.ctrl_wr (data_wr),
|
||||
.ctrl_ack (data_ack),
|
||||
.m_axis_tdata (tx_word_tdata),
|
||||
.m_axis_tvalid (tx_word_tvalid),
|
||||
.m_axis_tready (tx_word_tready)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Data Register Read Handling
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic [CTRLPORT_DATA_W-1:0] data_a2c_data;
|
||||
logic data_a2c_ack;
|
||||
logic data_a2c_rd;
|
||||
|
||||
assign data_a2c_rd = s_ctrlport_req_rd && is_data_win;
|
||||
|
||||
axis_to_ctrlport #(
|
||||
.WIDTH (CTRLPORT_DATA_W),
|
||||
.FIFO_SIZE_LOG2 (0)
|
||||
) axis_to_ctrlport_rx (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.s_axis_tdata (rx_fifo_tdata),
|
||||
.s_axis_tvalid (rx_fifo_tvalid),
|
||||
.s_axis_tready (rx_fifo_tready),
|
||||
.ctrl_rd (data_a2c_rd),
|
||||
.ctrl_data (data_a2c_data),
|
||||
.ctrl_ack (data_a2c_ack)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CtrlPort Response Acknowledgement
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// A REG_SI write triggers both ctrl_wr and data_wr simultaneously, so
|
||||
// ctrl_ack and data_ack fire independently at different times. This state
|
||||
// machine ensures exactly one s_ctrlport_resp_ack pulse is issued per
|
||||
// request, once all expected ACKs have arrived.
|
||||
//
|
||||
// ST_ACK_IDLE : Waiting for a write request.
|
||||
// ST_ACK_WAIT_ONE : Expecting one more ACK (REG_CONTROL or REG_DATA write,
|
||||
// or REG_SI write after one of the two ACKs has arrived).
|
||||
// ST_ACK_WAIT_TWO : Expecting two ACKs (REG_SI write, neither arrived yet);
|
||||
// if both arrive on the same cycle, coalesce immediately.
|
||||
//
|
||||
// data_a2c_ack (axis-to-ctrlport for REG_DATA reads) is always single-source
|
||||
// and bypasses the state machine, ORing directly into s_ctrlport_resp_ack.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic [1:0] {
|
||||
ST_ACK_IDLE = 0,
|
||||
ST_ACK_WAIT_ONE = 1,
|
||||
ST_ACK_WAIT_TWO = 2
|
||||
} ack_state_t;
|
||||
|
||||
ack_state_t ack_state;
|
||||
logic ack_out;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
ack_state <= ST_ACK_IDLE;
|
||||
ack_out <= 1'b0;
|
||||
end else begin
|
||||
ack_out <= 1'b0;
|
||||
|
||||
case (ack_state)
|
||||
ST_ACK_IDLE : begin
|
||||
if (s_ctrlport_req_wr) begin
|
||||
if (is_si_reg)
|
||||
ack_state <= ST_ACK_WAIT_TWO;
|
||||
else
|
||||
ack_state <= ST_ACK_WAIT_ONE;
|
||||
end
|
||||
end
|
||||
|
||||
ST_ACK_WAIT_ONE : begin
|
||||
if (ctrl_ack || data_ack) begin
|
||||
ack_out <= 1'b1;
|
||||
ack_state <= ST_ACK_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
ST_ACK_WAIT_TWO : begin
|
||||
if (ctrl_ack && data_ack) begin
|
||||
// Both arrived on the same cycle.
|
||||
ack_out <= 1'b1;
|
||||
ack_state <= ST_ACK_IDLE;
|
||||
end else if (ctrl_ack || data_ack) begin
|
||||
// One arrived; wait for the other.
|
||||
ack_state <= ST_ACK_WAIT_ONE;
|
||||
end
|
||||
end
|
||||
|
||||
default : ack_state <= ST_ACK_IDLE;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
s_ctrlport_resp_ack <= 1'b0;
|
||||
s_ctrlport_resp_data <= 'X;
|
||||
end else begin
|
||||
s_ctrlport_resp_ack <= ack_out | data_a2c_ack | half_per_ack;
|
||||
s_ctrlport_resp_data <= half_per_ack ? half_per : data_a2c_data;
|
||||
end
|
||||
end
|
||||
assign s_ctrlport_resp_status = STS_OKAY;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// TX Width Converter (32-bit -> 8-bit)
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axis_width_conv #(
|
||||
.WORD_W (8),
|
||||
.IN_WORDS (4),
|
||||
.OUT_WORDS (1),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE ("OUT")
|
||||
) axis_width_conv_tx (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (tx_word_tdata),
|
||||
.s_axis_tkeep ('1),
|
||||
.s_axis_tlast ('0),
|
||||
.s_axis_tvalid (tx_word_tvalid),
|
||||
.s_axis_tready (tx_word_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (tx_byte_tdata),
|
||||
.m_axis_tkeep (),
|
||||
.m_axis_tlast (tx_byte_tlast),
|
||||
.m_axis_tvalid (tx_byte_tvalid),
|
||||
.m_axis_tready (tx_byte_tready)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// TX State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// AXI-Stream from TX FSM into axis_spi.
|
||||
logic [7:0] spi_tx_tdata;
|
||||
logic spi_tx_tvalid;
|
||||
logic spi_tx_tready;
|
||||
logic spi_tx_tlast;
|
||||
|
||||
typedef enum logic [3:0] {
|
||||
ST_TX_IDLE = 0,
|
||||
ST_TX_RAW = 1,
|
||||
ST_TX_SI_ADDR_HI = 2,
|
||||
ST_TX_SI_ADDR_LO = 3,
|
||||
ST_TX_SI_DATA = 4,
|
||||
ST_TX_STREAM_ADDR_HI = 5,
|
||||
ST_TX_STREAM_ADDR_LO = 6,
|
||||
ST_TX_STREAM_DATA = 7,
|
||||
ST_TX_DRAIN = 8
|
||||
} tx_state_t;
|
||||
|
||||
tx_state_t tx_state;
|
||||
logic [SPI_NUM_BYTES_W-1:0] tx_bytes_rem; // Data bytes remaining
|
||||
logic [ 15:0] tx_cur_addr; // Running SPI address
|
||||
logic [ 1:0] tx_drain_cnt; // Padding bytes to drain after transaction
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
tx_state <= ST_TX_IDLE;
|
||||
tx_bytes_rem <= '0;
|
||||
tx_cur_addr <= '0;
|
||||
tx_drain_cnt <= 2'b0;
|
||||
end else begin
|
||||
case (tx_state)
|
||||
ST_TX_IDLE : begin
|
||||
if (ctrl_tx_tvalid) begin
|
||||
tx_bytes_rem <= ctrl_tx_tdata.num_bytes;
|
||||
tx_cur_addr <= ctrl_tx_tdata.addr;
|
||||
tx_drain_cnt <= 2'b0 - ctrl_tx_tdata.num_bytes[1:0];
|
||||
case (ctrl_tx_tdata.mode)
|
||||
MODE_RAW : tx_state <= ST_TX_RAW;
|
||||
MODE_SI_SEQ,
|
||||
MODE_SI_REP : tx_state <= ST_TX_SI_ADDR_HI;
|
||||
MODE_STREAMING : tx_state <= ST_TX_STREAM_ADDR_HI;
|
||||
default : tx_state <= ST_TX_RAW;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_RAW : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_bytes_rem <= tx_bytes_rem - 1;
|
||||
if (tx_bytes_rem == 1) begin
|
||||
tx_state <= (tx_drain_cnt != 0) ? ST_TX_DRAIN : ST_TX_IDLE;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_SI_ADDR_HI : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_state <= ST_TX_SI_ADDR_LO;
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_SI_ADDR_LO : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_state <= ST_TX_SI_DATA;
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_SI_DATA : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_bytes_rem <= tx_bytes_rem - 1;
|
||||
if (tx_bytes_rem == 1) begin
|
||||
if (ctrl_tx_tdata.dir == SPI_WRITE && tx_drain_cnt != 0) begin
|
||||
tx_state <= ST_TX_DRAIN;
|
||||
end else begin
|
||||
tx_state <= ST_TX_IDLE;
|
||||
end
|
||||
end else begin
|
||||
if (ctrl_tx_tdata.mode == MODE_SI_SEQ) begin
|
||||
tx_cur_addr <= tx_cur_addr + 1;
|
||||
end
|
||||
tx_state <= ST_TX_SI_ADDR_HI;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_STREAM_ADDR_HI : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_state <= ST_TX_STREAM_ADDR_LO;
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_STREAM_ADDR_LO : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_state <= ST_TX_STREAM_DATA;
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_STREAM_DATA : begin
|
||||
if (spi_tx_tvalid && spi_tx_tready) begin
|
||||
tx_bytes_rem <= tx_bytes_rem - 1;
|
||||
if (tx_bytes_rem == 1) begin
|
||||
if (ctrl_tx_tdata.dir == SPI_WRITE && tx_drain_cnt != 0) begin
|
||||
tx_state <= ST_TX_DRAIN;
|
||||
end else begin
|
||||
tx_state <= ST_TX_IDLE;
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_TX_DRAIN : begin
|
||||
if (tx_byte_tvalid) begin
|
||||
tx_drain_cnt <= tx_drain_cnt - 1;
|
||||
if (tx_drain_cnt == 1) begin
|
||||
tx_state <= ST_TX_IDLE;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
default : begin
|
||||
tx_state <= ST_TX_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
// ctrl_tx_tready is driven combinationally so that the control FIFO word is
|
||||
// consumed on the exact same cycle that the last SPI byte is accepted by
|
||||
// axis_spi. This ensures the FIFO advances before the FSM re-enters
|
||||
// ST_TX_IDLE, so it sees the correct next control word.
|
||||
always_comb begin
|
||||
spi_tx_tdata = 8'h00;
|
||||
spi_tx_tvalid = 1'b0;
|
||||
spi_tx_tlast = 1'b0;
|
||||
tx_byte_tready = 1'b0;
|
||||
ctrl_tx_tready = 1'b0;
|
||||
|
||||
case (tx_state)
|
||||
ST_TX_IDLE : begin
|
||||
// Waiting; nothing to send.
|
||||
end
|
||||
|
||||
ST_TX_RAW : begin
|
||||
// RAW: pass tx_byte bytes straight through regardless of direction.
|
||||
spi_tx_tdata = tx_byte_tdata;
|
||||
spi_tx_tvalid = tx_byte_tvalid;
|
||||
tx_byte_tready = spi_tx_tready;
|
||||
spi_tx_tlast = (tx_bytes_rem == 1);
|
||||
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
|
||||
end
|
||||
|
||||
ST_TX_SI_ADDR_HI : begin
|
||||
// addr[15:8] = addr_hi; first byte on the wire.
|
||||
spi_tx_tdata = tx_cur_addr[15:8];
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end
|
||||
|
||||
ST_TX_SI_ADDR_LO : begin
|
||||
// addr[7:0] = addr_lo; second byte on the wire.
|
||||
spi_tx_tdata = tx_cur_addr[7:0];
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end
|
||||
|
||||
ST_TX_SI_DATA : begin
|
||||
if (ctrl_tx_tdata.dir == SPI_READ) begin
|
||||
// Drive MOSI=0 for reads; do not consume TX data FIFO.
|
||||
spi_tx_tdata = 8'h00;
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end else begin
|
||||
spi_tx_tdata = tx_byte_tdata;
|
||||
spi_tx_tvalid = tx_byte_tvalid;
|
||||
tx_byte_tready = spi_tx_tready;
|
||||
end
|
||||
spi_tx_tlast = (tx_bytes_rem == 1);
|
||||
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
|
||||
end
|
||||
|
||||
ST_TX_STREAM_ADDR_HI : begin
|
||||
spi_tx_tdata = tx_cur_addr[15:8];
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end
|
||||
|
||||
ST_TX_STREAM_ADDR_LO : begin
|
||||
spi_tx_tdata = tx_cur_addr[7:0];
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end
|
||||
|
||||
ST_TX_STREAM_DATA : begin
|
||||
if (ctrl_tx_tdata.dir == SPI_READ) begin
|
||||
spi_tx_tdata = 8'h00;
|
||||
spi_tx_tvalid = 1'b1;
|
||||
end else begin
|
||||
spi_tx_tdata = tx_byte_tdata;
|
||||
spi_tx_tvalid = tx_byte_tvalid;
|
||||
tx_byte_tready = spi_tx_tready;
|
||||
end
|
||||
spi_tx_tlast = (tx_bytes_rem == 1);
|
||||
ctrl_tx_tready = spi_tx_tvalid && spi_tx_tready && (tx_bytes_rem == 1);
|
||||
end
|
||||
|
||||
ST_TX_DRAIN : begin
|
||||
// Consume width-converter padding bytes without sending them on MOSI.
|
||||
tx_byte_tready = 1'b1;
|
||||
end
|
||||
|
||||
default : begin
|
||||
// Nothing
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// SPI Interface
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axis_spi #(
|
||||
.HALF_PER_W (HALF_PER_W),
|
||||
.CS_HOLD (CS_HOLD),
|
||||
.CS_GUARD (CS_GUARD)
|
||||
) axis_spi_i (
|
||||
.clk (clk),
|
||||
.rst (rst),
|
||||
.half_per (half_per),
|
||||
.s_axis_tdata (spi_tx_tdata),
|
||||
.s_axis_tvalid (spi_tx_tvalid),
|
||||
.s_axis_tready (spi_tx_tready),
|
||||
.s_axis_tlast (spi_tx_tlast),
|
||||
.m_axis_tdata (spi_rx_tdata),
|
||||
.m_axis_tvalid (spi_rx_tvalid),
|
||||
.m_axis_tready (spi_rx_tready),
|
||||
.m_axis_tlast (spi_rx_tlast),
|
||||
.sclk (sclk),
|
||||
.cs_n (cs_n),
|
||||
.mosi (mosi),
|
||||
.miso (miso)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// RX State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
//
|
||||
// Filters bytes from spi_rx_*, forwarding data-phase bytes to rx_byte_*
|
||||
// (axis_width_conv_rx) per the mode table below:
|
||||
//
|
||||
// RAW write : Discard all bytes until tlast.
|
||||
// RAW read : Forward all bytes until tlast.
|
||||
// SI_SEQ/REP : For each group of 3, discard addr_hi, discard addr_lo,
|
||||
// forward data byte; repeat until tlast.
|
||||
// STREAMING : Discard addr_hi, discard addr_lo, then forward all bytes
|
||||
// until tlast.
|
||||
//
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic [3:0] {
|
||||
ST_RX_IDLE = 0,
|
||||
ST_RX_RAW_DROP = 1,
|
||||
ST_RX_SI_DISCARD_HI = 2,
|
||||
ST_RX_SI_DISCARD_LO = 3,
|
||||
ST_RX_SI_DATA = 4,
|
||||
ST_RX_STREAM_DISCARD_HI = 5,
|
||||
ST_RX_STREAM_DISCARD_LO = 6,
|
||||
ST_RX_PASS_DATA = 7
|
||||
} rx_state_t;
|
||||
|
||||
rx_state_t rx_state;
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
rx_state <= ST_RX_IDLE;
|
||||
end else begin
|
||||
case (rx_state)
|
||||
ST_RX_IDLE : begin
|
||||
if (ctrl_rx_tvalid) begin
|
||||
case (ctrl_rx_tdata.mode)
|
||||
MODE_RAW : begin
|
||||
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
|
||||
ST_RX_PASS_DATA : ST_RX_RAW_DROP;
|
||||
end
|
||||
MODE_SI_SEQ,
|
||||
MODE_SI_REP : begin
|
||||
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
|
||||
ST_RX_SI_DISCARD_HI : ST_RX_RAW_DROP;
|
||||
end
|
||||
MODE_STREAMING : begin
|
||||
rx_state <= ctrl_rx_tdata.dir == SPI_READ ?
|
||||
ST_RX_STREAM_DISCARD_HI : ST_RX_RAW_DROP;
|
||||
end
|
||||
default : rx_state <= ST_RX_RAW_DROP;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_PASS_DATA : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready && spi_rx_tlast) begin
|
||||
rx_state <= ST_RX_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_RAW_DROP : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready && spi_rx_tlast) begin
|
||||
rx_state <= ST_RX_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_SI_DISCARD_HI : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready) begin
|
||||
rx_state <= ST_RX_SI_DISCARD_LO;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_SI_DISCARD_LO : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready) begin
|
||||
rx_state <= ST_RX_SI_DATA;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_SI_DATA : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready) begin
|
||||
if (spi_rx_tlast) begin
|
||||
rx_state <= ST_RX_IDLE;
|
||||
end else begin
|
||||
rx_state <= ST_RX_SI_DISCARD_HI;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_STREAM_DISCARD_HI : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready) begin
|
||||
rx_state <= ST_RX_STREAM_DISCARD_LO;
|
||||
end
|
||||
end
|
||||
|
||||
ST_RX_STREAM_DISCARD_LO : begin
|
||||
if (spi_rx_tvalid && spi_rx_tready) begin
|
||||
rx_state <= ST_RX_PASS_DATA;
|
||||
end
|
||||
end
|
||||
|
||||
default : begin
|
||||
rx_state <= ST_RX_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
// Default: consume from spi_rx but don't forward (discard).
|
||||
spi_rx_tready = 1'b0;
|
||||
rx_byte_tdata = spi_rx_tdata;
|
||||
rx_byte_tvalid = 1'b0;
|
||||
rx_byte_tlast = spi_rx_tlast;
|
||||
ctrl_rx_tready = 1'b0;
|
||||
|
||||
case (rx_state)
|
||||
ST_RX_IDLE : begin
|
||||
// Waiting; hold spi_rx backpressured.
|
||||
end
|
||||
|
||||
ST_RX_PASS_DATA,
|
||||
ST_RX_SI_DATA : begin
|
||||
// Forward bytes to the width converter.
|
||||
spi_rx_tready = rx_byte_tready;
|
||||
rx_byte_tvalid = spi_rx_tvalid;
|
||||
ctrl_rx_tready = spi_rx_tvalid && spi_rx_tready && spi_rx_tlast;
|
||||
end
|
||||
|
||||
ST_RX_RAW_DROP : begin
|
||||
// Consume and discard; do not forward.
|
||||
spi_rx_tready = 1'b1;
|
||||
ctrl_rx_tready = spi_rx_tvalid && spi_rx_tready && spi_rx_tlast;
|
||||
end
|
||||
|
||||
ST_RX_SI_DISCARD_HI,
|
||||
ST_RX_SI_DISCARD_LO,
|
||||
ST_RX_STREAM_DISCARD_HI,
|
||||
ST_RX_STREAM_DISCARD_LO : begin
|
||||
// Consume and discard; do not forward.
|
||||
spi_rx_tready = 1'b1;
|
||||
end
|
||||
|
||||
default : begin
|
||||
// Discard.
|
||||
spi_rx_tready = 1'b1;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// RX Width Converter (8-bit -> 32-bit)
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axis_width_conv #(
|
||||
.WORD_W (8),
|
||||
.IN_WORDS (1),
|
||||
.OUT_WORDS (4),
|
||||
.SYNC_CLKS (1),
|
||||
.PIPELINE ("NONE")
|
||||
) axis_width_conv_rx (
|
||||
.s_axis_aclk (clk),
|
||||
.s_axis_rst (rst),
|
||||
.s_axis_tdata (rx_byte_tdata),
|
||||
.s_axis_tkeep (1'b1),
|
||||
.s_axis_tlast (rx_byte_tlast),
|
||||
.s_axis_tvalid (rx_byte_tvalid),
|
||||
.s_axis_tready (rx_byte_tready),
|
||||
.m_axis_aclk (clk),
|
||||
.m_axis_rst (rst),
|
||||
.m_axis_tdata (rx_word_tdata),
|
||||
.m_axis_tkeep (),
|
||||
.m_axis_tlast (rx_word_tlast),
|
||||
.m_axis_tvalid (rx_word_tvalid),
|
||||
.m_axis_tready (rx_word_tready)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// RX FIFO
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axi_fifo #(
|
||||
.WIDTH (32),
|
||||
.SIZE (RX_FIFO_SIZE)
|
||||
) axi_fifo_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (rx_word_tdata),
|
||||
.i_tvalid (rx_word_tvalid),
|
||||
.i_tready (rx_word_tready),
|
||||
.o_tdata (rx_fifo_tdata),
|
||||
.o_tvalid (rx_fifo_tvalid),
|
||||
.o_tready (rx_fifo_tready),
|
||||
.space (),
|
||||
.occupied ()
|
||||
);
|
||||
|
||||
endmodule : ctrlport_spi_adrv
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
|
||||
72
fpga/usrp3/lib/control/ctrlport_spi_adrv_pkg.sv
Normal file
72
fpga/usrp3/lib/control/ctrlport_spi_adrv_pkg.sv
Normal file
@ -0,0 +1,72 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Package: ctrlport_spi_adrv_pkg
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Shared constants and types for ctrlport_spi_adrv and its testbench.
|
||||
//
|
||||
//
|
||||
|
||||
package ctrlport_spi_adrv_pkg;
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Register offsets (byte addresses relative to BASE_ADDRESS)
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
localparam int REG_HALF_PER = 'h00;
|
||||
localparam int REG_SI = 'h04;
|
||||
localparam int REG_CONTROL = 'h3C;
|
||||
localparam int REG_DATA = 'h40;
|
||||
|
||||
// Actual address space required for this block.
|
||||
localparam int REG_ADDR_W = 7;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CONTROL register field widths
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
localparam int SPI_MODE_W = 2;
|
||||
localparam int SPI_NUM_BYTES_W = 12;
|
||||
localparam int SPI_ADDR_W = 16;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// SPI direction encoding
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic {
|
||||
SPI_WRITE = 1'b0,
|
||||
SPI_READ = 1'b1
|
||||
} spi_adrv_dir_t;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// SPI mode encoding
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic [SPI_MODE_W-1:0] {
|
||||
MODE_RAW = 0, // Raw byte stream
|
||||
MODE_SI_SEQ = 1, // Single Instruction, sequential addresses
|
||||
MODE_SI_REP = 2, // Single Instruction, same address repeated
|
||||
MODE_STREAMING = 3 // Send address once, followed by all data bytes back-to-back
|
||||
} spi_adrv_mode_t;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CONTROL register
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef struct packed {
|
||||
spi_adrv_dir_t dir; // [31]
|
||||
logic rsvd1; // [30]
|
||||
spi_adrv_mode_t mode; // [29:28]
|
||||
logic [SPI_NUM_BYTES_W-1:0] num_bytes; // [27:16]
|
||||
logic [ SPI_ADDR_W-1:0] addr; // [15:0]
|
||||
} spi_adrv_ctrl_t;
|
||||
|
||||
endpackage : ctrlport_spi_adrv_pkg
|
||||
150
fpga/usrp3/lib/control/ctrlport_to_axis.sv
Normal file
150
fpga/usrp3/lib/control/ctrlport_to_axis.sv
Normal file
@ -0,0 +1,150 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_axis
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Converts a CtrlPort write interface into an AXI-Stream bus. The module
|
||||
// contains an internal FIFO and will not acknowledge the write until the
|
||||
// FIFO has emptied enough to guarantee that another write can be accepted.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// WIDTH : Width of the data path in bits.
|
||||
// FIFO_SIZE_LOG2 : Log base 2 of the internal FIFO size.
|
||||
//
|
||||
// Signals:
|
||||
//
|
||||
// clk : Clock
|
||||
// rst : Active-high synchronous reset
|
||||
// ctrl_data : CtrlPort data word
|
||||
// ctrl_wr : CtrlPort write request strobe (single-cycle pulse)
|
||||
// ctrl_ack : CtrlPort acknowledgement strobe (single-cycle pulse)
|
||||
// m_axis_tdata : AXI-Stream data output
|
||||
// m_axis_tvalid : AXI-Stream valid
|
||||
// m_axis_tready : AXI-Stream ready (from downstream)
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module ctrlport_to_axis #(
|
||||
parameter int WIDTH = 32,
|
||||
parameter int FIFO_SIZE_LOG2 = 1
|
||||
) (
|
||||
input wire clk,
|
||||
input wire rst,
|
||||
|
||||
// CtrlPort write interface
|
||||
input wire [WIDTH-1:0] ctrl_data,
|
||||
input wire ctrl_wr,
|
||||
output logic ctrl_ack,
|
||||
|
||||
// AXI-Stream master
|
||||
output wire [WIDTH-1:0] m_axis_tdata,
|
||||
output wire m_axis_tvalid,
|
||||
input wire m_axis_tready
|
||||
);
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Declarations
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef enum logic { ST_IDLE, ST_FULL } state_t;
|
||||
state_t state;
|
||||
|
||||
logic fifo_i_tready;
|
||||
logic [15:0] fifo_space;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// State Machine
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= ST_IDLE;
|
||||
ctrl_ack <= 1'b0;
|
||||
end else begin
|
||||
// Default assignments
|
||||
ctrl_ack <= 1'b0;
|
||||
|
||||
unique case (state)
|
||||
ST_IDLE: begin
|
||||
if (ctrl_wr) begin
|
||||
if (fifo_space > 1 && FIFO_SIZE_LOG2 > 0) begin
|
||||
// If there's 2 or more spaces in the FIFO, acknowledge
|
||||
// immediately. If FIFO_SIZE_LOG2 is 0 (depth of 1), then the
|
||||
// space can never be greater than 1.
|
||||
ctrl_ack <= 1'b1;
|
||||
end else begin
|
||||
// If there's only one space, this write fills the FIFO, and we
|
||||
// can't acknowledge until we have enough room to accept the next
|
||||
// write.
|
||||
state <= ST_FULL;
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
ST_FULL: begin
|
||||
// Acknowledge the write as soon as space frees up in the FIFO.
|
||||
if (fifo_space > 0) begin
|
||||
ctrl_ack <= 1'b1;
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
end
|
||||
|
||||
default: begin
|
||||
state <= ST_IDLE;
|
||||
end
|
||||
endcase
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Output FIFO
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
axi_fifo #(
|
||||
.WIDTH (WIDTH),
|
||||
.SIZE (FIFO_SIZE_LOG2)
|
||||
) data_fifo_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.clear (1'b0),
|
||||
.i_tdata (ctrl_data),
|
||||
.i_tvalid (ctrl_wr),
|
||||
.i_tready (fifo_i_tready),
|
||||
.o_tdata (m_axis_tdata),
|
||||
.o_tvalid (m_axis_tvalid),
|
||||
.o_tready (m_axis_tready),
|
||||
.space (fifo_space),
|
||||
.occupied ()
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Assertions
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
if (FIFO_SIZE_LOG2 < 0) begin : gen_fifo_size_check
|
||||
$error("FIFO_SIZE_LOG2 must be >= 0");
|
||||
end
|
||||
|
||||
//synthesis translate_off
|
||||
always_ff @(posedge clk) begin
|
||||
if (ctrl_wr && !fifo_i_tready) begin
|
||||
$error("ctrlport_to_axis: Write strobe occurred when the FIFO was not ready!");
|
||||
end
|
||||
end
|
||||
//synthesis translate_on
|
||||
|
||||
|
||||
endmodule : ctrlport_to_axis
|
||||
|
||||
|
||||
`default_nettype wire
|
||||
371
fpga/usrp3/lib/control/ctrlport_to_simple_spi.sv
Normal file
371
fpga/usrp3/lib/control/ctrlport_to_simple_spi.sv
Normal file
@ -0,0 +1,371 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, A National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_simple_spi
|
||||
//
|
||||
// Description:
|
||||
// Implements a simple SPI core controlled via CtrlPort. This design
|
||||
// is an adaptation of the simple_spi_core module.
|
||||
//
|
||||
// Settings reg map:
|
||||
//
|
||||
// BASE_ADDRESS+0 divider setting
|
||||
// bits [15:0] spi clock divider
|
||||
//
|
||||
// BASE_ADDRESS+4 configuration input
|
||||
// bits [23:0] slave select, bit0 = slave0 enabled
|
||||
// bits [29:24] num bits (1 through 32)
|
||||
// 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_ADDRESS+8 output data
|
||||
// Writing this register begins a spi transaction.
|
||||
// Bits are latched out starting from bit 31.
|
||||
// Therefore, data has to be MSB aligned for transmission
|
||||
//
|
||||
// BASE_ADDRESS+12 Status
|
||||
// bit [0] ready signal, high when spi core can begin another transaction
|
||||
//
|
||||
// BASE_ADDRESS+16 Readback
|
||||
// Readback data from the last SPI transaction
|
||||
//
|
||||
|
||||
module ctrlport_to_simple_spi # (
|
||||
//settings register base address
|
||||
int BASE_ADDRESS = 0,
|
||||
|
||||
//width of serial enables (up to 24 is possible)
|
||||
int SEN_WIDTH = 8,
|
||||
|
||||
//idle state of the spi clock
|
||||
bit CLK_IDLE = 0,
|
||||
|
||||
//idle state of the serial enables
|
||||
logic [23:0] SEN_IDLE = '1
|
||||
)
|
||||
(
|
||||
input wire ctrlport_clk,
|
||||
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 logic s_ctrlport_resp_ack = 1'b0,
|
||||
output logic [ 1:0] s_ctrlport_resp_status = 2'b0,
|
||||
output logic [31:0] s_ctrlport_resp_data = 32'b0,
|
||||
|
||||
//spi interface, slave selects, clock, data in, data out
|
||||
output logic [SEN_WIDTH-1:0] sen,
|
||||
output logic sclk,
|
||||
output logic mosi,
|
||||
input wire miso,
|
||||
|
||||
//optional debug output
|
||||
output logic [31:0] debug
|
||||
);
|
||||
|
||||
`include "../rfnoc/core/ctrlport.vh"
|
||||
`include "ctrlport_to_simple_spi_regmap_utils.vh"
|
||||
|
||||
wire address_in_range =
|
||||
(s_ctrlport_req_addr >= BASE_ADDRESS) &&
|
||||
(s_ctrlport_req_addr < BASE_ADDRESS + 'h14); // 5 registers (20 bytes)
|
||||
|
||||
logic [15:0] sclk_divider;
|
||||
logic [23:0] slave_select;
|
||||
logic [5:0] num_bits;
|
||||
logic datain_edge, dataout_edge;
|
||||
logic [31:0] mosi_data;
|
||||
logic trigger_spi;
|
||||
logic [31:0] datain_reg;
|
||||
logic [23:0] readback_reg;
|
||||
logic readback_stb;
|
||||
logic ready;
|
||||
logic force_cs;
|
||||
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
if (ctrlport_rst) begin
|
||||
s_ctrlport_resp_ack <= '0;
|
||||
s_ctrlport_resp_data <= 'x;
|
||||
s_ctrlport_resp_status <= '0;
|
||||
trigger_spi <= 1'b0;
|
||||
end else begin
|
||||
// Reset response
|
||||
s_ctrlport_resp_ack <= '0;
|
||||
s_ctrlport_resp_data <= 'x;
|
||||
s_ctrlport_resp_status <= '0;
|
||||
trigger_spi <= 1'b0;
|
||||
|
||||
// Write request
|
||||
if (s_ctrlport_req_wr) begin
|
||||
s_ctrlport_resp_ack <= '1;
|
||||
|
||||
case (s_ctrlport_req_addr)
|
||||
|
||||
BASE_ADDRESS + SCLK_DIVIDER: begin
|
||||
sclk_divider <= s_ctrlport_req_data[SPI_CLK_DIV_MSB : SPI_CLK_DIV];
|
||||
force_cs <= s_ctrlport_req_data[FORCE_SS];
|
||||
end
|
||||
|
||||
BASE_ADDRESS + SPI_PERSONALITY: begin
|
||||
dataout_edge <= s_ctrlport_req_data[SPI_MOSI_EDGE];
|
||||
datain_edge <= s_ctrlport_req_data[SPI_MISO_EDGE];
|
||||
num_bits <= s_ctrlport_req_data[SPI_NUM_BITS_MSB : SPI_NUM_BITS];
|
||||
slave_select <= s_ctrlport_req_data[SPI_SLAVE_SELECT_MSB : SPI_SLAVE_SELECT];
|
||||
end
|
||||
|
||||
BASE_ADDRESS + SPI_MOSI_DATA: begin
|
||||
mosi_data <= s_ctrlport_req_data[SPI_DATA_OUT_MSB : SPI_DATA_OUT];
|
||||
trigger_spi <= 1'b1; // trigger the SPI transaction
|
||||
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 else if (s_ctrlport_req_rd) begin
|
||||
// read request handling
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + SCLK_DIVIDER: begin
|
||||
// Divider setting readback
|
||||
s_ctrlport_resp_data[SPI_CLK_DIV_MSB : SPI_CLK_DIV] <= sclk_divider[SPI_CLK_DIV_SIZE-1 : 0];
|
||||
s_ctrlport_resp_data[FORCE_SS] <= force_cs;
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
end
|
||||
|
||||
BASE_ADDRESS + SPI_PERSONALITY: begin
|
||||
// Control register readback
|
||||
s_ctrlport_resp_data[SPI_MOSI_EDGE] <= dataout_edge;
|
||||
s_ctrlport_resp_data[SPI_MISO_EDGE] <= datain_edge;
|
||||
s_ctrlport_resp_data[SPI_NUM_BITS_MSB : SPI_NUM_BITS] <= num_bits;
|
||||
s_ctrlport_resp_data[SPI_SLAVE_SELECT_MSB : SPI_SLAVE_SELECT] <= slave_select;
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
end
|
||||
|
||||
BASE_ADDRESS + SPI_STATUS: begin
|
||||
// Data register readback
|
||||
s_ctrlport_resp_data[SPI_READY] <= ready;
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
end
|
||||
|
||||
BASE_ADDRESS + SPI_READBACK: begin
|
||||
// Readback data from the last SPI transaction
|
||||
s_ctrlport_resp_data[SPI_READBACK_DATA_MSB : SPI_READBACK_DATA] <= readback_reg;
|
||||
s_ctrlport_resp_ack <= 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
|
||||
end
|
||||
|
||||
// Latch readback signal
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
if (ctrlport_rst) begin
|
||||
readback_reg <= '0;
|
||||
end else if(readback_stb) begin
|
||||
readback_reg <= datain_reg[23:0]; // readback data is latched from the last SPI transaction
|
||||
end
|
||||
end
|
||||
|
||||
// SPI Engine Implementation
|
||||
// The implementation below is leveraged from the simple_spi_core module.
|
||||
|
||||
typedef enum logic [2:0] {
|
||||
WAIT_TRIG,
|
||||
PRE_IDLE,
|
||||
CLK_REG,
|
||||
CLK_INV,
|
||||
POST_IDLE,
|
||||
IDLE_SEN
|
||||
} case_t;
|
||||
|
||||
case_t spi_state;
|
||||
|
||||
logic ready_reg;
|
||||
|
||||
// De-assert ready when a new transaction is triggered. ready_reg will be deasserted
|
||||
// on the clock cycle after the trigger, and keep the ready signal low until the
|
||||
// transaction is done.
|
||||
assign ready = ready_reg && ~trigger_spi;
|
||||
|
||||
// Serial clock either idles or is in one of two clock states
|
||||
logic sclk_reg;
|
||||
assign sclk = sclk_reg;
|
||||
|
||||
// Serial enables either idle or enabled (based on spi_state or forced
|
||||
// via FORCE_SS register field).
|
||||
// One pipeline stage to break critical path from register in I/O pads.
|
||||
wire sen_is_idle = ((spi_state == WAIT_TRIG) ||
|
||||
(spi_state == IDLE_SEN)) &&
|
||||
~force_cs;
|
||||
|
||||
wire [23:0] sen24 = (sen_is_idle)? SEN_IDLE : (SEN_IDLE ^ slave_select);
|
||||
logic [SEN_WIDTH-1:0] sen_reg = SEN_IDLE;
|
||||
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
if (ctrlport_rst) begin
|
||||
sen_reg <= SEN_IDLE;
|
||||
end else begin
|
||||
sen_reg <= sen24[SEN_WIDTH-1:0];
|
||||
end
|
||||
end
|
||||
|
||||
assign sen = sen_reg;
|
||||
|
||||
// Data output shift register
|
||||
// One pipeline stage to break critical path from register in I/O pads.
|
||||
logic [31:0] dataout_reg;
|
||||
wire [31:0] dataout_next = {dataout_reg[30:0], 1'b0};
|
||||
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
mosi <= dataout_reg[31];
|
||||
end
|
||||
|
||||
// Data input shift register
|
||||
// Two pipeline stages to break critical path from register in I/O pads.
|
||||
logic miso_pipe, miso_pipe2;
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
miso_pipe2 <= miso;
|
||||
miso_pipe <= miso_pipe2;
|
||||
end
|
||||
|
||||
wire [31:0] datain_next = {datain_reg[30:0], miso_pipe};
|
||||
|
||||
// Counter for spi clock
|
||||
logic [15:0] sclk_counter;
|
||||
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
|
||||
logic [6:0] bit_counter;
|
||||
wire [6:0] bit_counter_next = bit_counter + 1;
|
||||
wire bit_counter_done = (bit_counter_next == num_bits);
|
||||
|
||||
always_ff @(posedge ctrlport_clk) begin
|
||||
if (ctrlport_rst) begin
|
||||
spi_state <= WAIT_TRIG;
|
||||
sclk_reg <= CLK_IDLE;
|
||||
ready_reg <= 0;
|
||||
readback_stb <= 1'b0;
|
||||
end
|
||||
else begin
|
||||
case (spi_state)
|
||||
|
||||
WAIT_TRIG: begin
|
||||
if (trigger_spi) begin
|
||||
spi_state <= PRE_IDLE;
|
||||
end
|
||||
readback_stb <= 1'b0;
|
||||
ready_reg <= ~trigger_spi;
|
||||
dataout_reg <= mosi_data;
|
||||
sclk_counter <= 0;
|
||||
bit_counter <= 0;
|
||||
sclk_reg <= CLK_IDLE;
|
||||
end
|
||||
|
||||
// Drive clock to idle state. This also drives the serial enables
|
||||
// to their active state.
|
||||
PRE_IDLE: begin
|
||||
if (sclk_counter_done) begin
|
||||
spi_state <= CLK_REG;
|
||||
end
|
||||
sclk_counter <= sclk_counter_next;
|
||||
sclk_reg <= CLK_IDLE;
|
||||
end
|
||||
|
||||
// Clock generation states
|
||||
CLK_REG: begin
|
||||
if (sclk_counter_done) begin
|
||||
spi_state <= CLK_INV;
|
||||
if (datain_edge != CLK_IDLE) begin
|
||||
datain_reg <= datain_next;
|
||||
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
|
||||
// Have all bits been shifted out?
|
||||
spi_state <= (bit_counter_done)? POST_IDLE : CLK_REG;
|
||||
bit_counter <= bit_counter_next;
|
||||
if (datain_edge == CLK_IDLE) begin
|
||||
datain_reg <= datain_next;
|
||||
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
|
||||
|
||||
// Drive clock to idle state
|
||||
POST_IDLE: begin
|
||||
if (sclk_counter_done) begin
|
||||
spi_state <= IDLE_SEN;
|
||||
end
|
||||
sclk_counter <= sclk_counter_next;
|
||||
sclk_reg <= CLK_IDLE;
|
||||
end
|
||||
|
||||
// Drive serial enables to idle state
|
||||
IDLE_SEN: begin
|
||||
if (sclk_counter_done) begin
|
||||
ready_reg <= 1'b1;
|
||||
readback_stb <= 1'b1;
|
||||
spi_state <= WAIT_TRIG;
|
||||
end
|
||||
sclk_counter <= sclk_counter_next;
|
||||
sclk_reg <= CLK_IDLE;
|
||||
end
|
||||
|
||||
default: spi_state <= WAIT_TRIG;
|
||||
|
||||
endcase //spi_state
|
||||
end
|
||||
end
|
||||
|
||||
assign debug = {
|
||||
8'h00, //8
|
||||
trigger_spi, spi_state, //4
|
||||
sclk, mosi, miso, ready, //4
|
||||
1'b0, bit_counter[6:0], //8
|
||||
sclk_counter_done, bit_counter_done, //2
|
||||
sclk_counter[5:0] //6
|
||||
};
|
||||
|
||||
endmodule : ctrlport_to_simple_spi
|
||||
@ -0,0 +1,86 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_simple_spi_regmap_utils.vh
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// The constants in this file are autogenerated by XmlParse.
|
||||
//
|
||||
|
||||
//===============================================================================
|
||||
// A numerically ordered list of registers and their HDL source files
|
||||
//===============================================================================
|
||||
|
||||
// SCLK_DIVIDER : 0x0 (b310_core.sv)
|
||||
// SPI_PERSONALITY : 0x4 (b310_core.sv)
|
||||
// SPI_MOSI_DATA : 0x8 (b310_core.sv)
|
||||
// SPI_STATUS : 0xC (b310_core.sv)
|
||||
// SPI_READBACK : 0x10 (b310_core.sv)
|
||||
|
||||
//===============================================================================
|
||||
// RegTypes
|
||||
//===============================================================================
|
||||
|
||||
//===============================================================================
|
||||
// Register Group CTRLPORT_TO_SIMPLE_SPI_REGMAP
|
||||
//===============================================================================
|
||||
|
||||
// SCLK_DIVIDER Register (from b310_core.sv)
|
||||
localparam SCLK_DIVIDER = 'h0; // Register Offset
|
||||
localparam SCLK_DIVIDER_SIZE = 32; // register width in bits
|
||||
localparam SCLK_DIVIDER_MASK = 32'h1FFFF;
|
||||
localparam SPI_CLK_DIV_SIZE = 16; //SCLK_DIVIDER:SPI_CLK_DIV
|
||||
localparam SPI_CLK_DIV_MSB = 15; //SCLK_DIVIDER:SPI_CLK_DIV
|
||||
localparam SPI_CLK_DIV = 0; //SCLK_DIVIDER:SPI_CLK_DIV
|
||||
localparam FORCE_SS_SIZE = 1; //SCLK_DIVIDER:FORCE_SS
|
||||
localparam FORCE_SS_MSB = 16; //SCLK_DIVIDER:FORCE_SS
|
||||
localparam FORCE_SS = 16; //SCLK_DIVIDER:FORCE_SS
|
||||
|
||||
// SPI_PERSONALITY Register (from b310_core.sv)
|
||||
localparam SPI_PERSONALITY = 'h4; // Register Offset
|
||||
localparam SPI_PERSONALITY_SIZE = 32; // register width in bits
|
||||
localparam SPI_PERSONALITY_MASK = 32'hFFFFFFFF;
|
||||
localparam SPI_SLAVE_SELECT_SIZE = 24; //SPI_PERSONALITY:SPI_SLAVE_SELECT
|
||||
localparam SPI_SLAVE_SELECT_MSB = 23; //SPI_PERSONALITY:SPI_SLAVE_SELECT
|
||||
localparam SPI_SLAVE_SELECT = 0; //SPI_PERSONALITY:SPI_SLAVE_SELECT
|
||||
localparam SPI_NUM_BITS_SIZE = 6; //SPI_PERSONALITY:SPI_NUM_BITS
|
||||
localparam SPI_NUM_BITS_MSB = 29; //SPI_PERSONALITY:SPI_NUM_BITS
|
||||
localparam SPI_NUM_BITS = 24; //SPI_PERSONALITY:SPI_NUM_BITS
|
||||
localparam SPI_MISO_EDGE_SIZE = 1; //SPI_PERSONALITY:SPI_MISO_EDGE
|
||||
localparam SPI_MISO_EDGE_MSB = 30; //SPI_PERSONALITY:SPI_MISO_EDGE
|
||||
localparam SPI_MISO_EDGE = 30; //SPI_PERSONALITY:SPI_MISO_EDGE
|
||||
localparam SPI_MOSI_EDGE_SIZE = 1; //SPI_PERSONALITY:SPI_MOSI_EDGE
|
||||
localparam SPI_MOSI_EDGE_MSB = 31; //SPI_PERSONALITY:SPI_MOSI_EDGE
|
||||
localparam SPI_MOSI_EDGE = 31; //SPI_PERSONALITY:SPI_MOSI_EDGE
|
||||
|
||||
// SPI_MOSI_DATA Register (from b310_core.sv)
|
||||
localparam SPI_MOSI_DATA = 'h8; // Register Offset
|
||||
localparam SPI_MOSI_DATA_SIZE = 32; // register width in bits
|
||||
localparam SPI_MOSI_DATA_MASK = 32'hFFFFFFFF;
|
||||
localparam SPI_DATA_OUT_SIZE = 32; //SPI_MOSI_DATA:SPI_DATA_OUT
|
||||
localparam SPI_DATA_OUT_MSB = 31; //SPI_MOSI_DATA:SPI_DATA_OUT
|
||||
localparam SPI_DATA_OUT = 0; //SPI_MOSI_DATA:SPI_DATA_OUT
|
||||
|
||||
// SPI_STATUS Register (from b310_core.sv)
|
||||
localparam SPI_STATUS = 'hC; // Register Offset
|
||||
localparam SPI_STATUS_SIZE = 32; // register width in bits
|
||||
localparam SPI_STATUS_MASK = 32'h1;
|
||||
localparam SPI_READY_SIZE = 1; //SPI_STATUS:SPI_READY
|
||||
localparam SPI_READY_MSB = 0; //SPI_STATUS:SPI_READY
|
||||
localparam SPI_READY = 0; //SPI_STATUS:SPI_READY
|
||||
|
||||
// SPI_READBACK Register (from b310_core.sv)
|
||||
localparam SPI_READBACK = 'h10; // Register Offset
|
||||
localparam SPI_READBACK_SIZE = 32; // register width in bits
|
||||
localparam SPI_READBACK_MASK = 32'hFFFFFFFF;
|
||||
localparam SPI_READBACK_DATA_SIZE = 32; //SPI_READBACK:SPI_READBACK_DATA
|
||||
localparam SPI_READBACK_DATA_MSB = 31; //SPI_READBACK:SPI_READBACK_DATA
|
||||
localparam SPI_READBACK_DATA = 0; //SPI_READBACK:SPI_READBACK_DATA
|
||||
// function SCLK_DIVIDERRec not implemented for Verilog
|
||||
// function SPI_PERSONALITYRec not implemented for Verilog
|
||||
// function SPI_MOSI_DATARec not implemented for Verilog
|
||||
// function SPI_STATUSRec not implemented for Verilog
|
||||
// function SPI_READBACKRec not implemented for Verilog
|
||||
@ -11,14 +11,16 @@
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// BASE_ADDRESS : Base address for CtrlPort registers.
|
||||
// BASE_ADDRESS : Base address for CtrlPort registers.
|
||||
// REG_STRIDE_SIZE : Address separation between registers.
|
||||
//
|
||||
|
||||
`default_nettype wire
|
||||
|
||||
|
||||
module ctrlport_to_wb_i2c #(
|
||||
parameter BASE_ADDRESS = 0
|
||||
parameter BASE_ADDRESS = 0,
|
||||
parameter REG_STRIDE_SIZE = 1
|
||||
) (
|
||||
//---------------------------------------------------------------
|
||||
// ControlPort Slave
|
||||
@ -64,10 +66,11 @@ module ctrlport_to_wb_i2c #(
|
||||
wire wb_ack_o;
|
||||
wire [31:0] wb_dat_o;
|
||||
|
||||
// Check for address to be in range [base_addr..base_addr+8)
|
||||
localparam NUM_ADDRESSES = 8;
|
||||
// Check for address to be in range [base_addr..base_addr+8*stride)
|
||||
localparam REG_WINDOW_SIZE = 8*REG_STRIDE_SIZE;
|
||||
|
||||
wire address_in_range = (s_ctrlport_req_addr >= BASE_ADDRESS) &&
|
||||
(s_ctrlport_req_addr < BASE_ADDRESS + NUM_ADDRESSES);
|
||||
(s_ctrlport_req_addr < BASE_ADDRESS + REG_WINDOW_SIZE);
|
||||
|
||||
// Following chapter 3.2.3 (classic standard SINGLE WRITE cycle) of
|
||||
// https://cdn.opencores.org/downloads/wbspec_b4.pdf
|
||||
@ -100,25 +103,25 @@ module ctrlport_to_wb_i2c #(
|
||||
end else if (s_ctrlport_req_wr) begin
|
||||
// Assume there is a valid address
|
||||
//1-1 translation of wb to ctrl port interface
|
||||
if (s_ctrlport_req_addr < 8) begin
|
||||
if (address_in_range) begin
|
||||
wb_cyc_i <= 1'b1;
|
||||
wb_we_i <= 1'b1;
|
||||
wb_dat_i <= s_ctrlport_req_data;
|
||||
end
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + WB_PRER_LO: begin
|
||||
BASE_ADDRESS + WB_PRER_LO * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_PRER_LO;
|
||||
end
|
||||
BASE_ADDRESS + WB_PRER_HI: begin
|
||||
BASE_ADDRESS + WB_PRER_HI * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_PRER_HI;
|
||||
end
|
||||
BASE_ADDRESS + WB_CTR: begin
|
||||
BASE_ADDRESS + WB_CTR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_CTR;
|
||||
end
|
||||
BASE_ADDRESS + WB_TXR: begin
|
||||
BASE_ADDRESS + WB_TXR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_TXR;
|
||||
end
|
||||
BASE_ADDRESS + WB_CR: begin
|
||||
BASE_ADDRESS + WB_CR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_CR;
|
||||
end
|
||||
|
||||
@ -126,33 +129,36 @@ module ctrlport_to_wb_i2c #(
|
||||
|
||||
// Read requests
|
||||
end else if (s_ctrlport_req_rd) begin
|
||||
// Assume there is a valid address
|
||||
wb_cyc_i <= 1'b1;
|
||||
wb_we_i <= 1'b0;
|
||||
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + WB_PRER_LO: begin
|
||||
wb_adr_i <= WB_PRER_LO;
|
||||
end
|
||||
BASE_ADDRESS + WB_PRER_HI: begin
|
||||
wb_adr_i <= WB_PRER_HI;
|
||||
end
|
||||
BASE_ADDRESS + WB_CTR: begin
|
||||
wb_adr_i <= WB_CTR;
|
||||
end
|
||||
BASE_ADDRESS + WB_RXR: begin
|
||||
wb_adr_i = WB_RXR;
|
||||
end
|
||||
BASE_ADDRESS + WB_SR: begin
|
||||
wb_adr_i = WB_SR;
|
||||
end
|
||||
// Respond with 0
|
||||
default: begin
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
s_ctrlport_resp_data <= 'b0;
|
||||
end
|
||||
if(address_in_range) begin
|
||||
// Assume there is a valid address
|
||||
wb_cyc_i <= 1'b1;
|
||||
wb_we_i <= 1'b0;
|
||||
|
||||
endcase
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + WB_PRER_LO * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= WB_PRER_LO;
|
||||
end
|
||||
BASE_ADDRESS + WB_PRER_HI * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= WB_PRER_HI;
|
||||
end
|
||||
BASE_ADDRESS + WB_CTR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= WB_CTR;
|
||||
end
|
||||
BASE_ADDRESS + WB_RXR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_RXR;
|
||||
end
|
||||
BASE_ADDRESS + WB_SR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = WB_SR;
|
||||
end
|
||||
// Respond with 0
|
||||
default: begin
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
s_ctrlport_resp_data <= 'b0;
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
190
fpga/usrp3/lib/control/ctrlport_to_wb_uart.v
Normal file
190
fpga/usrp3/lib/control/ctrlport_to_wb_uart.v
Normal file
@ -0,0 +1,190 @@
|
||||
//
|
||||
// Copyright 2025 Ettus Research, A National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_wb_uart
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// This module wraps a simple UART and provides a ControlPort interface.
|
||||
//
|
||||
// Parameters:
|
||||
//
|
||||
// BASE_ADDRESS : Base address for CtrlPort registers.
|
||||
// REG_STRIDE_SIZE : Address separation between registers.
|
||||
//
|
||||
|
||||
`default_nettype wire
|
||||
|
||||
|
||||
module ctrlport_to_wb_uart #(
|
||||
parameter BASE_ADDRESS = 0,
|
||||
parameter REG_STRIDE_SIZE = 1,
|
||||
parameter CLKDIV_DEFAULT = 0,
|
||||
parameter RX_SIZE = 8
|
||||
) (
|
||||
//---------------------------------------------------------------
|
||||
// ControlPort Slave
|
||||
//---------------------------------------------------------------
|
||||
|
||||
input wire ctrlport_clk,
|
||||
input wire ctrlport_rst,
|
||||
|
||||
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,
|
||||
|
||||
output reg s_ctrlport_resp_ack,
|
||||
output reg [ 1:0] s_ctrlport_resp_status = 0,
|
||||
output reg [31:0] s_ctrlport_resp_data = 0,
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// UART signals
|
||||
//---------------------------------------------------------------
|
||||
|
||||
input wire uart_rx,
|
||||
output wire uart_tx
|
||||
);
|
||||
|
||||
`include "../rfnoc/core/ctrlport.vh"
|
||||
|
||||
//---------------------------------------------------------------
|
||||
// Translating CtrlPort <-> Wishbone UART core
|
||||
//---------------------------------------------------------------
|
||||
|
||||
reg wb_cyc_i; // Active bus cycle
|
||||
reg wb_we_i = 1'b0; // Write access
|
||||
reg [ 4:0] wb_adr_i = 3'b0;
|
||||
reg [31:0] wb_dat_i = 32'b0;
|
||||
wire wb_ack_o;
|
||||
wire [31:0] wb_dat_o;
|
||||
|
||||
localparam SUART_CLKDIV = 0;
|
||||
localparam SUART_TXLEVEL = 1;
|
||||
localparam SUART_RXLEVEL = 2;
|
||||
localparam SUART_TXCHAR = 3;
|
||||
localparam SUART_RXCHAR = 4;
|
||||
|
||||
// Check for address to be in range [base_addr..base_addr+5*stride)
|
||||
localparam REG_WINDOW_SIZE = 5*REG_STRIDE_SIZE;
|
||||
|
||||
wire address_in_range = (s_ctrlport_req_addr >= BASE_ADDRESS) &&
|
||||
(s_ctrlport_req_addr < BASE_ADDRESS + REG_WINDOW_SIZE);
|
||||
|
||||
// Following chapter 3.2.3 (classic standard SINGLE WRITE cycle) of
|
||||
// https://cdn.opencores.org/downloads/wbspec_b4.pdf
|
||||
always @(posedge ctrlport_clk) begin
|
||||
// Reset internal registers and responses
|
||||
if (ctrlport_rst) begin
|
||||
s_ctrlport_resp_ack <= 0;
|
||||
s_ctrlport_resp_data <= 0;
|
||||
s_ctrlport_resp_status <= CTRL_STS_OKAY;
|
||||
wb_cyc_i <= 0;
|
||||
wb_we_i <= 0;
|
||||
wb_dat_i <= 0;
|
||||
wb_adr_i <= 0;
|
||||
end else begin
|
||||
// Request independent default assignments
|
||||
s_ctrlport_resp_ack <= 1'b0;
|
||||
|
||||
// Wait for ack on active bus transactions
|
||||
if (wb_cyc_i) begin
|
||||
if (wb_ack_o) begin
|
||||
// End bus cycle and generate response
|
||||
wb_cyc_i <= 1'b0;
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
s_ctrlport_resp_data <= wb_dat_o;
|
||||
//removed error condition since there is no error signal in UART
|
||||
//core
|
||||
s_ctrlport_resp_status <= CTRL_STS_OKAY;
|
||||
end
|
||||
|
||||
// Write requests
|
||||
end else if (s_ctrlport_req_wr) begin
|
||||
// Assume there is a valid address
|
||||
//1-1 translation of wb to ctrl port interface
|
||||
if (address_in_range) begin
|
||||
wb_cyc_i <= 1'b1;
|
||||
wb_we_i <= 1'b1;
|
||||
wb_dat_i <= s_ctrlport_req_data;
|
||||
end
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + SUART_CLKDIV * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_CLKDIV;
|
||||
end
|
||||
BASE_ADDRESS + SUART_TXLEVEL * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_TXLEVEL;
|
||||
end
|
||||
BASE_ADDRESS + SUART_RXLEVEL * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_RXLEVEL;
|
||||
end
|
||||
BASE_ADDRESS + SUART_TXCHAR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_TXCHAR;
|
||||
end
|
||||
BASE_ADDRESS + SUART_RXCHAR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_RXCHAR;
|
||||
end
|
||||
|
||||
endcase
|
||||
|
||||
// Read requests
|
||||
end else if (s_ctrlport_req_rd) begin
|
||||
|
||||
if(address_in_range) begin
|
||||
// Assume there is a valid address
|
||||
wb_cyc_i <= 1'b1;
|
||||
wb_we_i <= 1'b0;
|
||||
|
||||
case (s_ctrlport_req_addr)
|
||||
BASE_ADDRESS + SUART_CLKDIV * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= SUART_CLKDIV;
|
||||
end
|
||||
BASE_ADDRESS + SUART_TXLEVEL * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= SUART_TXLEVEL;
|
||||
end
|
||||
BASE_ADDRESS + SUART_RXLEVEL * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i <= SUART_RXLEVEL;
|
||||
end
|
||||
BASE_ADDRESS + SUART_TXCHAR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_TXCHAR;
|
||||
end
|
||||
BASE_ADDRESS + SUART_RXCHAR * REG_STRIDE_SIZE: begin
|
||||
wb_adr_i = SUART_RXCHAR;
|
||||
end
|
||||
// Respond with 0
|
||||
default: begin
|
||||
s_ctrlport_resp_ack <= 1'b1;
|
||||
s_ctrlport_resp_data <= 'b0;
|
||||
end
|
||||
|
||||
endcase
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
//wishbone-based UART core
|
||||
// Makin default baud
|
||||
simple_uart #(
|
||||
.CLKDIV_DEFAULT(CLKDIV_DEFAULT),
|
||||
.RX_SIZE(RX_SIZE)
|
||||
) simple_uart_i (
|
||||
.clk_i(ctrlport_clk),
|
||||
.rst_i(ctrlport_rst),
|
||||
.adr_i(wb_adr_i),
|
||||
.dat_i(wb_dat_i),
|
||||
.dat_o(wb_dat_o),
|
||||
.we_i(wb_we_i),
|
||||
.stb_i(wb_cyc_i),
|
||||
.cyc_i(wb_cyc_i),
|
||||
.ack_o(wb_ack_o),
|
||||
.rx_int_o(),
|
||||
.tx_int_o(),
|
||||
.tx_o(uart_tx),
|
||||
.rx_i(uart_rx)
|
||||
);
|
||||
|
||||
endmodule
|
||||
|
||||
49
fpga/usrp3/lib/sim/control/ctrlport_spi_adrv_tb/Makefile
Normal file
49
fpga/usrp3/lib/sim/control/ctrlport_spi_adrv_tb/Makefile
Normal file
@ -0,0 +1,49 @@
|
||||
#
|
||||
# Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
#-------------------------------------------------
|
||||
# Top-of-Makefile
|
||||
#-------------------------------------------------
|
||||
# Define BASE_DIR to point to the "top" dir.
|
||||
BASE_DIR = $(abspath ../../../../top)
|
||||
# Include viv_sim_preamble after defining BASE_DIR
|
||||
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||
|
||||
#-------------------------------------------------
|
||||
# Design Specific
|
||||
#-------------------------------------------------
|
||||
# Pull in FIFO sources (axi_fifo and its dependencies)
|
||||
include $(BASE_DIR)/../lib/fifo/Makefile.srcs
|
||||
include $(BASE_DIR)/../lib/axi/Makefile.srcs
|
||||
include $(BASE_DIR)/../lib/control/Makefile.srcs
|
||||
include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs
|
||||
include Makefile.srcs
|
||||
|
||||
DESIGN_SRCS += $(abspath \
|
||||
$(FIFO_SRCS) \
|
||||
$(AXI_SRCS) \
|
||||
$(CONTROL_LIB_SRCS) \
|
||||
$(RFNOC_CORE_SRCS) \
|
||||
$(CTRLPORT_SPI_ADRV_SRCS) \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Testbench Specific
|
||||
#-------------------------------------------------
|
||||
SIM_TOP = ctrlport_spi_adrv_all_tb
|
||||
|
||||
SIM_SRCS = $(abspath \
|
||||
ctrlport_spi_adrv_tb.sv \
|
||||
ctrlport_spi_adrv_all_tb.sv \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Bottom-of-Makefile
|
||||
#-------------------------------------------------
|
||||
# Include all simulator specific makefiles here
|
||||
# Each should define a unique target to simulate
|
||||
# e.g. xsim, vsim, etc and a common "clean" target
|
||||
include $(BASE_DIR)/../tools/make/viv_simulator.mak
|
||||
@ -0,0 +1,11 @@
|
||||
#
|
||||
# Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
CTRLPORT_SPI_ADRV_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/control/, \
|
||||
axis_spi.sv \
|
||||
ctrlport_spi_adrv_pkg.sv \
|
||||
ctrlport_spi_adrv.sv \
|
||||
))
|
||||
@ -0,0 +1,29 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_spi_adrv_all_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Top-level testbench for ctrlport_spi_adrv, exercising multiple DUT
|
||||
// configurations. Each instance runs sequentially via the semaphore in
|
||||
// PkgTestExec::test.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module ctrlport_spi_adrv_all_tb;
|
||||
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W( 4), .HALF_PER( 0), .HALF_PER_EN(0), .CS_HOLD(0), .CS_GUARD(1)) tb_hold_guard_01 ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W( 4), .HALF_PER( 0), .HALF_PER_EN(0), .CS_HOLD(1), .CS_GUARD(1)) tb_hold_guard_11 ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W( 4), .HALF_PER( 0), .HALF_PER_EN(0), .CS_HOLD(0), .CS_GUARD(2)) tb_hold_guard_02 ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W( 4), .HALF_PER( 0), .HALF_PER_EN(0), .CS_HOLD(1), .CS_GUARD(2)) tb_hold_guard_12 ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W( 4), .HALF_PER(255), .HALF_PER_EN(0), .CS_HOLD(1), .CS_GUARD(2)) tb_slow_clk ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(1), .NUM_BYTES_W(12), .HALF_PER( 0), .HALF_PER_EN(1), .CS_HOLD(1), .CS_GUARD(2)) tb_max_len ();
|
||||
ctrlport_spi_adrv_tb #(.QUICK_TEST(0), .NUM_BYTES_W( 8), .HALF_PER( 4), .HALF_PER_EN(1), .CS_HOLD(1), .CS_GUARD(2)) tb_full_test ();
|
||||
|
||||
endmodule : ctrlport_spi_adrv_all_tb
|
||||
|
||||
`default_nettype wire
|
||||
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,37 @@
|
||||
#
|
||||
# Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
#-------------------------------------------------
|
||||
# Top-of-Makefile
|
||||
#-------------------------------------------------
|
||||
BASE_DIR = $(abspath ../../../../top)
|
||||
include $(BASE_DIR)/../tools/make/viv_sim_preamble.mak
|
||||
|
||||
#-------------------------------------------------
|
||||
# Design Specific
|
||||
#-------------------------------------------------
|
||||
include $(BASE_DIR)/../lib/rfnoc/core/Makefile.srcs
|
||||
include Makefile.srcs
|
||||
|
||||
DESIGN_SRCS += $(abspath \
|
||||
$(RFNOC_CORE_SRCS) \
|
||||
$(CTRLPORT_TO_IOPORT2_STREAM_SRCS) \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Testbench Specific
|
||||
#-------------------------------------------------
|
||||
SIM_TOP = ctrlport_to_ioport2_stream_all_tb
|
||||
|
||||
SIM_SRCS = $(abspath \
|
||||
ctrlport_to_ioport2_stream_tb.sv \
|
||||
ctrlport_to_ioport2_stream_all_tb.sv \
|
||||
)
|
||||
|
||||
#-------------------------------------------------
|
||||
# Bottom-of-Makefile
|
||||
#-------------------------------------------------
|
||||
include $(BASE_DIR)/../tools/make/viv_simulator.mak
|
||||
@ -0,0 +1,12 @@
|
||||
#
|
||||
# Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
#
|
||||
# SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
#
|
||||
|
||||
CTRLPORT_TO_IOPORT2_STREAM_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/io_port2/, \
|
||||
ioport2_msg_pkg.sv \
|
||||
ioport2_msg_codec.v \
|
||||
pcie_basic_regs.v \
|
||||
ctrlport_to_ioport2_stream.sv \
|
||||
))
|
||||
@ -0,0 +1,30 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_ioport2_stream_all_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Top-level testbench for ctrlport_to_ioport2_stream. Runs two parameter
|
||||
// variants.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
module ctrlport_to_ioport2_stream_all_tb;
|
||||
|
||||
ctrlport_to_ioport2_stream_tb #(
|
||||
.SIGNATURE(32'h1357_2468),
|
||||
.CLK_FREQ (32'd125_000_000)
|
||||
) tb_0 ();
|
||||
|
||||
ctrlport_to_ioport2_stream_tb #(
|
||||
.SIGNATURE(32'h89AB_CDEF),
|
||||
.CLK_FREQ (32'd200_000_000)
|
||||
) tb_1 ();
|
||||
|
||||
endmodule : ctrlport_to_ioport2_stream_all_tb
|
||||
|
||||
`default_nettype wire
|
||||
@ -0,0 +1,320 @@
|
||||
//
|
||||
// Copyright 2026 Ettus Research, a National Instruments Brand
|
||||
//
|
||||
// SPDX-License-Identifier: LGPL-3.0-or-later
|
||||
//
|
||||
// Module: ctrlport_to_ioport2_stream_tb
|
||||
//
|
||||
// Description:
|
||||
//
|
||||
// Testbench for ctrlport_to_ioport2_stream.
|
||||
//
|
||||
// The test drives CtrlPort requests through ctrlport_bfm and checks:
|
||||
// 1) End-to-end register behavior against pcie_basic_regs.
|
||||
// 2) ioport2 stream-request encoding produced by the bridge.
|
||||
//
|
||||
|
||||
`default_nettype none
|
||||
|
||||
|
||||
module ctrlport_to_ioport2_stream_tb #(
|
||||
parameter logic [31:0] SIGNATURE = 32'h1234_ABCD,
|
||||
parameter logic [31:0] CLK_FREQ = 32'd200_000_000
|
||||
);
|
||||
|
||||
`include "test_exec.svh"
|
||||
|
||||
import PkgTestExec::*;
|
||||
import ctrlport_pkg::*;
|
||||
import ctrlport_bfm_pkg::*;
|
||||
import ioport2_msg_pkg::*;
|
||||
|
||||
localparam real CLK_PERIOD_NS = 10.0;
|
||||
|
||||
localparam int REG_ADDR_WIDTH = 20;
|
||||
localparam int REG_DATA_WIDTH = 32;
|
||||
|
||||
|
||||
// Register offsets extracted from pcie_basic_regs.
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_SIG_REG_ADDR = 20'h00000;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_COUNTER_LO_REG_ADDR = 20'h00004;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_COUNTER_HI_REG_ADDR = 20'h00008;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_COUNTER_FREQ_ADDR = 20'h0000C;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_SCRATCH_0_ADDR = 20'h00010;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_SCRATCH_1_ADDR = 20'h00014;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_MISC_STATUS_ADDR = 20'h00020;
|
||||
localparam logic [REG_ADDR_WIDTH-1:0] PCIE_FPGA_USR_SIG_REG_ADDR = 20'h00030;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Clocks and reset
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
bit clk;
|
||||
bit rst;
|
||||
|
||||
sim_clock_gen #(.PERIOD(CLK_PERIOD_NS), .AUTOSTART(0))
|
||||
clk_gen (.clk(clk), .rst(rst));
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// CtrlPort interface + BFM
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
ctrlport_if cp_if (.clk(clk), .rst(rst));
|
||||
|
||||
ctrlport_bfm cp_bfm = new(cp_if);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Bridge <-> endpoint stream wiring
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
logic [63:0] regi_tdata;
|
||||
logic regi_tvalid;
|
||||
logic regi_tready;
|
||||
|
||||
logic [63:0] rego_tdata;
|
||||
logic rego_tvalid;
|
||||
logic rego_tready;
|
||||
|
||||
logic [REG_DATA_WIDTH-1:0] misc_status = 32'hD00D_CAFE;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// DUT + endpoint
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
ctrlport_to_ioport2_stream dut (
|
||||
.s_ctrlport (cp_if),
|
||||
.regi_tdata (regi_tdata),
|
||||
.regi_tvalid (regi_tvalid),
|
||||
.regi_tready (regi_tready),
|
||||
.rego_tdata (rego_tdata),
|
||||
.rego_tvalid (rego_tvalid),
|
||||
.rego_tready (rego_tready)
|
||||
);
|
||||
|
||||
|
||||
// Arbitrarily use pcie_basic_regs as a simple endpoint to check the
|
||||
// correctness of the stream requests generated by the bridge.
|
||||
pcie_basic_regs #(
|
||||
.SIGNATURE(SIGNATURE),
|
||||
.CLK_FREQ (CLK_FREQ)
|
||||
) pcie_basic_regs_i (
|
||||
.clk (clk),
|
||||
.reset (rst),
|
||||
.regi_tdata (regi_tdata),
|
||||
.regi_tvalid (regi_tvalid),
|
||||
.regi_tready (regi_tready),
|
||||
.rego_tdata (rego_tdata),
|
||||
.rego_tvalid (rego_tvalid),
|
||||
.rego_tready (rego_tready),
|
||||
.misc_status (misc_status)
|
||||
);
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Stream monitor for request-encoding checks
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef ioport2_msg_t req_msg_t;
|
||||
|
||||
mailbox #(req_msg_t) req_msg_mb = new();
|
||||
|
||||
// Populate req_msg_mb with decoded stream requests for checking
|
||||
// in test tasks.
|
||||
always @(posedge clk) begin
|
||||
req_msg_t msg;
|
||||
if (!rst && regi_tvalid && regi_tready) begin
|
||||
msg = req_msg_t'(regi_tdata);
|
||||
req_msg_mb.put(msg);
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Helper tasks
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
// Checks that a stream request message matches the expected encoding for a
|
||||
// given CtrlPort request.
|
||||
task automatic check_req_msg(
|
||||
input bit is_write,
|
||||
input logic [REG_ADDR_WIDTH-1:0] reg_addr,
|
||||
input logic [REG_DATA_WIDTH-1:0] req_data
|
||||
);
|
||||
req_msg_t msg;
|
||||
req_msg_mb.get(msg);
|
||||
|
||||
`ASSERT_ERROR(msg.rd_response == 1'b0,
|
||||
$sformatf("Unexpected rd_response bit in request message: %0b", msg.rd_response));
|
||||
`ASSERT_ERROR(msg.wr_request == is_write,
|
||||
$sformatf("wr_request mismatch. got=%0b exp=%0b", msg.wr_request, is_write));
|
||||
`ASSERT_ERROR(msg.rd_request == !is_write,
|
||||
$sformatf("rd_request mismatch. got=%0b exp=%0b", msg.rd_request, !is_write));
|
||||
`ASSERT_ERROR(msg.half_word == 1'b0,
|
||||
$sformatf("half_word should be 0 for 32-bit ctrlport ops, got=%0b", msg.half_word));
|
||||
`ASSERT_ERROR(msg.address == reg_addr,
|
||||
$sformatf("address mismatch. got=0x%05h exp=0x%05h", msg.address, reg_addr));
|
||||
`ASSERT_ERROR(msg.data == req_data,
|
||||
$sformatf("data mismatch. got=0x%08h exp=0x%08h", msg.data, req_data));
|
||||
endtask
|
||||
|
||||
task automatic cp_write_and_check(
|
||||
input logic [REG_ADDR_WIDTH-1:0] reg_addr,
|
||||
input logic [REG_DATA_WIDTH-1:0] wr_data
|
||||
);
|
||||
cp_bfm.write(reg_addr, wr_data, STS_OKAY);
|
||||
check_req_msg(1'b1, reg_addr, wr_data);
|
||||
endtask
|
||||
|
||||
task automatic cp_read_and_check(
|
||||
input logic [REG_ADDR_WIDTH-1:0] reg_addr,
|
||||
output logic [REG_DATA_WIDTH-1:0] rd_data
|
||||
);
|
||||
cp_bfm.read(reg_addr, rd_data, STS_OKAY);
|
||||
// ctrlport_bfm drives request.data as zero for reads.
|
||||
check_req_msg(1'b0, reg_addr, {REG_DATA_WIDTH{1'b0}});
|
||||
endtask
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Tests - Validate register endpoint read/write capabilities
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
task automatic test_id_and_status_regs();
|
||||
logic [REG_DATA_WIDTH-1:0] rd_data;
|
||||
|
||||
test.start_test("ID and status registers");
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_SIG_REG_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == SIGNATURE,
|
||||
$sformatf("SIGNATURE mismatch. got=0x%08h exp=0x%08h", rd_data, SIGNATURE));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_COUNTER_FREQ_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == CLK_FREQ,
|
||||
$sformatf("CLK_FREQ mismatch. got=0x%08h exp=0x%08h", rd_data, CLK_FREQ));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_MISC_STATUS_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == misc_status,
|
||||
$sformatf("misc_status mismatch. got=0x%08h exp=0x%08h", rd_data, misc_status));
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
task automatic test_scratch_regs();
|
||||
logic [REG_DATA_WIDTH-1:0] rd_data;
|
||||
|
||||
test.start_test("Scratch register read/write");
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_SCRATCH_0_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == {REG_DATA_WIDTH{1'b0}},
|
||||
$sformatf("scratch0 reset value mismatch. got=0x%08h", rd_data));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_SCRATCH_1_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == {REG_DATA_WIDTH{1'b0}},
|
||||
$sformatf("scratch1 reset value mismatch. got=0x%08h", rd_data));
|
||||
|
||||
cp_write_and_check(PCIE_FPGA_SCRATCH_0_ADDR, 32'hDEAD_BEEF);
|
||||
cp_write_and_check(PCIE_FPGA_SCRATCH_1_ADDR, 32'hFACE_CAFE);
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_SCRATCH_0_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'hDEAD_BEEF,
|
||||
$sformatf("scratch0 value mismatch. got=0x%08h exp=0xDEADBEEF", rd_data));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_SCRATCH_1_ADDR, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'hFACE_CAFE,
|
||||
$sformatf("scratch1 value mismatch. got=0x%08h exp=0xFACECAFE", rd_data));
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
task automatic test_usr_signature_regs();
|
||||
logic [REG_DATA_WIDTH-1:0] rd_data;
|
||||
|
||||
test.start_test("User signature register read/write");
|
||||
|
||||
cp_write_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00000, 32'h0123_4567);
|
||||
cp_write_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00004, 32'h89AB_CDEF);
|
||||
cp_write_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00008, 32'h0BAD_F00D);
|
||||
cp_write_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h0000C, 32'h55AA_33CC);
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00000, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'h0123_4567,
|
||||
$sformatf("usr_signature[31:0] mismatch. got=0x%08h", rd_data));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00004, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'h89AB_CDEF,
|
||||
$sformatf("usr_signature[63:32] mismatch. got=0x%08h", rd_data));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h00008, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'h0BAD_F00D,
|
||||
$sformatf("usr_signature[95:64] mismatch. got=0x%08h", rd_data));
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_USR_SIG_REG_ADDR + 20'h0000C, rd_data);
|
||||
`ASSERT_ERROR(rd_data == 32'h55AA_33CC,
|
||||
$sformatf("usr_signature[127:96] mismatch. got=0x%08h", rd_data));
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
task automatic test_counter_and_default();
|
||||
logic [REG_DATA_WIDTH-1:0] ctr_lo_0;
|
||||
logic [REG_DATA_WIDTH-1:0] ctr_lo_1;
|
||||
logic [REG_DATA_WIDTH-1:0] rd_data;
|
||||
|
||||
test.start_test("Counter activity and default readback");
|
||||
|
||||
cp_read_and_check(PCIE_FPGA_COUNTER_LO_REG_ADDR, ctr_lo_0);
|
||||
cp_read_and_check(PCIE_FPGA_COUNTER_HI_REG_ADDR, rd_data);
|
||||
cp_read_and_check(PCIE_FPGA_COUNTER_LO_REG_ADDR, ctr_lo_1);
|
||||
|
||||
`ASSERT_ERROR(ctr_lo_1 != ctr_lo_0,
|
||||
$sformatf("counter low did not change. first=0x%08h second=0x%08h", ctr_lo_0, ctr_lo_1));
|
||||
|
||||
cp_read_and_check(20'h001FC, rd_data);
|
||||
`ASSERT_ERROR(rd_data == {REG_DATA_WIDTH{1'b1}},
|
||||
$sformatf("default readback mismatch. got=0x%08h exp=0x%08h", rd_data,
|
||||
{REG_DATA_WIDTH{1'b1}}));
|
||||
|
||||
test.end_test();
|
||||
endtask
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Main test
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
initial begin : main
|
||||
string tb_name;
|
||||
|
||||
tb_name = $sformatf({"ctrlport_to_ioport2_stream_tb\n",
|
||||
"SIGNATURE=0x%08h\n",
|
||||
"CLK_FREQ=%0d"},
|
||||
SIGNATURE, CLK_FREQ);
|
||||
|
||||
test.start_tb(tb_name, 20ms);
|
||||
|
||||
// Avoid extra simulation activity before this testbench acquires test_exec.
|
||||
clk_gen.start();
|
||||
|
||||
cp_bfm.run();
|
||||
|
||||
// Reset for 8 cycles, then wait for release.
|
||||
clk_gen.reset();
|
||||
@(negedge rst);
|
||||
|
||||
test_id_and_status_regs();
|
||||
test_scratch_regs();
|
||||
test_usr_signature_regs();
|
||||
test_counter_and_default();
|
||||
|
||||
test.end_tb(0);
|
||||
|
||||
clk_gen.kill();
|
||||
end
|
||||
|
||||
endmodule : ctrlport_to_ioport2_stream_tb
|
||||
|
||||
`default_nettype wire
|
||||
Loading…
Reference in New Issue
Block a user