1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335
|
//
// Copyright 2015 Ettus Research LLC
// Copyright 2018 Ettus Research, a National Instruments Company
// Copyright 2020 Ettus Research, a National Instruments Brand
// Copyright 2023 Ettus Research, a National Instruments Brand
//
// SPDX-License-Identifier: LGPL-3.0-or-later
//
// Module: rx_frontend_gen3
//
// Description:
//
// RX Frontend Correction Module
// -----------------------------
//
// This module will perform the following modifications of the signal, in this
// order:
//
// 1) I/Q Swapping/Reordering: The I and Q values from the ADC can be remapped
// arbitrarily and can be inverted. The behaviour of this IQ mux is
// controlled by writing to the settings register at SR_IQ_MAPPING.
//
// This register uses the following bits:
//
// Bit 0: Set to 1 to swap I and Q.
// Bit 1: Set to 1 to enable real mode. If it is 1, then
// the Q input signal is ignored and assumed to be zero.
// Bit 2: Set to 1 to invert the Q input signal
// Bit 3: Set to 1 to invert the I input signal
// Bit 4: Set to 1 to enable the quarter-rate downconverter (only relevant
// when BYPASS_HETERODYNE is set to 0, see below).
// Bit 7: Disable all corrections in this module.
//
// 2) DC offset correction. See the rx_dcoffset module for details. This is
// either a fixed DC offset, or a notch filter around DC. The behaviour of
// this correction step is controlled by writing to settings registers at
// SR_OFFSET_I and SR_OFFSET_Q (they get forwarded to rx_dcoffset).
// Set BYPASS_DC_OFFSET_CORR to 1 to not synthesize this step.
//
// 3) IQ imbalance correction. This implements a simple, one-shot IQ imbalance
// correction. It will modify the I and Q signals as follows:
// _ _ _ _ _ _
// | I' | | A/64+1 0 | | I |
// | | = | | | |
// | Q' | | B/64 1 | | Q |
// ‾ ‾ ‾ ‾ ‾ ‾
//
// Here, A is the value written to the register at SR_MAG_CORRECTION, and
// B is the value written to the register at SR_PHASE_CORRECTION.
// Set BYPASS_IQ_COMP to 1 to not synthesize this step.
//
// 4) Heterodyne conversion. The converter is only enabled when the
// "downconvert" bit in the SR_IQ_MAPPING register is asserted. In this
// case, it enables a quarter-rate mixer. The direction of this mixer is
// controlled by the SR_HET_PHASE_INCR register (a 0 in this register
// rotates by pi/2 every clock cycle, a 1 in this register rotates by -pi/2).
//
// The mixer is followed by a non-decimating FIR filter.
//
// Set BYPASS_HETERODYNE to 1 to not synthesize this step.
//
//
// UHD Developers Note: This module is typically controlled by rx_frontend_core_3000 in UHD,
// and is also described in the calibration.dox manual page. When modifying this file, make
// sure to also modify those files if necessary.
//
// Parameters:
// SR_MAG_CORRECTION : Settings register address for the IQ correction "MAG" value
// SR_PHASE_CORRECTION : Settings register address for the IQ correction "PHASE" value
// SR_OFFSET_I : Settings register address for DC offset I correction
// value (goes to rx_dcoffset)
// SR_OFFSET_Q : Settings register address for DC offset Q correction
// value (goes to rx_dcoffset)
// SR_IQ_MAPPING : Settings register address for IQ mapping value
// SR_HET_PHASE_INCR : Settings register address for the real mode phase
// increment value
// BYPASS_DC_OFFSET_CORR : Set to 1 to disable DC offset correction
// BYPASS_IQ_COMP : Set to 1 to disable IQ offset correction
// BYPASS_HETERODYNE : Set to 1 to disable heterodyne conversion
// DEVICE : Unused.
//
module rx_frontend_gen3 #(
parameter SR_MAG_CORRECTION = 0,
parameter SR_PHASE_CORRECTION = 1,
parameter SR_OFFSET_I = 2,
parameter SR_OFFSET_Q = 3,
parameter SR_IQ_MAPPING = 4,
parameter SR_HET_PHASE_INCR = 5,
parameter BYPASS_DC_OFFSET_CORR = 0,
parameter BYPASS_IQ_COMP = 0,
parameter BYPASS_HETERODYNE = 0,
parameter DEVICE = "7SERIES"
)(
input clk, input reset, input sync_in,
input set_stb, input [7:0] set_addr, input [31:0] set_data,
input adc_stb, input [15:0] adc_i, input [15:0] adc_q,
output rx_stb, output [15:0] rx_i, output [15:0] rx_q
);
wire realmode;
wire swap_iq;
wire invert_i;
wire invert_q;
wire downconvert;
wire bypass_all;
wire [1:0] iq_map_reserved;
wire [17:0] mag_corr, phase_corr;
wire phase_dir;
wire phase_sync;
reg [23:0] adc_i_mux, adc_q_mux;
reg adc_mux_stb;
wire [23:0] adc_i_ofs, adc_q_ofs, adc_i_comp, adc_q_comp;
wire adc_ofs_stb, adc_comp_stb;
reg [1:0] adc_ofs_stb_dly;
wire [23:0] adc_i_dsp, adc_q_dsp;
wire adc_dsp_stb;
wire [15:0] rx_i_out, rx_q_out;
/********************************************************
** Settings Bus Registers
********************************************************/
setting_reg #(.my_addr(SR_MAG_CORRECTION),.width(18)) sr_mag_corr (
.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
.in(set_data),.out(mag_corr),.changed());
setting_reg #(.my_addr(SR_PHASE_CORRECTION),.width(18)) sr_phase_corr (
.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
.in(set_data),.out(phase_corr),.changed());
setting_reg #(.my_addr(SR_IQ_MAPPING), .width(8)) sr_mux_sel (
.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
.in(set_data),
.out({bypass_all,iq_map_reserved,downconvert,invert_i,invert_q,realmode,swap_iq}),
.changed());
// Setting reg: 1 bit to set phase direction: default to 0:
// direction bit == 0: the phase is increased by pi/2 (counter clockwise)
// direction bit == 1: the phase is increased by -pi/2 (clockwise)
setting_reg #(.my_addr(SR_HET_PHASE_INCR), .width(1)) sr_phase_dir (
.clk(clk),.rst(reset),.strobe(set_stb),.addr(set_addr),
.in(set_data),.out(phase_dir),.changed(phase_sync));
/********************************************************
** IQ Mapping (swapping, inversion, real-mode)
********************************************************/
// MUX so we can do realmode signals on either input
always @(posedge clk) begin
if (swap_iq) begin
adc_i_mux[23:8] <= invert_q ? ~adc_q : adc_q;
adc_q_mux[23:8] <= realmode ? 16'd0 : invert_i ? ~adc_i : adc_i;
end else begin
adc_i_mux[23:8] <= invert_i ? ~adc_i : adc_i;
adc_q_mux[23:8] <= realmode ? 16'd0 : invert_q ? ~adc_q : adc_q;
end
adc_mux_stb <= adc_stb;
adc_i_mux[7:0] <= 8'd0;
adc_q_mux[7:0] <= 8'd0;
end
/********************************************************
** DC offset Correction
********************************************************/
generate
if (BYPASS_DC_OFFSET_CORR == 0) begin
rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_I)) rx_dcoffset_i (
.clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data),
.in_stb(adc_mux_stb),.in(adc_i_mux),
.out_stb(adc_ofs_stb),.out(adc_i_ofs));
rx_dcoffset #(.WIDTH(24),.ADDR(SR_OFFSET_Q)) rx_dcoffset_q (
.clk(clk),.rst(reset),.set_stb(set_stb),.set_addr(set_addr),.set_data(set_data),
.in_stb(adc_mux_stb),.in(adc_q_mux),
.out_stb(),.out(adc_q_ofs));
end else begin
assign adc_ofs_stb = adc_mux_stb;
assign adc_i_ofs = adc_i_mux;
assign adc_q_ofs = adc_q_mux;
end
endgenerate
/********************************************************
** IQ Imbalance Compensation
********************************************************/
generate
if (BYPASS_IQ_COMP == 0) begin
mult_add_clip #(
.WIDTH_A(18),
.BIN_PT_A(17),
.WIDTH_B(18),
.BIN_PT_B(17),
.WIDTH_C(24),
.BIN_PT_C(23),
.WIDTH_O(24),
.BIN_PT_O(23),
.LATENCY(2)
) mult_i (
.clk(clk),
.reset(reset),
.CE(1'b1),
.A(adc_i_ofs[23:6]),
.B(mag_corr),
.C(adc_i_ofs),
.O(adc_i_comp)
);
mult_add_clip #(
.WIDTH_A(18),
.BIN_PT_A(17),
.WIDTH_B(18),
.BIN_PT_B(17),
.WIDTH_C(24),
.BIN_PT_C(23),
.WIDTH_O(24),
.BIN_PT_O(23),
.LATENCY(2)
) mult_q (
.clk(clk),
.reset(reset),
.CE(1'b1),
.A(adc_i_ofs[23:6]),
.B(phase_corr),
.C(adc_q_ofs),
.O(adc_q_comp)
);
// Delay to match path latencies
always @(posedge clk) begin
if (reset) begin
adc_ofs_stb_dly <= 2'b0;
end else begin
adc_ofs_stb_dly <= {adc_ofs_stb_dly[0], adc_ofs_stb};
end
end
assign adc_comp_stb = adc_ofs_stb_dly[1];
end else begin
assign adc_comp_stb = adc_ofs_stb;
assign adc_i_comp = adc_i_ofs;
assign adc_q_comp = adc_q_ofs;
end
endgenerate
/********************************************************
** Realmode DSP:
* - Heterodyne frequency translation
* - Realmode decimation (by 2)
********************************************************/
generate
if (BYPASS_HETERODYNE == 0) begin
wire [23:0] adc_i_dsp_cout, adc_q_dsp_cout;
wire [23:0] adc_i_filt, adc_q_filt;
wire adc_dsp_cout_stb;
wire adc_filt_stb;
// 90 degree mixer
quarter_rate_downconverter #(.WIDTH(24)) qr_dc_i(
.clk(clk), .reset(reset || sync_in), .phase_sync(phase_sync),
.i_tdata({adc_i_comp, adc_q_comp}), .i_tlast(1'b1), .i_tvalid(adc_comp_stb), .i_tready(),
.o_tdata({adc_i_dsp_cout, adc_q_dsp_cout}), .o_tlast(), .o_tvalid(adc_dsp_cout_stb), .o_tready(1'b1),
.dirctn(phase_dir));
// Double FIR block
localparam HB_COEFS = {-18'd62, 18'd0, 18'd194, 18'd0, -18'd440, 18'd0, 18'd855, 18'd0, -18'd1505, 18'd0, 18'd2478, 18'd0,
-18'd3900, 18'd0, 18'd5990, 18'd0, -18'd9187, 18'd0, 18'd14632, 18'd0, -18'd26536, 18'd0, 18'd83009, 18'd131071, 18'd83009,
18'd0, -18'd26536, 18'd0, 18'd14632, 18'd0, -18'd9187, 18'd0, 18'd5990, 18'd0, -18'd3900, 18'd0, 18'd2478, 18'd0, -18'd1505,
18'd0, 18'd855, 18'd0, -18'd440, 18'd0, 18'd194, 18'd0, -18'd62};
// FIR filter for real part
axi_fir_filter #(.IN_WIDTH(24), .COEFF_WIDTH(18), .OUT_WIDTH(24), .NUM_COEFFS(47), .COEFFS_VEC(HB_COEFS),
.RELOADABLE_COEFFS(0), .BLANK_OUTPUT(0), .SYMMETRIC_COEFFS(1), .SKIP_ZERO_COEFFS(1), .USE_EMBEDDED_REGS_COEFFS(0)
) hbfir0(
.clk(clk),
.reset(reset),
.clear(reset),
.s_axis_data_tdata(adc_i_dsp_cout),
.s_axis_data_tlast(1'b1),
.s_axis_data_tvalid(adc_dsp_cout_stb),
.s_axis_data_tready(),
.m_axis_data_tdata(adc_i_filt),
.m_axis_data_tlast(),
.m_axis_data_tvalid(adc_filt_stb),
.m_axis_data_tready(1'b1),
.s_axis_reload_tdata(18'd0),
.s_axis_reload_tvalid(1'b0),
.s_axis_reload_tlast(1'b0),
.s_axis_reload_tready()
);
// FIR filter for imag. part
axi_fir_filter #(.IN_WIDTH(24), .COEFF_WIDTH(18), .OUT_WIDTH(24), .NUM_COEFFS(47), .COEFFS_VEC(HB_COEFS),
.RELOADABLE_COEFFS(0), .BLANK_OUTPUT(0), .SYMMETRIC_COEFFS(1), .SKIP_ZERO_COEFFS(1), .USE_EMBEDDED_REGS_COEFFS(0)
) hbfir1(
.clk(clk),
.reset(reset),
.clear(reset),
.s_axis_data_tdata(adc_q_dsp_cout),
.s_axis_data_tlast(1'b1),
.s_axis_data_tvalid(adc_dsp_cout_stb),
.s_axis_data_tready(),
.m_axis_data_tdata(adc_q_filt),
.m_axis_data_tlast(),
.m_axis_data_tvalid(),
.m_axis_data_tready(1'b1),
.s_axis_reload_tdata(18'd0),
.s_axis_reload_tvalid(1'b0),
.s_axis_reload_tlast(1'b0),
.s_axis_reload_tready()
);
assign adc_dsp_stb = downconvert ? adc_filt_stb : adc_comp_stb;
assign adc_i_dsp = downconvert ? adc_i_filt : adc_i_comp;
assign adc_q_dsp = downconvert ? adc_q_filt : adc_q_comp;
end else begin
assign adc_dsp_stb = adc_comp_stb;
assign adc_i_dsp = adc_i_comp;
assign adc_q_dsp = adc_q_comp;
end
endgenerate
// Round to short complex (sc16)
round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_i (
.clk(clk),.reset(reset), .in(adc_i_dsp),.strobe_in(adc_dsp_stb), .out(rx_i_out), .strobe_out(rx_stb));
round_sd #(.WIDTH_IN(24),.WIDTH_OUT(16)) round_q (
.clk(clk),.reset(reset), .in(adc_q_dsp),.strobe_in(adc_dsp_stb), .out(rx_q_out), .strobe_out());
assign rx_i = bypass_all ? adc_i : rx_i_out;
assign rx_q = bypass_all ? adc_q : rx_q_out;
endmodule
|