diff options
author | Martin Braun <martin.braun@ettus.com> | 2020-01-23 16:10:22 -0800 |
---|---|---|
committer | Martin Braun <martin.braun@ettus.com> | 2020-01-28 09:35:36 -0800 |
commit | bafa9d95453387814ef25e6b6256ba8db2df612f (patch) | |
tree | 39ba24b5b67072d354775272e687796bb511848d /fpga/usrp3/lib/control | |
parent | 3075b981503002df3115d5f1d0b97d2619ba30f2 (diff) | |
download | uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.gz uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.tar.bz2 uhd-bafa9d95453387814ef25e6b6256ba8db2df612f.zip |
Merge FPGA repository back into UHD repository
The FPGA codebase was removed from the UHD repository in 2014 to reduce
the size of the repository. However, over the last half-decade, the
split between the repositories has proven more burdensome than it has
been helpful. By merging the FPGA code back, it will be possible to
create atomic commits that touch both FPGA and UHD codebases. Continuous
integration testing is also simplified by merging the repositories,
because it was previously difficult to automatically derive the correct
UHD branch when testing a feature branch on the FPGA repository.
This commit also updates the license files and paths therein.
We are therefore merging the repositories again. Future development for
FPGA code will happen in the same repository as the UHD host code and
MPM code.
== Original Codebase and Rebasing ==
The original FPGA repository will be hosted for the foreseeable future
at its original local location: https://github.com/EttusResearch/fpga/
It can be used for bisecting, reference, and a more detailed history.
The final commit from said repository to be merged here is
05003794e2da61cabf64dd278c45685a7abad7ec. This commit is tagged as
v4.0.0.0-pre-uhd-merge.
If you have changes in the FPGA repository that you want to rebase onto
the UHD repository, simply run the following commands:
- Create a directory to store patches (this should be an empty
directory):
mkdir ~/patches
- Now make sure that your FPGA codebase is based on the same state as
the code that was merged:
cd src/fpga # Or wherever your FPGA code is stored
git rebase v4.0.0.0-pre-uhd-merge
Note: The rebase command may look slightly different depending on what
exactly you're trying to rebase.
- Create a patch set for your changes versus v4.0.0.0-pre-uhd-merge:
git format-patch v4.0.0.0-pre-uhd-merge -o ~/patches
Note: Make sure that only patches are stored in your output directory.
It should otherwise be empty. Make sure that you picked the correct
range of commits, and only commits you wanted to rebase were exported
as patch files.
- Go to the UHD repository and apply the patches:
cd src/uhd # Or wherever your UHD repository is stored
git am --directory fpga ~/patches/*
rm -rf ~/patches # This is for cleanup
== Contributors ==
The following people have contributed mainly to these files (this list
is not complete):
Co-authored-by: Alex Williams <alex.williams@ni.com>
Co-authored-by: Andrej Rode <andrej.rode@ettus.com>
Co-authored-by: Ashish Chaudhari <ashish@ettus.com>
Co-authored-by: Ben Hilburn <ben.hilburn@ettus.com>
Co-authored-by: Ciro Nishiguchi <ciro.nishiguchi@ni.com>
Co-authored-by: Daniel Jepson <daniel.jepson@ni.com>
Co-authored-by: Derek Kozel <derek.kozel@ettus.com>
Co-authored-by: EJ Kreinar <ej@he360.com>
Co-authored-by: Humberto Jimenez <humberto.jimenez@ni.com>
Co-authored-by: Ian Buckley <ian.buckley@gmail.com>
Co-authored-by: Jörg Hofrichter <joerg.hofrichter@ni.com>
Co-authored-by: Jon Kiser <jon.kiser@ni.com>
Co-authored-by: Josh Blum <josh@joshknows.com>
Co-authored-by: Jonathon Pendlum <jonathan.pendlum@ettus.com>
Co-authored-by: Martin Braun <martin.braun@ettus.com>
Co-authored-by: Matt Ettus <matt@ettus.com>
Co-authored-by: Michael West <michael.west@ettus.com>
Co-authored-by: Moritz Fischer <moritz.fischer@ettus.com>
Co-authored-by: Nick Foster <nick@ettus.com>
Co-authored-by: Nicolas Cuervo <nicolas.cuervo@ettus.com>
Co-authored-by: Paul Butler <paul.butler@ni.com>
Co-authored-by: Paul David <paul.david@ettus.com>
Co-authored-by: Ryan Marlow <ryan.marlow@ettus.com>
Co-authored-by: Sugandha Gupta <sugandha.gupta@ettus.com>
Co-authored-by: Sylvain Munaut <tnt@246tNt.com>
Co-authored-by: Trung Tran <trung.tran@ettus.com>
Co-authored-by: Vidush Vishwanath <vidush.vishwanath@ettus.com>
Co-authored-by: Wade Fife <wade.fife@ettus.com>
Diffstat (limited to 'fpga/usrp3/lib/control')
53 files changed, 6423 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/control/Makefile.srcs b/fpga/usrp3/lib/control/Makefile.srcs new file mode 100644 index 000000000..578f19ea5 --- /dev/null +++ b/fpga/usrp3/lib/control/Makefile.srcs @@ -0,0 +1,61 @@ +# +# Copyright 2013 Ettus Research LLC +# Copyright 2017 Ettus Research, a National Instruments Company +# +# SPDX-License-Identifier: LGPL-3.0-or-later +# + +################################################## +# Control Lib Sources +################################################## +CONTROL_LIB_SRCS = $(abspath $(addprefix $(BASE_DIR)/../lib/control/, \ +ad5662_auto_spi.v \ +arb_qualify_master.v \ +axi_crossbar.v \ +axi_crossbar_regport.v \ +axi_fifo_header.v \ +axi_forwarding_cam.v \ +axi_setting_reg.v \ +axi_slave_mux.v \ +axi_test_vfifo.v \ +bin2gray.v \ +binary_encoder.v \ +db_control.v \ +fe_control.v \ +filter_bad_sid.v \ +gpio_atr_io.v \ +gpio_atr.v \ +gray2bin.v \ +por_gen.v \ +priority_encoder_one_hot.v \ +priority_encoder.v \ +ram_2port_impl.vh \ +ram_2port.v \ +reset_sync.v \ +s7_icap_wb.v \ +serial_to_settings.v \ +setting_reg.v \ +settings_bus_mux.v \ +settings_bus_timed_2clk.v \ +simple_i2c_core.v \ +simple_spi_core.v \ +synchronizer_impl.v \ +synchronizer.v \ +pulse_synchronizer.v \ +user_settings.v \ +axil_regport_master.v \ +axil_to_ni_regport.v \ +regport_resp_mux.v \ +regport_to_xbar_settingsbus.v \ +regport_to_settingsbus.v \ +pulse_stretch.v \ +pulse_stretch_min.v \ +mdio_master.v \ +map/cam_priority_encoder.v \ +map/cam_bram.v \ +map/cam_srl.v \ +map/cam.v \ +map/kv_map.v \ +map/axis_muxed_kv_map.v \ +axil_ctrlport_master.v\ +)) diff --git a/fpga/usrp3/lib/control/ad5662_auto_spi.v b/fpga/usrp3/lib/control/ad5662_auto_spi.v new file mode 100644 index 000000000..d9f2e53be --- /dev/null +++ b/fpga/usrp3/lib/control/ad5662_auto_spi.v @@ -0,0 +1,99 @@ +// +// Copyright 2015 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// The AD5662 DAC serial interface uses 24-bit transfers to encode 16-bits +// of actual data, two bits for power-down mode, and six pad bits. This +// module stores a copy of the last-programmed value, and will generate a +// serial stream if ever the input word (dat) changes. It will ignore +// changes to (dat) while it is busy with a serial update. +// +module ad5662_auto_spi +( + input clk, + input [15:0] dat, + output reg sclk, + output reg mosi, + output reg sync_n +); + // initialize ldat to 0, thus forcing + // a reload on init. + reg [15:0] ldat = 16'd0; + wire upd = (dat != ldat); // new data present, need to update hw + + reg [23:0] shft=24'b0; + wire [23:0] nxt_shft; + + // clock cycle counter to throttle generated spi cycles + // allowing one spi clock cycle every 16 cycles of clk, with clk at 200 MHz + // gives a spi clock rate of 12 MHz. This can be made more sophisticated + // or parameterized, if more flexibility in clk is needed, of course. + reg [3:0] ccnt=4'b0; + wire [3:0] nxt_ccnt = ccnt + 1'b1; + wire half = ccnt==4'b1000; + wire full = ccnt==4'b1111; + reg sena, hena; + wire cena; + always @(posedge clk) if (cena) ccnt <= nxt_ccnt; + always @(posedge clk) sena <= full; // state updates and rising sclk + always @(posedge clk) hena <= half; // for falling sclk + + // transfer state counter + reg [4:0] scnt = 5'b0; + reg [4:0] nxt_scnt; + always @(posedge clk) begin + if (sena) begin + scnt <= nxt_scnt; + shft <= nxt_shft; + mosi <= shft[23]; + end + end + + + // 32 possible states - more than enough to shift-out 24 bits and manage + // the sync_n line + + // particular scnt values of interest + localparam READY=5'b00000; // waiting for new data + localparam DCAPT=5'b00001; // new data transfers into ldat + localparam SYNCL=5'b00010; // assert sync_n low + localparam SYNCH=5'b11011; // return sync_n high + + assign cena = upd | scnt != READY; + + always @(scnt or upd) + begin + case (scnt) + READY: + nxt_scnt = upd ? DCAPT : READY; + SYNCH: + nxt_scnt = READY; + default: + nxt_scnt = scnt + 1'b1; + endcase + end + + // note: defining the power-down mode bits to 00 for "normal operation" + assign nxt_shft = (scnt == SYNCL) ? { 8'b000000_00, ldat } : { shft[22:0], 1'b0 }; + + // Update ldat when dat has changed, but only if READY. + // Changes to dat arriving faster than can be kept up with here are ignored + // until the cycle-in-progress is completed. + wire ldat_ena = sena & (scnt == DCAPT); + always @(posedge clk) begin + if (ldat_ena) ldat <= dat; + end + + // keep the sync_n line low when idle to minimize power consumption + // it gets brought high just before beginning each transaction + wire nxt_sync_n = (scnt==SYNCL) | (scnt==SYNCH); + always @(posedge clk) if (sena) sync_n <= nxt_sync_n; + + reg sclk_go; + always @(posedge clk) sclk_go <= (scnt > SYNCL); + wire nxt_sclk = ~sclk_go ? 1'b1 : ~sclk; + always @(posedge clk) if (sena | hena) sclk <= nxt_sclk; + +endmodule diff --git a/fpga/usrp3/lib/control/arb_qualify_master.v b/fpga/usrp3/lib/control/arb_qualify_master.v new file mode 100644 index 000000000..9a8fdb015 --- /dev/null +++ b/fpga/usrp3/lib/control/arb_qualify_master.v @@ -0,0 +1,91 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +// +// This module forms the qualification engine for a single master as +// part of a larger arbitration engine for a slave. It would typically +// be instantiated from arb_select_master.v to form a complete arbitor solution. +// + +module arb_qualify_master + #( + parameter WIDTH=16 // Bit width of destination field. + ) + ( + input clk, + input reset, + input clear, + // Header signals + input [WIDTH-1:0] header, + input header_valid, + // Slave Confg Signals + input [WIDTH-1:0] slave_addr, + input [WIDTH-1:0] slave_mask, + input slave_valid, + // Arbitration flags + output reg master_valid, + input master_ack + ); + + localparam WAIT_HEADER_VALID = 0; + localparam MATCH = 1; + localparam WAIT_HEADER_NOT_VALID = 2; + + + reg [1:0] state, next_state; + + + // Does masked slave address match header field for dest from master? + assign header_match = ((header & slave_mask) == (slave_addr & slave_mask)) && slave_valid; + + + always @(posedge clk) + if (reset | clear) begin + state <= WAIT_HEADER_VALID; + master_valid <= 0; + end else + begin + case(state) + // + // Wait here until Masters FIFO presents a valid header word. + // + WAIT_HEADER_VALID: begin + if (header_valid) + if (header_match) begin + state <= MATCH; + master_valid <= 1; + end else + next_state <= WAIT_HEADER_NOT_VALID; + end + // + // There should only ever be one match across various arbitors + // if they are configured correctly and since the backing FIFO in the + // master should not start to drain until the arbitration is won + // by that master, master_ack should always preceed de-assertion of + // header_valid so we don't check for the other order of deassertion. + // + MATCH: begin + if (master_ack) begin + master_valid <= 0; + state <= WAIT_HEADER_NOT_VALID; + end + end + // + // Wait here until this master starts to drain this packet from his FIFO. + // + WAIT_HEADER_NOT_VALID: begin + if (!header_valid) begin + state <= WAIT_HEADER_VALID; + end + end + endcase // case(state) + end // else: !if(reset | clear) + +endmodule // arb_qualify_master + +
\ No newline at end of file diff --git a/fpga/usrp3/lib/control/axi_crossbar.v b/fpga/usrp3/lib/control/axi_crossbar.v new file mode 100644 index 000000000..e34c3f43f --- /dev/null +++ b/fpga/usrp3/lib/control/axi_crossbar.v @@ -0,0 +1,164 @@ +///////////////////////////////////////////////////////////////////// +// +// Copyright 2012 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_crossbar +// Description: +// - Control Registers +// - CAM to setup routing between RFNoC blocks +// +///////////////////////////////////////////////////////////////////// + +module axi_crossbar + #( + parameter BASE = 0, // settings bus base address + parameter FIFO_WIDTH = 64, // AXI4-STREAM data bus width + parameter DST_WIDTH = 16, // Width of DST field we are routing on. + parameter NUM_INPUTS = 2, // number of input AXI4-STREAM buses + parameter NUM_OUTPUTS = 2 // number of output AXI4-STREAM buses + ) + ( + input clk, + input reset, + input clear, + input [7:0] local_addr, + // Inputs + input [(FIFO_WIDTH*NUM_INPUTS)-1:0] i_tdata, + input [NUM_INPUTS-1:0] i_tvalid, + input [NUM_INPUTS-1:0] i_tlast, + output [NUM_INPUTS-1:0] i_tready, + input [NUM_INPUTS-1:0] pkt_present, + // Setting Bus + input set_stb, + input [15:0] set_addr, + input [31:0] set_data, + // Output + output [(FIFO_WIDTH*NUM_OUTPUTS)-1:0] o_tdata, + output [NUM_OUTPUTS-1:0] o_tvalid, + output [NUM_OUTPUTS-1:0] o_tlast, + input [NUM_OUTPUTS-1:0] o_tready, + // readback bus + input rb_rd_stb, + input [$clog2(NUM_OUTPUTS)+$clog2(NUM_INPUTS)-1:0] rb_addr, + output reg [31:0] rb_data + ); + + genvar m,n; + + wire [(NUM_INPUTS*NUM_OUTPUTS)-1:0] forward_valid_in; + wire [(NUM_INPUTS*NUM_OUTPUTS)-1:0] forward_ack_in; + wire [(NUM_INPUTS*NUM_OUTPUTS)-1:0] forward_valid_out; + wire [(NUM_INPUTS*NUM_OUTPUTS)-1:0] forward_ack_out; + + wire [NUM_INPUTS-1:0] i_tready_slave [0:NUM_OUTPUTS-1]; + + // + // Instantiate an axi_slave_mux for every slave/output of the Crossbar switch. + // Each axi_slave_mux contains logic to maux and resolve arbitration + // for this particular slave/output. + // + + generate + for (m = 0; m < NUM_OUTPUTS; m = m + 1) begin: instantiate_slave_mux + + wire [NUM_INPUTS-1:0] i_tready_tmp; + + axi_slave_mux + #( + .FIFO_WIDTH(FIFO_WIDTH), // AXI4-STREAM data bus width + .DST_WIDTH(DST_WIDTH), // Width of DST field we are routing on. + .NUM_INPUTS(NUM_INPUTS) // number of input AXI buses + ) axi_slave_mux_i + ( + .clk(clk), + .reset(reset), + .clear(clear), + // Inputs + .i_tdata(i_tdata), + .i_tvalid(i_tvalid), + .i_tlast(i_tlast), + .i_tready(i_tready_tmp), + // Forwarding flags (One from each Input/Master) + .forward_valid(forward_valid_in[(m+1)*NUM_INPUTS-1:m*NUM_INPUTS]), + .forward_ack(forward_ack_out[(m+1)*NUM_INPUTS-1:m*NUM_INPUTS]), + // Output + .o_tdata(o_tdata[(m*FIFO_WIDTH)+FIFO_WIDTH-1:m*FIFO_WIDTH]), + .o_tvalid(o_tvalid[m]), + .o_tlast(o_tlast[m]), + .o_tready(o_tready[m]) + ); + + if (m==0) + assign i_tready_slave[0] = i_tready_tmp; + else + assign i_tready_slave[m] = i_tready_tmp | i_tready_slave[m-1] ; + + end // block: instantiate_slave_mux + endgenerate + + assign i_tready = i_tready_slave[NUM_OUTPUTS-1]; + + // + // Permute the forwarding flag buses + // + + generate + for (m = 0; m < NUM_OUTPUTS; m = m + 1) begin: permute_outer + for (n = 0; n < NUM_INPUTS; n = n + 1) begin: permute_inner + assign forward_valid_in[n*NUM_OUTPUTS+m] = forward_valid_out[n+m*NUM_INPUTS]; + assign forward_ack_in[n+m*NUM_INPUTS] = forward_ack_out[n*NUM_OUTPUTS+m]; + end + end + + endgenerate + + + // + // Instantiate an axi_forwarding_cam for every Input/Master of the Crossbar switch. + // Each contains a TCAM like lookup that allocates an egress port. + // + + wire [31:0] rb_data_mux[0:NUM_INPUTS-1]; + + generate + for (m = 0; m < NUM_INPUTS; m = m + 1) begin: instantiate_cam + axi_forwarding_cam + #( + .BASE(BASE), + .WIDTH(FIFO_WIDTH), // Bit width of FIFO word. + .NUM_OUTPUTS(NUM_OUTPUTS) + ) axi_forwarding_cam_i + ( + .clk(clk), + .reset(reset), + .clear(clear), + // Monitored FIFO signals + .o_tdata(i_tdata[(m*FIFO_WIDTH)+FIFO_WIDTH-1:m*FIFO_WIDTH]), + .o_tvalid(i_tvalid[m]), + .o_tready(i_tready[m]), + .o_tlast(i_tlast[m]), + .pkt_present(pkt_present[m]), + // Configuration + .local_addr(local_addr), + // Setting Bus + .set_stb(set_stb), + .set_addr(set_addr), + .set_data(set_data), + // Header signals + .forward_valid(forward_valid_out[(m+1)*NUM_OUTPUTS-1:m*NUM_OUTPUTS]), + .forward_ack(forward_ack_in[(m+1)*NUM_OUTPUTS-1:m*NUM_OUTPUTS]), + // Readback bus + .rb_rd_stb(rb_rd_stb && (rb_addr[$clog2(NUM_OUTPUTS)+$clog2(NUM_INPUTS)-1:$clog2(NUM_OUTPUTS)] == m)), + .rb_addr(rb_addr[$clog2(NUM_OUTPUTS)-1:0]), + .rb_data(rb_data_mux[m]) + ); + end // block: instantiate_fifo_header + endgenerate + + // Pipeline readback data to alleviate timing issues + always @(posedge clk) rb_data <= rb_data_mux[rb_addr[$clog2(NUM_OUTPUTS)+$clog2(NUM_INPUTS)-1:$clog2(NUM_OUTPUTS)]]; + + +endmodule // axi_crossbar diff --git a/fpga/usrp3/lib/control/axi_crossbar_intf.sv b/fpga/usrp3/lib/control/axi_crossbar_intf.sv new file mode 100644 index 000000000..b0c07ba9f --- /dev/null +++ b/fpga/usrp3/lib/control/axi_crossbar_intf.sv @@ -0,0 +1,87 @@ +// +// Copyright 2015 Ettus Research LLC +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Wraps AXI crossbar and exposes cvita_stream_t and settings_bus_t interfaces + +`include "sim_cvita_lib.svh" +`include "sim_set_rb_lib.svh" + +module axi_crossbar_intf +#( + parameter BASE = 0, // settings bus base address + parameter FIFO_WIDTH = 64, // AXI4-STREAM data bus width + parameter DST_WIDTH = 16, // Width of DST field we are routing on. + parameter NUM_PORTS = 2 // number of cvita busses +)( + input clk, + input reset, + input clear, + input [7:0] local_addr, + axis_t.slave s_cvita[0:NUM_PORTS-1], + axis_t.master m_cvita[0:NUM_PORTS-1], + settings_bus_t.slave set_bus, + readback_bus_t.master rb_bus +); + + wire [NUM_PORTS*64-1:0] flat_i_tdata; + wire [NUM_PORTS-1:0] i_tlast, i_tvalid, i_tready; + wire [NUM_PORTS*64-1:0] flat_o_tdata; + wire [NUM_PORTS-1:0] o_tlast, o_tvalid, o_tready; + + // Flattern CE tdata arrays + genvar i; + generate + for (i = 0; i < NUM_PORTS; i = i + 1) begin + assign flat_i_tdata[i*FIFO_WIDTH+FIFO_WIDTH-1:i*FIFO_WIDTH] = s_cvita[i].tdata; + assign i_tlast[i] = s_cvita[i].tlast; + assign i_tvalid[i] = s_cvita[i].tvalid; + assign s_cvita[i].tready = i_tready[i]; + end + for (i = 0; i < NUM_PORTS; i = i + 1) begin + assign m_cvita[i].tdata = flat_o_tdata[i*FIFO_WIDTH+FIFO_WIDTH-1:i*FIFO_WIDTH]; + assign m_cvita[i].tlast = o_tlast[i]; + assign m_cvita[i].tvalid = o_tvalid[i]; + assign o_tready[i] = m_cvita[i].tready; + end + endgenerate + + wire set_stb = set_bus.stb; + wire [15:0] set_addr = set_bus.addr; + wire [31:0] set_data = set_bus.data; + wire rb_rd_stb = rb_bus.stb; + wire [2*$clog2(NUM_PORTS):0] rb_addr = rb_bus.addr[2*$clog2(NUM_PORTS):0]; + wire [31:0] rb_data; + assign rb_bus.data = rb_data; + + axi_crossbar #( + .BASE(BASE), + .FIFO_WIDTH(FIFO_WIDTH), + .DST_WIDTH(DST_WIDTH), + .NUM_INPUTS(NUM_PORTS), + .NUM_OUTPUTS(NUM_PORTS)) + inst_axi_crossbar ( + .clk(clk), + .reset(reset), + .clear(clear), + .local_addr(local_addr), + .i_tdata(flat_i_tdata), + .i_tvalid(i_tvalid), + .i_tlast(i_tlast), + .i_tready(i_tready), + .pkt_present(i_tvalid), + .set_stb(set_stb), + .set_addr(set_addr), + .set_data(set_data), + .o_tdata(flat_o_tdata), + .o_tvalid(o_tvalid), + .o_tlast(o_tlast), + .o_tready(o_tready), + .rb_rd_stb(rb_rd_stb), + .rb_addr(rb_addr), + .rb_data(rb_data)); + +endmodule + diff --git a/fpga/usrp3/lib/control/axi_crossbar_regport.v b/fpga/usrp3/lib/control/axi_crossbar_regport.v new file mode 100644 index 000000000..15b74517d --- /dev/null +++ b/fpga/usrp3/lib/control/axi_crossbar_regport.v @@ -0,0 +1,193 @@ +///////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axi_crossbar_regport +// Description: +// - axi_crossbar with regport interface for register/CAM access +// +////////////////////////////////////////////////////////////////////// + +module axi_crossbar_regport #( + parameter REG_BASE = 0, // settings bus base address + parameter FIFO_WIDTH = 64, // AXI4-STREAM data bus width + parameter DST_WIDTH = 16, // Width of DST field we are routing on. + parameter NUM_INPUTS = 2, // number of input AXI4-STREAM buses + parameter NUM_OUTPUTS = 2, // number of output AXI4-STREAM buses + parameter REG_DWIDTH = 32, // Width of the AXI4-Lite data bus (must be 32 or 64) + parameter REG_AWIDTH = 14 // Width of the address bus +)( + input clk, + input reset, + input clear, + + input reg_wr_req, + input [REG_AWIDTH-1:0] reg_wr_addr, + input [REG_DWIDTH-1:0] reg_wr_data, + + input reg_rd_req, + input [REG_AWIDTH-1:0] reg_rd_addr, + output [REG_DWIDTH-1:0] reg_rd_data, + output reg_rd_resp, + + // Inputs + input [(FIFO_WIDTH*NUM_INPUTS)-1:0] i_tdata, + input [NUM_INPUTS-1:0] i_tvalid, + input [NUM_INPUTS-1:0] i_tlast, + output [NUM_INPUTS-1:0] i_tready, + input [NUM_INPUTS-1:0] pkt_present, + + // Output + output [(FIFO_WIDTH*NUM_OUTPUTS)-1:0] o_tdata, + output [NUM_OUTPUTS-1:0] o_tvalid, + output [NUM_OUTPUTS-1:0] o_tlast, + input [NUM_OUTPUTS-1:0] o_tready +); + + localparam XBAR_VERSION = 32'b1; + localparam XBAR_NUM_PORTS = NUM_INPUTS; //or NUM_OUTPUTS + + localparam REG_XBAR_VERSION = REG_BASE + 14'h10; + localparam REG_XBAR_NUM_PORTS = REG_BASE + 14'h14; + localparam REG_XBAR_LOCAL_ADDR = REG_BASE + 14'h18; + localparam REG_BASE_XBAR_SETTING_REG = REG_BASE + 14'h20; + localparam REG_END_ADDR_XBAR_SETTING_REG = REG_BASE + 14'h1000; + + // Settings bus address width + localparam SR_AWIDTH = 12; + + wire xbar_set_stb; + wire [REG_DWIDTH-1:0] xbar_set_data; + wire [SR_AWIDTH-1:0] xbar_set_addr; + + wire xbar_rb_stb; + wire [SR_AWIDTH-1:0] xbar_rb_addr; + wire [REG_DWIDTH-1:0] xbar_rb_data; + + reg [31:0] local_addr_reg; + reg reg_rd_resp_glob; + reg [REG_DWIDTH-1:0] reg_rd_data_glob; + + wire [REG_DWIDTH-1:0] reg_rd_data_xbar; + wire reg_rd_resp_xbar; + + regport_resp_mux #( + .WIDTH(REG_DWIDTH), + .NUM_SLAVES(2) + ) inst_regport_resp_mux_xbar ( + .clk(clk), + .reset(reset), + .sla_rd_resp({reg_rd_resp_glob, reg_rd_resp_xbar}), + .sla_rd_data({reg_rd_data_glob, reg_rd_data_xbar}), + .mst_rd_resp(reg_rd_resp), + .mst_rd_data(reg_rd_data) + ); + + // Read Registers + always @ (posedge clk) begin + if (reset) begin + local_addr_reg <= 32'h0; + end + else begin + if (reg_wr_req) + case (reg_wr_addr) + REG_XBAR_LOCAL_ADDR: + local_addr_reg <= reg_wr_data; + endcase + end + end + + // Write Registers + always @ (posedge clk) begin + if (reset) + reg_rd_resp_glob <= 1'b0; + + else begin + if (reg_rd_req) begin + reg_rd_resp_glob <= 1'b1; + + case (reg_rd_addr) + REG_XBAR_VERSION: + reg_rd_data_glob <= XBAR_VERSION; + + REG_XBAR_NUM_PORTS: + reg_rd_data_glob <= XBAR_NUM_PORTS; + + REG_XBAR_LOCAL_ADDR: + reg_rd_data_glob <= local_addr_reg; + + default: + reg_rd_resp_glob <= 1'b0; + endcase + end + else if (reg_rd_resp_glob) begin + reg_rd_resp_glob <= 1'b0; + end + end + end + + regport_to_xbar_settingsbus #( + .BASE(REG_BASE_XBAR_SETTING_REG), + .END_ADDR(REG_END_ADDR_XBAR_SETTING_REG), + .DWIDTH(REG_DWIDTH), + .AWIDTH(REG_AWIDTH), + .SR_AWIDTH(SR_AWIDTH), + .ADDRESSING("WORD") + ) inst_regport_to_xbar_settingsbus ( + .clk(clk), + .reset(reset), + + .reg_wr_req(reg_wr_req), + .reg_wr_addr(reg_wr_addr), + .reg_wr_data(reg_wr_data), + .reg_rd_req(reg_rd_req), + .reg_rd_addr(reg_rd_addr), + .reg_rd_data(reg_rd_data_xbar), + .reg_rd_resp(reg_rd_resp_xbar), + + .set_stb(xbar_set_stb), + .set_addr(xbar_set_addr), + .set_data(xbar_set_data), + .rb_stb(xbar_rb_stb), + .rb_addr(xbar_rb_addr), + .rb_data(xbar_rb_data) + ); + + axi_crossbar #( + .BASE(0), // Set to 0 as logic for other values has not been tested + .FIFO_WIDTH(FIFO_WIDTH), + .DST_WIDTH(DST_WIDTH), + .NUM_INPUTS(NUM_INPUTS), + .NUM_OUTPUTS(NUM_OUTPUTS) + ) axi_crossbar ( + .clk(clk), + .reset(reset), + .clear(1'b0), + .local_addr(local_addr_reg), + + // settings bus for config + .set_stb(xbar_set_stb), + .set_addr({4'b0000,xbar_set_addr}), + .set_data(xbar_set_data), + .rb_rd_stb(xbar_rb_stb), + .rb_addr(xbar_rb_addr[$clog2(NUM_INPUTS)+$clog2(NUM_OUTPUTS)-1:0]), + .rb_data(xbar_rb_data), + + // inputs, real men flatten busses + .i_tdata(i_tdata), + .i_tlast(i_tlast), + .i_tvalid(i_tvalid), + .i_tready(i_tready), + + // outputs, real men flatten busses + .o_tdata(o_tdata), + .o_tlast(o_tlast), + .o_tvalid(o_tvalid), + .o_tready(o_tready), + .pkt_present(pkt_present) + ); + +endmodule // axi_crossbar_regport + diff --git a/fpga/usrp3/lib/control/axi_fifo_header.v b/fpga/usrp3/lib/control/axi_fifo_header.v new file mode 100644 index 000000000..551c2b760 --- /dev/null +++ b/fpga/usrp3/lib/control/axi_fifo_header.v @@ -0,0 +1,87 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +// +// This module is connected to the output port of an AXI4-STREAM FIFO that is used to move packetized data. +// It extracts and indicates the header (first word) of a packet in the FIFO. The header and flag are pipelined +// for timing closure. +// + +module axi_fifo_header + #( + parameter WIDTH=64 // Bit width of FIFO word. + ) + ( + input clk, + input reset, + input clear, + // Monitored FIFO signals + input [WIDTH-1:0] o_tdata, + input o_tvalid, + input o_tready, + input o_tlast, + input pkt_present, + // Header signals + output reg [WIDTH-1:0] header, + output reg header_valid + ); + + localparam WAIT_SOF = 0; + localparam WAIT_EOF = 1; + + reg out_state; + + + // + // Monitor packets leaving FIFO + // + always @(posedge clk) + if (reset | clear) begin + out_state <= WAIT_SOF; + end else + case(out_state) + // + // After RESET or the EOF of previous packet, the first cycle with + // output valid asserted is the SOF and presents the Header word. + // The cycle following the concurrent presentation of asserted output + // valid and output ready presents the word following the header. + // + WAIT_SOF: + if (o_tvalid && o_tready) begin + out_state <= WAIT_EOF; + end else begin + out_state <= WAIT_SOF; + end + // + // EOF is signalled by o_tlast asserted whilst output valid and ready asserted. + // + WAIT_EOF: + if (o_tlast && o_tvalid && o_tready) begin + out_state <= WAIT_SOF; + end else begin + out_state <= WAIT_EOF; + end + endcase // case(in_state) + + // + // Pipeline Header signals + // + always @(posedge clk) + if (reset | clear) begin + header <= 0; + header_valid <= 0; + end else if (o_tvalid && (out_state == WAIT_SOF) && pkt_present) begin + // Header will remian valid until o_tready is asserted as this will cause a state transition. + header <= o_tdata; + header_valid <= 1; + end else begin + header_valid <= 0; + end + + +endmodule // axi_fifo_header diff --git a/fpga/usrp3/lib/control/axi_forwarding_cam.v b/fpga/usrp3/lib/control/axi_forwarding_cam.v new file mode 100644 index 000000000..64076bd0a --- /dev/null +++ b/fpga/usrp3/lib/control/axi_forwarding_cam.v @@ -0,0 +1,232 @@ +// +// Copyright 2013 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This module implements a highly customized content-addressable memory (CAM) +// that enables forwarding decisions to be made on a 16 bit field from a stream ID (SID) field. +// The forwarding is generic in the sense that a SID's host destination can map to any endpoint / crossbar port. +// +// The 16 bits are allocated by convention as 8 bits of Network address (addresses USRP's / AXI crossbars) and +// 8 bits of Host address (addresses endpoints / crossbar ports in a USRP). +// +// By definition if the destination field in the SID addresses a different +// USRP / crossbar than this one then we don't care about the Host field, only the Network field. +// We only look at the Host field when the Network field addresses us. +// Thus we need a CAM of 256+256 entries with Log2(N) bits, where N is the number of +// slave(output) ports on the crossbar switch. +// +// SID format: +// +// |---------|---------|---------|---------| +// | SRC | SRC | DST | DST | +// | NETWORK | HOST | NETWORK | HOST | +// |---------|---------|---------|---------| +// 8 8 8 8 + +module axi_forwarding_cam + #( + parameter BASE = 0, // BASE address for setting registers in this block. (512 addrs used) + parameter WIDTH=64, // Bit width of FIFO word. + parameter NUM_OUTPUTS=2 // Number of outputs (destinations) in crossbar. + ) + ( + input clk, + input reset, + input clear, + // Monitored FIFO signals + input [WIDTH-1:0] o_tdata, + input o_tvalid, + input o_tready, + input o_tlast, + input pkt_present, + // Configuration + input [7:0] local_addr, + // Setting Bus + input set_stb, + input [15:0] set_addr, + input [31:0] set_data, + + output reg [NUM_OUTPUTS-1:0] forward_valid, + input [NUM_OUTPUTS-1:0] forward_ack, + + input rb_rd_stb, + input [$clog2(NUM_OUTPUTS)-1:0] rb_addr, + output [31:0] rb_data + ); + + + localparam WAIT_SOF = 0; + localparam WAIT_EOF = 1; + reg state; + + localparam IDLE = 0; + localparam FORWARD = 1; + localparam WAIT = 2; + + reg [1:0] demux_state; + + reg [15:0] dst; + reg dst_valid, dst_valid_reg; + wire local_dst; + wire [8:0] read_addr; + + // + // Monitor packets leaving FIFO + // + always @(posedge clk) + if (reset | clear) begin + state <= WAIT_SOF; + end else + case(state) + // + // After RESET or the EOF of previous packet, the first cycle with + // output valid asserted is the SOF and presents the Header word. + // The cycle following the concurrent presentation of asserted output + // valid and output ready presents the word following the header. + // + WAIT_SOF: + if (o_tvalid && o_tready) begin + state <= WAIT_EOF; + end else begin + state <= WAIT_SOF; + end + // + // EOF is signalled by o_tlast asserted whilst output valid and ready asserted. + // + WAIT_EOF: + if (o_tlast && o_tvalid && o_tready) begin + state <= WAIT_SOF; + end else begin + state <= WAIT_EOF; + end + endcase // case(in_state) + + // + // Extract Destination fields(s) from SID + // + always @(posedge clk) + if (reset | clear) begin + dst <= 0; + dst_valid <= 0; + dst_valid_reg <= 0; + end else if (o_tvalid && (state == WAIT_SOF) && pkt_present) begin + // SID will remain valid until o_tready is asserted as this will cause a state transition. + dst <= o_tdata[15:0]; + dst_valid <= 1; + dst_valid_reg <= dst_valid; + end else begin + dst_valid <= 0; + dst_valid_reg <= dst_valid; + end + + // + // Is Network field in DST our local address? + // + assign local_dst = (dst[15:8] == local_addr) && dst_valid; + + + // + // Mux address to RAM so that it searches CAM for Network field or Host field. + // Network addresses are stored in the lower 256 locations, host addresses the upper 256. + // + assign read_addr = {local_dst,(local_dst ? dst[7:0] : dst[15:8])}; + + // + // Implement CAM as block RAM here, 512xCeil(Log2(NUM_OUTPUTS)) + // + //synthesis attribute ram_style of mem is block + reg [$clog2(NUM_OUTPUTS)-1 : 0] mem [0:511]; + + // Initialize the CAM's local address forwarding decisions with sensible defaults by + // assuming dst[7:4] = crossbar port, dst[3:0] = block port. Setup a one-to-one mapping + // for crossbar ports and always map same crossbar port regardless of block port. + // i.e. + // dst 8'h00 => forward to crossbar port 0 + // dst 8'h01 => forward to crossbar port 0 + // dst 8'h10 => forward to crossbar port 1 + // etc. + integer xbar_port; + integer block_port; + initial begin + for (xbar_port = 0; xbar_port < NUM_OUTPUTS; xbar_port = xbar_port + 1) begin + for (block_port = 0; block_port < 16; block_port = block_port + 1) begin + mem[256+(xbar_port << 4)+block_port] = xbar_port; + end + end + end + + reg [8:0] read_addr_reg; + wire write; + wire [$clog2(NUM_OUTPUTS)-1:0] read_data; + + assign write = (set_addr[15:9] == (BASE >>9)) && set_stb; // Addr decode. + + always @(posedge clk) + begin + read_addr_reg <= read_addr; + + if (write) begin + mem[set_addr[8:0]] <= set_data[$clog2(NUM_OUTPUTS)-1:0]; + end + + end + + assign read_data = mem[read_addr_reg]; + + + // + // State machine to manage forwarding flags. + // + always @(posedge clk) + if (reset | clear) begin + forward_valid <= {NUM_OUTPUTS{1'b0}}; + demux_state <= IDLE; + end else + case(demux_state) + + // Wait for Valid DST which indicates a new packet lookup in the CAM. + IDLE: begin + if (dst_valid_reg == 1) begin + forward_valid <= 1'b1 << read_data; + demux_state <= FORWARD; + end + end + // When Slave/Output thats forwarding ACK's the forward flag, clear request and wait for packet to be transfered + FORWARD: begin + if ((forward_ack & forward_valid) != 0) begin + forward_valid <= {NUM_OUTPUTS{1'b0}}; + demux_state <= WAIT; + end + end + // When packet transfered go back to idle. + WAIT: begin + if (forward_ack == 0) + demux_state <= IDLE; + end + + endcase // case (demux_state) + + // + // Compile forwarding statistics + // (This uses a lot of registers!) + // + genvar m; + reg [31:0] statistics [0:NUM_OUTPUTS-1]; + + generate + for (m = 0; m < NUM_OUTPUTS; m = m + 1) begin: generate_stats + always @(posedge clk) begin + if (reset | clear) begin + statistics[m] <= 0; + end else if (forward_ack[m] & forward_valid[m]) begin + statistics[m] <= statistics[m] + 1; + end + end + end + endgenerate + + assign rb_data = statistics[rb_addr]; + +endmodule diff --git a/fpga/usrp3/lib/control/axi_setting_reg.v b/fpga/usrp3/lib/control/axi_setting_reg.v new file mode 100644 index 000000000..9d419ec32 --- /dev/null +++ b/fpga/usrp3/lib/control/axi_setting_reg.v @@ -0,0 +1,97 @@ +// +// Copyright 2016 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Settings register with AXI stream output. +// +// Parameters / common use cases: +// USE_ADDR_LAST & ADDR_LAST User wants additional address that when written to asserts tlast. +// Useful for the last word in a packet. +// USE_FIFO & FIFO_SIZE Downstream block can throttle and a FIFO is needed to handle that case. +// STROBE_LAST User always wants to assert tlast on writes. More efficient than USE_ADDR_LAST +// since only one address is used instead of two. +// REPEATS Keep tvalid asserted after initial write. +// STROBE_LAST & REPEATS tlast is asserted on the initial write then deasserted for repeating output. +// MSB_ALIGN Left justify data versus right justify. + +module axi_setting_reg #( + parameter ADDR = 0, + parameter USE_ADDR_LAST = 0, + parameter ADDR_LAST = ADDR+1, + parameter AWIDTH = 8, + parameter WIDTH = 32, + parameter USE_FIFO = 0, + parameter FIFO_SIZE = 5, + parameter DATA_AT_RESET = 0, + parameter VALID_AT_RESET = 0, + parameter LAST_AT_RESET = 0, + parameter STROBE_LAST = 0, + parameter REPEATS = 0, + parameter MSB_ALIGN = 0 +) +( + input clk, input reset, output reg error_stb, + input set_stb, input [AWIDTH-1:0] set_addr, input [31:0] set_data, + output [WIDTH-1:0] o_tdata, output o_tlast, output o_tvalid, input o_tready +); + + reg init; + + reg [WIDTH-1:0] o_tdata_int; + reg o_tlast_int, o_tvalid_int; + wire o_tready_int; + + always @(posedge clk) begin + if (reset) begin + o_tdata_int <= DATA_AT_RESET; + o_tvalid_int <= VALID_AT_RESET; + o_tlast_int <= LAST_AT_RESET; + init <= 1'b0; + error_stb <= 1'b0; + end else begin + error_stb <= 1'b0; + if (o_tvalid_int & o_tready_int) begin + // Deassert tvalid / tlast only if not repeating the output + if (REPEATS == 0) begin + o_tvalid_int <= 1'b0; + end + if ((REPEATS == 0) | (STROBE_LAST == 1)) begin + o_tlast_int <= 1'b0; + end + end + if (set_stb & ((ADDR[AWIDTH-1:0] == set_addr) | (USE_ADDR_LAST & (ADDR_LAST[AWIDTH-1:0] == set_addr)))) begin + init <= 1'b1; + o_tdata_int <= (MSB_ALIGN == 0) ? set_data[WIDTH-1:0] : set_data[31:32-WIDTH]; + o_tvalid_int <= 1'b1; + if (set_stb & (STROBE_LAST | (USE_ADDR_LAST & (ADDR_LAST[AWIDTH-1:0] == set_addr)))) begin + o_tlast_int <= 1'b1; + end else begin + o_tlast_int <= 1'b0; + end + if (~o_tready_int) begin + error_stb <= 1'b1; + end + end + end + end + + generate + if (USE_FIFO) begin + axi_fifo #( + .WIDTH(WIDTH+1), .SIZE(FIFO_SIZE)) + axi_fifo ( + .clk(clk), .reset(reset), .clear(1'b0), + .i_tdata({o_tlast_int,o_tdata_int}), .i_tvalid(o_tvalid_int), .i_tready(o_tready_int), + .o_tdata({o_tlast,o_tdata}), .o_tvalid(o_tvalid), .o_tready(o_tready), + .space(), .occupied()); + end else begin + assign o_tdata = o_tdata_int; + assign o_tlast = o_tlast_int; + assign o_tvalid = o_tvalid_int; + assign o_tready_int = o_tready; + end + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/axi_slave_mux.v b/fpga/usrp3/lib/control/axi_slave_mux.v new file mode 100644 index 000000000..0b217dc92 --- /dev/null +++ b/fpga/usrp3/lib/control/axi_slave_mux.v @@ -0,0 +1,122 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +`ifndef LOG2 +`define LOG2(N) (\ + N < 2 ? 0 : \ + N < 4 ? 1 : \ + N < 8 ? 2 : \ + N < 16 ? 3 : \ + N < 32 ? 4 : \ + N < 64 ? 5 : \ + N < 128 ? 6 : \ + N < 256 ? 7 : \ + N < 512 ? 8 : \ + N < 1024 ? 9 : 10) +`endif + +module axi_slave_mux + #( + parameter FIFO_WIDTH = 64, // AXI4-STREAM data bus width + parameter DST_WIDTH = 16, // Width of DST field we are routing on. + parameter NUM_INPUTS = 2 // number of input AXI buses + ) + ( + input clk, + input reset, + input clear, + // Inputs + input [(FIFO_WIDTH*NUM_INPUTS)-1:0] i_tdata, + input [NUM_INPUTS-1:0] i_tvalid, + input [NUM_INPUTS-1:0] i_tlast, + output [NUM_INPUTS-1:0] i_tready, + // Forwarding Flags + input [NUM_INPUTS-1:0] forward_valid, + output reg [NUM_INPUTS-1:0] forward_ack, + // Output + output [FIFO_WIDTH-1:0] o_tdata, + output o_tvalid, + output o_tlast, + input o_tready + ); + + wire [FIFO_WIDTH-1:0] i_tdata_array [0:NUM_INPUTS-1]; + + reg [`LOG2(NUM_INPUTS):0] select; + reg enable; + + + reg state; + + localparam CHECK_THIS_INPUT = 0; + localparam WAIT_LAST = 1; + + + always @(posedge clk) + if (reset | clear) begin + state <= CHECK_THIS_INPUT; + select <= 0; + enable <= 0; + forward_ack <= 0; + end else begin + case(state) + // Is the currently selected input addressing this slave with a ready packet? + CHECK_THIS_INPUT: begin + if (forward_valid[select]) begin + enable <= 1; + forward_ack[select] <= 1; + state <= WAIT_LAST; + end else if (select == NUM_INPUTS - 1 ) begin + select <= 0; + end else begin + select <= select + 1; + end + end + // Assert ACK immediately to forwarding logic and then wait for end of packet. + WAIT_LAST: begin + + if (i_tlast[select] && i_tvalid[select] && o_tready) begin + if (select == NUM_INPUTS - 1 ) begin + select <= 0; + end else begin + select <= select + 1; + end + state <= CHECK_THIS_INPUT; + forward_ack <= 0; + enable <= 0; + end else begin + forward_ack[select] <= 1; + enable <= 1; + end + end + endcase // case(state) + end + + // + // Combinatorial mux + // + genvar m; + + generate + for (m = 0; m < NUM_INPUTS; m = m + 1) begin: form_buses + assign i_tdata_array[m] = i_tdata[(m*FIFO_WIDTH)+FIFO_WIDTH-1:m*FIFO_WIDTH]; + end + endgenerate + + assign o_tdata = i_tdata_array[select]; + assign o_tvalid = enable && i_tvalid[select]; + assign o_tlast = enable && i_tlast[select]; + // assign i_tready = {NUM_INPUTS{o_tready}} & (enable << select); + + generate + for (m = 0; m < NUM_INPUTS; m = m + 1) begin: form_ready + assign i_tready[m] = o_tready && enable && (select == m); + end + endgenerate + + +endmodule // axi_slave_mux diff --git a/fpga/usrp3/lib/control/axi_test_vfifo.v b/fpga/usrp3/lib/control/axi_test_vfifo.v new file mode 100644 index 000000000..1d37bfd1b --- /dev/null +++ b/fpga/usrp3/lib/control/axi_test_vfifo.v @@ -0,0 +1,145 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// +// Test Virtual FIFO's by streaming modulo 2^32 counter (replicated in upper +// and lower 32bits). Test result by tracking count on receive and using +// sticky flag for error indication. +// Also provide signal from MSB of 32bit count to blink LED. +// + +module axi_test_vfifo + #(parameter PACKET_SIZE = 128) + ( + input aclk, + input aresetn, + input enable, + // AXI Stream Out + output reg out_axis_tvalid, + input out_axis_tready, + output [63 : 0] out_axis_tdata, + output reg [7 : 0] out_axis_tstrb, + output reg [7 : 0] out_axis_tkeep, + output reg out_axis_tlast, + output reg [0 : 0] out_axis_tid, + output reg [0 : 0] out_axis_tdest, + input vfifo_full, + // AXI Stream In + input in_axis_tvalid, + output reg in_axis_tready, + input [63 : 0] in_axis_tdata, + input [7 : 0] in_axis_tstrb, + input [7 : 0] in_axis_tkeep, + input in_axis_tlast, + input [0 : 0] in_axis_tid, + input [0 : 0] in_axis_tdest, + // Flags + output reg flag_error, + output heartbeat_in, + output heartbeat_out, + output [31:0] expected_count + ); + + + reg [31:0] out_count; + reg [31:0] in_count; + reg [63:0] in_axis_tdata_reg; + reg in_data_valid; + + + + // + // Output + // + always @(posedge aclk) + if (!aresetn) begin + out_count <= 0; + out_axis_tvalid <= 0; + out_axis_tid <= 0; // Don't care. + out_axis_tdest <= 0; // Only use port 0 of VFIFO. + out_axis_tstrb <= 0; // Unused in VFIFO + out_axis_tkeep <= 8'hFF; // Always use every byte of data + out_axis_tlast <= 1'b0; + end else if (enable) begin + if (~vfifo_full) begin + // Always ready to output new count value. + out_axis_tvalid <= 1; + if (out_axis_tready) + out_count <= out_count + 1; + // Assert TLAST every PACKET_SIZE beats. + if (out_count[15:0] == PACKET_SIZE) + out_axis_tlast <= 1'b1; + else + out_axis_tlast <= 1'b0; + end else begin + out_axis_tvalid <= 0; + end + end else begin + out_axis_tlast <= 1'b0; + out_axis_tvalid <= 0; + end + + assign out_axis_tdata = {out_count,out_count}; + + assign heartbeat_out = out_count[28]; + + + // + // Input (Ignore TLAST signal) + // + always @(posedge aclk) + if (!aresetn) begin + in_axis_tready <= 0; + in_axis_tdata_reg <= 0; + in_data_valid <= 0; + + end else if (enable) begin + in_axis_tready <= 1; + in_axis_tdata_reg <= in_axis_tdata; + if (in_axis_tvalid) + in_data_valid <= 1; + else + in_data_valid <= 0; + end else begin + in_data_valid <= 0; + in_axis_tready <= 0; + end // else: !if(enable) + + + assign heartbeat_in = in_count[28]; + + // + // Input Checker + // + always @(posedge aclk) + if (!aresetn) begin + in_count <= 0; + flag_error <= 0; + end else if (enable) begin + if (in_data_valid) begin + + if ((in_axis_tdata_reg[63:32] != in_count) || (in_axis_tdata_reg[31:0] != in_count)) + begin + flag_error <= 1; + in_count <= in_axis_tdata_reg[63:32] + 1; + end + else + begin + flag_error <= 0; + in_count <= in_count + 1; + end + + end + end + + assign expected_count = in_count; + + +endmodule // axi_test_vfifo + + + + diff --git a/fpga/usrp3/lib/control/axil_ctrlport_master.v b/fpga/usrp3/lib/control/axil_ctrlport_master.v new file mode 100644 index 000000000..b1b5a7d2c --- /dev/null +++ b/fpga/usrp3/lib/control/axil_ctrlport_master.v @@ -0,0 +1,248 @@ +// +// Copyright 2019 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axil_ctrlport_master +// Description: +// An AXI4-Lite read/write control port adapter +// +// Converts AXI4-Lite transactions into control port requests. +// Converts all AXI requests to control port by only forwarding the +// CTRLPORT_AWIDTH LSBs of the address. +// +// Limitation: +// The control port interface will only use address, data, byte enable and +// wr/rd flags. All other signals are tied to 0. + + +module axil_ctrlport_master #( + parameter TIMEOUT = 10, // log2(timeout). Control port will timeout after 2^TIMEOUT AXI clock cycles + parameter AXI_AWIDTH = 17, // Width of the AXI bus. Aliasing occurs of AXI_AWIDTH > CTRLPORT_AWIDTH + parameter CTRLPORT_AWIDTH = 17 // Number of address LSBs forwarded to m_ctrlport_req_addr +)( + //Clock and reset + input wire s_axi_aclk, + input wire s_axi_aresetn, + // AXI4-Lite: Write address port (domain: s_axi_aclk) + input wire [AXI_AWIDTH-1:0] s_axi_awaddr, + input wire s_axi_awvalid, + output reg s_axi_awready, + // AXI4-Lite: Write data port (domain: s_axi_aclk) + input wire [31:0] s_axi_wdata, + input wire [ 3:0] s_axi_wstrb, + input wire s_axi_wvalid, + output reg s_axi_wready, + // AXI4-Lite: Write response port (domain: s_axi_aclk) + output reg [ 1:0] s_axi_bresp = 0, + output reg s_axi_bvalid, + input wire s_axi_bready, + // AXI4-Lite: Read address port (domain: s_axi_aclk) + input wire [AXI_AWIDTH-1:0] s_axi_araddr, + input wire s_axi_arvalid, + output reg s_axi_arready, + // AXI4-Lite: Read data port (domain: s_axi_aclk) + output reg [31:0] s_axi_rdata = 0, + output reg [ 1:0] s_axi_rresp = 0, + output reg s_axi_rvalid, + input wire s_axi_rready, + // Control port master request interface + output reg m_ctrlport_req_wr, + output reg m_ctrlport_req_rd, + output reg [19:0] m_ctrlport_req_addr = 0, + output wire [ 9:0] m_ctrlport_req_portid, + output wire [15:0] m_ctrlport_req_rem_epid, + output wire [ 9:0] m_ctrlport_req_rem_portid, + output reg [31:0] m_ctrlport_req_data = 0, + output reg [ 3:0] m_ctrlport_req_byte_en = 0, + output wire m_ctrlport_req_has_time, + output wire [63:0] m_ctrlport_req_time, + // Control port master response interface + input wire m_ctrlport_resp_ack, + input wire [ 1:0] m_ctrlport_resp_status, + input wire [31:0] m_ctrlport_resp_data +); + + `include "../axi/axi_defs.v" + `include "../rfnoc/core/ctrlport.vh" + + //---------------------------------------------------------- + // unused ctrlport outputs + //---------------------------------------------------------- + assign m_ctrlport_req_portid = 10'b0; + assign m_ctrlport_req_rem_epid = 16'b0; + assign m_ctrlport_req_rem_portid = 10'b0; + assign m_ctrlport_req_has_time = 1'b0; + assign m_ctrlport_req_time = 64'b0; + + //---------------------------------------------------------- + // Address calculation + //---------------------------------------------------------- + // define configuration for the address calculation + localparam [CTRLPORT_ADDR_W-1:0] ADDRESS_MASK = {CTRLPORT_ADDR_W {1'b0}} | {CTRLPORT_AWIDTH {1'b1}}; + + // bits to extract from AXI address + localparam AXI_ADDR_BITS_TO_FORWARD = (AXI_AWIDTH < CTRLPORT_ADDR_W) ? AXI_AWIDTH : CTRLPORT_ADDR_W; + + //---------------------------------------------------------- + // State machine for read and write + //---------------------------------------------------------- + localparam IDLE = 4'd0; + localparam READ_INIT = 4'd1; + localparam WRITE_INIT = 4'd2; + localparam READ_TRANSFER = 4'd3; + localparam WRITE_TRANSFER = 4'd4; + localparam READ_IN_PROGRESS = 4'd5; + localparam WRITE_IN_PROGRESS = 4'd6; + localparam WRITE_DONE = 4'd7; + localparam READ_DONE = 4'd8; + + reg [3:0] state; + reg [TIMEOUT-1:0] timeout_counter; + + always @ (posedge s_axi_aclk) begin + if (~s_axi_aresetn) begin + state <= IDLE; + + // clear AXI feedback paths and controlport requests + s_axi_awready <= 1'b0; + s_axi_wready <= 1'b0; + s_axi_bvalid <= 1'b0; + s_axi_arready <= 1'b0; + s_axi_rvalid <= 1'b0; + m_ctrlport_req_rd <= 1'b0; + m_ctrlport_req_wr <= 1'b0; + end else begin + case (state) + // decide whether a read or write should be handled + IDLE: begin + timeout_counter <= {TIMEOUT {1'b1}}; + + if (s_axi_arvalid) begin + state <= READ_INIT; + end + else if (s_axi_awvalid) begin + state <= WRITE_INIT; + end + end + + // wait for FIFO to get read to assign valid + READ_INIT: begin + // signal ready to upstream module + s_axi_arready <= 1'b1; + + state <= READ_TRANSFER; + end + + // transfer data to FIFO + READ_TRANSFER: begin + // clear ready flag from READ_INIT state + s_axi_arready <= 1'b0; + // transfer data to controlport + m_ctrlport_req_rd <= 1'b1; + m_ctrlport_req_addr <= s_axi_araddr[AXI_ADDR_BITS_TO_FORWARD-1:0] & ADDRESS_MASK; + m_ctrlport_req_byte_en <= 4'b1111; + + state <= READ_IN_PROGRESS; + end + + // wait for controlport response is available + READ_IN_PROGRESS: begin + // clear read flag from previous state + m_ctrlport_req_rd <= 1'b0; + + //decrement timeout + timeout_counter <= timeout_counter - 1; + + if (m_ctrlport_resp_ack == 1'b1 || timeout_counter == 0) begin + s_axi_rvalid <= 1'b1; + s_axi_rdata <= m_ctrlport_resp_data; + s_axi_rresp <= `AXI4_RESP_OKAY; + + // use AXI DECERR to inform about failed transaction + if (timeout_counter == 0) begin + s_axi_rresp <= `AXI4_RESP_DECERR; + end else begin + // if controlport response is not OKAY use AXI SLVERR to propagate error + if (m_ctrlport_resp_status != CTRL_STS_OKAY) begin + s_axi_rresp <= `AXI4_RESP_SLVERR; + end + end + + state <= READ_DONE; + end + end + + // wait until read response is transferred + READ_DONE: begin + if (s_axi_rready) begin + s_axi_rvalid <= 1'b0; + state <= IDLE; + end + end + + //wait for FIFO and data to process + WRITE_INIT: begin + if (s_axi_wvalid) begin + s_axi_awready <= 1'b1; + s_axi_wready <= 1'b1; + state <= WRITE_TRANSFER; + end + end + + // transfer data to FIFO + WRITE_TRANSFER: begin + // clear ready flags from READ_INIT state + s_axi_awready <= 1'b0; + s_axi_wready <= 1'b0; + // transfer data to controlport + m_ctrlport_req_wr <= 1'b1; + m_ctrlport_req_addr <= s_axi_awaddr[AXI_ADDR_BITS_TO_FORWARD-1:0] & ADDRESS_MASK; + m_ctrlport_req_data <= s_axi_wdata; + m_ctrlport_req_byte_en <= s_axi_wstrb; + + state <= WRITE_IN_PROGRESS; + end + + // wait for write to complete + WRITE_IN_PROGRESS: begin + // clear write flag from previous state + m_ctrlport_req_wr <= 1'b0; + + //decrement timeout + timeout_counter <= timeout_counter - 1; + + if (m_ctrlport_resp_ack == 1'b1 || timeout_counter == 0) begin + s_axi_bvalid <= 1'b1; + s_axi_rdata <= 32'b0; + s_axi_bresp <= `AXI4_RESP_OKAY; + + // use AXI DECERR to inform about failed transaction + if (timeout_counter == 0) begin + s_axi_bresp <= `AXI4_RESP_DECERR; + end else begin + // if controlport response is not OKAY use AXI SLVERR to propagate error + if (m_ctrlport_resp_status != CTRL_STS_OKAY) begin + s_axi_bresp <= `AXI4_RESP_SLVERR; + end + end + + state <= WRITE_DONE; + end + end + + WRITE_DONE: begin + if (s_axi_bready) begin + state <= IDLE; + s_axi_bvalid <= 1'b0; + end + end + + default: begin + state <= IDLE; + end + endcase + end + end + +endmodule diff --git a/fpga/usrp3/lib/control/axil_regport_master.v b/fpga/usrp3/lib/control/axil_regport_master.v new file mode 100644 index 000000000..de23e7697 --- /dev/null +++ b/fpga/usrp3/lib/control/axil_regport_master.v @@ -0,0 +1,228 @@ +// +// Copyright 2016-2017 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +// An AXI4-Lite read/write register port adapter +// +// Converts memory mapped flow controlled AXI4-Lite transactions into a much +// simpler non flow controlled write and read register bus. +// +// WRITE Transaction: +// - Transaction completes in one cycle +// - Valid, Strobe, Address and Data asserted in same cycle +// __ __ __ __ +// clk __| |__| |__| |__| |__ +// _____ +// reg_wr_req ________| |___________ +// _____ +// reg_wr_keep XXXXXXXX|_____|XXXXXXXXXXX +// _____ +// reg_wr_addr XXXXXXXX|_____|XXXXXXXXXXX +// _____ +// reg_wr_data XXXXXXXX|_____|XXXXXXXXXXX +//; +// READ Transaction: +// - Transaction request completes in one cycle, with valid and address assertion +// - Transaction response must complete in at least one cycle with resp and data +// - resp must be asserted between 1 and pow(2, TIMEOUT) cycles otherwise the read will timeout +// __ __ __ __ __ +// clk __| |__| |__| |__| |__| |__ +// _____ +// reg_rd_req ________| |_________________ +// _____ +// reg_rd_addr XXXXXXXX|_____|XXXXXXXXXXXXXXXXX +// _____ +// reg_rd_resp ____________________| |_____ +// _____ +// reg_rd_data XXXXXXXXXXXXXXXXXXXX|_____|XXXXX + + +module axil_regport_master #( + parameter DWIDTH = 32, // Width of the AXI4-Lite data bus (must be 32 or 64) + parameter AWIDTH = 32, // Width of the address bus + parameter WRBASE = 32'h0, // Write address base + parameter RDBASE = 32'h0, // Read address base + parameter TIMEOUT = 10 // log2(timeout). Read will timeout after (2^TIMEOUT - 1) cycles +)( + // Clock and reset + input s_axi_aclk, + input s_axi_aresetn, + input reg_clk, + // AXI4-Lite: Write address port (domain: s_axi_aclk) + input [AWIDTH-1:0] s_axi_awaddr, + input s_axi_awvalid, + output reg s_axi_awready, + // AXI4-Lite: Write data port (domain: s_axi_aclk) + input [DWIDTH-1:0] s_axi_wdata, + input [DWIDTH/8-1:0] s_axi_wstrb, + input s_axi_wvalid, + output reg s_axi_wready, + // AXI4-Lite: Write response port (domain: s_axi_aclk) + output reg [1:0] s_axi_bresp, + output reg s_axi_bvalid, + input s_axi_bready, + // AXI4-Lite: Read address port (domain: s_axi_aclk) + input [AWIDTH-1:0] s_axi_araddr, + input s_axi_arvalid, + output reg s_axi_arready, + // AXI4-Lite: Read data port (domain: s_axi_aclk) + output reg [DWIDTH-1:0] s_axi_rdata, + output reg [1:0] s_axi_rresp, + output reg s_axi_rvalid, + input s_axi_rready, + // Register port: Write port (domain: reg_clk) + output reg_wr_req, + output [AWIDTH-1:0] reg_wr_addr, + output [DWIDTH-1:0] reg_wr_data, + output [DWIDTH/8-1:0] reg_wr_keep, + // Register port: Read port (domain: reg_clk) + output reg_rd_req, + output [AWIDTH-1:0] reg_rd_addr, + input reg_rd_resp, + input [DWIDTH-1:0] reg_rd_data +); + + //NOTE: clog2 only works when assigned to a parameter + // localparam does not work + parameter ADDR_LSB = $clog2(DWIDTH/8); //Do not modify + + //---------------------------------------------------------- + // Write state machine + //---------------------------------------------------------- + reg [AWIDTH-1:0] wr_addr_cache; + wire wr_fifo_valid, wr_fifo_ready; + wire [AWIDTH-1:0] wr_addr_rel = (s_axi_awaddr - WRBASE); + + // Generate s_axi_awready and latch write address + always @(posedge s_axi_aclk) begin + if (!s_axi_aresetn) begin + s_axi_awready <= 1'b0; + wr_addr_cache <= {AWIDTH{1'b0}}; + end else begin + if (~s_axi_awready && s_axi_awvalid && s_axi_wvalid && wr_fifo_ready) begin + s_axi_awready <= 1'b1; + wr_addr_cache <= {wr_addr_rel[AWIDTH-1:ADDR_LSB], {ADDR_LSB{1'b0}}}; + end else begin + s_axi_awready <= 1'b0; + end + end + end + + // Generate s_axi_wready + always @(posedge s_axi_aclk) begin + if (!s_axi_aresetn) begin + s_axi_wready <= 1'b0; + end else begin + if (~s_axi_wready && s_axi_wvalid && s_axi_awvalid) + s_axi_wready <= 1'b1; + else + s_axi_wready <= 1'b0; + end + end + + // Generate write response + assign wr_fifo_valid = s_axi_awready && s_axi_awvalid && s_axi_wready && s_axi_wvalid && ~s_axi_bvalid; + + always @(posedge s_axi_aclk) begin + if (!s_axi_aresetn) begin + s_axi_bvalid <= 1'b0; + s_axi_bresp <= 2'b0; + end else begin + if (wr_fifo_valid && wr_fifo_ready) begin + // indicates a valid write response is available + s_axi_bvalid <= 1'b1; + s_axi_bresp <= 2'b0; // 'OKAY' response + end else begin + if (s_axi_bready && s_axi_bvalid) + s_axi_bvalid <= 1'b0; + end + end + end + + axi_fifo_2clk #( .WIDTH(DWIDTH/8 + AWIDTH + DWIDTH), .SIZE(0) ) wr_fifo_2clk_i ( + .reset(~s_axi_aresetn), .i_aclk(s_axi_aclk), + .i_tdata({s_axi_wstrb, wr_addr_cache, s_axi_wdata}), + .i_tvalid(wr_fifo_valid), .i_tready(wr_fifo_ready), + .o_aclk(reg_clk), + .o_tdata({reg_wr_keep, reg_wr_addr, reg_wr_data}), + .o_tvalid(reg_wr_req), .o_tready(1'b1) + ); + + //---------------------------------------------------------- + // Read state machine + //---------------------------------------------------------- + reg [TIMEOUT-1:0] read_pending_ctr = {TIMEOUT{1'b0}}; + wire read_timed_out = (read_pending_ctr == {{(TIMEOUT-1){1'b0}}, 1'b1}); + wire read_pending = (read_pending_ctr != {TIMEOUT{1'b0}}); + wire [AWIDTH-1:0] rd_addr_rel = (s_axi_araddr - RDBASE); + + wire rdreq_fifo_ready, rdresp_fifo_valid; + wire [DWIDTH-1:0] rdresp_fifo_data; + + // Generate s_axi_arready and latch read address + always @(posedge s_axi_aclk) begin + if (!s_axi_aresetn) begin + s_axi_arready <= 1'b0; + read_pending_ctr <= {TIMEOUT{1'b0}}; + end else begin + if (~s_axi_arready && s_axi_arvalid && rdreq_fifo_ready) begin + s_axi_arready <= 1'b1; + read_pending_ctr <= {TIMEOUT{1'b1}}; + end else begin + s_axi_arready <= 1'b0; + end + if (read_pending) begin + if (rdresp_fifo_valid && ~s_axi_rvalid) + read_pending_ctr <= {TIMEOUT{1'b0}}; + else + read_pending_ctr <= read_pending_ctr - 1'b1; + end + end + end + + // Perform read transaction + always @(posedge s_axi_aclk) begin + if (!s_axi_aresetn) begin + s_axi_rvalid <= 1'b0; + s_axi_rresp <= 2'b00; + s_axi_rdata <= 0; + end else begin + if (read_pending && rdresp_fifo_valid && ~s_axi_rvalid) begin + // Valid read data is available at the read data bus + s_axi_rvalid <= 1'b1; + s_axi_rresp <= 2'b00; // 'OKAY' response + s_axi_rdata <= rdresp_fifo_data; + end else if (read_pending && read_timed_out && ~s_axi_rvalid) begin + // Read timed out. Assert error. + s_axi_rvalid <= 1'b1; + s_axi_rresp <= 2'b10; // 'SLVERR' response + s_axi_rdata <= {DWIDTH{1'b1}}; + end else if (s_axi_rvalid && s_axi_rready) begin + // Read data is accepted by the master + s_axi_rvalid <= 1'b0; + end + end + end + + axi_fifo_2clk #( .WIDTH(AWIDTH), .SIZE(0) ) readreq_fifo_2clk_i ( + .reset(~s_axi_aresetn), .i_aclk(s_axi_aclk), + .i_tdata({rd_addr_rel[AWIDTH-1:ADDR_LSB], {ADDR_LSB{1'b0}}}), + .i_tvalid(s_axi_arready && s_axi_arvalid), .i_tready(rdreq_fifo_ready), + .o_aclk(reg_clk), + .o_tdata(reg_rd_addr), + .o_tvalid(reg_rd_req), .o_tready(1'b1) + ); + + axi_fifo_2clk #( .WIDTH(DWIDTH), .SIZE(0) ) rdresp_fifo_2clk_i ( + .reset(~s_axi_aresetn), .i_aclk(reg_clk), + .i_tdata(reg_rd_data), + .i_tvalid(reg_rd_resp), .i_tready(/* lossy */), + .o_aclk(s_axi_aclk), + .o_tdata(rdresp_fifo_data), + .o_tvalid(rdresp_fifo_valid), .o_tready(~read_pending || (s_axi_rvalid && (s_axi_rresp == 2'b00))) + ); + +endmodule diff --git a/fpga/usrp3/lib/control/axil_to_ni_regport.v b/fpga/usrp3/lib/control/axil_to_ni_regport.v new file mode 100644 index 000000000..876991ed9 --- /dev/null +++ b/fpga/usrp3/lib/control/axil_to_ni_regport.v @@ -0,0 +1,164 @@ +// +// Copyright 2016 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// AXI4lite to NI Register Port interface +// + +module axil_to_ni_regport #( + parameter RP_AWIDTH = 16, + parameter RP_DWIDTH = 32, + parameter TIMEOUT = 512 +)( + input s_axi_aclk, + input s_axi_areset, + + // AXI4lite interface + input [31:0] s_axi_awaddr, + input s_axi_awvalid, + output s_axi_awready, + input [31:0] s_axi_wdata, + input [3:0] s_axi_wstrb, + input s_axi_wvalid, + output s_axi_wready, + output [1:0] s_axi_bresp, + output s_axi_bvalid, + input s_axi_bready, + input [31:0] s_axi_araddr, + input s_axi_arvalid, + output s_axi_arready, + output [31:0] s_axi_rdata, + output [1:0] s_axi_rresp, + output s_axi_rvalid, + input s_axi_rready, + + // RegPort interface, the out vs in + // is seen from the slave device + // hooked up to the regport + output reg_port_in_rd, + output reg_port_in_wt, + output [RP_AWIDTH-1:0] reg_port_in_addr, + output [RP_DWIDTH-1:0] reg_port_in_data, + input [RP_DWIDTH-1:0] reg_port_out_data, + input reg_port_out_ready +); + + localparam IDLE = 3'd0; + localparam READ_INIT = 3'd1; + localparam WRITE_INIT = 3'd2; + localparam READ_IN_PROGRESS = 3'd3; + localparam WRITE_IN_PROGRESS = 3'd4; + localparam WRITE_DONE = 3'd5; + localparam READ_DONE = 3'd6; + + reg [RP_AWIDTH-1:0] addr; + reg [RP_DWIDTH-1:0] rb_data; + reg [RP_DWIDTH-1:0] wr_data; + reg [2:0] state; + reg [9:0] count; + reg [1:0] rresp; + reg [1:0] bresp; + + always @ (posedge s_axi_aclk) begin + if (s_axi_areset) begin + state <= IDLE; + addr <= 'd0; + rb_data <= 'd0; + wr_data <= 'd0; + + count <= 10'd0; + rresp <= 2'd0; + bresp <= 2'd0; + end + else case (state) + + IDLE: begin + if (s_axi_arvalid) begin + state <= READ_INIT; + addr <= s_axi_araddr[RP_AWIDTH-1:0]; + end + else if (s_axi_awvalid) begin + state <= WRITE_INIT; + addr <= s_axi_awaddr[RP_AWIDTH-1:0]; + end + end + + READ_INIT: begin + state <= READ_IN_PROGRESS; + count <= 10'd0; + rresp <= 2'b00; + end + + READ_IN_PROGRESS: begin + if (reg_port_out_ready) begin + rb_data <= reg_port_out_data; + state <= READ_DONE; + end + else if (count >= TIMEOUT) begin + state <= READ_DONE; + rresp <= 2'b10; + end + else begin + count <= count + 1'b1; + end + end + + READ_DONE: begin + if (s_axi_rready) begin + state <= IDLE; + end + end + + WRITE_INIT: begin + if (s_axi_wvalid) begin + wr_data <= s_axi_wdata[RP_DWIDTH-1:0]; + state <= WRITE_IN_PROGRESS; + count <= 10'd0; + bresp <= 2'b00; + end + end + + WRITE_IN_PROGRESS: begin + if (reg_port_out_ready) begin + state <= WRITE_DONE; + end + else if (count >= TIMEOUT) begin + state <= READ_DONE; + bresp <= 2'b10; + end + else begin + count <= count + 1'b1; + end + end + + WRITE_DONE: begin + if (s_axi_bready) + state <= IDLE; + end + + default: begin + state <= IDLE; + end + + endcase + end + + assign s_axi_awready = (state == IDLE); + assign s_axi_wready = (state == WRITE_INIT); + assign s_axi_bvalid = (state == WRITE_DONE); + assign s_axi_bresp = bresp; + + assign s_axi_arready = (state == IDLE); + assign s_axi_rdata = rb_data; + assign s_axi_rvalid = (state == READ_DONE); + assign s_axi_rresp = rresp; + + assign reg_port_in_wt = (state == WRITE_INIT) & s_axi_wvalid; + assign reg_port_in_data = (state == WRITE_INIT) ? s_axi_wdata : wr_data; + assign reg_port_in_addr = addr; + + assign reg_port_in_rd = (state == READ_INIT); + +endmodule diff --git a/fpga/usrp3/lib/control/bin2gray.v b/fpga/usrp3/lib/control/bin2gray.v new file mode 100644 index 000000000..bd68cdb80 --- /dev/null +++ b/fpga/usrp3/lib/control/bin2gray.v @@ -0,0 +1,30 @@ +// +// Copyright 2011 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + + + +module bin2gray + #(parameter WIDTH=8) + (input [WIDTH-1:0] bin, + output [WIDTH-1:0] gray); + + assign gray = (bin >> 1) ^ bin; + +endmodule // bin2gray diff --git a/fpga/usrp3/lib/control/binary_encoder.v b/fpga/usrp3/lib/control/binary_encoder.v new file mode 100644 index 000000000..6262b3df0 --- /dev/null +++ b/fpga/usrp3/lib/control/binary_encoder.v @@ -0,0 +1,45 @@ +// +// Copyright 2013 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +`define log2(N) ( N < 2 ? 0 : \ + N < 4 ? 1 : \ + N < 8 ? 2 : \ + N < 16 ? 3 : \ + N < 32 ? 4 : \ + N < 64 ? 5 : \ + N < 128 ? 6 : \ + N < 256 ? 7 : \ + N < 512 ? 8 : \ + N < 1024 ? 9 : \ + 10 \ + ) + +module binary_encoder +#( + parameter SIZE = 16 +) +( + input [SIZE-1:0] in, + output [`log2(SIZE)-1:0] out +); + + genvar m,n; + + generate + // Loop enough times to represent the total number of input bits as an encoded value + for (m = 0; m <= `log2(SIZE-1); m = m + 1) begin: expand_or_tree + wire [SIZE-1:0] encoding; + // Build enable mask by iterating through every input bit. + for (n = 0; n < SIZE ; n = n + 1) begin: encode_this_bit + assign encoding[n] = n[m]; + end + // OR tree for this output bit with appropriate bits enabled. + assign out[m] = |(encoding & in); + end + endgenerate + +endmodule // binary_encoder diff --git a/fpga/usrp3/lib/control/db_control.v b/fpga/usrp3/lib/control/db_control.v new file mode 100644 index 000000000..757657a22 --- /dev/null +++ b/fpga/usrp3/lib/control/db_control.v @@ -0,0 +1,148 @@ +// +// Copyright 2016 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module db_control #( + // Drive SPI core with input spi_clk instead of ce_clk. This is useful if ce_clk is very slow which + // would cause spi transactions to take a long time. WARNING: This adds a clock crossing FIFO! + parameter USE_SPI_CLK = 0, + parameter SR_BASE = 160, + parameter RB_BASE = 16, + parameter NUM_SPI_SEN = 8 +)( + // Commands from Radio Core + input clk, input reset, + input set_stb, input [7:0] set_addr, input [31:0] set_data, + output reg rb_stb, input [7:0] rb_addr, output reg [63:0] rb_data, + input run_rx, input run_tx, + // Frontend / Daughterboard I/O + input [31:0] misc_ins, output [31:0] misc_outs, + input [31:0] fp_gpio_in, output [31:0] fp_gpio_out, output [31:0] fp_gpio_ddr, input [31:0] fp_gpio_fab, + input [31:0] db_gpio_in, output [31:0] db_gpio_out, output [31:0] db_gpio_ddr, input [31:0] db_gpio_fab, + output [31:0] leds, + input spi_clk, input spi_rst, output [NUM_SPI_SEN-1:0] sen, output sclk, output mosi, input miso +); + + localparam [7:0] SR_MISC_OUTS = SR_BASE + 8'd0; + localparam [7:0] SR_SPI = SR_BASE + 8'd8; + localparam [7:0] SR_LEDS = SR_BASE + 8'd16; + localparam [7:0] SR_FP_GPIO = SR_BASE + 8'd24; + localparam [7:0] SR_DB_GPIO = SR_BASE + 8'd32; + + localparam [7:0] RB_MISC_IO = RB_BASE + 0; + localparam [7:0] RB_SPI = RB_BASE + 1; + localparam [7:0] RB_LEDS = RB_BASE + 2; + localparam [7:0] RB_DB_GPIO = RB_BASE + 3; + localparam [7:0] RB_FP_GPIO = RB_BASE + 4; + + /******************************************************** + ** Settings registers + ********************************************************/ + setting_reg #(.my_addr(SR_MISC_OUTS), .width(32)) sr_misc_outs ( + .clk(clk), .rst(reset), + .strobe(set_stb), .addr(set_addr), .in(set_data), + .out(misc_outs), .changed()); + + // Readback + reg spi_readback_stb_hold; + reg [31:0] spi_readback_hold; + wire [31:0] spi_readback_sync; + wire [31:0] fp_gpio_readback, db_gpio_readback; + always @* begin + case(rb_addr) + // Use a latched spi readback stobe so additional readbacks after a SPI transaction will work + RB_MISC_IO : {rb_stb, rb_data} <= {spi_readback_stb_hold, {misc_ins, misc_outs}}; + RB_SPI : {rb_stb, rb_data} <= {spi_readback_stb_hold, {32'd0, spi_readback_hold}}; + RB_LEDS : {rb_stb, rb_data} <= {spi_readback_stb_hold, {32'd0, leds}}; + RB_DB_GPIO : {rb_stb, rb_data} <= {spi_readback_stb_hold, {32'd0, db_gpio_readback}}; + RB_FP_GPIO : {rb_stb, rb_data} <= {spi_readback_stb_hold, {32'd0, fp_gpio_readback}}; + default : {rb_stb, rb_data} <= {spi_readback_stb_hold, {64'h0BADC0DE0BADC0DE}}; + endcase + end + + /******************************************************** + ** GPIO + ********************************************************/ + gpio_atr #(.BASE(SR_LEDS), .WIDTH(32), .FAB_CTRL_EN(0), .DEFAULT_DDR(32'hFFFF_FFFF), .DEFAULT_IDLE(32'd0)) leds_gpio_atr ( + .clk(clk), .reset(reset), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .rx(run_rx), .tx(run_tx), + .gpio_in(32'd0), .gpio_out(leds), .gpio_ddr(/*unused, assumed output only*/), + .gpio_out_fab(32'h00000000 /*LEDs don't have fabric control*/), .gpio_sw_rb()); + + gpio_atr #(.BASE(SR_FP_GPIO), .WIDTH(32), .FAB_CTRL_EN(1), .DEFAULT_DDR(32'hFFFF_FFFF), .DEFAULT_IDLE(32'd0)) fp_gpio_atr ( + .clk(clk), .reset(reset), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .rx(run_rx), .tx(run_tx), + .gpio_in(fp_gpio_in), .gpio_out(fp_gpio_out), .gpio_ddr(fp_gpio_ddr), + .gpio_out_fab(fp_gpio_fab), .gpio_sw_rb(fp_gpio_readback)); + + gpio_atr #(.BASE(SR_DB_GPIO), .WIDTH(32), .FAB_CTRL_EN(1), .DEFAULT_DDR(32'hFFFF_FFFF), .DEFAULT_IDLE(32'd0)) db_gpio_atr ( + .clk(clk), .reset(reset), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .rx(run_rx), .tx(run_tx), + .gpio_in(db_gpio_in), .gpio_out(db_gpio_out), .gpio_ddr(db_gpio_ddr), + .gpio_out_fab(db_gpio_fab), .gpio_sw_rb(db_gpio_readback)); + + /******************************************************** + ** SPI + ********************************************************/ + wire spi_set_stb; + wire [7:0] spi_set_addr; + wire [31:0] spi_set_data; + wire spi_readback_stb, spi_readback_stb_sync; + wire [31:0] spi_readback; + wire spi_clk_int, spi_rst_int; + generate + if (USE_SPI_CLK) begin + axi_fifo_2clk #(.WIDTH(8 + 32), .SIZE(0)) set_2clk_i ( + .reset(reset), + .i_aclk(clk), .i_tdata({set_addr, set_data}), .i_tvalid(set_stb), .i_tready(), + .o_aclk(spi_clk), .o_tdata({spi_set_addr, spi_set_data}), .o_tvalid(spi_set_stb), .o_tready(spi_set_stb)); + + axi_fifo_2clk #(.WIDTH(32), .SIZE(0)) rb_2clk_i ( + .reset(reset), + .i_aclk(spi_clk), .i_tdata(spi_readback), .i_tvalid(spi_readback_stb), .i_tready(), + .o_aclk(clk), .o_tdata(spi_readback_sync), .o_tvalid(spi_readback_stb_sync), .o_tready(spi_readback_stb_sync)); + + assign spi_clk_int = spi_clk; + assign spi_rst_int = spi_rst; + end else begin + assign spi_set_stb = set_stb; + assign spi_set_addr = set_addr; + assign spi_set_data = set_data; + assign spi_readback_stb_sync = spi_readback_stb; + assign spi_readback_sync = spi_readback; + assign spi_clk_int = clk; + assign spi_rst_int = reset; + end + endgenerate + + // Need to latch spi_readback_stb in case of additional readbacks + // after the initial spi transaction. + always @(posedge clk) begin + if (reset) begin + spi_readback_stb_hold <= 1'b1; + end else begin + if (set_stb & (set_addr == SR_SPI+2 /* Trigger address */)) begin + spi_readback_stb_hold <= 1'b0; + end else if (spi_readback_stb_sync) begin + spi_readback_hold <= spi_readback_sync; + spi_readback_stb_hold <= 1'b1; + end + end + end + + // SPI Core instantiation + // Note: We don't use "ready" because we use readback_stb to backpressure the settings bus + simple_spi_core #(.BASE(SR_SPI), .WIDTH(NUM_SPI_SEN), .CLK_IDLE(0), .SEN_IDLE(8'hFF)) simple_spi_core ( + .clock(spi_clk_int), .reset(spi_rst_int), + .set_stb(spi_set_stb), .set_addr(spi_set_addr), .set_data(spi_set_data), + .readback(spi_readback), .readback_stb(spi_readback_stb), .ready(/* Unused */), + .sen(sen), .sclk(sclk), .mosi(mosi), .miso(miso), + .debug()); + +endmodule diff --git a/fpga/usrp3/lib/control/fe_control.v b/fpga/usrp3/lib/control/fe_control.v new file mode 100644 index 000000000..9e0c6cea5 --- /dev/null +++ b/fpga/usrp3/lib/control/fe_control.v @@ -0,0 +1,70 @@ +// +// Copyright 2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: fe_control +// +// Description: Handle the front end control from the radio settings bus. +// The module gets generated NUM_CHANNELS times to give independent control to +// the individual channels. +// + +module fe_control #( + parameter NUM_CHANNELS = 2, + parameter [7:0] SR_FE_CHAN_OFFSET = 16, + parameter [7:0] SR_TX_FE_BASE = 192, + parameter [7:0] SR_RX_FE_BASE = 200 +)( + input clk, input reset, + // Commands from Radio Core + input set_stb, input [7:0] set_addr, input [31:0] set_data, + input time_sync, + // Radio datapath + input [NUM_CHANNELS-1:0] tx_stb, input [32*NUM_CHANNELS-1:0] tx_data_in, output [32*NUM_CHANNELS-1:0] tx_data_out, + output [NUM_CHANNELS-1:0] rx_stb, input [32*NUM_CHANNELS-1:0] rx_data_in, output [32*NUM_CHANNELS-1:0] rx_data_out +); + + genvar i; + generate for (i = 0; i < NUM_CHANNELS; i = i + 1) + begin + localparam SR_TX_OFFSET_I = SR_TX_FE_BASE + SR_FE_CHAN_OFFSET*i + 0; + localparam SR_TX_OFFSET_Q = SR_TX_FE_BASE + SR_FE_CHAN_OFFSET*i + 1; + localparam SR_TX_MAG_CORRECTION = SR_TX_FE_BASE + SR_FE_CHAN_OFFSET*i + 2; + localparam SR_TX_PHASE_CORRECTION = SR_TX_FE_BASE + SR_FE_CHAN_OFFSET*i + 3; + localparam SR_TX_MUX = SR_TX_FE_BASE + SR_FE_CHAN_OFFSET*i + 4; + + tx_frontend_gen3 #( + .SR_OFFSET_I(SR_TX_OFFSET_I), .SR_OFFSET_Q(SR_TX_OFFSET_Q),.SR_MAG_CORRECTION(SR_TX_MAG_CORRECTION), + .SR_PHASE_CORRECTION(SR_TX_PHASE_CORRECTION), .SR_MUX(SR_TX_MUX), + .BYPASS_DC_OFFSET_CORR(0), .BYPASS_IQ_COMP(0), + .DEVICE("7SERIES") + ) tx_fe_corr_i ( + .clk(clk), .reset(reset), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .tx_stb(tx_stb[i]), .tx_i(tx_data_in[32+(32*i)-1:16+(32*i)]), .tx_q(tx_data_in[16+(32*i)-1:(32*i)]), + .dac_stb(), .dac_i(tx_data_out[32+(32*i)-1:16+(32*i)]), .dac_q(tx_data_out[16+(32*i)-1:(32*i)]) + ); + + localparam SR_RX_MAG_CORRECTION = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 0; + localparam SR_RX_PHASE_CORRECTION = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 1; + localparam SR_RX_OFFSET_I = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 2; + localparam SR_RX_OFFSET_Q = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 3; + localparam SR_RX_IQ_MAPPING = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 4; + localparam SR_RX_HET_PHASE_INCR = SR_RX_FE_BASE + SR_FE_CHAN_OFFSET*i + 5; + + rx_frontend_gen3 #( + .SR_MAG_CORRECTION(SR_RX_MAG_CORRECTION), .SR_PHASE_CORRECTION(SR_RX_PHASE_CORRECTION), .SR_OFFSET_I(SR_RX_OFFSET_I), + .SR_OFFSET_Q(SR_RX_OFFSET_Q), .SR_IQ_MAPPING(SR_RX_IQ_MAPPING), .SR_HET_PHASE_INCR(SR_RX_HET_PHASE_INCR), + .BYPASS_DC_OFFSET_CORR(0), .BYPASS_IQ_COMP(0), .BYPASS_REALMODE_DSP(0), + .DEVICE("7SERIES") + ) rx_fe_corr_i ( + .clk(clk), .reset(reset), .sync_in(time_sync), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .adc_stb(1'b1), .adc_i(rx_data_in[32+(32*i)-1:16+(32*i)]), .adc_q(rx_data_in[16+(32*i)-1:(32*i)]), + .rx_stb(rx_stb[i]), .rx_i(rx_data_out[32+(32*i)-1:16+(32*i)]), .rx_q(rx_data_out[16+(32*i)-1:(32*i)]) + ); + end + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/filter_bad_sid.v b/fpga/usrp3/lib/control/filter_bad_sid.v new file mode 100644 index 000000000..41e42c516 --- /dev/null +++ b/fpga/usrp3/lib/control/filter_bad_sid.v @@ -0,0 +1,78 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Discard silently packets which don't match this SID + +module filter_bad_sid + ( + input clk, + input reset, + input clear, + // + input [64:0] i_tdata, + input i_tvalid, + output i_tready, + // + output [64:0] o_tdata, + output o_tvalid, + input o_tready, + // + output reg [15:0] count + ); + + reg [1:0] state; + wire good_sid; + wire qualify_i_tvalid; + + localparam IDLE = 0; + localparam ACCEPT = 1; + localparam DISCARD = 2; + + + always @(posedge clk) + if (reset | clear) begin + state <= IDLE; + count <= 0; + end else + case(state) + // + IDLE: begin + if (i_tvalid && i_tready) + if (good_sid) + state <= ACCEPT; + else begin + count <= count + 1; + state <= DISCARD; + end + end + // + ACCEPT: begin + if (i_tvalid && i_tready && i_tdata[64]) + state <= IDLE; + end + // + DISCARD: begin + if (i_tvalid && i_tready && i_tdata[64]) + state <= IDLE; + end + endcase // case(state) + + assign good_sid = ((i_tdata[15:0] == 16'h00A0) || (i_tdata[15:0] == 16'h00B0)); + + assign qualify_i_tvalid = (state == IDLE) ? good_sid : ((state == DISCARD) ? 1'b0 : 1'b1); + + // + // Buffer output, break combinatorial timing paths + // + axi_fifo_short #(.WIDTH(65)) fifo_short + ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata(i_tdata), .i_tvalid(i_tvalid && qualify_i_tvalid), .i_tready(i_tready), + .o_tdata(o_tdata), .o_tvalid(o_tvalid), .o_tready(o_tready), + .space(), .occupied() + ); + +endmodule // filter_bad_sid diff --git a/fpga/usrp3/lib/control/gpio_atr.v b/fpga/usrp3/lib/control/gpio_atr.v new file mode 100644 index 000000000..f99ac55b1 --- /dev/null +++ b/fpga/usrp3/lib/control/gpio_atr.v @@ -0,0 +1,98 @@ + +// +// Copyright 2011 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module gpio_atr #( + parameter BASE = 0, + parameter WIDTH = 32, + parameter FAB_CTRL_EN = 0, + parameter DEFAULT_DDR = 0, + parameter DEFAULT_IDLE = 0 +) ( + input clk, input reset, //Clock and reset + input set_stb, input [7:0] set_addr, input [31:0] set_data, //Settings control interface + input rx, input tx, //Run signals that indicate tx and rx operation + input [WIDTH-1:0] gpio_in, //GPIO input state + output reg [WIDTH-1:0] gpio_out, //GPIO output state + output reg [WIDTH-1:0] gpio_ddr, //GPIO direction (0=input, 1=output) + input [WIDTH-1:0] gpio_out_fab, //GPIO driver bus from fabric + output reg [WIDTH-1:0] gpio_sw_rb //Readback value for software +); + genvar i; + + wire [WIDTH-1:0] in_idle, in_tx, in_rx, in_fdx, ddr_reg, atr_disable, fabric_ctrl; + reg [WIDTH-1:0] ogpio, igpio; + + setting_reg #(.my_addr(BASE+0), .width(WIDTH), .at_reset(DEFAULT_IDLE)) reg_idle ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(in_idle),.changed()); + + setting_reg #(.my_addr(BASE+1), .width(WIDTH)) reg_rx ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(in_rx),.changed()); + + setting_reg #(.my_addr(BASE+2), .width(WIDTH)) reg_tx ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(in_tx),.changed()); + + setting_reg #(.my_addr(BASE+3), .width(WIDTH)) reg_fdx ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(in_fdx),.changed()); + + setting_reg #(.my_addr(BASE+4), .width(WIDTH), .at_reset(DEFAULT_DDR)) reg_ddr ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(ddr_reg),.changed()); + + setting_reg #(.my_addr(BASE+5), .width(WIDTH)) reg_atr_disable ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(atr_disable),.changed()); + + generate if (FAB_CTRL_EN == 1) begin + setting_reg #(.my_addr(BASE+6), .width(WIDTH)) reg_fabric_ctrl ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), .in(set_data), + .out(fabric_ctrl),.changed()); + end else begin + assign fabric_ctrl = {WIDTH{1'b0}}; + end endgenerate + + //Pipeline rx and tx signals for easier timing closure + reg rx_d, tx_d; + always @(posedge clk) + {rx_d, tx_d} <= {rx, tx}; + + generate for (i=0; i<WIDTH; i=i+1) begin: gpio_mux_gen + //ATR selection MUX + always @(posedge clk) begin + case({atr_disable[i], tx_d, rx_d}) + 3'b000: ogpio[i] <= in_idle[i]; + 3'b001: ogpio[i] <= in_rx[i]; + 3'b010: ogpio[i] <= in_tx[i]; + 3'b011: ogpio[i] <= in_fdx[i]; + default: ogpio[i] <= in_idle[i]; //If ATR mode is disabled, always use IDLE value + endcase + end + + //Pipeline input, output and direction + //For fabric access, insert MUX as close to the IO as possible + always @(posedge clk) begin + gpio_out[i] <= fabric_ctrl[i] ? gpio_out_fab[i] : ogpio[i]; + end + end endgenerate + + always @(posedge clk) + igpio <= gpio_in; + + always @(posedge clk) + gpio_ddr <= ddr_reg; + + //Generate software readback state + generate for (i=0; i<WIDTH; i=i+1) begin: gpio_rb_gen + always @(posedge clk) + gpio_sw_rb[i] <= gpio_ddr[i] ? gpio_out[i] : igpio[i]; + end endgenerate + +endmodule // gpio_atr diff --git a/fpga/usrp3/lib/control/gpio_atr_io.v b/fpga/usrp3/lib/control/gpio_atr_io.v new file mode 100644 index 000000000..4c427177b --- /dev/null +++ b/fpga/usrp3/lib/control/gpio_atr_io.v @@ -0,0 +1,38 @@ + +// +// Copyright 2015 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module gpio_atr_io #( + parameter WIDTH = 32 +) ( + input clk, + input [WIDTH-1:0] gpio_ddr, + input [WIDTH-1:0] gpio_out, + output [WIDTH-1:0] gpio_in, + inout [WIDTH-1:0] gpio_pins +); + + //Instantiate registers in the IOB + (* IOB = "true" *) reg [WIDTH-1:0] gpio_in_iob, gpio_out_iob; + always @(posedge clk) begin + gpio_in_iob <= gpio_pins; + gpio_out_iob <= gpio_out; + end + assign gpio_in = gpio_in_iob; + + //Pipeline the data direction bus + reg [WIDTH-1:0] gpio_ddr_reg; + always @(posedge clk) + gpio_ddr_reg <= gpio_ddr; + + //Tristate buffers + genvar i; + generate for (i=0; i<WIDTH; i=i+1) begin: io_tristate_gen + assign gpio_pins[i] = gpio_ddr_reg[i] ? gpio_out_iob[i] : 1'bz; + end endgenerate + +endmodule // gpio_atr_io diff --git a/fpga/usrp3/lib/control/gray2bin.v b/fpga/usrp3/lib/control/gray2bin.v new file mode 100644 index 000000000..c75ad80c6 --- /dev/null +++ b/fpga/usrp3/lib/control/gray2bin.v @@ -0,0 +1,25 @@ +// +// Copyright 2016 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// +// Gray: {a,b,c,d} +// Bits: {a,a^b,a^b^c,a^b^c^d} +// +module gray2bin #( + parameter WIDTH = 8) +( + input [WIDTH-1:0] gray, + output reg [WIDTH-1:0] bin +); + + integer i; + always @(*) begin + bin[WIDTH-1] = gray[WIDTH-1]; + for (i = WIDTH-2; i >= 0; i = i - 1) begin + bin[i] = bin[i+1] ^ gray[i]; + end + end + +endmodule diff --git a/fpga/usrp3/lib/control/map/AUTHORS b/fpga/usrp3/lib/control/map/AUTHORS new file mode 100644 index 000000000..c727d8d3c --- /dev/null +++ b/fpga/usrp3/lib/control/map/AUTHORS @@ -0,0 +1,3 @@ +Contributions from: +Ettus Research, A National Instruments Company +Alex Forencich <alex@alexforencich.com>
\ No newline at end of file diff --git a/fpga/usrp3/lib/control/map/axis_muxed_kv_map.v b/fpga/usrp3/lib/control/map/axis_muxed_kv_map.v new file mode 100644 index 000000000..152cee8eb --- /dev/null +++ b/fpga/usrp3/lib/control/map/axis_muxed_kv_map.v @@ -0,0 +1,206 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: axis_muxed_kv_map +// +// Description: +// +// This module implements a memory that stores key and value (KV) pairs such +// that the value can be looked up using the key (e.g., for a routing table). +// This implementation uses AXI stream for both inserting key-value pairs and +// for looking up a value by its key. It also supports multiple find/result +// AXI streams, which share the same KV map internally. +// +// Values are inserted into the KV map using the axis_insert_* AXI stream. A +// value can be looked up by its key using the axis_find_* AXI stream, in +// which case the resulting value is output on the axis_result_* AXI stream. +// +// Ports: +// +// axis_insert_tdest : Key to insert into the KV map +// axis_insert_tdata : Value to associate with the key in TDEST +// axis_insert_tvalid : Standard AXI stream TVALID +// axis_insert_tready : Standard AXI stream TREADY +// +// axis_find_tdata : Key to look up in the KV map +// axis_find_tvalid : Standard AXI stream TVALID +// axis_find_tready : Standard AXI stream TREADY +// +// axis_result_tdata : Value associated with key that was input on axis_find +// axis_result_tkeep : Indicates if TDATA contains a valid value (i.e., +// TKEEP is 0 if the lookup fails to find a match) +// axis_result_tvalid : Standard AXI stream TVALID +// axis_result_tready : Standard AXI stream TREADY +// +// Parameters: +// +// KEY_WIDTH : Width of the key (axis_insert_tdest, axis_find_tdata) +// VAL_WIDTH : Width of the value (axis_insert_tdata, axis_result_tdata) +// SIZE : Size of the KV map (i.e., 2**SIZE key-value pairs) +// NUM_PORTS : Number of AXI-Stream ports for the find and result interfaces +// + +module axis_muxed_kv_map #( + parameter KEY_WIDTH = 16, + parameter VAL_WIDTH = 32, + parameter SIZE = 6, + parameter NUM_PORTS = 4 +) ( + input wire clk, + input wire reset, + + input wire [KEY_WIDTH-1:0] axis_insert_tdest, + input wire [VAL_WIDTH-1:0] axis_insert_tdata, + input wire axis_insert_tvalid, + output wire axis_insert_tready, + + input wire [(KEY_WIDTH*NUM_PORTS)-1:0] axis_find_tdata, + input wire [NUM_PORTS-1:0] axis_find_tvalid, + output wire [NUM_PORTS-1:0] axis_find_tready, + + output wire [(VAL_WIDTH*NUM_PORTS)-1:0] axis_result_tdata, + output wire [NUM_PORTS-1:0] axis_result_tkeep, + output wire [NUM_PORTS-1:0] axis_result_tvalid, + input wire [NUM_PORTS-1:0] axis_result_tready +); + + localparam MUX_W = $clog2(NUM_PORTS) + KEY_WIDTH; + localparam DEMUX_W = $clog2(NUM_PORTS) + VAL_WIDTH + 1; + genvar i; + + localparam [1:0] ST_IDLE = 2'd0; + localparam [1:0] ST_REQUEST = 2'd1; + localparam [1:0] ST_PENDING = 2'd2; + + //--------------------------------------------------------- + // Demux find ports + //--------------------------------------------------------- + wire [KEY_WIDTH-1:0] find_key, find_key_reg; + wire find_key_stb; + wire [$clog2(NUM_PORTS)-1:0] find_dest, find_dest_reg; + wire find_key_valid, find_key_valid_reg; + wire find_ready; + reg find_in_progress = 1'b0; + wire insert_stb; + wire insert_busy; + wire find_res_stb; + wire [VAL_WIDTH-1:0] find_res_val; + wire find_res_match, find_res_ready; + + wire [(MUX_W*NUM_PORTS)-1:0] mux_tdata; + generate for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_mux_input + assign mux_tdata[(MUX_W*i)+KEY_WIDTH-1:MUX_W*i] = axis_find_tdata[(KEY_WIDTH*i)+:KEY_WIDTH]; + assign mux_tdata[(MUX_W*(i+1))-1:(MUX_W*i)+KEY_WIDTH] = i; + end endgenerate + + axi_mux #( + .WIDTH(KEY_WIDTH+$clog2(NUM_PORTS)), .SIZE(NUM_PORTS), + .PRE_FIFO_SIZE(0), .POST_FIFO_SIZE($clog2(NUM_PORTS)) + ) mux_i ( + .clk(clk), .reset(reset), .clear(1'b0), + .i_tdata(mux_tdata), .i_tlast({NUM_PORTS{1'b1}}), + .i_tvalid(axis_find_tvalid), .i_tready(axis_find_tready), + .o_tdata({find_dest_reg, find_key_reg}), .o_tlast(), + .o_tvalid(find_key_valid_reg), .o_tready(find_ready) + ); + + axi_fifo #( + .WIDTH(KEY_WIDTH+$clog2(NUM_PORTS)), .SIZE(1) + ) mux_reg_i ( + .clk(clk), .reset(reset), .clear(1'b0), + .i_tdata({find_dest_reg, find_key_reg}), + .i_tvalid(find_key_valid_reg), .i_tready(find_ready), + .o_tdata({find_dest, find_key}), + .o_tvalid(find_key_valid), .o_tready(find_res_stb), + .space(), .occupied() + ); + + always @(posedge clk) begin + if (reset) begin + find_in_progress <= 1'b0; + end else begin + if (find_key_stb) begin + find_in_progress <= 1'b1; + end else if (find_res_stb) begin + find_in_progress <= 1'b0; + end + end + end + + // find_key_stb indicates when to begin a new KV map lookup. We must wait + // until the output mux is ready before starting a lookup. + assign find_key_stb = find_key_valid & find_res_ready & ~find_in_progress; + + //--------------------------------------------------------- + // Insert logic + //--------------------------------------------------------- + reg [1:0] ins_state = ST_IDLE; + always @(posedge clk) begin + if (reset) begin + ins_state <= ST_IDLE; + end else begin + case (ins_state) + ST_IDLE: + if (axis_insert_tvalid & ~insert_busy) + ins_state <= ST_REQUEST; + ST_REQUEST: + ins_state <= ST_PENDING; + ST_PENDING: + if (~insert_busy) + ins_state <= ST_IDLE; + default: + ins_state <= ST_IDLE; + endcase + end + end + + assign axis_insert_tready = axis_insert_tvalid & (ins_state == ST_PENDING) & ~insert_busy; + assign insert_stb = axis_insert_tvalid & (ins_state == ST_REQUEST); + + //--------------------------------------------------------- + // KV map instantiation + //--------------------------------------------------------- + kv_map #( + .KEY_WIDTH (KEY_WIDTH), + .VAL_WIDTH (VAL_WIDTH), + .SIZE (SIZE) + ) map_i ( + .clk (clk), + .reset (reset), + .insert_stb (insert_stb), + .insert_key (axis_insert_tdest), + .insert_val (axis_insert_tdata), + .insert_busy (insert_busy), + .find_key_stb (find_key_stb), + .find_key (find_key), + .find_res_stb (find_res_stb), + .find_res_match (find_res_match), + .find_res_val (find_res_val), + .count (/* unused */) + ); + + //--------------------------------------------------------- + // Mux results port + //--------------------------------------------------------- + wire [(DEMUX_W*NUM_PORTS)-1:0] demux_tdata; + wire [DEMUX_W-1:0] hdr; + axi_demux #( + .WIDTH(DEMUX_W), .SIZE(NUM_PORTS), + .PRE_FIFO_SIZE(1), .POST_FIFO_SIZE(0) + ) demux_i ( + .clk(clk), .reset(reset), .clear(1'b0), + .header(hdr), .dest(hdr[DEMUX_W-1:VAL_WIDTH+1]), + .i_tdata({find_dest, find_res_match, find_res_val}), .i_tlast(1'b1), + .i_tvalid(find_res_stb), .i_tready(find_res_ready), + .o_tdata(demux_tdata), .o_tlast(), + .o_tvalid(axis_result_tvalid), .o_tready(axis_result_tready) + ); + + generate for (i = 0; i < NUM_PORTS; i = i + 1) begin : gen_result_output + assign axis_result_tdata[(VAL_WIDTH*i)+:VAL_WIDTH] = demux_tdata[(DEMUX_W*i)+VAL_WIDTH-1:DEMUX_W*i]; + assign axis_result_tkeep[i] = demux_tdata[(DEMUX_W*i)+VAL_WIDTH]; + end endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/map/cam.v b/fpga/usrp3/lib/control/map/cam.v new file mode 100644 index 000000000..6b3237b6f --- /dev/null +++ b/fpga/usrp3/lib/control/map/cam.v @@ -0,0 +1,103 @@ +/* + +Copyright (c) 2015-2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Content Addressable Memory + */ +module cam #( + // search data bus width + parameter DATA_WIDTH = 64, + // memory size in log2(words) + parameter ADDR_WIDTH = 5, + // CAM style (SRL, BRAM) + parameter CAM_STYLE = "SRL", + // width of data bus slices + parameter SLICE_WIDTH = 4 +) +( + input wire clk, + input wire rst, + + input wire [ADDR_WIDTH-1:0] write_addr, + input wire [DATA_WIDTH-1:0] write_data, + input wire write_delete, + input wire write_enable, + output wire write_busy, + + input wire [DATA_WIDTH-1:0] compare_data, + output wire [2**ADDR_WIDTH-1:0] match_many, + output wire [2**ADDR_WIDTH-1:0] match_single, + output wire [ADDR_WIDTH-1:0] match_addr, + output wire match +); + +generate + if (CAM_STYLE == "SRL") begin + cam_srl #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .SLICE_WIDTH(SLICE_WIDTH) + ) + cam_inst ( + .clk(clk), + .rst(rst), + .write_addr(write_addr), + .write_data(write_data), + .write_delete(write_delete), + .write_enable(write_enable), + .write_busy(write_busy), + .compare_data(compare_data), + .match_many(match_many), + .match_single(match_single), + .match_addr(match_addr), + .match(match) + ); + end else if (CAM_STYLE == "BRAM") begin + cam_bram #( + .DATA_WIDTH(DATA_WIDTH), + .ADDR_WIDTH(ADDR_WIDTH), + .SLICE_WIDTH(SLICE_WIDTH) + ) + cam_inst ( + .clk(clk), + .rst(rst), + .write_addr(write_addr), + .write_data(write_data), + .write_delete(write_delete), + .write_enable(write_enable), + .write_busy(write_busy), + .compare_data(compare_data), + .match_many(match_many), + .match_single(match_single), + .match_addr(match_addr), + .match(match) + ); + end +endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/map/cam_bram.v b/fpga/usrp3/lib/control/map/cam_bram.v new file mode 100644 index 000000000..c18df1084 --- /dev/null +++ b/fpga/usrp3/lib/control/map/cam_bram.v @@ -0,0 +1,259 @@ +/* + +Copyright (c) 2015-2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Content Addressable Memory (block RAM based) + */ +module cam_bram #( + // search data bus width + parameter DATA_WIDTH = 64, + // memory size in log2(words) + parameter ADDR_WIDTH = 5, + // width of data bus slices + parameter SLICE_WIDTH = 9 +) +( + input wire clk, + input wire rst, + + input wire [ADDR_WIDTH-1:0] write_addr, + input wire [DATA_WIDTH-1:0] write_data, + input wire write_delete, + input wire write_enable, + output wire write_busy, + + input wire [DATA_WIDTH-1:0] compare_data, + output wire [2**ADDR_WIDTH-1:0] match_many, + output wire [2**ADDR_WIDTH-1:0] match_single, + output wire [ADDR_WIDTH-1:0] match_addr, + output wire match +); + +// total number of slices (enough to cover DATA_WIDTH with address inputs) +localparam SLICE_COUNT = (DATA_WIDTH + SLICE_WIDTH - 1) / SLICE_WIDTH; +// depth of RAMs +localparam RAM_DEPTH = 2**ADDR_WIDTH; + +localparam [2:0] + STATE_INIT = 3'd0, + STATE_IDLE = 3'd1, + STATE_DELETE_1 = 3'd2, + STATE_DELETE_2 = 3'd3, + STATE_WRITE_1 = 3'd4, + STATE_WRITE_2 = 3'd5; + +reg [2:0] state_reg = STATE_INIT, state_next; + +wire [SLICE_COUNT*SLICE_WIDTH-1:0] compare_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, compare_data}; +wire [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, write_data}; + +reg [SLICE_WIDTH-1:0] count_reg = {SLICE_WIDTH{1'b1}}, count_next; + +reg [SLICE_COUNT*SLICE_WIDTH-1:0] ram_addr = {SLICE_COUNT*SLICE_WIDTH{1'b0}}; +reg [RAM_DEPTH-1:0] set_bit; +reg [RAM_DEPTH-1:0] clear_bit; +reg wr_en; + +reg [ADDR_WIDTH-1:0] write_addr_reg = {ADDR_WIDTH{1'b0}}, write_addr_next; +reg [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded_reg = {SLICE_COUNT*SLICE_WIDTH{1'b0}}, write_data_padded_next; +reg write_delete_reg = 1'b0, write_delete_next; + +reg write_busy_reg = 1'b1; + +assign write_busy = write_busy_reg; + +reg [RAM_DEPTH-1:0] match_raw_out[SLICE_COUNT-1:0]; +reg [RAM_DEPTH-1:0] match_many_raw; + +assign match_many = match_many_raw; + +reg [DATA_WIDTH-1:0] erase_ram [RAM_DEPTH-1:0]; +reg [DATA_WIDTH-1:0] erase_data = {DATA_WIDTH{1'b0}}; +reg erase_ram_wr_en; + +integer i; + +initial begin + for (i = 0; i < RAM_DEPTH; i = i + 1) begin + erase_ram[i] = {SLICE_COUNT*SLICE_WIDTH{1'b0}}; + end +end + +integer k; + +always @* begin + match_many_raw = {RAM_DEPTH{1'b1}}; + for (k = 0; k < SLICE_COUNT; k = k + 1) begin + match_many_raw = match_many_raw & match_raw_out[k]; + end +end + +cam_priority_encoder #( + .WIDTH(RAM_DEPTH), + .LSB_PRIORITY("HIGH") +) +priority_encoder_inst ( + .input_unencoded(match_many_raw), + .output_valid(match), + .output_encoded(match_addr), + .output_unencoded(match_single) +); + +// BRAMs +genvar slice_ind; +generate + for (slice_ind = 0; slice_ind < SLICE_COUNT; slice_ind = slice_ind + 1) begin : slice + localparam W = slice_ind == SLICE_COUNT-1 ? DATA_WIDTH-SLICE_WIDTH*slice_ind : SLICE_WIDTH; + + wire [RAM_DEPTH-1:0] match_data; + wire [RAM_DEPTH-1:0] ram_data; + + ram_2port #( + .DWIDTH(RAM_DEPTH), + .AWIDTH(W) + ) + ram_inst + ( + .clka(clk), + .ena(1'b1), + .wea(1'b0), + .addra(compare_data[SLICE_WIDTH * slice_ind +: W]), + .dia({RAM_DEPTH{1'b0}}), + .doa(match_data), + .clkb(clk), + .enb(1'b1), + .web(wr_en), + .addrb(ram_addr[SLICE_WIDTH * slice_ind +: W]), + .dib((ram_data & ~clear_bit) | set_bit), + .dob(ram_data) + ); + + always @* begin + match_raw_out[slice_ind] <= match_data; + end + end +endgenerate + +// erase +always @(posedge clk) begin + erase_data <= erase_ram[write_addr_next]; + if (erase_ram_wr_en) begin + erase_data <= write_data_padded_reg; + erase_ram[write_addr_next] <= write_data_padded_reg; + end +end + +// write +always @* begin + state_next = STATE_IDLE; + + count_next = count_reg; + ram_addr = erase_data; + set_bit = {RAM_DEPTH{1'b0}}; + clear_bit = {RAM_DEPTH{1'b0}}; + wr_en = 1'b0; + + erase_ram_wr_en = 1'b0; + + write_addr_next = write_addr_reg; + write_data_padded_next = write_data_padded_reg; + write_delete_next = write_delete_reg; + + case (state_reg) + STATE_INIT: begin + // zero out RAMs + ram_addr = {SLICE_COUNT{count_reg}} & {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, {DATA_WIDTH{1'b1}}}; + set_bit = {RAM_DEPTH{1'b0}}; + clear_bit = {RAM_DEPTH{1'b1}}; + wr_en = 1'b1; + + if (count_reg == 0) begin + state_next = STATE_IDLE; + end else begin + count_next = count_reg - 1; + state_next = STATE_INIT; + end + end + STATE_IDLE: begin + // idle state + write_addr_next = write_addr; + write_data_padded_next = write_data_padded; + write_delete_next = write_delete; + + if (write_enable) begin + // wait for read from erase_ram + state_next = STATE_DELETE_1; + end else begin + state_next = STATE_IDLE; + end + end + STATE_DELETE_1: begin + // wait for read + state_next = STATE_DELETE_2; + end + STATE_DELETE_2: begin + // clear bit and write back + clear_bit = 1'b1 << write_addr; + wr_en = 1'b1; + if (write_delete_reg) begin + state_next = STATE_IDLE; + end else begin + erase_ram_wr_en = 1'b1; + state_next = STATE_WRITE_1; + end + end + STATE_WRITE_1: begin + // wait for read + state_next = STATE_WRITE_2; + end + STATE_WRITE_2: begin + // set bit and write back + set_bit = 1'b1 << write_addr; + wr_en = 1'b1; + state_next = STATE_IDLE; + end + endcase +end + +always @(posedge clk) begin + if (rst) begin + state_reg <= STATE_INIT; + count_reg <= {SLICE_WIDTH{1'b1}}; + write_busy_reg <= 1'b1; + end else begin + state_reg <= state_next; + count_reg <= count_next; + write_busy_reg <= state_next != STATE_IDLE; + end + + write_addr_reg <= write_addr_next; + write_data_padded_reg <= write_data_padded_next; + write_delete_reg <= write_delete_next; +end + +endmodule diff --git a/fpga/usrp3/lib/control/map/cam_priority_encoder.v b/fpga/usrp3/lib/control/map/cam_priority_encoder.v new file mode 100644 index 000000000..5125134f6 --- /dev/null +++ b/fpga/usrp3/lib/control/map/cam_priority_encoder.v @@ -0,0 +1,94 @@ +/* + +Copyright (c) 2014-2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Priority encoder module + */ +module cam_priority_encoder # +( + parameter WIDTH = 4, + // LSB priority: "LOW", "HIGH" + parameter LSB_PRIORITY = "LOW" +) +( + input wire [WIDTH-1:0] input_unencoded, + output wire output_valid, + output wire [$clog2(WIDTH)-1:0] output_encoded, + output wire [WIDTH-1:0] output_unencoded +); + +// power-of-two width +parameter W1 = 2**$clog2(WIDTH); +parameter W2 = W1/2; + +generate + if (WIDTH == 2) begin + // two inputs - just an OR gate + assign output_valid = |input_unencoded; + if (LSB_PRIORITY == "LOW") begin + assign output_encoded = input_unencoded[1]; + end else begin + assign output_encoded = ~input_unencoded[0]; + end + end else begin + // more than two inputs - split into two parts and recurse + // also pad input to correct power-of-two width + wire [$clog2(W2)-1:0] out1, out2; + wire valid1, valid2; + cam_priority_encoder #( + .WIDTH(W2), + .LSB_PRIORITY(LSB_PRIORITY) + ) + priority_encoder_inst1 ( + .input_unencoded(input_unencoded[W2-1:0]), + .output_valid(valid1), + .output_encoded(out1) + ); + cam_priority_encoder #( + .WIDTH(W2), + .LSB_PRIORITY(LSB_PRIORITY) + ) + priority_encoder_inst2 ( + .input_unencoded({{W1-WIDTH{1'b0}}, input_unencoded[WIDTH-1:W2]}), + .output_valid(valid2), + .output_encoded(out2) + ); + // multiplexer to select part + assign output_valid = valid1 | valid2; + if (LSB_PRIORITY == "LOW") begin + assign output_encoded = valid2 ? {1'b1, out2} : {1'b0, out1}; + end else begin + assign output_encoded = valid1 ? {1'b0, out1} : {1'b1, out2}; + end + end +endgenerate + +// unencoded output +assign output_unencoded = 1 << output_encoded; + +endmodule diff --git a/fpga/usrp3/lib/control/map/cam_srl.v b/fpga/usrp3/lib/control/map/cam_srl.v new file mode 100644 index 000000000..6bc4146b0 --- /dev/null +++ b/fpga/usrp3/lib/control/map/cam_srl.v @@ -0,0 +1,223 @@ +/* + +Copyright (c) 2015-2016 Alex Forencich + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. + +*/ + +// Language: Verilog 2001 + +`timescale 1ns / 1ps + +/* + * Content Addressable Memory (shift register based) + */ +module cam_srl #( + // search data bus width + parameter DATA_WIDTH = 64, + // memory size in log2(words) + parameter ADDR_WIDTH = 5, + // width of data bus slices (4 for SRL16, 5 for SRL32) + parameter SLICE_WIDTH = 4 +) +( + input wire clk, + input wire rst, + + input wire [ADDR_WIDTH-1:0] write_addr, + input wire [DATA_WIDTH-1:0] write_data, + input wire write_delete, + input wire write_enable, + output wire write_busy, + + input wire [DATA_WIDTH-1:0] compare_data, + output wire [2**ADDR_WIDTH-1:0] match_many, + output wire [2**ADDR_WIDTH-1:0] match_single, + output wire [ADDR_WIDTH-1:0] match_addr, + output wire match +); + +// total number of slices (enough to cover DATA_WIDTH with address inputs) +localparam SLICE_COUNT = (DATA_WIDTH + SLICE_WIDTH - 1) / SLICE_WIDTH; +// depth of RAMs +localparam RAM_DEPTH = 2**ADDR_WIDTH; + +localparam [1:0] + STATE_INIT = 2'd0, + STATE_IDLE = 2'd1, + STATE_WRITE = 2'd2, + STATE_DELETE = 2'd3; + +reg [1:0] state_reg = STATE_INIT, state_next; + +wire [SLICE_COUNT*SLICE_WIDTH-1:0] compare_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, compare_data}; +wire [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded = {{SLICE_COUNT*SLICE_WIDTH-DATA_WIDTH{1'b0}}, write_data}; + +reg [SLICE_WIDTH-1:0] count_reg = {SLICE_WIDTH{1'b1}}, count_next; + +reg [SLICE_COUNT-1:0] shift_data; +reg [RAM_DEPTH-1:0] shift_en; + +reg [ADDR_WIDTH-1:0] write_addr_reg = {ADDR_WIDTH{1'b0}}, write_addr_next; +reg [SLICE_COUNT*SLICE_WIDTH-1:0] write_data_padded_reg = {SLICE_COUNT*SLICE_WIDTH{1'b0}}, write_data_padded_next; + +reg write_busy_reg = 1'b1; + +assign write_busy = write_busy_reg; + +reg [RAM_DEPTH-1:0] match_raw_out[SLICE_COUNT-1:0]; +reg [RAM_DEPTH-1:0] match_many_raw; +reg [RAM_DEPTH-1:0] match_many_reg = {RAM_DEPTH{1'b0}}; + +assign match_many = match_many_reg; + +integer k; + +always @* begin + match_many_raw = ~shift_en; + for (k = 0; k < SLICE_COUNT; k = k + 1) begin + match_many_raw = match_many_raw & match_raw_out[k]; + end +end + +cam_priority_encoder #( + .WIDTH(RAM_DEPTH), + .LSB_PRIORITY("HIGH") +) +priority_encoder_inst ( + .input_unencoded(match_many_reg), + .output_valid(match), + .output_encoded(match_addr), + .output_unencoded(match_single) +); + +integer i; + +// SRLs +genvar row_ind, slice_ind; +generate + for (row_ind = 0; row_ind < RAM_DEPTH; row_ind = row_ind + 1) begin : row + for (slice_ind = 0; slice_ind < SLICE_COUNT; slice_ind = slice_ind + 1) begin : slice + reg [2**SLICE_WIDTH-1:0] srl_mem = {2**SLICE_WIDTH{1'b0}}; + + // match + always @* begin + match_raw_out[slice_ind][row_ind] = srl_mem[compare_data_padded[SLICE_WIDTH * slice_ind +: SLICE_WIDTH]]; + end + + // write + always @(posedge clk) begin + if (shift_en[row_ind]) begin + srl_mem <= {srl_mem[2**SLICE_WIDTH-2:0], shift_data[slice_ind]}; + end + end + end + end +endgenerate + +// match +always @(posedge clk) begin + match_many_reg <= match_many_raw; +end + +// write +always @* begin + state_next = STATE_IDLE; + + count_next = count_reg; + shift_data = {SLICE_COUNT{1'b0}}; + shift_en = {RAM_DEPTH{1'b0}}; + + write_addr_next = write_addr_reg; + write_data_padded_next = write_data_padded_reg; + + case (state_reg) + STATE_INIT: begin + // zero out shift registers + shift_en = {RAM_DEPTH{1'b1}}; + shift_data = {SLICE_COUNT{1'b0}}; + + if (count_reg == 0) begin + state_next = STATE_IDLE; + end else begin + count_next = count_reg - 1; + state_next = STATE_INIT; + end + end + STATE_IDLE: begin + if (write_enable) begin + write_addr_next = write_addr; + write_data_padded_next = write_data_padded; + count_next = {SLICE_WIDTH{1'b1}}; + if (write_delete) begin + state_next = STATE_DELETE; + end else begin + state_next = STATE_WRITE; + end + end else begin + state_next = STATE_IDLE; + end + end + STATE_WRITE: begin + // write entry + shift_en = 1'b1 << write_addr; + + for (i = 0; i < SLICE_COUNT; i = i + 1) begin + shift_data[i] = count_reg == write_data_padded_reg[SLICE_WIDTH * i +: SLICE_WIDTH]; + end + + if (count_reg == 0) begin + state_next = STATE_IDLE; + end else begin + count_next = count_reg - 1; + state_next = STATE_WRITE; + end + end + STATE_DELETE: begin + // delete entry + shift_en = 1'b1 << write_addr; + shift_data = {SLICE_COUNT{1'b0}}; + + if (count_reg == 0) begin + state_next = STATE_IDLE; + end else begin + count_next = count_reg - 1; + state_next = STATE_DELETE; + end + end + endcase +end + +always @(posedge clk) begin + if (rst) begin + state_reg <= STATE_INIT; + count_reg <= {SLICE_WIDTH{1'b1}}; + write_busy_reg <= 1'b1; + end else begin + state_reg <= state_next; + count_reg <= count_next; + write_busy_reg <= state_next != STATE_IDLE; + end + + write_addr_reg <= write_addr_next; + write_data_padded_reg <= write_data_padded_next; +end + +endmodule diff --git a/fpga/usrp3/lib/control/map/kv_map.v b/fpga/usrp3/lib/control/map/kv_map.v new file mode 100644 index 000000000..dfb0bca43 --- /dev/null +++ b/fpga/usrp3/lib/control/map/kv_map.v @@ -0,0 +1,253 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: kv_map + +module kv_map #( + parameter KEY_WIDTH = 16, + parameter VAL_WIDTH = 32, + parameter SIZE = 6 +) ( + // Clock and reset + input wire clk, + input wire reset, + // Insert port + input wire insert_stb, + input wire [KEY_WIDTH-1:0] insert_key, + input wire [VAL_WIDTH-1:0] insert_val, + output wire insert_busy, + // Find port + input wire find_key_stb, + input wire [KEY_WIDTH-1:0] find_key, + output wire find_res_stb, + output wire find_res_match, + output wire [VAL_WIDTH-1:0] find_res_val, + // Count + output reg [SIZE-1:0] count = {SIZE{1'b0}} +); + + //------------------------------------------------- + // Instantiate a CAM and a RAM + //------------------------------------------------- + // The CAM serves as a "set" and the RAM serves as a + // random addressable "array". Using thse two data structures + // we can build a map. The role of the CAM is to compress + // the key to an address that can be used to lookup data + // stored in the RAM + + wire cam_wr_en, cam_wr_busy, cam_rd_match; + wire [SIZE-1:0] cam_wr_addr, cam_rd_addr; + wire [KEY_WIDTH-1:0] cam_wr_data, cam_rd_key; + + wire ram_wr_en; + wire [SIZE-1:0] ram_wr_addr; + reg [SIZE-1:0] ram_rd_addr; + wire [VAL_WIDTH-1:0] ram_wr_data, ram_rd_data; + + cam #( + .DATA_WIDTH (KEY_WIDTH), + .ADDR_WIDTH (SIZE), + .CAM_STYLE (SIZE > 8 ? "BRAM" : "SRL"), + .SLICE_WIDTH (SIZE > 8 ? 9 : 5) + ) cam_i ( + .clk (clk), + .rst (reset), + .write_addr (cam_wr_addr), + .write_data (cam_wr_data), + .write_delete(1'b0), + .write_enable(cam_wr_en), + .write_busy (cam_wr_busy), + .compare_data(cam_rd_key), + .match_addr (cam_rd_addr), + .match (cam_rd_match), + .match_many (), + .match_single() + ); + + ram_2port #( + .DWIDTH(VAL_WIDTH), + .AWIDTH(SIZE) + ) mem_i ( + .clka (clk), + .ena (ram_wr_en), + .wea (1'b1), + .addra (ram_wr_addr), + .dia (ram_wr_data), + .doa (/* Write port only */), + .clkb (clk), + .enb (1'b1), + .web (1'b0), + .addrb (ram_rd_addr), + .dib (/* Read port only */), + .dob (ram_rd_data) + ); + + // Pipeline read address into RAM + always @(posedge clk) + ram_rd_addr <= cam_rd_addr; + + //------------------------------------------------- + // Find state machine + //------------------------------------------------- + // The lookup process has three cycles of latency + // - CAM lookup has a 1 cycle latency + // - The lookup address into the RAM is delayed by 1 cycle for timing + // - The RAM takes 1 cycle to produce data + + localparam FIND_CYC = 3; + + reg [FIND_CYC-1:0] find_key_stb_shreg = {FIND_CYC{1'b0}}; + reg [FIND_CYC-2:0] find_match_shreg = {(FIND_CYC-1){1'b0}}; + reg find_pending = 1'b0; + + wire find_busy = find_pending | find_key_stb; + + // Delay the find valid signal to account for the latency + // of the CAM and RAM + always @(posedge clk) begin + find_key_stb_shreg <= reset ? {FIND_CYC{1'b0}} : + {find_key_stb_shreg[FIND_CYC-2:0], find_key_stb}; + end + assign find_res_stb = find_key_stb_shreg[FIND_CYC-1]; + + // Latch the find signal to compute pending + always @(posedge clk) begin + if (find_key_stb) + find_pending <= 1'b1; + else if (find_pending) + find_pending <= ~find_res_stb; + end + + // Delay the match signal to account for the latency of the RAM + always @(posedge clk) begin + find_match_shreg <= reset ? {(FIND_CYC-1){1'b0}} : + {find_match_shreg[FIND_CYC-3:0], cam_rd_match}; + end + assign find_res_match = find_match_shreg[FIND_CYC-2]; + + + //------------------------------------------------- + // Insert state machine + //------------------------------------------------- + + localparam [2:0] ST_IDLE = 3'd0; + localparam [2:0] ST_WAIT_FIND = 3'd1; + localparam [2:0] ST_CAM_READ = 3'd2; + localparam [2:0] ST_CAM_CHECK_MATCH = 3'd3; + localparam [2:0] ST_CAM_RAM_WRITE = 3'd4; + localparam [2:0] ST_CAM_WRITE_WAIT = 3'd5; + localparam [2:0] ST_RAM_WRITE = 3'd6; + + reg [2:0] ins_state = ST_IDLE; + + reg [KEY_WIDTH-1:0] ins_key_cached; + reg [VAL_WIDTH-1:0] ins_val_cached; + reg [SIZE-1:0] write_addr = {SIZE{1'b0}}; + reg [SIZE-1:0] next_addr = {SIZE{1'b0}}; + + + always @(posedge clk) begin + if (reset) begin + ins_state <= ST_IDLE; + next_addr <= {SIZE{1'b0}}; + end else begin + case (ins_state) + + // Idle and waiting for an insert transaction + // + ST_IDLE: begin + // Cache insertion parameters + if (insert_stb) begin + ins_key_cached <= insert_key; + ins_val_cached <= insert_val; + // Wait for find to finish + ins_state <= find_busy ? ST_WAIT_FIND : ST_CAM_READ; + end + end + + // Wait for a find transaction to finish + // + ST_WAIT_FIND: begin + // Wait for find to finish + if (~find_busy) + ins_state <= ST_CAM_READ; + end + + // Read the CAM to check if the key to insert already exists + // + ST_CAM_READ: begin + // Ensure that find always has priority + if (~find_key_stb) + ins_state <= ST_CAM_CHECK_MATCH; + end + + // Look at the CAM match signal to evaluate if we skip writing the CAM + // + ST_CAM_CHECK_MATCH: begin + // If the CAM already has this key, then overwrite it + if (cam_rd_match) begin + ins_state <= ST_RAM_WRITE; + write_addr <= cam_rd_addr; + end else if (~cam_wr_busy) begin + ins_state <= ST_CAM_RAM_WRITE; + write_addr <= next_addr; + next_addr <= next_addr + 1'b1; + end + end + + // Write the specified key to the CAM and value to the RAM + // + ST_CAM_RAM_WRITE: begin + ins_state <= ST_CAM_WRITE_WAIT; + end + + // Wait for CAM write to finish + // + ST_CAM_WRITE_WAIT: begin + if (~cam_wr_busy) begin + ins_state <= ST_IDLE; + count <= next_addr; + end + end + + // Write the specified value to the RAM + // + ST_RAM_WRITE: begin + ins_state <= ST_IDLE; + count <= next_addr; + end + + default: begin + // We should not get here + ins_state <= ST_IDLE; + end + endcase + end + end + + // CAM Read Port: + // - Find has priority so it can interrupt an insert + assign cam_rd_key = + (ins_state != ST_CAM_READ || find_key_stb) ? find_key : ins_key_cached; + + // RAM Write Port: + // - The RAM write enable is held high for 1 cycle + // - The address may come from a CAM lookup or could generated + assign ram_wr_en = (ins_state == ST_RAM_WRITE || ins_state == ST_CAM_RAM_WRITE); + assign ram_wr_addr = write_addr; + assign ram_wr_data = ins_val_cached; + + // CAM Write Port: + // - The CAM write enable is held high for 1 cycle + // - The address may come from a CAM lookup or could generated (same as RAM) + assign cam_wr_en = (ins_state == ST_CAM_RAM_WRITE); + assign cam_wr_addr = write_addr; + assign cam_wr_data = ins_key_cached; + + // Outputs + assign insert_busy = (ins_state != ST_IDLE); + assign find_res_val = ram_rd_data; + +endmodule diff --git a/fpga/usrp3/lib/control/mdio_master.v b/fpga/usrp3/lib/control/mdio_master.v new file mode 100644 index 000000000..755fb94ce --- /dev/null +++ b/fpga/usrp3/lib/control/mdio_master.v @@ -0,0 +1,772 @@ +// +// Copyright 2016 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module mdio_master #( + parameter REG_AWIDTH = 32, + parameter REG_BASE = 'h0, + parameter [7:0] MDC_DIVIDER = 8'd200 +) ( + // Clock and reset + input clk, + input rst, + // MDIO ports + output reg mdc, + output reg mdio_out, + output reg mdio_tri, // Assert to tristate driver. + input mdio_in, + // Register ports + input reg_wr_req, + input [REG_AWIDTH-1:0] reg_wr_addr, + input [31:0] reg_wr_data, + input reg_rd_req, + input [REG_AWIDTH-1:0] reg_rd_addr, + output reg reg_rd_resp, + output reg [31:0] reg_rd_data +); + + localparam [7:0] + IDLE = 0, + PREAMBLE1 = 1, + PREAMBLE2 = 2, + PREAMBLE3 = 3, + PREAMBLE4 = 4, + PREAMBLE5 = 5, + PREAMBLE6 = 6, + PREAMBLE7 = 7, + PREAMBLE8 = 8, + PREAMBLE9 = 9, + PREAMBLE10 = 10, + PREAMBLE11 = 11, + PREAMBLE12 = 12, + PREAMBLE13 = 13, + PREAMBLE14 = 14, + PREAMBLE15 = 15, + PREAMBLE16 = 16, + PREAMBLE17 = 17, + PREAMBLE18 = 18, + PREAMBLE19 = 19, + PREAMBLE20 = 20, + PREAMBLE21 = 21, + PREAMBLE22 = 22, + PREAMBLE23 = 23, + PREAMBLE24 = 24, + PREAMBLE25 = 25, + PREAMBLE26 = 26, + PREAMBLE27 = 27, + PREAMBLE28 = 28, + PREAMBLE29 = 29, + PREAMBLE30 = 30, + PREAMBLE31 = 31, + PREAMBLE32 = 32, + START1 = 33, + C22_START2 = 34, + C45_START2 = 35, + OP1 = 36, + OP2 = 37, + PRTAD1 = 38, + PRTAD2 = 39, + PRTAD3 = 40, + PRTAD4 = 41, + PRTAD5 = 42, + DEVAD1 = 43, + DEVAD2 = 44, + DEVAD3 = 45, + DEVAD4 = 46, + DEVAD5 = 47, + TA1 = 48, + TA2 = 49, + TA3 = 50, + READ1 = 51, + READ2 = 52, + READ3 = 53, + READ4 = 54, + READ5 = 55, + READ6 = 56, + READ7 = 57, + READ8 = 58, + READ9 = 59, + READ10 = 60, + READ11 = 61, + READ12 = 62, + READ13 = 63, + READ14 = 64, + READ15 = 65, + READ16 = 66, + WRITE1 = 67, + WRITE2 = 68, + WRITE3 = 69, + WRITE4 = 70, + WRITE5 = 71, + WRITE6 = 72, + WRITE7 = 73, + WRITE8 = 74, + WRITE9 = 75, + WRITE10 = 76, + WRITE11 = 77, + WRITE12 = 78, + WRITE13 = 79, + WRITE14 = 80, + WRITE15 = 81, + WRITE16 = 82, + C45_ADDR1 = 83, + C45_ADDR2 = 84, + C45_ADDR3 = 85, + C45_ADDR4 = 86, + C45_ADDR5 = 87, + C45_ADDR6 = 88, + C45_ADDR7 = 89, + C45_ADDR8 = 90, + C45_ADDR9 = 91, + C45_ADDR10 = 92, + C45_ADDR11 = 93, + C45_ADDR12 = 94, + C45_ADDR13 = 95, + C45_ADDR14 = 96, + C45_ADDR15 = 97, + C45_ADDR16 = 98, + PREIDLE = 99 + ; + + localparam REG_MDIO_DATA = REG_BASE + 'h0; + localparam REG_MDIO_ADDR = REG_BASE + 'h4; + localparam REG_MDIO_OP = REG_BASE + 'h8; + localparam REG_MDIO_CTRL_STATUS = REG_BASE + 'hC; + + reg [15:0] mdio_read_data, mdio_write_data; + reg [15:0] mdio_address; + reg [12:0] mdio_operation; + reg [7:0] mdc_clk_count; + reg mdc_falling_edge; + reg mdio_running; + reg mdio_done; + reg [7:0] state; + + always @(posedge clk) begin + if (rst) begin + mdio_write_data <= 16'h0; + mdio_address <= 16'h0; + mdio_operation <= 13'h0; + mdio_running <= 1'b0; + end else begin + // Handshake to MDIO state machine to reset running flag in status. + // Wait for falling MDC edge to prevent S/W race condition occuring + // where done flag still asserted but running flag now cleared (repeatedly). + if (mdio_done && mdc_falling_edge) + mdio_running <= 1'b0; + + // Readable registers + if (reg_rd_req) begin + reg_rd_resp <= 1'b1; + case (reg_rd_addr) + REG_MDIO_DATA: + reg_rd_data <= {16'h0, mdio_read_data}; + REG_MDIO_ADDR: + reg_rd_data <= {16'h0, mdio_address}; + REG_MDIO_OP: + reg_rd_data <= {16'h0, mdio_operation}; + REG_MDIO_CTRL_STATUS: + reg_rd_data <= {31'b0, mdio_running}; + default: + reg_rd_resp <= 1'b0; + endcase + end else if (reg_rd_resp) begin + reg_rd_resp <= 1'b0; + end + + // Writable registers + if (reg_wr_req) begin + case(reg_wr_addr) + REG_MDIO_DATA: + mdio_write_data <= reg_wr_data[15:0]; + REG_MDIO_ADDR: + mdio_address <= reg_wr_data[15:0]; + REG_MDIO_OP: + mdio_operation <= reg_wr_data[12:0]; + REG_MDIO_CTRL_STATUS: + if (reg_wr_data[0]) + mdio_running <= 1'b1; // Trigger mdio operation here. Cleared by state machine at end of bus transaction. + endcase + end + end + end + + // + // Produce mdc clock as a signal synchronously from Wishbone clock. + // + always @(posedge clk) begin + if (rst) begin + mdc_clk_count <= 8'd1; + mdc <= 1'b0; + mdc_falling_edge <= 1'b0; + end else if (mdc_clk_count == (MDC_DIVIDER/2)) begin + mdc_clk_count <= 8'd1; + mdc <= ~mdc; + mdc_falling_edge <= mdc; + end else begin + mdc_clk_count <= mdc_clk_count + 8'd1; + mdc_falling_edge <= 1'b0; + end + end + + // + // MDIO state machine + // + always @(posedge clk) begin + if (rst) begin + mdio_tri <= 1'b1; + mdio_out <= 1'b0; + mdio_done <= 1'b0; + mdio_read_data <= 16'b0; + state <= IDLE; + end else if (mdc_falling_edge) begin + // This is the MDIO bus controller. Use falling edge of MDC. + mdio_tri <= 1'b1; + mdio_out <= 1'b0; + mdio_done <= 1'b0; + case(state) + // IDLE. + // In Clause 22 & 45 the master of the MDIO bus is tristate during idle. + IDLE: begin + mdio_tri <= 1'b1; + mdio_out <= 1'b0; + if (mdio_running) + state <= PREAMBLE1; + end + // Preamble. All MDIO transactions begin witrh 32bits of 1 bits as a preamble. + PREAMBLE1: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE2; + end + PREAMBLE2: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE3; + end + PREAMBLE3: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE4; + end + PREAMBLE4: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE5; + end + PREAMBLE5: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE6; + end + PREAMBLE6: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE7; + end + PREAMBLE7: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE8; + end + PREAMBLE8: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE9; + end + PREAMBLE9: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE10; + end + PREAMBLE10: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE11; + end + PREAMBLE11: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE12; + end + PREAMBLE12: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE13; + end + PREAMBLE13: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE14; + end + PREAMBLE14: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE15; + end + PREAMBLE15: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE16; + end + PREAMBLE16: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE17; + end + PREAMBLE17: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE18; + end + PREAMBLE18: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE19; + end + PREAMBLE19: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE20; + end + PREAMBLE20: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE21; + end + PREAMBLE21: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE22; + end + PREAMBLE22: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE23; + end + PREAMBLE23: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE24; + end + PREAMBLE24: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE25; + end + PREAMBLE25: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE26; + end + PREAMBLE26: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE27; + end + PREAMBLE27: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE28; + end + PREAMBLE28: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE29; + end + PREAMBLE29: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE30; + end + PREAMBLE30: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE31; + end + PREAMBLE31: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= PREAMBLE32; + end + PREAMBLE32: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= START1; + end + // Start code for Clause 22 is 01 and Clause 45 is 00 + START1: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b0; + if (mdio_operation[12]) + // Clause 45 bit set. + state <= C45_START2; + else + state <= C22_START2; + end + // 2nd Clause 22 start bit is a 1 + C22_START2: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= OP1; + end + // 2nd Clause 45 start bit is a 0 + C45_START2: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b0; + state <= OP1; + end + // Both Clause 22 & 45 use 2 bits for operation and are compatable. + // Note we don't screen here for illegal Clause 22 ops. + OP1: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[11]; + state <= OP2; + end + OP2: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[10]; + state <= PRTAD1; + end + // Both Clause 22 & 45 use 2 sucsessive 5 bit fields to form a hierarchical address + // though it's used slightly different between the 2 standards. + PRTAD1: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[9]; + state <= PRTAD2; + end + PRTAD2: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[8]; + state <= PRTAD3; + end + PRTAD3: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[7]; + state <= PRTAD4; + end + PRTAD4: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[6]; + state <= PRTAD5; + end + PRTAD5: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[5]; + state <= DEVAD1; + end + DEVAD1: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[4]; + state <= DEVAD2; + end + DEVAD2: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[3]; + state <= DEVAD3; + end + DEVAD3: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[2]; + state <= DEVAD4; + end + DEVAD4: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[1]; + state <= DEVAD5; + end + DEVAD5: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_operation[0]; + state <= TA1; + end + // Both Clause 22 & Clause 45 use the same turn around on the bus. + // Reads have Z as the first bit and 0 driven by the slave for the 2nd bit. + // Note that slaves drive the bus on the rising edge of MDC. + // Writes and Address cycles have 10 driven by the master. + TA1: begin + if (mdio_operation[11] == 1'b0) // Write/Address + begin + mdio_tri <= 1'b0; + mdio_out <= 1'b1; + state <= TA2; + end + else // Read + begin + mdio_tri <= 1'b1; + state <= TA3; + end + end + TA2: begin + mdio_tri <= 1'b0; + mdio_out <= 1'b0; + if ( !mdio_operation[12]) // Clause 22 Write + state <= WRITE1; + else if (mdio_operation[10]) // Clause 45 Write + state <= WRITE1; + else // Clause 45 ADDRESS + state <= C45_ADDR1; + end + TA3: begin + mdio_tri <= 1'b1; + state <= READ1; + end + // Clause 22 Reads and both forms of clause 45 Reads have the same bus transaction from here out. + READ1: begin + mdio_tri <= 1'b1; + mdio_read_data[15] <= mdio_in; + state <= READ2; + end + READ2: begin + mdio_tri <= 1'b1; + mdio_read_data[14] <= mdio_in; + state <= READ3; + end + READ3: begin + mdio_tri <= 1'b1; + mdio_read_data[13] <= mdio_in; + state <= READ4; + end + READ4: begin + mdio_tri <= 1'b1; + mdio_read_data[12] <= mdio_in; + state <= READ5; + end + READ5: begin + mdio_tri <= 1'b1; + mdio_read_data[11] <= mdio_in; + state <= READ6; + end + READ6: begin + mdio_tri <= 1'b1; + mdio_read_data[10] <= mdio_in; + state <= READ7; + end + READ7: begin + mdio_tri <= 1'b1; + mdio_read_data[9] <= mdio_in; + state <= READ8; + end + READ8: begin + mdio_tri <= 1'b1; + mdio_read_data[8] <= mdio_in; + state <= READ9; + end + READ9: begin + mdio_tri <= 1'b1; + mdio_read_data[7] <= mdio_in; + state <= READ10; + end + READ10: begin + mdio_tri <= 1'b1; + mdio_read_data[6] <= mdio_in; + state <= READ11; + end + READ11: begin + mdio_tri <= 1'b1; + mdio_read_data[5] <= mdio_in; + state <= READ12; + end + READ12: begin + mdio_tri <= 1'b1; + mdio_read_data[4] <= mdio_in; + state <= READ13; + end + READ13: begin + mdio_tri <= 1'b1; + mdio_read_data[3] <= mdio_in; + state <= READ14; + end + READ14: begin + mdio_tri <= 1'b1; + mdio_read_data[2] <= mdio_in; + state <= READ15; + end + READ15: begin + mdio_tri <= 1'b1; + mdio_read_data[1] <= mdio_in; + state <= READ16; + end + READ16: begin + mdio_tri <= 1'b1; + mdio_read_data[0] <= mdio_in; + state <= PREIDLE; + mdio_done <= 1'b1; + end + // Write 16bits of data for all types of Write. + WRITE1: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[15]; + state <= WRITE2; + end + WRITE2: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[14]; + state <= WRITE3; + end + WRITE3: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[13]; + state <= WRITE4; + end + WRITE4: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[12]; + state <= WRITE5; + end + WRITE5: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[11]; + state <= WRITE6; + end + WRITE6: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[10]; + state <= WRITE7; + end + WRITE7: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[9]; + state <= WRITE8; + end + WRITE8: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[8]; + state <= WRITE9; + end + WRITE9: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[7]; + state <= WRITE10; + end + WRITE10: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[6]; + state <= WRITE11; + end + WRITE11: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[5]; + state <= WRITE12; + end + WRITE12: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[4]; + state <= WRITE13; + end + WRITE13: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[3]; + state <= WRITE14; + end + WRITE14: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[2]; + state <= WRITE15; + end + WRITE15: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[1]; + state <= WRITE16; + end + WRITE16: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_write_data[0]; + state <= PREIDLE; + mdio_done <= 1'b1; + end + // Write 16bits of address for a Clause 45 Address transaction + C45_ADDR1: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[15]; + state <= C45_ADDR2; + end + C45_ADDR2: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[14]; + state <= C45_ADDR3; + end + C45_ADDR3: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[13]; + state <= C45_ADDR4; + end + C45_ADDR4: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[12]; + state <= C45_ADDR5; + end + C45_ADDR5: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[11]; + state <= C45_ADDR6; + end + C45_ADDR6: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[10]; + state <= C45_ADDR7; + end + C45_ADDR7: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[9]; + state <= C45_ADDR8; + end + C45_ADDR8: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[8]; + state <= C45_ADDR9; + end + C45_ADDR9: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[7]; + state <= C45_ADDR10; + end + C45_ADDR10: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[6]; + state <= C45_ADDR11; + end + C45_ADDR11: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[5]; + state <= C45_ADDR12; + end + C45_ADDR12: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[4]; + state <= C45_ADDR13; + end + C45_ADDR13: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[3]; + state <= C45_ADDR14; + end + C45_ADDR14: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[2]; + state <= C45_ADDR15; + end + C45_ADDR15: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[1]; + state <= C45_ADDR16; + end + C45_ADDR16: begin + mdio_tri <= 1'b0; + mdio_out <= mdio_address[0]; + state <= PREIDLE; + mdio_done <= 1'b1; + end + // PREIDLE allows the mdio_running bit to reset. + PREIDLE: begin + state <= IDLE; + end + endcase // case(state) + end // if (mdc_falling_edge) + end + +endmodule + diff --git a/fpga/usrp3/lib/control/por_gen.v b/fpga/usrp3/lib/control/por_gen.v new file mode 100644 index 000000000..b16ceb5bf --- /dev/null +++ b/fpga/usrp3/lib/control/por_gen.v @@ -0,0 +1,28 @@ +// +// Copyright 2013 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + + +module por_gen + (input clk, + output reset_out); + + reg por_rst; + reg [7:0] por_counter = 8'h0; + + always @(posedge clk) + if (por_counter != 8'h55) + begin + por_counter <= por_counter + 8'h1; + por_rst <= 1'b1; + end + else + por_rst <= 1'b0; + + assign reset_out = por_rst; + +endmodule // por_gen diff --git a/fpga/usrp3/lib/control/priority_encoder.v b/fpga/usrp3/lib/control/priority_encoder.v new file mode 100644 index 000000000..8e84fb57a --- /dev/null +++ b/fpga/usrp3/lib/control/priority_encoder.v @@ -0,0 +1,55 @@ +// +// Copyright 2013 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +`define log2(N) ( N < 2 ? 0 : \ + N < 4 ? 1 : \ + N < 8 ? 2 : \ + N < 16 ? 3 : \ + N < 32 ? 4 : \ + N < 64 ? 5 : \ + N < 128 ? 6 : \ + N < 256 ? 7 : \ + N < 512 ? 8 : \ + N < 1024 ? 9 : \ + 10 \ + ) + +module priority_encoder +#( + parameter WIDTH = 16 +) +( + input [WIDTH-1:0] in, + output [`log2(WIDTH)-1:0] out +); + + wire [WIDTH-1:0] one_hot; + + // the priority encoder spits out the position + // of the leading bit as one hot coding + priority_encoder_one_hot # + ( + .WIDTH(WIDTH) + ) + prio_one_hot0 + ( + .in(in), + .out(one_hot) + ); + + // binary encoder turns the one hot coding + // into binary encoding + binary_encoder # + ( + .WIDTH(WIDTH) + ) + binary_enc0 + ( + .in(one_hot), + .out(out) + ); +endmodule diff --git a/fpga/usrp3/lib/control/priority_encoder_one_hot.v b/fpga/usrp3/lib/control/priority_encoder_one_hot.v new file mode 100644 index 000000000..d2839ddde --- /dev/null +++ b/fpga/usrp3/lib/control/priority_encoder_one_hot.v @@ -0,0 +1,34 @@ +// +// Copyright 2013 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module priority_encoder_one_hot +#( + parameter WIDTH = 16 +) +( + input [WIDTH-1:0] in, + output [WIDTH-1:0] out +); + + wire [WIDTH-1:0] in_rev; + wire [WIDTH-1:0] in_rev_inv_po = ~in_rev + 1; + + wire [WIDTH-1:0] mask; + + generate + genvar i,j; + + for (i=0; i<WIDTH; i=i+1) + assign in_rev[i] = in[WIDTH-1-i]; + + for (j=0; j<WIDTH; j=j+1) + assign mask[j] = in_rev_inv_po[WIDTH-1-j]; + endgenerate + + assign out = in & mask; + +endmodule diff --git a/fpga/usrp3/lib/control/pulse_stretch.v b/fpga/usrp3/lib/control/pulse_stretch.v new file mode 100644 index 000000000..2235286e1 --- /dev/null +++ b/fpga/usrp3/lib/control/pulse_stretch.v @@ -0,0 +1,79 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: pulse_stretch +// +// Description: +// +// Pulse stretcher. Takes any input pulse that is SCALE+2 clock cycles or +// less and outputs a pulse that is SCALE+1 clock cycles. However, if an +// input pulse is longer than SCALE+2 clock cycles then the output pulse +// repeats. If more than one pulse is input within SCALE+2 clock cycles then +// additional pulses will be ignored. +// +// Examples (SCALE = 2): +// +// Clock _/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_ +// +// +// pulse _/‾‾‾\_______________/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\___________________ +// +// pulse_stretched _____/‾‾‾‾‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾‾‾‾‾\___________________ +// +// +// pulse _/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\___________________________________ +// +// pulse_stretched _____/‾‾‾‾‾‾‾‾‾‾‾\___/‾‾‾‾‾‾‾‾‾‾‾\_______________________ +// +// +// pulse _/‾‾‾\_______/‾‾‾\___/‾‾‾\_______/‾‾‾‾‾‾‾\_______________ +// +// pulse_stretched _____/‾‾‾‾‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾‾‾‾‾\___/‾‾‾‾‾‾‾‾‾‾‾\___ + +// Parameters: +// +// SCALE : The number of clock cycles to add to a single cycle pulse. Or, the +// number of clock cycles, minus 1, for the output pulse. +// + +module pulse_stretch #( + parameter SCALE = 64'd12_500_000 +)( + input clk, + input rst, + input pulse, + output pulse_stretched +); + + reg [$clog2(SCALE+1)-1:0] count = 'd0; + reg state = 1'b0; + + always @ (posedge clk) + if (rst) begin + state <= 1'b0; + count <= 'd0; + end + else begin + case (state) + + 1'b0: begin + if (pulse) begin + state <= 1'b1; + count <= 'd0; + end + end + + 1'b1: begin + if (count == SCALE) + state <= 1'b0; + else + count <= count + 1'b1; + end + endcase + end + + assign pulse_stretched = (state == 1'b1); + +endmodule diff --git a/fpga/usrp3/lib/control/pulse_stretch_min.v b/fpga/usrp3/lib/control/pulse_stretch_min.v new file mode 100644 index 000000000..84bf3ecd9 --- /dev/null +++ b/fpga/usrp3/lib/control/pulse_stretch_min.v @@ -0,0 +1,79 @@ +// +// Copyright 2017 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: pulse_stretch_min +// +// Description: +// +// Pulse stretcher, to guarantee a minimum pulse width. Takes a short input +// pulse and outputs a pulse that is LENGTH clock cycles long. If the input +// pulse is longer than LENGTH then the output pulse will be the same length +// as the input pulse. The output is registered so the output is delayed by +// one clock cycle relative to the input. If more than one pulse is input +// within LENGTH+1 clock cycles, then the extra input pulses will not +// generate output pulses. +// +// Examples: (LENGTH = 3) +// +// Clock _/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_/‾\_ +// +// +// pulse_in _/‾‾‾\_______________/‾‾‾‾‾‾‾\___________/‾‾‾‾‾‾‾‾‾‾‾\_______ +// +// pulse_out _____/‾‾‾‾‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾‾‾‾‾\_______/‾‾‾‾‾‾‾‾‾‾‾\___ +// +// +// pulse_in _/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\______________/‾‾‾\_______/‾‾‾\______ +// +// pulse_out ______/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\______________/‾‾‾‾‾‾‾‾‾‾‾‾‾‾‾\__ +// +// +// Parameters: +// +// LENGTH : Length of the minimum pulse to output, in clock cycles. +// + +module pulse_stretch_min #( + parameter LENGTH = 4 +) ( + input wire clk, + input wire rst, + input wire pulse_in, + output reg pulse_out = 0 +); + + reg [$clog2(LENGTH)-1:0] count = 0; + reg state = 0; + + always @ (posedge clk) + if (rst) begin + state <= 0; + count <= 0; + pulse_out <= 0; + end + else begin + case (state) + + 1'b0: begin + if (pulse_in) begin + state <= 1; + pulse_out <= 1; + count <= 0; + end + end + + 1'b1: begin + if (count == LENGTH-1) begin + if (!pulse_in) begin + state <= 0; + pulse_out <= 0; + end + end else + count <= count + 1; + end + endcase + end + +endmodule diff --git a/fpga/usrp3/lib/control/pulse_synchronizer.v b/fpga/usrp3/lib/control/pulse_synchronizer.v new file mode 100644 index 000000000..af3460878 --- /dev/null +++ b/fpga/usrp3/lib/control/pulse_synchronizer.v @@ -0,0 +1,70 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: pulse_synchronizer +// Description: +// - Synchronizes a single-cycle pulse or an edge from one +// clock domain to another +// - Clocks A and B can be asynchronous +// + +module pulse_synchronizer #( + parameter MODE = "PULSE", // Capture mode {PULSE, POSEDGE, NEGEDGE} + parameter STAGES = 2 // Number of synchronizer stages +) ( + input clk_a, // Clock A + input rst_a, // Reset in clock domain A + input pulse_a, // Pulse in clock domain A to synchronize + output busy_a, // Synchronizer is busy (pulse_a ignored when asserted) + input clk_b, // Clock B + output pulse_b // Pulse in clock domain B +); + // Trigger logic based on the capture mode + wire trigger; + generate if (MODE == "POSEDGE") begin + reg pulse_a_del_pe = 1'b0; + always @ (posedge clk_a) + pulse_a_del_pe <= rst_a ? 1'b0 : pulse_a; + assign trigger = pulse_a & ~pulse_a_del_pe; + end else if (MODE == "NEGEDGE") begin + reg pulse_a_del_ne = 1'b1; + always @ (posedge clk_a) + pulse_a_del_ne <= rst_a ? 1'b1 : pulse_a; + assign trigger = ~pulse_a & pulse_a_del_ne; + end else begin + assign trigger = pulse_a; + end endgenerate + + // Translate pulse/edge to a level and synchronize that into the B domain + reg pulse_toggle_a = 1'b0; + always @(posedge clk_a) begin + pulse_toggle_a <= rst_a ? 1'b0 : (pulse_toggle_a ^ (trigger & ~busy_a)); + end + + wire pulse_toggle_b; + reg pulse_toggle_b_del = 1'b0; + wire handshake_toggle_a; + + synchronizer #( + .STAGES(STAGES), .INITIAL_VAL(0) + ) toggle_sync_i ( + .clk(clk_b), .rst(1'b0), .in(pulse_toggle_a), .out(pulse_toggle_b) + ); + + // Handshake toggle signal back into the A domain to deassert busy + synchronizer #( + .STAGES(STAGES), .INITIAL_VAL(0) + ) handshake_sync_i ( + .clk(clk_a), .rst(1'b0), .in(pulse_toggle_b_del), .out(handshake_toggle_a) + ); + + always @(posedge clk_b) begin + pulse_toggle_b_del <= pulse_toggle_b; + end + + assign pulse_b = pulse_toggle_b_del ^ pulse_toggle_b; + assign busy_a = pulse_toggle_a ^ handshake_toggle_a; + +endmodule
\ No newline at end of file diff --git a/fpga/usrp3/lib/control/ram_2port.v b/fpga/usrp3/lib/control/ram_2port.v new file mode 100644 index 000000000..96db90061 --- /dev/null +++ b/fpga/usrp3/lib/control/ram_2port.v @@ -0,0 +1,120 @@ +// +// Copyright 2011 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Description +// This code implements a parameterizable true dual port memory +// (both ports can read and write). If an enable is not necessary +// it may be tied off. +// +// Note +// This module requires the ram_2port_impl.vh header file. The +// header is included multiple times with different values of +// the RAM_DIRECTIVE macro to create different implementations of the +// RAM. An implementation is chosen in ram_2port based on the +// user parameter for RAM_TYPE. + +// Mode: AUTOMATIC +`define RAM_DIRECTIVE +`define RAM_MOD_NAME ram_2port_impl_auto +`include "ram_2port_impl.vh" +`undef RAM_MOD_NAME +`undef RAM_DIRECTIVE + +// Mode: REG +`define RAM_DIRECTIVE (* ram_style = "registers" *) +`define RAM_MOD_NAME ram_2port_impl_reg +`include "ram_2port_impl.vh" +`undef RAM_MOD_NAME +`undef RAM_DIRECTIVE + +// Mode: LUTRAM +`define RAM_DIRECTIVE (* ram_style = "distributed" *) +`define RAM_MOD_NAME ram_2port_impl_lutram +`include "ram_2port_impl.vh" +`undef RAM_MOD_NAME +`undef RAM_DIRECTIVE + +// Mode: BRAM +`define RAM_DIRECTIVE (* ram_style = "block" *) +`define RAM_MOD_NAME ram_2port_impl_bram +`include "ram_2port_impl.vh" +`undef RAM_MOD_NAME +`undef RAM_DIRECTIVE + +// Mode: URAM +`define RAM_DIRECTIVE (* ram_style = "ultra" *) +`define RAM_MOD_NAME ram_2port_impl_uram +`include "ram_2port_impl.vh" +`undef RAM_MOD_NAME +`undef RAM_DIRECTIVE + +module ram_2port #( + parameter DWIDTH = 32, // Width of the memory block + parameter AWIDTH = 9, // log2 of the depth of the memory block + parameter RW_MODE = "READ-FIRST", // Read-write mode {READ-FIRST, WRITE-FIRST, NO-CHANGE} + parameter RAM_TYPE = "AUTOMATIC", // Type of RAM to infer {AUTOMATIC, REG, LUTRAM, BRAM, URAM} + parameter OUT_REG = 0, // Instantiate an output register? (+1 cycle of read latency) + parameter INIT_FILE = "" // Optionally initialize memory with this file +) ( + input wire clka, + input wire ena, + input wire wea, + input wire [AWIDTH-1:0] addra, + input wire [DWIDTH-1:0] dia, + output wire [DWIDTH-1:0] doa, + + input wire clkb, + input wire enb, + input wire web, + input wire [AWIDTH-1:0] addrb, + input wire [DWIDTH-1:0] dib, + output wire [DWIDTH-1:0] dob +); + + generate + if (RAM_TYPE == "URAM") + ram_2port_impl_uram #( + .DWIDTH(DWIDTH), .AWIDTH(AWIDTH), .RW_MODE(RW_MODE), + .OUT_REG(OUT_REG), .INIT_FILE(INIT_FILE) + ) impl ( + .clka(clka), .ena(ena), .wea(wea), .addra(addra), .dia(dia), .doa(doa), + .clkb(clkb), .enb(enb), .web(web), .addrb(addrb), .dib(dib), .dob(dob) + ); + else if (RAM_TYPE == "BRAM") + ram_2port_impl_bram #( + .DWIDTH(DWIDTH), .AWIDTH(AWIDTH), .RW_MODE(RW_MODE), + .OUT_REG(OUT_REG), .INIT_FILE(INIT_FILE) + ) impl ( + .clka(clka), .ena(ena), .wea(wea), .addra(addra), .dia(dia), .doa(doa), + .clkb(clkb), .enb(enb), .web(web), .addrb(addrb), .dib(dib), .dob(dob) + ); + else if (RAM_TYPE == "LUTRAM") + ram_2port_impl_lutram #( + .DWIDTH(DWIDTH), .AWIDTH(AWIDTH), .RW_MODE(RW_MODE), + .OUT_REG(OUT_REG), .INIT_FILE(INIT_FILE) + ) impl ( + .clka(clka), .ena(ena), .wea(wea), .addra(addra), .dia(dia), .doa(doa), + .clkb(clkb), .enb(enb), .web(web), .addrb(addrb), .dib(dib), .dob(dob) + ); + else if (RAM_TYPE == "REG") + ram_2port_impl_reg #( + .DWIDTH(DWIDTH), .AWIDTH(AWIDTH), .RW_MODE(RW_MODE), + .OUT_REG(OUT_REG), .INIT_FILE(INIT_FILE) + ) impl ( + .clka(clka), .ena(ena), .wea(wea), .addra(addra), .dia(dia), .doa(doa), + .clkb(clkb), .enb(enb), .web(web), .addrb(addrb), .dib(dib), .dob(dob) + ); + else + ram_2port_impl_auto #( + .DWIDTH(DWIDTH), .AWIDTH(AWIDTH), .RW_MODE(RW_MODE), + .OUT_REG(OUT_REG), .INIT_FILE(INIT_FILE) + ) impl ( + .clka(clka), .ena(ena), .wea(wea), .addra(addra), .dia(dia), .doa(doa), + .clkb(clkb), .enb(enb), .web(web), .addrb(addrb), .dib(dib), .dob(dob) + ); + endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/ram_2port_impl.vh b/fpga/usrp3/lib/control/ram_2port_impl.vh new file mode 100644 index 000000000..8f8f3bab3 --- /dev/null +++ b/fpga/usrp3/lib/control/ram_2port_impl.vh @@ -0,0 +1,129 @@ +// +// Copyright 2011 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Used by ram_2port.v +// Requires `RAM_MOD_NAME and `RAM_DIRECTIVE to be defined + +module `RAM_MOD_NAME #( + parameter DWIDTH = 32, // Width of the memory block + parameter AWIDTH = 9, // log2 of the depth of the memory block + parameter RW_MODE = "READ-FIRST", // Read-write mode {READ-FIRST, WRITE-FIRST, NO-CHANGE} + parameter OUT_REG = 0, // Instantiate an output register? (+1 cycle of read latency) + parameter INIT_FILE = "" // Optionally initialize memory with this file +) ( + input wire clka, + input wire ena, + input wire wea, + input wire [AWIDTH-1:0] addra, + input wire [DWIDTH-1:0] dia, + output wire [DWIDTH-1:0] doa, + + input wire clkb, + input wire enb, + input wire web, + input wire [AWIDTH-1:0] addrb, + input wire [DWIDTH-1:0] dib, + output wire [DWIDTH-1:0] dob +); + + `RAM_DIRECTIVE reg [DWIDTH-1:0] ram [(1<<AWIDTH)-1:0]; + + // Initialize ram to a specified file or to all zeros to match hardware + generate if (INIT_FILE != "") begin + initial + $readmemh(INIT_FILE, ram, 0, (1<<AWIDTH)-1); + end else begin + integer i; + initial + for (i = 0; i < (1<<AWIDTH); i = i + 1) + ram[i] = {DWIDTH{1'b0}}; + end endgenerate + + reg [DWIDTH-1:0] doa_r = 'h0, dob_r = 'h0; + generate if (OUT_REG == 1) begin + // A 2 clock cycle read latency with improve clock-to-out timing + reg [DWIDTH-1:0] doa_rr = 'h0, dob_rr = 'h0; + + always @(posedge clka) + if (ena) + doa_rr <= doa_r; + + always @(posedge clkb) + if (enb) + dob_rr <= dob_r; + + assign doa = doa_rr; + assign dob = dob_rr; + end else begin + // A 1 clock cycle read latency at the cost of a longer clock-to-out timing + assign doa = doa_r; + assign dob = dob_r; + end endgenerate + + generate if (RW_MODE == "READ-FIRST") begin + // When data is written, the prior memory contents at the write + // address are presented on the output port. + always @(posedge clka) begin + if (ena) begin + if (wea) + ram[addra] <= dia; + doa_r <= ram[addra]; + end + end + always @(posedge clkb) begin + if (enb) begin + if (web) + ram[addrb] <= dib; + dob_r <= ram[addrb]; + end + end + + end else if (RW_MODE == "WRITE-FIRST") begin + // The data being written to the RAM also resides on the output port. + always @(posedge clka) begin + if (ena) begin + if (wea) begin + ram[addra] <= dia; + doa_r <= dia; + end else begin + doa_r <= ram[addra]; + end + end + end + always @(posedge clkb) begin + if (enb) begin + if (web) begin + ram[addrb] <= dib; + dob_r <= dib; + end else begin + dob_r <= ram[addrb]; + end + end + end + + end else begin + // This is a no change RAM which retains the last read value on the output during writes + // which is the most power efficient mode. + always @(posedge clka) begin + if (ena) begin + if (wea) + ram[addra] <= dia; + else + doa_r <= ram[addra]; + end + end + always @(posedge clkb) begin + if (enb) begin + if (web) + ram[addrb] <= dib; + else + dob_r <= ram[addrb]; + end + end + + end endgenerate + +endmodule diff --git a/fpga/usrp3/lib/control/regport_resp_mux.v b/fpga/usrp3/lib/control/regport_resp_mux.v new file mode 100644 index 000000000..93048bec8 --- /dev/null +++ b/fpga/usrp3/lib/control/regport_resp_mux.v @@ -0,0 +1,46 @@ +// +// Copyright 2016 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module regport_resp_mux #( + parameter WIDTH = 32, + parameter NUM_SLAVES = 2 +)( + input clk, + input reset, + + input [NUM_SLAVES-1:0] sla_rd_resp, + input [(NUM_SLAVES*WIDTH)-1:0] sla_rd_data, + + output reg mst_rd_resp, + output reg [WIDTH-1:0] mst_rd_data +); + // Treat sla_rd_resp as a one-hot bus. + // If multiple resp lines are asserted at the same time, then + // it is a violation of the register port protocol + + wire [NUM_SLAVES-1:0] bit_options[0:WIDTH-1]; + wire [WIDTH-1:0] data_out; + + genvar i, b; + generate + for (b = 0; b < WIDTH; b = b + 1) begin + for (i = 0; i < NUM_SLAVES; i = i + 1) begin + assign bit_options[b][i] = sla_rd_data[(i*WIDTH)+b]; + end + assign data_out[b] = |(bit_options[b] & sla_rd_resp); + end + endgenerate + + always @(posedge clk) begin + mst_rd_data <= data_out; + if (reset) + mst_rd_resp <= 1'b0; + else + mst_rd_resp <= |(sla_rd_resp); + end + +endmodule diff --git a/fpga/usrp3/lib/control/regport_to_settingsbus.v b/fpga/usrp3/lib/control/regport_to_settingsbus.v new file mode 100644 index 000000000..532380ef3 --- /dev/null +++ b/fpga/usrp3/lib/control/regport_to_settingsbus.v @@ -0,0 +1,63 @@ +///////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: regport_to_settingsbus +// Description: +// Converts regport write bus to the a setting bus +// ADDRESSING: Set to "WORD" in case of settings bus. The settings bus +// uses word addressing and hence the address needs to be shifted by +// to convert to set_addr. +// +///////////////////////////////////////////////////////////////////// + +module regport_to_settingsbus #( + parameter BASE = 14'h0, + parameter END_ADDR = 14'h3FFF, + parameter DWIDTH = 32, + parameter AWIDTH = 14, + parameter SR_AWIDTH = 12, + // Dealign for settings bus by shifting by 2 + parameter ADDRESSING = "WORD", + parameter SHIFT = $clog2(DWIDTH/8) +)( + input reset, + input clk, + input reg_wr_req, + input [AWIDTH-1:0] reg_wr_addr, + input [DWIDTH-1:0] reg_wr_data, + + output reg set_stb, + output reg [SR_AWIDTH-1:0] set_addr, + output reg [DWIDTH-1:0] set_data +); + + wire set_stb_int; + wire [DWIDTH-1:0] set_data_int; + wire [SR_AWIDTH-1:0] set_addr_base; + wire [SR_AWIDTH-1:0] set_addr_int; + + // Strobe asserted only when address is between BASE and END ADDR + assign set_stb_int = reg_wr_req && (reg_wr_addr >= BASE) && (reg_wr_addr <= END_ADDR); + assign set_addr_base = reg_wr_addr - BASE; + // Shift by 2 in case of setting bus + assign set_addr_int = (ADDRESSING == "WORD") ? {{SHIFT{1'b0}}, set_addr_base[SR_AWIDTH-1:SHIFT]} + : set_addr_base[SR_AWIDTH-1:0]; + assign set_data_int = reg_wr_data; + + // Adding a pipeline stage + always @(posedge clk) begin + if (reset) begin + set_stb <= 'b0; + set_addr <= 'h0; + set_data <= 'h0; + end else begin + set_stb <= set_stb_int; + set_addr <= set_addr_int; + set_data <= set_data_int; + end + end + +endmodule // regport_to_settingsbus diff --git a/fpga/usrp3/lib/control/regport_to_xbar_settingsbus.v b/fpga/usrp3/lib/control/regport_to_xbar_settingsbus.v new file mode 100644 index 000000000..be050dd20 --- /dev/null +++ b/fpga/usrp3/lib/control/regport_to_xbar_settingsbus.v @@ -0,0 +1,113 @@ +///////////////////////////////////////////////////////////////////// +// +// Copyright 2017 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: regport_to_xbar_settingsbus +// Description: +// Converts regport to xbar setting bus. +// The module is designed only for the crossbar. The readback bus for the +// rfnoc crossbar reads from the same address as it writes to. Also +// there is an extra cycle delay in read data in the crossbar, which is +// why the rb_stb needs to be delayed by a cycle. +// +// ADDRESSING: Set to "WORD" in case of settings bus. The settings bus +// uses word addressing and hence the address needs to be shifted by +// to convert to set_addr. +// +///////////////////////////////////////////////////////////////////// + +module regport_to_xbar_settingsbus #( + parameter BASE = 14'h0, + parameter END_ADDR = 14'h3FFF, + parameter DWIDTH = 32, + parameter AWIDTH = 14, + parameter SR_AWIDTH = 12, + // Dealign for settings bus by shifting by 2 + parameter ADDRESSING = "WORD", + parameter SHIFT = $clog2(DWIDTH/8) + )( + input clk, + input reset, + + input reg_wr_req, + input [AWIDTH-1:0] reg_wr_addr, + input [DWIDTH-1:0] reg_wr_data, + + input reg_rd_req, + input [AWIDTH-1:0] reg_rd_addr, + output [DWIDTH-1:0] reg_rd_data, + output reg_rd_resp, + + output set_stb, + output [SR_AWIDTH-1:0] set_addr, + output [DWIDTH-1:0] set_data, + + output rb_stb, + output [SR_AWIDTH-1:0] rb_addr, + input [DWIDTH-1:0] rb_data +); + + reg reg_rd_req_delay; + reg reg_rd_req_delay2; + wire [AWIDTH-1:0] set_addr_int; + reg [AWIDTH-1:0] rb_addr_int; + + always @(posedge clk) begin + if (reset) begin + reg_rd_req_delay <= 1'b0; + reg_rd_req_delay2 <= 1'b0; + rb_addr_int <= 'd0; + end + else if (reg_rd_req) begin + rb_addr_int <= reg_rd_addr - BASE; + reg_rd_req_delay <= 1'b1; + end + else if (reg_rd_req_delay) begin + reg_rd_req_delay2 <= 1'b1; + reg_rd_req_delay <= 1'b0; + end + // Deassert after two clock cycles + else if (reg_rd_req_delay2) begin + reg_rd_req_delay <= 1'b0; + reg_rd_req_delay2 <= 1'b0; + rb_addr_int <= 'd0; + end + else begin + reg_rd_req_delay <= 1'b0; + reg_rd_req_delay2 <= 1'b0; + rb_addr_int <= 'd0; + end + end + + // Write mode of settings bus + regport_to_settingsbus #( + .BASE(BASE), + .END_ADDR(END_ADDR), + .DWIDTH(DWIDTH), + .AWIDTH(AWIDTH), + .SR_AWIDTH(SR_AWIDTH), + .ADDRESSING(ADDRESSING) + ) xbar_write_settings_bus ( + .clk(clk), + .reset(reset), + .reg_wr_req(reg_wr_req), + .reg_wr_addr(reg_wr_addr), + .reg_wr_data(reg_wr_data), + + .set_stb(set_stb), + .set_addr(set_addr), + .set_data(set_data) + ); + + assign rb_addr = (ADDRESSING == "WORD") ? {{SHIFT{1'b0}}, rb_addr_int[SR_AWIDTH-1:SHIFT]} + : rb_addr_int[SR_AWIDTH-1:0]; + // Strobe asserted two cycle after read request only when address is between BASE and END ADDR + // This is specific to the xbar as the xbar delays read data by an extra clock + // cycle to relax timing. + assign rb_stb = reg_rd_req_delay2 && (reg_rd_addr >= BASE) && (reg_rd_addr <= END_ADDR); + assign reg_rd_resp = rb_stb; + assign reg_rd_data = rb_data; + +endmodule diff --git a/fpga/usrp3/lib/control/reset_sync.v b/fpga/usrp3/lib/control/reset_sync.v new file mode 100644 index 000000000..2e13abfd4 --- /dev/null +++ b/fpga/usrp3/lib/control/reset_sync.v @@ -0,0 +1,37 @@ +// +// Copyright 2011 Ettus Research LLC +// Copyright 2018-2019 Ettus Research, a National Instruments Brand +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// The purpose of this module is to synchronize a reset from one clock domain +// to another. The reset_in signal must be driven by a glitch-free source. +// + +module reset_sync ( + // clock for the output reset + input clk, + // glitch-free input reset + input reset_in, + // output reset in the clk domain + output reg reset_out); + + wire reset_c; + + synchronizer #( + // The input reset is async to the output clk domain... so timing should not be + // analyzed here! + .FALSE_PATH_TO_IN(1), + // Assert reset_out by default. When clk starts toggling the downstream logic will + // be in reset for at least 10 clk cycles. This allows the clock to settle (if needed) + // and the reset to propagate fully to all logic. + .INITIAL_VAL(1), + .STAGES(10) + ) reset_double_sync ( + .clk(clk), .rst(1'b0), .in(reset_in), .out(reset_c) + ); + + always @(posedge clk) + reset_out <= reset_c; + +endmodule // reset_sync diff --git a/fpga/usrp3/lib/control/s7_icap_wb.v b/fpga/usrp3/lib/control/s7_icap_wb.v new file mode 100644 index 000000000..114d370eb --- /dev/null +++ b/fpga/usrp3/lib/control/s7_icap_wb.v @@ -0,0 +1,144 @@ +// +// Copyright 2011-2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// +// Refer to SelectMAP and ICAP docs in UG470 +// + +module s7_icap_wb + ( + input clk, + input reset, + input cyc_i, + input stb_i, + input we_i, + output ack_o, + input [31:0] dat_i, + output [31:0] dat_o + ); + + reg rdwrb, csib; + + + reg [2:0] icap_state; + localparam ICAP_IDLE = 0; + localparam ICAP_WR0 = 1; + localparam ICAP_WR1 = 2; + localparam ICAP_RD0 = 3; + localparam ICAP_RD1 = 4; + + localparam IDLE = 1'b1; + localparam ACTIVE = 1'b0; + localparam READ = 1'b1; + localparam WRITE = 1'b0; + + + always @(posedge clk) + if(reset) begin + rdwrb <= READ; + csib <= IDLE; + icap_state <= ICAP_IDLE; + end + else + case(icap_state) + // + // In IDLE state waiting for a READ or WRITE to be signalled from the WB bus. + // (In this state rdwrb can flip state without effect because ICAP is not selected) + // + ICAP_IDLE : + begin + if(stb_i & cyc_i) begin + if(we_i) begin + // Start WRITE, assert RDWR_B LOW whilst CSI_B remains HIGH. + rdwrb <= WRITE; + csib <= IDLE; + icap_state <= ICAP_WR0; + end else begin + // Start READ + rdwrb <= READ; + csib <= IDLE; + icap_state <= ICAP_RD0; + end + end else begin + // Stay IDLE + rdwrb <= READ; + csib <= IDLE; + icap_state <= ICAP_IDLE; + end + end // case: ICAP_IDLE + // + // First cycle of WRITE. + // Next cycle assert RDWR_B LOW and assert CSI_B LOW. + // + ICAP_WR0 : begin + rdwrb <= WRITE; + csib <= ACTIVE; + icap_state <= ICAP_WR1; + end + // + // Second cycle of WRITE. + // Next cycle assert RDWR_B LOW and assert CSI_B HIGH whilst transitioning to IDLE state + // + ICAP_WR1 : begin + rdwrb <= WRITE; + csib <= IDLE; + icap_state <= ICAP_IDLE; + end + // + // First cycle of READ. + // Next cycle assert RDWR_B HIGH and assert CSI_B LOW. + // + ICAP_RD0 : begin + rdwrb <= READ; + csib <= ACTIVE; + icap_state <= ICAP_WR1; + end + // + // Second cycle of READ. + // Next cycle assert RDWR_B HIGH and assert CSI_B HIGH whilst transitioning to IDLE state + // + ICAP_RD1 : begin + rdwrb <= READ; + csib <= IDLE; + icap_state <= ICAP_IDLE; + end + + endcase // case (icap_state) + + assign ack_o = (icap_state == ICAP_WR1) | (icap_state == ICAP_RD1); + //assign debug_out = {17'd0, BUSY, dat_i[7:0], ~CE, ICAPCLK, ~WRITE, icap_state}; + + + ICAPE2 #( + .DEVICE_ID(32'h03651093), + .ICAP_WIDTH("X32"), + .SIM_CFG_FILE_NAME("NONE") + ) + ICAPE2_inst ( + .O(/*dat_o[31:0]*/), + .CLK(clk), // Rising edge referenced for both reads and writes. + .CSIB(csib), // CSIB = 0 to select ICAP + .I(dat_i[31:0]), // Bitswaped as per SELECTMAP (See UG470 page 40) + .RDWRB(rdwrb) // RDWB = 0 for WRITE, = 1 for READ + ); + + assign dat_0 = 32'h0; + +endmodule // s3a_icap_wb diff --git a/fpga/usrp3/lib/control/serial_to_settings.v b/fpga/usrp3/lib/control/serial_to_settings.v new file mode 100644 index 000000000..180191fa5 --- /dev/null +++ b/fpga/usrp3/lib/control/serial_to_settings.v @@ -0,0 +1,126 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module serial_to_settings + ( + input clk, + input reset, + // Serial signals (async) + input scl, + input sda, + // Settngs bus out + output reg set_stb, + output reg [7:0] set_addr, + output reg [31:0] set_data, + // Debug + output [31:0] debug + ); + + reg [2:0] state; + + localparam SEARCH = 3'h0; + localparam ADDRESS = 3'h1; + localparam DATA = 3'h2; + localparam STOP1 = 3'h3; + localparam STOP2 = 3'h4; + + reg scl_pre_reg, scl_reg, scl_reg2; + reg sda_pre_reg, sda_reg, sda_reg2; + reg [4:0] counter; + + + always @(posedge clk) begin + scl_reg2 <= scl_reg; + scl_reg <= scl_pre_reg; + scl_pre_reg <= scl; + sda_reg2 <= sda_reg; + sda_reg <= sda_pre_reg; + sda_pre_reg <= sda; + end + + + always @(posedge clk) + if (reset) begin + state <= SEARCH; + counter <= 0; + set_addr <= 0; + set_data <= 0; + set_stb <= 0; + end else begin + case(state) + // + // Search for I2C like start indication: SDA goes low whilst clock is high. + // + SEARCH: begin + set_stb <= 0; + // Look for START. + if (scl_reg && scl_reg2 && !sda_reg && sda_reg2) begin + state <= ADDRESS; + counter <= 0; + end + end + // + // Count 8 Address bits. + // Master changes SDA on falling edge of SCL, we sample on the rising edge. + // + ADDRESS: begin + if (scl_reg && !scl_reg2) begin + set_addr[7:0] <= {set_addr[6:0],sda_reg}; + if (counter == 7) begin + state <= DATA; + counter <= 0; + end else + counter <= counter + 1; + end + end + // + // Count 32 data bits. + // Master changes SDA on falling edge of SCL, we sample on the rising edge. + // + DATA: begin + if (scl_reg && !scl_reg2) begin + set_data[31:0] <= {set_data[30:0],sda_reg}; + if (counter == 31) begin + state <= STOP1; + counter <= 0; + end else + counter <= counter + 1; + end + end + // + // Looks for rising SCL edge before STOP bit. + // + STOP1: begin + if (scl_reg && !scl_reg2) begin + state <= STOP2; + end + end + // + // Looks for STOP bit + // + STOP2: begin + if (scl_reg && scl_reg2 && sda_reg && !sda_reg2) begin + state <= SEARCH; + counter <= 0; + set_stb <= 1; + end + end + + endcase // case(state) + end // else: !if(reset) + + assign debug = + { + counter[4:0], + state[2:0], + scl_reg, + sda_reg + }; + + + +endmodule // serial_to_settings diff --git a/fpga/usrp3/lib/control/serial_to_settings_tb.v b/fpga/usrp3/lib/control/serial_to_settings_tb.v new file mode 100644 index 000000000..d3688f216 --- /dev/null +++ b/fpga/usrp3/lib/control/serial_to_settings_tb.v @@ -0,0 +1,86 @@ +// +// Copyright 2014 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module serial_to_settings_tb(); + + + + reg clk; + reg reset; + + wire scl; + wire sda; + wire set_stb; + wire [7:0] set_addr; + wire [31:0] set_data; + + // + // These registers optionaly used + // to drive nets through procedural assignments in test bench. + // These drivers default to tri-stated. + // + reg scl_r; + reg sda_r; + + assign scl = scl_r; + assign sda = sda_r; + + initial + begin + scl_r <= 1'bz; + sda_r <= 1'bz; + end + + + + serial_to_settings serial_to_settings_i + ( + .clk(clk), + .reset(reset), + // Serial signals (async) + .scl(scl), + .sda(sda), + // Settngs bus out + .set_stb(set_stb), + .set_addr(set_addr), + .set_data(set_data) + ); + + // Nasty HAck to convert settings to wishbone crudely. + reg wb_stb; + wire wb_ack_o; + + + always @(posedge clk) + if (reset) + wb_stb <= 0; + else + wb_stb <= set_stb ? 1 : ((wb_ack_o) ? 0 : wb_stb); + + simple_uart debug_uart + ( + .clk_i(clk), + .rst_i(reset), + .we_i(wb_stb), + .stb_i(wb_stb), + .cyc_i(wb_stb), + .ack_o(wb_ack_o), + .adr_i(set_addr[2:0]), + .dat_i(set_data[31:0]), + .dat_o(), + .rx_int_o(), + .tx_int_o(), + .tx_o(txd), + .rx_i(rxd), + .baud_o() + ); + + // + // Bring in a simulation script here + // + `include "simulation_script.v" + +endmodule diff --git a/fpga/usrp3/lib/control/setting_reg.v b/fpga/usrp3/lib/control/setting_reg.v new file mode 100644 index 000000000..09bf66286 --- /dev/null +++ b/fpga/usrp3/lib/control/setting_reg.v @@ -0,0 +1,38 @@ +// +// Copyright 2011-2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +//---------------------------------------------------------------------- +//-- A settings register is a peripheral for the settings register bus. +//-- When the settings register sees strobe abd a matching address, +//-- the outputs will be become registered to the given input bus. +//---------------------------------------------------------------------- + +module setting_reg + #(parameter my_addr = 0, + parameter awidth = 8, + parameter width = 32, + parameter at_reset=0) + (input clk, input rst, input strobe, input wire [awidth-1:0] addr, + input wire [31:0] in, output reg [width-1:0] out, output reg changed); + + always @(posedge clk) + if(rst) + begin + out <= at_reset; + changed <= 1'b0; + end + else + if(strobe & (my_addr==addr)) + begin + out <= in[width-1:0]; + changed <= 1'b1; + end + else + changed <= 1'b0; + +endmodule // setting_reg diff --git a/fpga/usrp3/lib/control/settings_bus_mux.v b/fpga/usrp3/lib/control/settings_bus_mux.v new file mode 100644 index 000000000..8d120c9c7 --- /dev/null +++ b/fpga/usrp3/lib/control/settings_bus_mux.v @@ -0,0 +1,44 @@ +// +// Copyright 2016 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Mux multiple settings buses + +module settings_bus_mux #( + parameter PRIO=0, // 0 = Round robin, 1 = Lower ports get priority (see axi_mux) + parameter AWIDTH=8, + parameter DWIDTH=32, + parameter FIFO_SIZE=1, + parameter NUM_BUSES=2) +( + input clk, input reset, input clear, + input [NUM_BUSES-1:0] in_set_stb, input [NUM_BUSES*AWIDTH-1:0] in_set_addr, input [NUM_BUSES*DWIDTH-1:0] in_set_data, + output out_set_stb, output [AWIDTH-1:0] out_set_addr, output [DWIDTH-1:0] out_set_data, input ready +); + + wire [NUM_BUSES*(AWIDTH+DWIDTH)-1:0] i_tdata; + generate + if(NUM_BUSES <= 1) begin + assign out_set_stb = in_set_stb; + assign out_set_addr = in_set_addr; + assign out_set_data = in_set_data; + end else begin + genvar i; + for (i = 0; i < NUM_BUSES; i = i + 1) begin + assign i_tdata[(i+1)*(AWIDTH+DWIDTH)-1:i*(AWIDTH+DWIDTH)] = {in_set_addr[(i+1)*AWIDTH-1:i*AWIDTH],in_set_data[(i+1)*DWIDTH-1:i*DWIDTH]}; + end + axi_mux #( + .PRIO(PRIO), + .WIDTH(AWIDTH+DWIDTH), + .PRE_FIFO_SIZE($clog2(NUM_BUSES)), + .POST_FIFO_SIZE(FIFO_SIZE), + .SIZE(NUM_BUSES)) + axi_mux ( + .clk(clk), .reset(reset), .clear(clear), + .i_tdata(i_tdata), .i_tlast({NUM_BUSES{1'b1}}), .i_tvalid(in_set_stb), .i_tready(), + .o_tdata({out_set_addr,out_set_data}), .o_tlast(), .o_tvalid(out_set_stb), .o_tready(ready)); + end + endgenerate +endmodule
\ No newline at end of file diff --git a/fpga/usrp3/lib/control/settings_bus_timed_2clk.v b/fpga/usrp3/lib/control/settings_bus_timed_2clk.v new file mode 100644 index 000000000..2b62ffd26 --- /dev/null +++ b/fpga/usrp3/lib/control/settings_bus_timed_2clk.v @@ -0,0 +1,170 @@ +// +// Copyright 2018 Ettus Research, A National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// Module: settings_bus_timed_2clk +// Description: +// - Stores settings bus transaction in a FIFO and +// releases them based on VITA time input +// - Also moves the settings bus to the timebase +// clock domain +// + +module settings_bus_timed_2clk #( + parameter SR_AWIDTH = 8, + parameter SR_DWIDTH = 32, + parameter RB_AWIDTH = 8, + parameter RB_DWIDTH = 64, + parameter TIMED_CMDS_EN = 0 +) ( + input sb_clk, // Settings bus clock + input sb_rst, // Reset (sb_clk) + input tb_clk, // Timebase clock + input tb_rst, // Reset (tb_clk) + + input [63:0] vita_time, // Current timebase time + + input s_set_stb, // Settings bus strobe + input [SR_AWIDTH-1:0] s_set_addr, // Settings address + input [SR_DWIDTH-1:0] s_set_data, // Settings data + input s_set_has_time, // Is this a timed command? + input [63:0] s_set_time, // Command time + output s_set_pending, // Is settings transaction pending? + input [RB_AWIDTH-1:0] s_rb_addr, // Readback address + output s_rb_stb, // Readback data strobe + output [RB_DWIDTH-1:0] s_rb_data, // Readback data value + + output m_set_stb, // Settings bus strobe + output [SR_AWIDTH-1:0] m_set_addr, // Settings address + output [SR_DWIDTH-1:0] m_set_data, // Settings data + output m_set_has_time, // Is this a timed command? + output [63:0] m_set_time, // Command time + input m_set_pending, // Is settings transaction pending? + output [RB_AWIDTH-1:0] m_rb_addr, // Readback address + input m_rb_stb, // Readback data strobe + input [RB_DWIDTH-1:0] m_rb_data // Readback data value +); + + // States for input and output state machines + localparam [2:0] ST_IDLE = 3'd0; // Nothing is happening on the bus + localparam [2:0] ST_SET_ISSUED = 3'd1; // A settings transaction has been issued + localparam [2:0] ST_SET_PENDING = 3'd2; // A settings transaction is pending + localparam [2:0] ST_RB_PENDING = 3'd3; // Waiting for readback data + localparam [2:0] ST_RB_DONE = 3'd4; // Readback data is valid + + wire rb_valid; + + // Input state machine + reg [2:0] in_state = ST_IDLE; + always @(posedge sb_clk) begin + if (sb_rst) begin + in_state <= ST_IDLE; + end else begin + case (in_state) + ST_IDLE: begin + if (s_set_stb) begin + in_state <= ST_SET_PENDING; + end + end + ST_SET_PENDING: begin + if (rb_valid) begin + in_state <= ST_RB_DONE; + end + end + ST_RB_DONE: begin + in_state <= ST_IDLE; + end + default: begin + in_state <= ST_IDLE; + end + endcase + end + end + assign s_set_pending = (in_state == ST_SET_PENDING); + assign s_rb_stb = (in_state == ST_RB_DONE); + + // Clock crossing FIFO (settings) + // TODO: Look into a more efficient implementation for a single element + // clock crossing FIFO. + wire set_pending, set_finished; + axi_fifo_2clk #( + .WIDTH(SR_AWIDTH+SR_DWIDTH+1+64+RB_AWIDTH), .SIZE(0) + ) sb_2clk_fifo_i ( + .i_aclk(sb_clk), .reset(sb_rst), + .i_tdata({s_set_addr, s_set_data, s_set_has_time, s_set_time, s_rb_addr}), + .i_tvalid(s_set_stb), .i_tready(/* Ignored: FIFO may not have an exact size*/), + .o_aclk(tb_clk), + .o_tdata({m_set_addr, m_set_data, m_set_has_time, m_set_time, m_rb_addr}), + .o_tvalid(set_pending), .o_tready(set_finished) + ); + + // Time compare logic + // If ~has_time then pass the transaction through, otherwise wait for time + // to tick up to command time + wire now, late; + wire go = ((TIMED_CMDS_EN == 1) && m_set_has_time) ? (now | late) : 1'b1; + + // If this is a timed command then vita_time == m_set_time one cycle before + // strobe is asserted i.e. timed strobe assertion has a one cycle latency + time_compare time_compare ( + .clk(tb_clk), .reset(tb_rst), + .time_now(vita_time), .trigger_time(m_set_time), + .now(now), .early(), .late(late), .too_early() + ); + + // Clock crossing FIFO (readback) + reg [RB_DWIDTH-1:0] cached_rb_data; + axi_fifo_2clk #( + .WIDTH(RB_DWIDTH), .SIZE(0) + ) rbdata_2clk_fifo_i ( + .reset(tb_rst), + .i_aclk(tb_clk), .i_tdata(cached_rb_data), .i_tvalid(set_finished), .i_tready(), + .o_aclk(sb_clk), .o_tdata(s_rb_data), .o_tvalid(rb_valid), .o_tready(s_rb_stb) + ); + + // Output state machine + reg [2:0] out_state = ST_IDLE; + always @(posedge tb_clk) begin + if (tb_rst) begin + out_state <= ST_IDLE; + end else begin + case (out_state) + ST_IDLE: begin + if (go & set_pending) begin + out_state <= ST_SET_ISSUED; + end + end + ST_SET_ISSUED: begin + out_state <= ST_SET_PENDING; + end + ST_SET_PENDING: begin + if (~m_set_pending) begin + if (m_rb_stb) begin + out_state <= ST_RB_DONE; + cached_rb_data <= m_rb_data; + end else begin + out_state <= ST_RB_PENDING; + end + end + end + ST_RB_PENDING: begin + if (m_rb_stb) begin + out_state <= ST_RB_DONE; + cached_rb_data <= m_rb_data; + end + end + ST_RB_DONE: begin + out_state <= ST_IDLE; + end + default: begin + out_state <= ST_IDLE; + end + endcase + end + end + + assign m_set_stb = (out_state == ST_SET_ISSUED); + assign set_finished = (out_state == ST_RB_DONE); + +endmodule diff --git a/fpga/usrp3/lib/control/simple_i2c_core.v b/fpga/usrp3/lib/control/simple_i2c_core.v new file mode 100644 index 000000000..fbbd2e5a1 --- /dev/null +++ b/fpga/usrp3/lib/control/simple_i2c_core.v @@ -0,0 +1,107 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +// Simple I2C core + +// Settings reg map: +// +// BASE+0 control register +// byte0 - control bits, data byte, or command bits, prescaler +// byte1 - what to do? (documented in cpp file) +// write prescaler lo +// write prescaler hi +// write control +// write data +// write command +// read data +// read status +// + +// Readback: +// +// byte0 has readback value based on the last read command +// + +module simple_i2c_core + #( + //settings register base address + parameter BASE = 0, + + //i2c line level at reset + parameter ARST_LVL = 1 + ) + ( + //clock and synchronous reset + input clock, input reset, + + //32-bit settings bus inputs + input set_stb, input [7:0] set_addr, input [31:0] set_data, + + //32-bit data readback + output reg [31:0] readback, + + //read is high when i2c core can begin another transaction + output reg ready, + + // I2C signals + // i2c clock line + input scl_pad_i, // SCL-line input + output scl_pad_o, // SCL-line output (always 1'b0) + output scl_padoen_o, // SCL-line output enable (active low) + + // i2c data line + input sda_pad_i, // SDA-line input + output sda_pad_o, // SDA-line output (always 1'b0) + output sda_padoen_o, // SDA-line output enable (active low) + + //optional debug output + output [31:0] debug + ); + + //declare command settings register + wire [7:0] sr_what, sr_data; + wire sr_changed; + setting_reg #(.my_addr(BASE+0),.width(16)) i2c_cmd_sr( + .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data), + .out({sr_what, sr_data}),.changed(sr_changed)); + + //declare wb interface signals + wire [2:0] wb_addr; + wire [7:0] wb_data_mosi; + wire [7:0] wb_data_miso; + wire wb_we, wb_stb, wb_cyc; + wire wb_ack; + + //create wishbone-based i2c core + i2c_master_top #(.ARST_LVL(ARST_LVL)) i2c + (.wb_clk_i(clock),.wb_rst_i(reset),.arst_i(1'b0), + .wb_adr_i(wb_addr),.wb_dat_i(wb_data_mosi),.wb_dat_o(wb_data_miso), + .wb_we_i(wb_we),.wb_stb_i(wb_stb),.wb_cyc_i(wb_cyc), + .wb_ack_o(wb_ack),.wb_inta_o(), + .scl_pad_i(scl_pad_i),.scl_pad_o(scl_pad_o),.scl_padoen_o(scl_padoen_o), + .sda_pad_i(sda_pad_i),.sda_pad_o(sda_pad_o),.sda_padoen_o(sda_padoen_o) ); + + //not ready between setting register and wishbone ack + always @(posedge clock) begin + if (reset || wb_ack) ready <= 1; + else if (sr_changed) ready <= 0; + end + + //register wishbone data on every ack + always @(posedge clock) begin + if (wb_ack) readback <= {24'b0, wb_data_miso}; + end + + //assign wishbone signals + assign wb_addr = sr_what[2:0]; + assign wb_stb = sr_changed; + assign wb_we = wb_stb && sr_what[3]; + assign wb_cyc = wb_stb; + assign wb_data_mosi = sr_data; + +endmodule //simple_i2c_core diff --git a/fpga/usrp3/lib/control/simple_spi_core.v b/fpga/usrp3/lib/control/simple_spi_core.v new file mode 100644 index 000000000..dd94b1b2d --- /dev/null +++ b/fpga/usrp3/lib/control/simple_spi_core.v @@ -0,0 +1,229 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + + +// Simple SPI core, the simplest, yet complete spi core I can think of + +// Settings register controlled. +// 2 settings regs, control and data +// 1 32-bit readback and status signal + +// Settings reg map: +// +// BASE+0 divider setting +// bits [15:0] spi clock divider +// +// BASE+1 configuration input +// bits [23:0] slave select, bit0 = slave0 enabled +// bits [29:24] num bits (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+2 input data +// Writing this register begins a spi transaction. +// Bits are latched out from bit 0. +// Therefore, load this register in reverse. +// +// Readback +// Bits are latched into bit 0. +// Therefore, data will be in-order. + +module simple_spi_core + #( + //settings register base address + parameter BASE = 0, + + //width of serial enables (up to 24 is possible) + parameter WIDTH = 8, + + //idle state of the spi clock + parameter CLK_IDLE = 0, + + //idle state of the serial enables + parameter SEN_IDLE = 24'hffffff + ) + ( + //clock and synchronous reset + input clock, input reset, + + //32-bit settings bus inputs + input set_stb, input [7:0] set_addr, input [31:0] set_data, + + //32-bit data readback + output [31:0] readback, + output reg readback_stb, + + //read is high when spi core can begin another transaction + output ready, + + //spi interface, slave selects, clock, data in, data out + output [WIDTH-1:0] sen, + output sclk, + output reg mosi, + input miso, + + //optional debug output + output [31:0] debug + ); + + wire [15:0] sclk_divider; + setting_reg #(.my_addr(BASE+0),.width(16)) divider_sr( + .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data), + .out(sclk_divider),.changed()); + + wire [23:0] slave_select; + wire [5:0] num_bits; + wire datain_edge, dataout_edge; + setting_reg #(.my_addr(BASE+1),.width(32)) ctrl_sr( + .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data), + .out({dataout_edge, datain_edge, num_bits, slave_select}),.changed()); + + wire [31:0] mosi_data; + wire trigger_spi; + setting_reg #(.my_addr(BASE+2),.width(32)) data_sr( + .clk(clock),.rst(reset),.strobe(set_stb),.addr(set_addr),.in(set_data), + .out(mosi_data),.changed(trigger_spi)); + + localparam WAIT_TRIG = 0; + localparam PRE_IDLE = 1; + localparam CLK_REG = 2; + localparam CLK_INV = 3; + localparam POST_IDLE = 4; + localparam IDLE_SEN = 5; + + reg [2:0] state; + + reg ready_reg; + assign ready = ready_reg && ~trigger_spi; + + //serial clock either idles or is in one of two clock states + reg sclk_reg; + assign sclk = sclk_reg; + + //serial enables either idle or enabled based on state + // IJB. One pipeline stage to break critical path from register in I/O pads. + wire sen_is_idle = (state == WAIT_TRIG) || (state == IDLE_SEN); + wire [23:0] sen24 = (sen_is_idle)? SEN_IDLE : (SEN_IDLE ^ slave_select); + reg [WIDTH-1:0] sen_reg = SEN_IDLE; + always @(posedge clock) begin + if (reset) begin + sen_reg <= SEN_IDLE; + end else begin + sen_reg <= sen24[WIDTH-1:0]; + end + end + assign sen = sen_reg; + + //data output shift register + // IJB. One pipeline stage to break critical path from register in I/O pads. + reg [31:0] dataout_reg; + wire [31:0] dataout_next = {dataout_reg[30:0], 1'b0}; + + always @(posedge clock) + mosi <= dataout_reg[31]; + + //data input shift register + // IJB. Two pipeline stages to break critical path from register in I/O pads. + reg miso_pipe, miso_pipe2; + always @(posedge clock) begin + miso_pipe2 <= miso; + miso_pipe <= miso_pipe2; + end + + reg [31:0] datain_reg; + wire [31:0] datain_next = {datain_reg[30:0], miso_pipe}; + assign readback = datain_reg; + + //counter for spi clock + reg [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 + reg [6:0] bit_counter; + wire [6:0] bit_counter_next = bit_counter + 1; + wire bit_counter_done = (bit_counter_next == num_bits); + + always @(posedge clock) begin + if (reset) begin + state <= WAIT_TRIG; + sclk_reg <= CLK_IDLE; + ready_reg <= 0; + readback_stb <= 1'b0; + end + else begin + case (state) + + WAIT_TRIG: begin + if (trigger_spi) state <= PRE_IDLE; + readback_stb <= 1'b0; + ready_reg <= ~trigger_spi; + dataout_reg <= mosi_data; + sclk_counter <= 0; + bit_counter <= 0; + sclk_reg <= CLK_IDLE; + end + + PRE_IDLE: begin + if (sclk_counter_done) state <= CLK_REG; + sclk_counter <= sclk_counter_next; + sclk_reg <= CLK_IDLE; + end + + CLK_REG: begin + if (sclk_counter_done) begin + state <= CLK_INV; + if (datain_edge != CLK_IDLE) datain_reg <= datain_next; + if (dataout_edge != CLK_IDLE && bit_counter != 0) dataout_reg <= dataout_next; + sclk_reg <= ~CLK_IDLE; //transition to rising when CLK_IDLE == 0 + end + sclk_counter <= sclk_counter_next; + end + + CLK_INV: begin + if (sclk_counter_done) begin + state <= (bit_counter_done)? POST_IDLE : CLK_REG; + bit_counter <= bit_counter_next; + if (datain_edge == CLK_IDLE) datain_reg <= datain_next; + if (dataout_edge == CLK_IDLE && ~bit_counter_done) dataout_reg <= dataout_next; + sclk_reg <= CLK_IDLE; //transition to falling when CLK_IDLE == 0 + end + sclk_counter <= sclk_counter_next; + end + + POST_IDLE: begin + if (sclk_counter_done) state <= IDLE_SEN; + sclk_counter <= sclk_counter_next; + sclk_reg <= CLK_IDLE; + end + + IDLE_SEN: begin + if (sclk_counter_done) begin + ready_reg <= 1'b1; + readback_stb <= 1'b1; + state <= WAIT_TRIG; + end + sclk_counter <= sclk_counter_next; + sclk_reg <= CLK_IDLE; + end + + default: state <= WAIT_TRIG; + + endcase //state + end + end + + assign debug = { + trigger_spi, state, //4 + sclk, mosi, miso, ready, //4 + //sen[7:0], //8 + 1'b0, bit_counter[6:0], //8 + sclk_counter_done, bit_counter_done, //2 + sclk_counter[5:0] //6 + }; + +endmodule //simple_spi_core diff --git a/fpga/usrp3/lib/control/synchronizer.v b/fpga/usrp3/lib/control/synchronizer.v new file mode 100644 index 000000000..177b1219c --- /dev/null +++ b/fpga/usrp3/lib/control/synchronizer.v @@ -0,0 +1,50 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module synchronizer #( + parameter WIDTH = 1, + parameter STAGES = 2, + parameter INITIAL_VAL = 0, + parameter FALSE_PATH_TO_IN = 1 +)( + input clk, + input rst, + input [WIDTH-1:0] in, + output [WIDTH-1:0] out +); + + //Q: Why do we have a separate impl and instantiate + //it with a different instance name based on this + //arbitrary parameter FALSE_PATH_TO_IN? + //A: To make constraining these synchronizers easier. + //We would like to write a single false path constraint + //for all synchronizers when the input is truly async. + //However other cases might require constraining the input + //of this module. + //To enable this, all clients that hook up async signals to + //the "in" port can set FALSE_PATH_TO_IN=1 (or use the default) + //and all clients that want the "in" delay to be constrained can + //set FALSE_PATH_TO_IN=0. + //In the XDC we can write the following async constraint: + //set_false_path -to [get_pins */synchronizer_false_path/stages[0].value_reg[0]/D] + //and this will take care of all instances of this module with FALSE_PATH_TO_IN==1 + + generate if (FALSE_PATH_TO_IN == 1) begin + synchronizer_impl #( + .WIDTH(WIDTH), .STAGES(STAGES), .INITIAL_VAL(INITIAL_VAL) + ) synchronizer_false_path ( + .clk(clk), .rst(rst), .in(in), .out(out) + ); + end else begin + synchronizer_impl #( + .WIDTH(WIDTH), .STAGES(STAGES), .INITIAL_VAL(INITIAL_VAL) + ) synchronizer_constrained ( + .clk(clk), .rst(rst), .in(in), .out(out) + ); + end endgenerate + +endmodule //synchronizer diff --git a/fpga/usrp3/lib/control/synchronizer_impl.v b/fpga/usrp3/lib/control/synchronizer_impl.v new file mode 100644 index 000000000..1c4f77707 --- /dev/null +++ b/fpga/usrp3/lib/control/synchronizer_impl.v @@ -0,0 +1,47 @@ +// +// Copyright 2014 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +module synchronizer_impl #( + parameter WIDTH = 1, + parameter STAGES = 2, + parameter INITIAL_VAL = 0 +)( + input clk, + input rst, + input [WIDTH-1:0] in, + output [WIDTH-1:0] out +); + + (* ASYNC_REG = "TRUE" *) reg [WIDTH-1:0] value[0:STAGES-1]; + + integer k; + initial begin + for (k = 0; k < STAGES; k = k + 1) begin + value[k] = INITIAL_VAL; + end + end + + genvar i; + generate + for (i=0; i<STAGES; i=i+1) begin: stages + always @(posedge clk) begin + if (rst) begin + value[i] <= INITIAL_VAL; + end else begin + if (i == 0) begin + value[i] <= in; + end else begin + value[i] <= value[i-1]; + end + end + end + end + endgenerate + + assign out = value[STAGES-1]; + +endmodule //synchronizer_impl diff --git a/fpga/usrp3/lib/control/user_settings.v b/fpga/usrp3/lib/control/user_settings.v new file mode 100644 index 000000000..6f4641b33 --- /dev/null +++ b/fpga/usrp3/lib/control/user_settings.v @@ -0,0 +1,66 @@ +// +// Copyright 2012 Ettus Research LLC +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation, either version 3 of the License, or +// (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU General Public License for more details. +// +// You should have received a copy of the GNU General Public License +// along with this program. If not, see <http://www.gnu.org/licenses/>. +// + +// User settings bus +// +// Provides 8-bit address, 32-bit data write only bus for user settings, consumes to addresses in +// normal settings bus. +// +// Write user address to BASE +// Write user data to BASE+1 +// +// The user_set_stb will strobe after data write, must write new address even if same as previous one. + +module user_settings + #(parameter BASE=0) + (input clk, + input rst, + + input set_stb, + input [7:0] set_addr, + input [31:0] set_data, + + output set_stb_user, + output [7:0] set_addr_user, + output [31:0] set_data_user + ); + + wire addr_changed, data_changed; + reg stb_int; + + setting_reg #(.my_addr(BASE+0),.width(8)) sr_0 + (.clk(clk),.rst(rst),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(set_addr_user),.changed(addr_changed) ); + + setting_reg #(.my_addr(BASE+1)) sr_1 + (.clk(clk),.rst(rst),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(set_data_user),.changed(data_changed) ); + + always @(posedge clk) + if (rst|set_stb_user) + stb_int <= 0; + else + if (addr_changed) + stb_int <= 1; + + assign set_stb_user = stb_int & data_changed; + +endmodule // user_settings + |