diff options
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 + |