Update copyright on recently changed files. Signed-off-by: michael-west <michael.west@ettus.com> Original-commit: 92c09f7f766586683ebd67bb3a57005250c4d81d
336 lines
13 KiB
Verilog
336 lines
13 KiB
Verilog
//
|
|
// 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
|