fpga: rfnoc: Fix DDC overflow issue
Fixed an issue where the DDC could experience an arithmetic overflow when receiving a full-scale signal with non-zero DDS frequency. Original-commit: 5030d0106b6d2230a1c28e72f765098c7916cf1b
This commit is contained in:
committed by
Wade Fife
parent
2535aa4f1b
commit
cfa8afcf37
@@ -17,6 +17,8 @@ module rfnoc_block_ddc_tb();
|
||||
import PkgTestExec::*;
|
||||
import PkgChdrUtils::*;
|
||||
import PkgRfnocBlockCtrlBfm::*;
|
||||
import PkgRfnocItemUtils::*;
|
||||
import PkgMath::*;
|
||||
|
||||
`include "rfnoc_block_ddc_regs.vh"
|
||||
|
||||
@@ -43,7 +45,6 @@ module rfnoc_block_ddc_tb();
|
||||
localparam int CIC_MAX_DECIM = 255;
|
||||
localparam int NOC_ID = 32'hDDC00000;
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Clocks
|
||||
//---------------------------------------------------------------------------
|
||||
@@ -62,6 +63,8 @@ module rfnoc_block_ddc_tb();
|
||||
//---------------------------------------------------------------------------
|
||||
|
||||
typedef ChdrData #(CHDR_W, SAMP_W)::chdr_word_t chdr_word_t;
|
||||
typedef ChdrData #(CHDR_W, SAMP_W)::item_t item_t;
|
||||
|
||||
|
||||
RfnocBackendIf backend (rfnoc_chdr_clk, rfnoc_ctrl_clk);
|
||||
AxiStreamIf #(32) m_ctrl (rfnoc_ctrl_clk, 1'b0);
|
||||
@@ -185,15 +188,18 @@ module rfnoc_block_ddc_tb();
|
||||
);
|
||||
`ASSERT_ERROR(
|
||||
cic_rate > 0 && cic_rate <= CIC_MAX_DECIM,
|
||||
"CIC Decimation rate must be positive, not exceed the max cic decimation rate, and cannot equal 0!"
|
||||
{"CIC Decimation rate must be positive, not exceed the max cic ",
|
||||
"decimation rate, and cannot equal 0!"}
|
||||
);
|
||||
|
||||
// Setup DDC
|
||||
$display("Set decimation to %0d", decim_rate);
|
||||
$display("- Number of enabled HBs: %0d", hb_enables);
|
||||
$display("- CIC Rate: %0d", cic_rate);
|
||||
write_reg(port, SR_N_ADDR, decim_rate); // Set decimation rate in AXI rate change
|
||||
write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate}); // Enable HBs, set CIC rate
|
||||
// Set decimation rate in AXI rate change
|
||||
write_reg(port, SR_N_ADDR, decim_rate);
|
||||
// Enable HBs, set CIC rate
|
||||
write_reg(port, SR_DECIM_ADDR, {hb_enables,cic_rate});
|
||||
endtask
|
||||
|
||||
|
||||
@@ -219,7 +225,9 @@ module rfnoc_block_ddc_tb();
|
||||
|
||||
pkt_info = 0;
|
||||
for (int i = 0; i < decim_rate*(PKT_SIZE_BYTES/8 + extra_samples); i++) begin
|
||||
send_payload.push_back({16'(2*i/decim_rate), 16'(2*i/decim_rate), 16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)});
|
||||
send_payload.push_back(
|
||||
{16'(2*i/decim_rate), 16'(2*i/decim_rate),
|
||||
16'((2*i+1)/decim_rate), 16'((2*i+1)/decim_rate)});
|
||||
end
|
||||
$display("Send ramp (%0d words)", send_payload.size());
|
||||
pkt_info.eob = 1;
|
||||
@@ -248,10 +256,12 @@ module rfnoc_block_ddc_tb();
|
||||
`ASSERT_ERROR(pkt_info.eob == 1'b1, s);
|
||||
recv_payload = {temp_payload, recv_payload};
|
||||
if (drop_partial_packet) begin
|
||||
$sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size());
|
||||
$sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d",
|
||||
PKT_SIZE_BYTES/8, recv_payload.size());
|
||||
`ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8, s);
|
||||
end else begin
|
||||
$sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d", PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples);
|
||||
$sformat(s, "Incorrect packet size! Expected: %0d, Actual: %0d",
|
||||
PKT_SIZE_BYTES/8, recv_payload.size() + extra_samples);
|
||||
`ASSERT_ERROR(recv_payload.size() == PKT_SIZE_BYTES/8 + extra_samples, s);
|
||||
end
|
||||
samples = 64'd0;
|
||||
@@ -260,9 +270,13 @@ module rfnoc_block_ddc_tb();
|
||||
samples = recv_payload[i];
|
||||
for (int j = 0; j < 4; j++) begin
|
||||
// Need to check a range of values due to imperfect gain compensation
|
||||
$sformat(s, "Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i,
|
||||
samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4, samples[16*j +: 16]);
|
||||
`ASSERT_ERROR((samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) && (samples >= samples_old[16*j +: 16]), s);
|
||||
$sformat(s,
|
||||
"Ramp word %0d invalid! Expected: %0d-%0d, Received: %0d", 2*i,
|
||||
samples_old[16*j +: 16], samples_old[16*j +: 16]+16'd4,
|
||||
samples[16*j +: 16]);
|
||||
`ASSERT_ERROR(
|
||||
(samples_old[16*j +: 16]+16'd4 >= samples[16*j +: 16]) &&
|
||||
(samples >= samples_old[16*j +: 16]), s);
|
||||
end
|
||||
samples_old = samples;
|
||||
end
|
||||
@@ -271,6 +285,146 @@ module rfnoc_block_ddc_tb();
|
||||
join
|
||||
endtask
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Send tone
|
||||
//---------------------------------------------------------------------------
|
||||
// Generates samples for a sine wave tone and sends them to the specified BFM
|
||||
// port.
|
||||
//
|
||||
// Parameters:
|
||||
// port: Port number
|
||||
// decim_rate: Decimation rate
|
||||
// tone_ampl: Amplitude of generated sine wave
|
||||
// tone_freq_norm: Normalized frequency of generated sine wave
|
||||
// dsp_tune_norm: Normalized digital frequency shift
|
||||
// num_samps_to_send: Number of samples to send
|
||||
// restrict_input: Coerce input tone samples to 16-bit signed range
|
||||
//
|
||||
// Note: The tone_freq_norm and dsp_tune_norm parameters must be in the range
|
||||
// (-1/2, +1/2).
|
||||
//---------------------------------------------------------------------------
|
||||
task automatic send_tone (
|
||||
input int unsigned port = 0,
|
||||
input int unsigned decim_rate = 1,
|
||||
input real tone_ampl = 0.9,
|
||||
input real tone_freq_norm = 0.0,
|
||||
input real dsp_tune_norm = 0.0,
|
||||
input int num_samps_to_send = 1000,
|
||||
input int restrict_input = 1
|
||||
);
|
||||
chdr_word_t recv_metadata[$];
|
||||
item_t recv_payload[$], send_payload[$];
|
||||
packet_info_t recv_pkt_info, send_pkt_info;
|
||||
int unsigned total_decim, cic_decim;
|
||||
real cic_gain_float;
|
||||
logic [31:0] cic_gain;
|
||||
logic [31:0] dsp_tune_word;
|
||||
|
||||
|
||||
// Tuning word = F_shift/Fs * 2^32 (Shift freq must be [-Fs/2,Fs/2))
|
||||
dsp_tune_word = int'(dsp_tune_norm * 2.0**32);
|
||||
$display("send_tone(): dsp_tune_word = %d", dsp_tune_word);
|
||||
write_reg(port, rfnoc_block_ddc_i.SR_FREQ_ADDR, dsp_tune_word);
|
||||
|
||||
// Calculate CIC gain
|
||||
total_decim = decim_rate;
|
||||
cic_decim = 1;
|
||||
for (int i = 0; i < NUM_HB; i++) begin
|
||||
if (total_decim > 1) begin
|
||||
if (total_decim[0]) begin // Not divisible by 2
|
||||
cic_decim = total_decim;
|
||||
break;
|
||||
end else begin // Divisible by 2
|
||||
total_decim = total_decim/2;
|
||||
cic_decim = total_decim;
|
||||
end
|
||||
end
|
||||
end
|
||||
// DDS Gain is 2.0
|
||||
cic_gain_float = 1/((2.0*(cic_decim**4))/(2.0**$clog2(cic_decim**4)));
|
||||
cic_gain = int'((1 << 15)*cic_gain_float);
|
||||
$display("send_tone(): CIC Rate = %d", cic_decim);
|
||||
$display("send_tone(): CIC Gain = %f", cic_gain_float);
|
||||
|
||||
set_decim_rate(port, decim_rate);
|
||||
write_reg(port, rfnoc_block_ddc_i.SR_SCALE_IQ_ADDR, cic_gain);
|
||||
|
||||
@(posedge rfnoc_block_ddc_i.ce_clk);
|
||||
if (num_samps_to_send > 0) begin
|
||||
fork
|
||||
// Receive samples
|
||||
begin
|
||||
automatic item_t prev_item, curr_item;
|
||||
real prev, curr;
|
||||
do begin
|
||||
blk_ctrl.recv_items_adv(port, recv_payload, recv_metadata, recv_pkt_info);
|
||||
end while (recv_pkt_info.eob == 0);
|
||||
//check for jumps in of I/Q data
|
||||
prev_item = recv_payload.pop_front();
|
||||
foreach (recv_payload[recv_idx]) begin
|
||||
curr_item = recv_payload.pop_front();
|
||||
// Compare previous sample data to current sample data. If delta is
|
||||
// more than half of the 16-bit signed value range(e.g. SHORT_MAX),
|
||||
// we assume an arithmetic overflow has occurred.
|
||||
prev = real'(signed'(prev_item[SAMP_W/2+:SAMP_W/2]));
|
||||
curr = real'(signed'(curr_item[SAMP_W/2+:SAMP_W/2]));
|
||||
`ASSERT_ERROR((Math#(real)::abs(curr-prev) < SHORT_MAX/2),
|
||||
"Detected jump in I data.");
|
||||
prev = real'(signed'(prev_item[0+:SAMP_W/2]));
|
||||
curr = real'(signed'(curr_item[0+:SAMP_W/2]));
|
||||
`ASSERT_ERROR((Math#(real)::abs(curr-prev) < SHORT_MAX/2),
|
||||
"Detected jump in Q data.");
|
||||
prev_item = curr_item;
|
||||
end
|
||||
end
|
||||
// Send tone
|
||||
begin
|
||||
typedef logic [15:0] logic_t; //needed for typecasting to packed logic array
|
||||
automatic real i_float, q_float;
|
||||
automatic logic [15:0] i, q;
|
||||
automatic longint phase = 0;
|
||||
|
||||
send_pkt_info = '{
|
||||
vc : 0,
|
||||
eov : 1'b0,
|
||||
eob : 1'b0,
|
||||
has_time : 1'b0,
|
||||
timestamp : 64'd0
|
||||
};
|
||||
|
||||
while (num_samps_to_send > 0) begin
|
||||
send_payload = {}; // Clear out previous iteration's samples
|
||||
for (int n = 0; n < SPP; n++) begin
|
||||
i_float = tone_ampl*(2.0**15)*$cos(2*PI*phase*tone_freq_norm);
|
||||
q_float = tone_ampl*(2.0**15)*$sin(2*PI*phase*tone_freq_norm);
|
||||
phase++;
|
||||
// if restrict_input is set, we need to coerce the float values to
|
||||
// 16-bit signed values instead of cutting off the additional MSBs,
|
||||
// ensuring a smooth transition in the tone.
|
||||
if (restrict_input) begin
|
||||
i = logic_t'(coerce_to_int16(i_float));
|
||||
q = logic_t'(coerce_to_int16(q_float));
|
||||
end else begin
|
||||
i = logic_t'(i_float);
|
||||
q = logic_t'(q_float);
|
||||
end
|
||||
send_payload.push_back({i,q});
|
||||
|
||||
num_samps_to_send--;
|
||||
if (num_samps_to_send == 0) begin
|
||||
break;
|
||||
end
|
||||
end
|
||||
|
||||
send_pkt_info.eob = (num_samps_to_send == 0);
|
||||
blk_ctrl.send_items(port, send_payload, {}, send_pkt_info);
|
||||
blk_ctrl.wait_complete(port);
|
||||
end
|
||||
end
|
||||
join
|
||||
end
|
||||
endtask
|
||||
|
||||
|
||||
//---------------------------------------------------------------------------
|
||||
// Test Process
|
||||
@@ -318,7 +472,23 @@ module rfnoc_block_ddc_tb();
|
||||
read_user_reg(port, RB_NUM_HB, val64);
|
||||
`ASSERT_ERROR(val64 == NUM_HB, "Register NUM_HB didn't read back expected value");
|
||||
read_user_reg(port, RB_CIC_MAX_DECIM, val64);
|
||||
`ASSERT_ERROR(val64 == CIC_MAX_DECIM, "Register CIC_MAX_DECIM didn't read back expected value");
|
||||
`ASSERT_ERROR(val64 == CIC_MAX_DECIM,
|
||||
"Register CIC_MAX_DECIM didn't read back expected value");
|
||||
test.end_test();
|
||||
end
|
||||
|
||||
//-------------------------------------------------------------------------
|
||||
// Test overflow
|
||||
//-------------------------------------------------------------------------
|
||||
begin
|
||||
test.start_test("Test tone", 0.5ms);
|
||||
send_tone(.port (0),
|
||||
.decim_rate (2),
|
||||
.tone_ampl (1.0),
|
||||
.tone_freq_norm (1.0/1024.0),
|
||||
.dsp_tune_norm (1.0/1024.0),
|
||||
.num_samps_to_send(10000),
|
||||
.restrict_input (1));
|
||||
test.end_test();
|
||||
end
|
||||
|
||||
|
||||
+20
-7
@@ -67,6 +67,7 @@ module ddc #(
|
||||
wire phase_tvalid, phase_tready, phase_tlast;
|
||||
wire dds_out_tlast;
|
||||
wire dds_out_tvalid;
|
||||
wire dds_out_tready;
|
||||
wire [15:0] dds_input_fifo_space, dds_input_fifo_occupied;
|
||||
|
||||
wire [17:0] scale_factor;
|
||||
@@ -271,17 +272,29 @@ module ddc #(
|
||||
/* IQ output */
|
||||
.m_axis_dout_tlast(dds_out_tlast),
|
||||
.m_axis_dout_tvalid(dds_out_tvalid),
|
||||
.m_axis_dout_tready(ddc_chain_tready),
|
||||
.m_axis_dout_tready(dds_out_tready),
|
||||
.m_axis_dout_tdata({dds_out_q_tdata, dds_out_i_tdata})
|
||||
|
||||
);
|
||||
|
||||
//48 = WIDTH*2
|
||||
//chop off top byte because it's not actually used and we want to match expected gain/bit use found in freq shift
|
||||
assign i_dds_clip = {dds_out_i_tdata[15:0],8'h00};
|
||||
assign q_dds_clip = {dds_out_q_tdata[15:0],8'h00};
|
||||
assign strobe_dds_clip = dds_out_tvalid & sample_out_tready;
|
||||
assign last_cic_decimate_in = dds_out_tlast;
|
||||
// Drop MSBs to match expected gain/bit use found in freq shift
|
||||
axi_clip_complex #(
|
||||
.WIDTH_IN (WIDTH),
|
||||
.WIDTH_OUT (WIDTH-8),
|
||||
.FIFOSIZE (0))
|
||||
axi_clip_complex_post_dds (
|
||||
.clk(clk),
|
||||
.reset(reset | clear),
|
||||
.i_tdata({dds_out_i_tdata, dds_out_q_tdata}),
|
||||
.i_tlast(dds_out_tlast),
|
||||
.i_tvalid(dds_out_tvalid),
|
||||
.i_tready(dds_out_tready),
|
||||
.o_tdata({i_dds_clip[WIDTH-1:8], q_dds_clip[WIDTH-1:8]}),
|
||||
.o_tlast(last_cic_decimate_in),
|
||||
.o_tvalid(strobe_dds_clip),
|
||||
.o_tready(ddc_chain_tready));
|
||||
assign i_dds_clip[7:0] = 8'h00;
|
||||
assign q_dds_clip[7:0] = 8'h00;
|
||||
|
||||
/** CIC DECIMATE **/
|
||||
cic_decimate #(.WIDTH(WIDTH), .N(4), .MAX_RATE(CIC_MAX_DECIM)) cic_decimate_i (
|
||||
|
||||
Reference in New Issue
Block a user