diff options
Diffstat (limited to 'fpga/usrp3/lib/rfnoc/duc.v')
-rw-r--r-- | fpga/usrp3/lib/rfnoc/duc.v | 275 |
1 files changed, 275 insertions, 0 deletions
diff --git a/fpga/usrp3/lib/rfnoc/duc.v b/fpga/usrp3/lib/rfnoc/duc.v new file mode 100644 index 000000000..7a4fcc602 --- /dev/null +++ b/fpga/usrp3/lib/rfnoc/duc.v @@ -0,0 +1,275 @@ +// +// Copyright 2016 Ettus Research +// Copyright 2018 Ettus Research, a National Instruments Company +// +// SPDX-License-Identifier: LGPL-3.0-or-later +// + +//! RFNoC specific digital up-conversion chain +// High level block diagram: +// +// HB1 -> HB2 -> CIC -> DDS/multiplier -> Scaler + +// We don't care about framing here, hence no tlast + +module duc #( + parameter SR_PHASE_INC_ADDR = 0, + parameter SR_SCALE_ADDR = 1, + parameter SR_INTERP_ADDR = 2, + parameter NUM_HB = 2, + parameter CIC_MAX_INTERP = 128 + +)( + input clk, input reset, input clear, + input set_stb, input [7:0] set_addr, input [31:0] set_data, + input [31:0] i_tdata, input [127:0] i_tuser, input i_tvalid, output i_tready, + output [31:0] o_tdata, output [127:0] o_tuser, output o_tvalid, input o_tready +); + + localparam RESET_DELAY = 3; + + localparam WIDTH = 16; // Input/output bitwidth of the module + localparam CWIDTH = 24; // Internal bitwidth needed for CORDIC accuracy + localparam PWIDTH = 32; // Phase accumulator bitwidth + + reg [1:0] hb_rate; // Current Halfband rate + reg [7:0] cic_interp_rate; // Current CIC rate + + wire [1:0] hb_rate_int; + wire [7:0] cic_interp_rate_int; + + wire [2*CWIDTH-1:0] o_tdata_halfbands; // Halfband output + wire o_tvalid_halfbands; + + wire rate_changed; // Rate changed by the settings registers + wire reset_on_change; // Reset the halfbands and the cic everytime there is a rate change + wire reset_on_live_change; // Reset when rate changes while streaming + + wire [PWIDTH-1:0] o_tdata_phase; + wire o_tvalid_phase; + wire o_tlast_phase; + wire i_tready_phase; + + wire [17:0] scale_factor; + + /************************************************************************** + * Settings registers + **************************************************************************/ + // AXI settings bus for phase values + axi_setting_reg #( + .ADDR(SR_PHASE_INC_ADDR), .AWIDTH(8), .WIDTH(PWIDTH), .STROBE_LAST(1), .REPEATS(1)) + axi_sr_phase ( + .clk(clk), .reset(reset), + .set_stb(set_stb), .set_addr(set_addr), .set_data(set_data), + .o_tdata(o_tdata_phase), .o_tlast(o_tlast_phase), .o_tvalid(o_tvalid_phase), .o_tready(i_tready_phase)); + + // AXI settings bus for scale + setting_reg #(.my_addr(SR_SCALE_ADDR), .width(18)) sr_scale ( + .clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out(scale_factor),.changed()); + + // AXI settings bus for interpolation rate + setting_reg #(.my_addr(SR_INTERP_ADDR), .width(10), .at_reset(1)) sr_interp + (.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr), + .in(set_data),.out({hb_rate_int,cic_interp_rate_int}),.changed(rate_changed)); + + // Changing interpolation rates while processing only when axi_rate_change sends a clear + reg active, rate_changed_hold; + reg [RESET_DELAY-1:0] shift_reset; + always @(posedge clk) begin + if (reset) begin + active <= 1'b0; + rate_changed_hold <= 1'b0; + cic_interp_rate <= 'd1; + hb_rate <= 'd0; + shift_reset <= 'd0; + end else begin + if (clear | reset_on_change) begin + active <= 1'b0; + end else if (i_tready & i_tvalid) begin + active <= 1'b1; + end + if (rate_changed & active) begin + rate_changed_hold <= 1'b1; + end + if ((clear | ~active) & (rate_changed | rate_changed_hold)) begin + rate_changed_hold <= 1'b0; + cic_interp_rate <= cic_interp_rate_int; + hb_rate <= hb_rate_int; + shift_reset <= {shift_reset[RESET_DELAY-1:0], 1'b1}; + end else begin + shift_reset <= {shift_reset[RESET_DELAY-1:0], 1'b0}; + end + end + end + + // Long reset for the halfbands + assign reset_on_change = |shift_reset; + assign reset_on_live_change = (clear | reset_on_change | (~active & rate_changed)); + + /************************************************************************** + * Halfbands + *************************************************************************/ + + // Sign extend from 16 to 24 bits to increase the accuracy from the frequency shifter + wire [2*CWIDTH-1:0] o_tdata_extd; + + sign_extend #(.bits_in(WIDTH), .bits_out(CWIDTH)) sign_extend_in_i ( + .in(i_tdata[2*WIDTH-1:WIDTH]), .out(o_tdata_extd[2*CWIDTH-1:CWIDTH])); + + sign_extend #(.bits_in(WIDTH), .bits_out(CWIDTH)) sign_extend_in_q ( + .in(i_tdata[WIDTH-1:0]), .out(o_tdata_extd[CWIDTH-1:0])); + + // Halfband 1 wires + wire i_tready_hb1; + wire [2*CWIDTH-1:0] o_tdata_hb1; + wire o_tvalid_hb1, o_tready_hb1; + // Halfband 2 wires + wire i_tready_hb2; + wire [2*CWIDTH-1:0] o_tdata_hb2; + wire o_tvalid_hb2, o_tready_hb2; + // Halfband 3 wires + wire i_tready_hb3; + wire [2*CWIDTH-1:0] o_tdata_hb3; + wire o_tvalid_hb3, o_tready_hb3; + generate + if( NUM_HB > 0 ) begin + axi_hb47 halfband1 ( + .aclk(clk), + .aresetn(~(reset | clear | reset_on_change)), + .s_axis_data_tvalid(i_tvalid), + .s_axis_data_tready(i_tready_hb1), + .s_axis_data_tdata(o_tdata_extd), + .m_axis_data_tvalid(o_tvalid_hb1), + .m_axis_data_tready(o_tready_hb1), + .m_axis_data_tdata(o_tdata_hb1) + ); + end else begin + assign o_tdata_hb1 = 'h0; + assign o_tvalid_hb1 = 1'h0; + assign i_tready_hb1 = 1'b0; + end + if( NUM_HB > 1 ) begin + axi_hb47 halfband2 ( + .aclk(clk), + .aresetn(~(reset | clear | reset_on_change)), + .s_axis_data_tvalid(o_tvalid_hb1), + .s_axis_data_tready(i_tready_hb2), + .s_axis_data_tdata({o_tdata_hb1[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb1[CWIDTH-1:0] << 2}), + .m_axis_data_tvalid(o_tvalid_hb2), + .m_axis_data_tready(o_tready_hb2), + .m_axis_data_tdata(o_tdata_hb2) + ); + end else begin + assign o_tdata_hb2 = 'h0; + assign o_tvalid_hb2 = 1'h0; + assign i_tready_hb2 = 1'b0; + end + if( NUM_HB > 2 ) begin + axi_hb47 halfband3 ( + .aclk(clk), + .aresetn(~(reset | clear | reset_on_change)), + .s_axis_data_tvalid(o_tvalid_hb2), + .s_axis_data_tready(i_tready_hb3), + .s_axis_data_tdata({o_tdata_hb2[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb2[CWIDTH-1:0] << 2}), + .m_axis_data_tvalid(o_tvalid_hb3), + .m_axis_data_tready(o_tready_hb3), + .m_axis_data_tdata(o_tdata_hb3) + ); + end else begin + assign o_tdata_hb3 = 'h0; + assign o_tvalid_hb3 = 1'h0; + assign i_tready_hb3 = 1'b0; + end + endgenerate + /************************************************************************** + * Halfband selection multiplexing + *************************************************************************/ + wire [2*CWIDTH-1:0] o_tdata_cic; + wire [2*CWIDTH-1:0] o_cic; + wire o_tvalid_cic, i_tready_cic; + wire o_tready_cic; + + assign o_tdata_halfbands = (hb_rate == 2'b0) ? o_tdata_extd : + (hb_rate == 2'b1) ? {o_tdata_hb1[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb1[CWIDTH-1:0] << 2} : + (hb_rate == 2'b10) ? {o_tdata_hb2[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb2[CWIDTH-1:0] << 2} : + {o_tdata_hb3[2*CWIDTH-1:CWIDTH] << 2, o_tdata_hb3[CWIDTH-1:0] << 2}; + // Clearing valid on rate change as the halfbands take 2 cycles to clear + assign o_tvalid_halfbands = reset_on_live_change ? 1'b0 : + (hb_rate == 2'b0) ? i_tvalid : + (hb_rate == 2'b1) ? o_tvalid_hb1 : + (hb_rate == 2'b10) ? o_tvalid_hb2 : + o_tvalid_hb3; + // Throttle input data while rate change is going on + assign i_tready = reset_on_live_change ? 1'b0 : + (hb_rate == 2'b0) ? i_tready_cic : + i_tready_hb1; + assign o_tready_hb1 = reset_on_live_change ? 1'b0 : + (hb_rate == 2'b1) ? i_tready_cic : + i_tready_hb2; + assign o_tready_hb2 = reset_on_live_change ? 1'b0 : + (hb_rate == 2'b10) ? i_tready_cic : + i_tready_hb3; + + assign o_tready_hb3 = reset_on_live_change ? 1'b0 : i_tready_cic; + + /************************************************************************** + * Ettus CIC; the Xilinx CIC has a minimum interpolation of 4, + * so we use the strobed version and convert to and from AXI. + *************************************************************************/ + wire to_cic_stb, from_cic_stb; + wire [2*CWIDTH-1:0] to_cic_data; + wire [CWIDTH-1:0] i_cic; + wire [CWIDTH-1:0] q_cic; + + // Convert from AXI to strobed and back to AXI again for the CIC interpolation module + axi_to_strobed #(.WIDTH(2*CWIDTH), .FIFO_SIZE(1), .MIN_RATE(128)) axi_to_strobed ( + .clk(clk), .reset(reset | reset_on_change), .clear(clear), + .out_rate(cic_interp_rate), .ready(i_tready_cartesian & o_tready), .error(), + .i_tdata(o_tdata_halfbands), .i_tvalid(o_tvalid_halfbands), .i_tlast(1'b0), .i_tready(i_tready_cic), + .out_stb(to_cic_stb), .out_last(), .out_data(to_cic_data) + ); + + cic_interpolate #(.WIDTH(CWIDTH), .N(4), .MAX_RATE(CIC_MAX_INTERP)) cic_interpolate_i ( + .clk(clk), .reset(reset | clear | reset_on_change), + .rate_stb(reset_on_change), + .rate(cic_interp_rate), .strobe_in(to_cic_stb), .strobe_out(from_cic_stb), + .signal_in(to_cic_data[2*CWIDTH-1:CWIDTH]), .signal_out(i_cic) + ); + + cic_interpolate #(.WIDTH(CWIDTH), .N(4), .MAX_RATE(CIC_MAX_INTERP)) cic_interpolate_q ( + .clk(clk), .reset(reset | clear | reset_on_change), + .rate_stb(reset_on_change), + .rate(cic_interp_rate), .strobe_in(to_cic_stb), .strobe_out(), + .signal_in(to_cic_data[CWIDTH-1:0]), .signal_out(q_cic) + ); + + assign o_cic = {i_cic, q_cic}; + + //FIFO_SIZE = 8 infers a bram fifo + strobed_to_axi #(.WIDTH(2*CWIDTH), .FIFO_SIZE(8)) strobed_to_axi ( + .clk(clk), .reset(reset | reset_on_change), .clear(clear), + .in_stb(from_cic_stb), .in_data(o_cic), .in_last(1'b0), + .o_tdata(o_tdata_cic), .o_tvalid(o_tvalid_cic), .o_tlast(), .o_tready(o_tready_cic) + ); + + + /************************************************************************** + * Clip back to 16 bits + *************************************************************************/ + wire o_tvalid_clip; + + axi_round_and_clip_complex #( + .WIDTH_IN(CWIDTH), .WIDTH_OUT(WIDTH), .CLIP_BITS(CWIDTH-WIDTH)) // No rounding, all clip + axi_round_and_clip_complex ( + .clk(clk), .reset(reset | clear | reset_on_change), + .i_tdata(o_tdata_cic), .i_tlast(1'b0), .i_tvalid(o_tvalid_cic), .i_tready(o_tready_cic), + .o_tdata(o_tdata), .o_tlast(), .o_tvalid(o_tvalid_clip), .o_tready(i_tready_cartesian)); + + assign o_tvalid = reset_on_live_change ? 1'b0 : o_tvalid_clip; + assign i_tready_cartesian = reset_on_live_change ? 1'b0 : o_tready; + + // Note: To facilitate timed tunes, the code has been moved outside + // the duc module to dds_timed.v. + +endmodule // duc |