mirror of
https://github.com/jankae/LibreVNA.git
synced 2026-02-24 08:24:16 +01:00
implement FPGA-based CDS to avoid BRAM overflow
Move CDS phase computation from per-point config storage to FPGA logic. The FPGA now computes the 180° phase shift internally (M/2) and loops twice per point when CDS is enabled. This keeps config memory at 96 bits instead of 112, avoiding BRAM overflow on the Spartan 6. FPGA changes: - Add CDS_ENABLED input to Sweep module (controlled via SPI register 6) - Compute source_phase = M/2 for 180° shift when cds_phase=1 - State machine loops through all stages twice per point when CDS enabled - RESULT_INDEX now includes cds_phase bit: stage[2:0] & point[11:0] & cds_phase Firmware changes: - Add FPGA::SetCDSEnabled() to control CDS via register 6 bit 11 - Update SamplingResult to include cdsPhase field (1 bit) - Simplify VNA.cpp: FPGA handles phase switching, MCU combines results Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
0b571688a9
commit
1ea012aa57
10
FPGA/VNA/SPIConfig.vhd
Normal file → Executable file
10
FPGA/VNA/SPIConfig.vhd
Normal file → Executable file
|
|
@ -45,7 +45,7 @@ entity SPICommands is
|
|||
MAX2871_DEF_3 : out STD_LOGIC_VECTOR (31 downto 0);
|
||||
MAX2871_DEF_1 : out STD_LOGIC_VECTOR (31 downto 0);
|
||||
MAX2871_DEF_0 : out STD_LOGIC_VECTOR (31 downto 0);
|
||||
SWEEP_DATA : out STD_LOGIC_VECTOR (111 downto 0);
|
||||
SWEEP_DATA : out STD_LOGIC_VECTOR (95 downto 0);
|
||||
SWEEP_ADDRESS : out STD_LOGIC_VECTOR (12 downto 0);
|
||||
SWEEP_WRITE : out STD_LOGIC_VECTOR (0 downto 0);
|
||||
SWEEP_POINTS : out STD_LOGIC_VECTOR (12 downto 0);
|
||||
|
|
@ -54,6 +54,7 @@ entity SPICommands is
|
|||
SETTLING_TIME : out STD_LOGIC_VECTOR (19 downto 0);
|
||||
SYNC_ENABLED : out STD_LOGIC;
|
||||
SYNC_MASTER : out STD_LOGIC;
|
||||
CDS_ENABLED : out STD_LOGIC;
|
||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_EN : out STD_LOGIC;
|
||||
|
|
@ -288,6 +289,7 @@ begin
|
|||
when 5 => ADC_PHASEINC <= spi_buf_out(11 downto 0);
|
||||
when 6 => STAGES <= spi_buf_out(15 downto 13);
|
||||
SYNC_ENABLED <= spi_buf_out(12);
|
||||
CDS_ENABLED <= spi_buf_out(11);
|
||||
PORT1_STAGE <= spi_buf_out(5 downto 3);
|
||||
PORT2_STAGE <= spi_buf_out(2 downto 0);
|
||||
when 7 => SPI_OVERWRITE_ENABLED <= spi_buf_out(15);
|
||||
|
|
@ -308,9 +310,9 @@ begin
|
|||
end case;
|
||||
selected_register <= selected_register + 1;
|
||||
when WriteSweepConfig =>
|
||||
if word_cnt = 7 then
|
||||
-- Sweep config data is complete pass on (112 bits = 96 + 16)
|
||||
SWEEP_DATA <= sweepconfig_buffer & spi_buf_out;
|
||||
if word_cnt = 5 then
|
||||
-- Sweep config data is complete (96 bits = 6 x 16-bit words)
|
||||
SWEEP_DATA <= sweepconfig_buffer(79 downto 0) & spi_buf_out;
|
||||
sweep_config_write <= '1';
|
||||
else
|
||||
-- shift next word into buffer
|
||||
|
|
|
|||
53
FPGA/VNA/Sweep.vhd
Normal file → Executable file
53
FPGA/VNA/Sweep.vhd
Normal file → Executable file
|
|
@ -34,7 +34,7 @@ entity Sweep is
|
|||
RESET : in STD_LOGIC;
|
||||
NPOINTS : in STD_LOGIC_VECTOR (12 downto 0);
|
||||
CONFIG_ADDRESS : out STD_LOGIC_VECTOR (12 downto 0);
|
||||
CONFIG_DATA : in STD_LOGIC_VECTOR (111 downto 0);
|
||||
CONFIG_DATA : in STD_LOGIC_VECTOR (95 downto 0);
|
||||
USER_NSAMPLES : in STD_LOGIC_VECTOR (12 downto 0);
|
||||
NSAMPLES : out STD_LOGIC_VECTOR (12 downto 0);
|
||||
SETTLING_TIME : in STD_LOGIC_VECTOR (19 downto 0);
|
||||
|
|
@ -86,6 +86,9 @@ entity Sweep is
|
|||
|
||||
SOURCE_CE : out STD_LOGIC;
|
||||
|
||||
-- Correlated Double Sampling
|
||||
CDS_ENABLED : in STD_LOGIC;
|
||||
|
||||
-- Debug signals
|
||||
DEBUG_STATUS : out STD_LOGIC_VECTOR (10 downto 0);
|
||||
RESULT_INDEX : out STD_LOGIC_VECTOR (15 downto 0)
|
||||
|
|
@ -98,19 +101,31 @@ architecture Behavioral of Sweep is
|
|||
signal state : Point_states;
|
||||
signal settling_cnt : unsigned(19 downto 0);
|
||||
signal stage_cnt : unsigned (2 downto 0);
|
||||
signal config_reg : std_logic_vector(111 downto 0);
|
||||
signal config_reg : std_logic_vector(95 downto 0);
|
||||
signal source_active : std_logic;
|
||||
|
||||
-- Source phase value extracted from config (bits 111:100)
|
||||
-- CDS phase tracking: 0 = first measurement (phase 0), 1 = second measurement (phase 180)
|
||||
signal cds_phase : std_logic;
|
||||
|
||||
-- Source phase value: computed from CDS state
|
||||
-- When CDS disabled or cds_phase=0: phase = 0
|
||||
-- When CDS enabled and cds_phase=1: phase = M/2 (180 degrees)
|
||||
signal source_phase : std_logic_vector(11 downto 0);
|
||||
signal source_M : std_logic_vector(11 downto 0);
|
||||
begin
|
||||
|
||||
CONFIG_ADDRESS <= std_logic_vector(point_cnt);
|
||||
|
||||
-- Extract source phase from config (new bits 111:100)
|
||||
source_phase <= config_reg(111 downto 100);
|
||||
-- Extract Source M from config (bits 38:27)
|
||||
source_M <= config_reg(38 downto 27);
|
||||
|
||||
-- Phase adjustment is enabled when source_phase is non-zero
|
||||
-- Compute source phase for CDS
|
||||
-- Phase 0: source_phase = 0
|
||||
-- Phase 180: source_phase = M/2 (right shift by 1)
|
||||
source_phase <= (others => '0') when (CDS_ENABLED = '0' or cds_phase = '0')
|
||||
else '0' & source_M(11 downto 1); -- M/2 for 180 degree shift
|
||||
|
||||
-- Phase adjustment is enabled when source_phase is non-zero (i.e., CDS phase 1)
|
||||
SOURCE_PHASE_ADJUST <= '0' when source_phase = x"000" else '1';
|
||||
|
||||
-- assemble registers
|
||||
|
|
@ -164,7 +179,7 @@ begin
|
|||
DEBUG_STATUS(2) <= source_active;
|
||||
DEBUG_STATUS(1 downto 0) <= (others => '1');
|
||||
|
||||
config_reg <= CONFIG_DATA;
|
||||
config_reg <= CONFIG_DATA(95 downto 0);
|
||||
|
||||
process(CLK, RESET)
|
||||
begin
|
||||
|
|
@ -172,6 +187,7 @@ begin
|
|||
if RESET = '1' then
|
||||
point_cnt <= (others => '0');
|
||||
stage_cnt <= (others => '0');
|
||||
cds_phase <= '0';
|
||||
state <= WaitInitialLow;
|
||||
START_SAMPLING <= '0';
|
||||
RELOAD_PLL_REGS <= '0';
|
||||
|
|
@ -243,7 +259,9 @@ begin
|
|||
-- wait for sampling to finish
|
||||
START_SAMPLING <= '0';
|
||||
if SAMPLING_BUSY = '0' then
|
||||
RESULT_INDEX <= std_logic_vector(stage_cnt) & std_logic_vector(point_cnt);
|
||||
-- RESULT_INDEX format: stage(2:0) & point(11:0) & cds_phase when CDS enabled
|
||||
-- When CDS disabled, cds_phase is always 0
|
||||
RESULT_INDEX <= std_logic_vector(stage_cnt) & std_logic_vector(point_cnt(11 downto 0)) & cds_phase;
|
||||
state <= WaitTriggerLow;
|
||||
end if;
|
||||
when WaitTriggerLow =>
|
||||
|
|
@ -259,19 +277,32 @@ begin
|
|||
NEW_DATA <= '1';
|
||||
if stage_cnt < unsigned(STAGES) then
|
||||
stage_cnt <= stage_cnt + 1;
|
||||
-- can go directly to preperation for next stage
|
||||
-- can go directly to preparation for next stage
|
||||
state <= Settling;
|
||||
else
|
||||
state <= NextPoint;
|
||||
-- All stages done for this measurement
|
||||
-- Check if we need another CDS phase
|
||||
if CDS_ENABLED = '1' and cds_phase = '0' then
|
||||
-- First CDS phase done, do second phase at 180 degrees
|
||||
cds_phase <= '1';
|
||||
stage_cnt <= (others => '0');
|
||||
-- Go back to reload PLL with new phase
|
||||
state <= TriggerSetup;
|
||||
else
|
||||
-- CDS complete or disabled, move to next point
|
||||
state <= NextPoint;
|
||||
end if;
|
||||
end if;
|
||||
settling_cnt <= unsigned(SETTLING_TIME);
|
||||
when NextPoint =>
|
||||
NEW_DATA <= '0';
|
||||
-- Reset CDS phase for next point
|
||||
cds_phase <= '0';
|
||||
if point_cnt < unsigned(NPOINTS) then
|
||||
point_cnt <= point_cnt + 1;
|
||||
stage_cnt <= (others => '0');
|
||||
state <= TriggerSetup;
|
||||
else
|
||||
else
|
||||
point_cnt <= (others => '0');
|
||||
state <= Done;
|
||||
TRIGGER_OUT <= '0';
|
||||
|
|
|
|||
216
FPGA/VNA/ipcore_dir/SweepConfigMem.xco
Normal file → Executable file
216
FPGA/VNA/ipcore_dir/SweepConfigMem.xco
Normal file → Executable file
|
|
@ -1,108 +1,108 @@
|
|||
##############################################################
|
||||
#
|
||||
# Xilinx Core Generator version 14.6
|
||||
# Date: Mon Sep 14 08:50:12 2020
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# This file contains the customisation parameters for a
|
||||
# Xilinx CORE Generator IP GUI. It is strongly recommended
|
||||
# that you do not manually alter this file as it may cause
|
||||
# unexpected and unsupported behavior.
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# Generated from component: xilinx.com:ip:blk_mem_gen:7.3
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# BEGIN Project Options
|
||||
SET addpads = false
|
||||
SET asysymbol = true
|
||||
SET busformat = BusFormatAngleBracketNotRipped
|
||||
SET createndf = false
|
||||
SET designentry = VHDL
|
||||
SET device = xc6slx9
|
||||
SET devicefamily = spartan6
|
||||
SET flowvendor = Other
|
||||
SET formalverification = false
|
||||
SET foundationsym = false
|
||||
SET implementationfiletype = Ngc
|
||||
SET package = tqg144
|
||||
SET removerpms = false
|
||||
SET simulationfiles = Behavioral
|
||||
SET speedgrade = -2
|
||||
SET verilogsim = false
|
||||
SET vhdlsim = true
|
||||
# END Project Options
|
||||
# BEGIN Select
|
||||
SELECT Block_Memory_Generator xilinx.com:ip:blk_mem_gen:7.3
|
||||
# END Select
|
||||
# BEGIN Parameters
|
||||
CSET additional_inputs_for_power_estimation=false
|
||||
CSET algorithm=Minimum_Area
|
||||
CSET assume_synchronous_clk=false
|
||||
CSET axi_id_width=4
|
||||
CSET axi_slave_type=Memory_Slave
|
||||
CSET axi_type=AXI4_Full
|
||||
CSET byte_size=9
|
||||
CSET coe_file=no_coe_file_loaded
|
||||
CSET collision_warnings=ALL
|
||||
CSET component_name=SweepConfigMem
|
||||
CSET disable_collision_warnings=false
|
||||
CSET disable_out_of_range_warnings=false
|
||||
CSET ecc=false
|
||||
CSET ecctype=No_ECC
|
||||
CSET enable_32bit_address=false
|
||||
CSET enable_a=Use_ENA_Pin
|
||||
CSET enable_b=Always_Enabled
|
||||
CSET error_injection_type=Single_Bit_Error_Injection
|
||||
CSET fill_remaining_memory_locations=false
|
||||
CSET interface_type=Native
|
||||
CSET load_init_file=false
|
||||
CSET mem_file=no_Mem_file_loaded
|
||||
CSET memory_type=Simple_Dual_Port_RAM
|
||||
CSET operating_mode_a=WRITE_FIRST
|
||||
CSET operating_mode_b=WRITE_FIRST
|
||||
CSET output_reset_value_a=0
|
||||
CSET output_reset_value_b=0
|
||||
CSET pipeline_stages=0
|
||||
CSET port_a_clock=100
|
||||
CSET port_a_enable_rate=100
|
||||
CSET port_a_write_rate=50
|
||||
CSET port_b_clock=100
|
||||
CSET port_b_enable_rate=100
|
||||
CSET port_b_write_rate=0
|
||||
CSET primitive=8kx2
|
||||
CSET read_width_a=112
|
||||
CSET read_width_b=112
|
||||
CSET register_porta_input_of_softecc=false
|
||||
CSET register_porta_output_of_memory_core=false
|
||||
CSET register_porta_output_of_memory_primitives=false
|
||||
CSET register_portb_output_of_memory_core=false
|
||||
CSET register_portb_output_of_memory_primitives=false
|
||||
CSET register_portb_output_of_softecc=false
|
||||
CSET remaining_memory_locations=0
|
||||
CSET reset_memory_latch_a=false
|
||||
CSET reset_memory_latch_b=false
|
||||
CSET reset_priority_a=CE
|
||||
CSET reset_priority_b=CE
|
||||
CSET reset_type=SYNC
|
||||
CSET softecc=false
|
||||
CSET use_axi_id=false
|
||||
CSET use_bram_block=Stand_Alone
|
||||
CSET use_byte_write_enable=false
|
||||
CSET use_error_injection_pins=false
|
||||
CSET use_regcea_pin=false
|
||||
CSET use_regceb_pin=false
|
||||
CSET use_rsta_pin=false
|
||||
CSET use_rstb_pin=false
|
||||
CSET write_depth_a=4501
|
||||
CSET write_width_a=112
|
||||
CSET write_width_b=112
|
||||
# END Parameters
|
||||
# BEGIN Extra information
|
||||
MISC pkg_timestamp=2012-11-19T16:22:25Z
|
||||
# END Extra information
|
||||
GENERATE
|
||||
# CRC: e7b4a756
|
||||
##############################################################
|
||||
#
|
||||
# Xilinx Core Generator version 14.7
|
||||
# Date: Sun Feb 01 07:48:09 2026
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# This file contains the customisation parameters for a
|
||||
# Xilinx CORE Generator IP GUI. It is strongly recommended
|
||||
# that you do not manually alter this file as it may cause
|
||||
# unexpected and unsupported behavior.
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# Generated from component: xilinx.com:ip:blk_mem_gen:7.3
|
||||
#
|
||||
##############################################################
|
||||
#
|
||||
# BEGIN Project Options
|
||||
SET addpads = false
|
||||
SET asysymbol = true
|
||||
SET busformat = BusFormatAngleBracketNotRipped
|
||||
SET createndf = false
|
||||
SET designentry = VHDL
|
||||
SET device = xc6slx9
|
||||
SET devicefamily = spartan6
|
||||
SET flowvendor = Other
|
||||
SET formalverification = false
|
||||
SET foundationsym = false
|
||||
SET implementationfiletype = Ngc
|
||||
SET package = tqg144
|
||||
SET removerpms = false
|
||||
SET simulationfiles = Behavioral
|
||||
SET speedgrade = -2
|
||||
SET verilogsim = false
|
||||
SET vhdlsim = true
|
||||
# END Project Options
|
||||
# BEGIN Select
|
||||
SELECT Block_Memory_Generator xilinx.com:ip:blk_mem_gen:7.3
|
||||
# END Select
|
||||
# BEGIN Parameters
|
||||
CSET additional_inputs_for_power_estimation=false
|
||||
CSET algorithm=Minimum_Area
|
||||
CSET assume_synchronous_clk=false
|
||||
CSET axi_id_width=4
|
||||
CSET axi_slave_type=Memory_Slave
|
||||
CSET axi_type=AXI4_Full
|
||||
CSET byte_size=9
|
||||
CSET coe_file=no_coe_file_loaded
|
||||
CSET collision_warnings=ALL
|
||||
CSET component_name=SweepConfigMem
|
||||
CSET disable_collision_warnings=false
|
||||
CSET disable_out_of_range_warnings=false
|
||||
CSET ecc=false
|
||||
CSET ecctype=No_ECC
|
||||
CSET enable_32bit_address=false
|
||||
CSET enable_a=Use_ENA_Pin
|
||||
CSET enable_b=Always_Enabled
|
||||
CSET error_injection_type=Single_Bit_Error_Injection
|
||||
CSET fill_remaining_memory_locations=false
|
||||
CSET interface_type=Native
|
||||
CSET load_init_file=false
|
||||
CSET mem_file=no_Mem_file_loaded
|
||||
CSET memory_type=Simple_Dual_Port_RAM
|
||||
CSET operating_mode_a=WRITE_FIRST
|
||||
CSET operating_mode_b=WRITE_FIRST
|
||||
CSET output_reset_value_a=0
|
||||
CSET output_reset_value_b=0
|
||||
CSET pipeline_stages=0
|
||||
CSET port_a_clock=100
|
||||
CSET port_a_enable_rate=100
|
||||
CSET port_a_write_rate=50
|
||||
CSET port_b_clock=100
|
||||
CSET port_b_enable_rate=100
|
||||
CSET port_b_write_rate=0
|
||||
CSET primitive=8kx2
|
||||
CSET read_width_a=96
|
||||
CSET read_width_b=96
|
||||
CSET register_porta_input_of_softecc=false
|
||||
CSET register_porta_output_of_memory_core=false
|
||||
CSET register_porta_output_of_memory_primitives=false
|
||||
CSET register_portb_output_of_memory_core=false
|
||||
CSET register_portb_output_of_memory_primitives=false
|
||||
CSET register_portb_output_of_softecc=false
|
||||
CSET remaining_memory_locations=0
|
||||
CSET reset_memory_latch_a=false
|
||||
CSET reset_memory_latch_b=false
|
||||
CSET reset_priority_a=CE
|
||||
CSET reset_priority_b=CE
|
||||
CSET reset_type=SYNC
|
||||
CSET softecc=false
|
||||
CSET use_axi_id=false
|
||||
CSET use_bram_block=Stand_Alone
|
||||
CSET use_byte_write_enable=false
|
||||
CSET use_error_injection_pins=false
|
||||
CSET use_regcea_pin=false
|
||||
CSET use_regceb_pin=false
|
||||
CSET use_rsta_pin=false
|
||||
CSET use_rstb_pin=false
|
||||
CSET write_depth_a=4501
|
||||
CSET write_width_a=96
|
||||
CSET write_width_b=96
|
||||
# END Parameters
|
||||
# BEGIN Extra information
|
||||
MISC pkg_timestamp=2012-11-19T16:22:25Z
|
||||
# END Extra information
|
||||
GENERATE
|
||||
# CRC: e7b4a756
|
||||
|
|
|
|||
BIN
FPGA/VNA/top.bin
Normal file → Executable file
BIN
FPGA/VNA/top.bin
Normal file → Executable file
Binary file not shown.
19
FPGA/VNA/top.vhd
Normal file → Executable file
19
FPGA/VNA/top.vhd
Normal file → Executable file
|
|
@ -113,7 +113,7 @@ architecture Behavioral of top is
|
|||
CLK : IN std_logic;
|
||||
RESET : IN std_logic;
|
||||
NPOINTS : IN std_logic_vector(12 downto 0);
|
||||
CONFIG_DATA : IN std_logic_vector(111 downto 0);
|
||||
CONFIG_DATA : IN std_logic_vector(95 downto 0);
|
||||
USER_NSAMPLES : in STD_LOGIC_VECTOR (12 downto 0);
|
||||
NSAMPLES : out STD_LOGIC_VECTOR (12 downto 0);
|
||||
SETTLING_TIME : in STD_LOGIC_VECTOR (19 downto 0);
|
||||
|
|
@ -154,11 +154,12 @@ architecture Behavioral of top is
|
|||
PORT1_ACTIVE : out STD_LOGIC;
|
||||
PORT2_ACTIVE : out STD_LOGIC;
|
||||
SOURCE_CE : out STD_LOGIC;
|
||||
CDS_ENABLED : in STD_LOGIC;
|
||||
RESULT_INDEX : out STD_LOGIC_VECTOR (15 downto 0);
|
||||
DEBUG_STATUS : out STD_LOGIC_VECTOR (10 downto 0)
|
||||
);
|
||||
END COMPONENT;
|
||||
|
||||
|
||||
COMPONENT Windowing
|
||||
PORT(
|
||||
CLK : IN std_logic;
|
||||
|
|
@ -252,7 +253,7 @@ architecture Behavioral of top is
|
|||
MAX2871_DEF_3 : OUT std_logic_vector(31 downto 0);
|
||||
MAX2871_DEF_1 : OUT std_logic_vector(31 downto 0);
|
||||
MAX2871_DEF_0 : OUT std_logic_vector(31 downto 0);
|
||||
SWEEP_DATA : OUT std_logic_vector(111 downto 0);
|
||||
SWEEP_DATA : OUT std_logic_vector(95 downto 0);
|
||||
SWEEP_ADDRESS : OUT std_logic_vector(12 downto 0);
|
||||
SWEEP_WRITE : OUT std_logic_vector(0 to 0);
|
||||
SWEEP_POINTS : OUT std_logic_vector(12 downto 0);
|
||||
|
|
@ -261,6 +262,7 @@ architecture Behavioral of top is
|
|||
SETTLING_TIME : out STD_LOGIC_VECTOR (19 downto 0);
|
||||
SYNC_ENABLED : out STD_LOGIC;
|
||||
SYNC_MASTER : out STD_LOGIC;
|
||||
CDS_ENABLED : out STD_LOGIC;
|
||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_EN : out STD_LOGIC;
|
||||
|
|
@ -315,10 +317,10 @@ architecture Behavioral of top is
|
|||
ena : IN STD_LOGIC;
|
||||
wea : IN STD_LOGIC_VECTOR(0 DOWNTO 0);
|
||||
addra : IN STD_LOGIC_VECTOR(12 DOWNTO 0);
|
||||
dina : IN STD_LOGIC_VECTOR(111 DOWNTO 0);
|
||||
dina : IN STD_LOGIC_VECTOR(95 DOWNTO 0);
|
||||
clkb : IN STD_LOGIC;
|
||||
addrb : IN STD_LOGIC_VECTOR(12 DOWNTO 0);
|
||||
doutb : OUT STD_LOGIC_VECTOR(111 DOWNTO 0)
|
||||
doutb : OUT STD_LOGIC_VECTOR(95 DOWNTO 0)
|
||||
);
|
||||
END COMPONENT;
|
||||
|
||||
|
|
@ -387,14 +389,15 @@ architecture Behavioral of top is
|
|||
signal sweep_sync_master : STD_LOGIC;
|
||||
signal sweep_port1_stage : STD_LOGIC_VECTOR (2 downto 0);
|
||||
signal sweep_port2_stage : STD_LOGIC_VECTOR (2 downto 0);
|
||||
signal sweep_config_data : std_logic_vector(111 downto 0);
|
||||
signal sweep_config_data : std_logic_vector(95 downto 0);
|
||||
signal cds_enabled : std_logic;
|
||||
signal sweep_config_address : std_logic_vector(12 downto 0);
|
||||
signal sweep_source_filter : std_logic_vector(1 downto 0);
|
||||
signal sweep_band : std_logic;
|
||||
signal sweep_attenuator : std_logic_vector(6 downto 0);
|
||||
|
||||
signal sweep_config_write_address : std_logic_vector(12 downto 0);
|
||||
signal sweep_config_write_data : std_logic_vector(111 downto 0);
|
||||
signal sweep_config_write_data : std_logic_vector(95 downto 0);
|
||||
signal sweep_config_write : std_logic_vector(0 downto 0);
|
||||
|
||||
-- Phase adjustment signal from Sweep to Source PLL
|
||||
|
|
@ -724,6 +727,7 @@ begin
|
|||
SWEEP_HALTED => sweep_halted,
|
||||
SWEEP_RESUME => sweep_resume,
|
||||
SOURCE_PHASE_ADJUST => source_phase_adjust,
|
||||
CDS_ENABLED => cds_enabled,
|
||||
SYNC_ENABLED => sweep_sync_enabled,
|
||||
SYNC_MASTER => sweep_sync_master,
|
||||
TRIGGER_IN => sweep_trigger_in,
|
||||
|
|
@ -815,6 +819,7 @@ begin
|
|||
SETTLING_TIME => settling_time,
|
||||
SYNC_ENABLED => sweep_sync_enabled,
|
||||
SYNC_MASTER => sweep_sync_master,
|
||||
CDS_ENABLED => cds_enabled,
|
||||
PORT1_STAGE => sweep_port1_stage,
|
||||
PORT2_STAGE => sweep_port2_stage,
|
||||
SPI_OVERWRITE_ENABLED => HW_overwrite_enabled,
|
||||
|
|
|
|||
|
|
@ -13,6 +13,7 @@
|
|||
static FPGA::HaltedCallback halted_cb;
|
||||
static uint16_t SysCtrlReg = 0x0000;
|
||||
static uint16_t ISRMaskReg = 0x0000;
|
||||
static uint16_t SweepSetupReg = 0x0000;
|
||||
static uint32_t ADC_samplerate;
|
||||
static volatile bool busy_reading = false;
|
||||
|
||||
|
|
@ -144,17 +145,27 @@ void FPGA::SetSettlingTime(uint16_t us) {
|
|||
}
|
||||
|
||||
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize, bool syncMaster) {
|
||||
uint16_t value = 0x0000;
|
||||
value |= (uint16_t) (stages & 0x07) << 13;
|
||||
// Preserve CDS bit when updating sweep setup
|
||||
SweepSetupReg = SweepSetupReg & 0x0800; // Keep bit 11 (CDS_ENABLED)
|
||||
SweepSetupReg |= (uint16_t) (stages & 0x07) << 13;
|
||||
if(synchronize) {
|
||||
value |= 0x1000;
|
||||
SweepSetupReg |= 0x1000;
|
||||
}
|
||||
value |= (port1_stage & 0x07) << 3;
|
||||
value |= (port2_stage & 0x07) << 0;
|
||||
WriteRegister(Reg::SweepSetup, value);
|
||||
SweepSetupReg |= (port1_stage & 0x07) << 3;
|
||||
SweepSetupReg |= (port2_stage & 0x07) << 0;
|
||||
WriteRegister(Reg::SweepSetup, SweepSetupReg);
|
||||
Enable(Periphery::SyncMaster, syncMaster);
|
||||
}
|
||||
|
||||
void FPGA::SetCDSEnabled(bool enabled) {
|
||||
if(enabled) {
|
||||
SweepSetupReg |= 0x0800; // Set bit 11
|
||||
} else {
|
||||
SweepSetupReg &= ~0x0800; // Clear bit 11
|
||||
}
|
||||
WriteRegister(Reg::SweepSetup, SweepSetupReg);
|
||||
}
|
||||
|
||||
void FPGA::Enable(Periphery p, bool enable) {
|
||||
if (enable) {
|
||||
SysCtrlReg |= (uint16_t) p;
|
||||
|
|
@ -210,9 +221,8 @@ void FPGA::WriteMAX2871Default(uint32_t *DefaultRegs) {
|
|||
}
|
||||
|
||||
void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceRegs, uint32_t *LORegs,
|
||||
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt, LowpassFilter filter,
|
||||
uint16_t sourcePhase) {
|
||||
uint16_t send[8];
|
||||
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt, LowpassFilter filter) {
|
||||
uint16_t send[7];
|
||||
// select which point this sweep config is for
|
||||
send[0] = pointnum & 0x1FFF;
|
||||
// assemble sweep config from required fields of PLL registers
|
||||
|
|
@ -230,48 +240,48 @@ void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceReg
|
|||
|
||||
uint16_t Source_Power = (SourceRegs[4] & 0x00000018) >> 3;
|
||||
|
||||
// Config memory layout (112 bits):
|
||||
// bits 111:100 - Source Phase P[11:0]
|
||||
// bits 99:96 - Reserved
|
||||
// bits 95:0 - Original 96-bit config
|
||||
// Config memory layout (96 bits):
|
||||
// bits 95:80 - LO_M[11:4], halt, LO_N[6], Source_N[6], samples[2:0], filter[1:0]
|
||||
// bits 79:64 - LO_M[3:0], LO_FRAC[11:0]
|
||||
// bits 63:48 - LO_DIV_A[2:0], LO_VCO[5:0], LO_N[5:0], lowband
|
||||
// bits 47:32 - Source_Power[1:0], attenuation[6:0], Source_M[11:5]
|
||||
// bits 31:16 - Source_M[4:0], Source_FRAC[11:1]
|
||||
// bits 15:0 - Source_FRAC[0], Source_DIV_A[2:0], Source_VCO[5:0], Source_N[5:0]
|
||||
|
||||
// send[1]: bits 111:96 - Source Phase (12 bits) + Reserved (4 bits)
|
||||
send[1] = (sourcePhase & 0x0FFF) << 4; // Phase in bits 15:4, reserved bits 3:0
|
||||
|
||||
// send[2]: bits 95:80 - LO_M[11:4], halt, LO_N[6], Source_N[6], samples[2:0], filter[1:0]
|
||||
send[2] = LO_M >> 4;
|
||||
// send[1]: bits 95:80
|
||||
send[1] = LO_M >> 4;
|
||||
if (halt) {
|
||||
send[2] |= 0x8000;
|
||||
send[1] |= 0x8000;
|
||||
}
|
||||
if(LO_N & 0x40) {
|
||||
send[2] |= 0x4000;
|
||||
send[1] |= 0x4000;
|
||||
}
|
||||
if(Source_N & 0x40) {
|
||||
send[2] |= 0x2000;
|
||||
send[1] |= 0x2000;
|
||||
}
|
||||
send[2] |= (int) samples << 10;
|
||||
send[1] |= (int) samples << 10;
|
||||
if(filter == LowpassFilter::Auto) {
|
||||
// Select source LP filter
|
||||
if (frequency >= 3500000000) {
|
||||
send[2] |= (int) LowpassFilter::None << 8;
|
||||
send[1] |= (int) LowpassFilter::None << 8;
|
||||
} else if (frequency >= 1800000000) {
|
||||
send[2] |= (int) LowpassFilter::M3500 << 8;
|
||||
send[1] |= (int) LowpassFilter::M3500 << 8;
|
||||
} else if (frequency >= 900000000) {
|
||||
send[2] |= (int) LowpassFilter::M1880 << 8;
|
||||
send[1] |= (int) LowpassFilter::M1880 << 8;
|
||||
} else {
|
||||
send[2] |= (int) LowpassFilter::M947 << 8;
|
||||
send[1] |= (int) LowpassFilter::M947 << 8;
|
||||
}
|
||||
} else {
|
||||
send[2] |= (int) filter << 8;
|
||||
send[1] |= (int) filter << 8;
|
||||
}
|
||||
send[3] = (LO_M & 0x000F) << 12 | LO_FRAC;
|
||||
send[4] = LO_DIV_A << 13 | LO_VCO << 7 | (LO_N & 0x3F) << 1;
|
||||
send[2] = (LO_M & 0x000F) << 12 | LO_FRAC;
|
||||
send[3] = LO_DIV_A << 13 | LO_VCO << 7 | (LO_N & 0x3F) << 1;
|
||||
if (lowband) {
|
||||
send[4] |= 0x0001;
|
||||
send[3] |= 0x0001;
|
||||
}
|
||||
send[5] = Source_Power << 14 | (uint16_t) attenuation << 7 | Source_M >> 5;
|
||||
send[6] = (Source_M & 0x001F) << 11 | Source_FRAC >> 1;
|
||||
send[7] = (Source_FRAC & 0x0001) << 15 | Source_DIV_A << 12 | Source_VCO << 6 | (Source_N & 0x3F);
|
||||
send[4] = Source_Power << 14 | (uint16_t) attenuation << 7 | Source_M >> 5;
|
||||
send[5] = (Source_M & 0x001F) << 11 | Source_FRAC >> 1;
|
||||
send[6] = (Source_FRAC & 0x0001) << 15 | Source_DIV_A << 12 | Source_VCO << 6 | (Source_N & 0x3F);
|
||||
SwitchBytes(send[0]);
|
||||
SwitchBytes(send[1]);
|
||||
SwitchBytes(send[2]);
|
||||
|
|
@ -279,9 +289,8 @@ void FPGA::WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceReg
|
|||
SwitchBytes(send[4]);
|
||||
SwitchBytes(send[5]);
|
||||
SwitchBytes(send[6]);
|
||||
SwitchBytes(send[7]);
|
||||
Low(CS);
|
||||
HAL_SPI_Transmit(&FPGA_SPI, (uint8_t*) send, 16, 100);
|
||||
HAL_SPI_Transmit(&FPGA_SPI, (uint8_t*) send, 14, 100);
|
||||
High(CS);
|
||||
}
|
||||
|
||||
|
|
@ -333,8 +342,10 @@ void HAL_SPI_TxRxCpltCallback(SPI_HandleTypeDef *hspi) {
|
|||
result.P2Q = assembleSampleResultValue(&raw[14]);
|
||||
result.RefI = assembleSampleResultValue(&raw[8]);
|
||||
result.RefQ = assembleSampleResultValue(&raw[2]);
|
||||
result.pointNum = (uint16_t)(raw[38]&0x1F) << 8 | raw[39];
|
||||
// RESULT_INDEX format: stage[2:0] & point[11:0] & cds_phase
|
||||
result.stageNum = (raw[38] & 0xE0) >> 5;
|
||||
result.pointNum = ((uint16_t)(raw[38] & 0x1F) << 7) | (raw[39] >> 1);
|
||||
result.cdsPhase = raw[39] & 0x01;
|
||||
High(CS);
|
||||
busy_reading = false;
|
||||
if ((status & 0x0004) && callback) {
|
||||
|
|
|
|||
|
|
@ -37,8 +37,9 @@ using SamplingResult = struct _samplingresult {
|
|||
int64_t P1I, P1Q;
|
||||
int64_t P2I, P2Q;
|
||||
int64_t RefI, RefQ;
|
||||
uint16_t pointNum :13;
|
||||
uint16_t pointNum :12;
|
||||
uint16_t stageNum :3;
|
||||
uint16_t cdsPhase :1; // 0 = first measurement (phase 0), 1 = second measurement (phase 180)
|
||||
};
|
||||
|
||||
using DFTResult = struct _dftresult {
|
||||
|
|
@ -120,8 +121,8 @@ void DisableInterrupt(Interrupt i);
|
|||
void DisableAllInterrupts();
|
||||
void WriteMAX2871Default(uint32_t *DefaultRegs);
|
||||
void WriteSweepConfig(uint16_t pointnum, bool lowband, uint32_t *SourceRegs, uint32_t *LORegs,
|
||||
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt = false, LowpassFilter filter = LowpassFilter::Auto,
|
||||
uint16_t sourcePhase = 0);
|
||||
uint8_t attenuation, uint64_t frequency, Samples samples, bool halt = false, LowpassFilter filter = LowpassFilter::Auto);
|
||||
void SetCDSEnabled(bool enabled);
|
||||
using ReadCallback = void(*)(const SamplingResult &result);
|
||||
bool InitiateSampleRead(ReadCallback cb);
|
||||
void SetupDFT(uint32_t f_firstBin, uint32_t f_binSpacing);
|
||||
|
|
|
|||
|
|
@ -55,14 +55,13 @@ static constexpr uint8_t PLLRefFreqsNum = sizeof(PLLRefFreqs)/sizeof(PLLRefFreqs
|
|||
static uint8_t sourceRefIndex, LO1RefIndex;
|
||||
|
||||
// Correlated Double Sampling (CDS) state
|
||||
static uint8_t cdsPhaseCount; // Number of phase samples (0 or 2-7)
|
||||
// CDS is now handled by FPGA which takes 2 measurements at 0° and 180° phase
|
||||
static bool cdsEnabled;
|
||||
// CDS accumulators for weighted samples, per stage (max 8 stages)
|
||||
// I and Q for each receiver: Port1, Port2, Reference
|
||||
static double cdsAccumP1I[8], cdsAccumP1Q[8];
|
||||
static double cdsAccumP2I[8], cdsAccumP2Q[8];
|
||||
static double cdsAccumRefI[8], cdsAccumRefQ[8];
|
||||
// Precomputed cosine weights for CDS
|
||||
static float cdsWeights[7]; // Max 7 phases
|
||||
|
||||
using namespace HWHAL;
|
||||
|
||||
|
|
@ -177,34 +176,20 @@ bool VNA::Setup(Protocol::SweepSettings s) {
|
|||
logMultiplier = pow((double) settings.f_stop / settings.f_start, 1.0 / (settings.points-1));
|
||||
|
||||
// Initialize Correlated Double Sampling (CDS)
|
||||
cdsPhaseCount = settings.cdsPhases >= 2 ? settings.cdsPhases : 0;
|
||||
// CDS is now handled by FPGA with 180° phase shift (2 measurements per point)
|
||||
cdsEnabled = settings.cdsPhases >= 2;
|
||||
// Clear per-stage accumulators
|
||||
for(uint8_t stg = 0; stg < 8; stg++) {
|
||||
cdsAccumP1I[stg] = cdsAccumP1Q[stg] = 0;
|
||||
cdsAccumP2I[stg] = cdsAccumP2Q[stg] = 0;
|
||||
cdsAccumRefI[stg] = cdsAccumRefQ[stg] = 0;
|
||||
}
|
||||
// Precompute cosine weights: cos(2*pi*k/N)
|
||||
if(cdsPhaseCount >= 2) {
|
||||
for(uint8_t k = 0; k < cdsPhaseCount; k++) {
|
||||
cdsWeights[k] = cosf(2.0f * M_PI * k / cdsPhaseCount);
|
||||
}
|
||||
}
|
||||
|
||||
// Calculate internal point count (multiply by CDS phases if enabled)
|
||||
uint16_t internalPoints = settings.points;
|
||||
if(cdsPhaseCount >= 2) {
|
||||
internalPoints = settings.points * cdsPhaseCount;
|
||||
if(internalPoints > FPGA::MaxPoints) {
|
||||
// Reduce user points to fit
|
||||
settings.points = FPGA::MaxPoints / cdsPhaseCount;
|
||||
internalPoints = settings.points * cdsPhaseCount;
|
||||
LOG_WARN("CDS: reduced points to %u to fit FPGA limit", settings.points);
|
||||
}
|
||||
}
|
||||
// Configure CDS in FPGA (FPGA handles the phase switching internally)
|
||||
FPGA::SetCDSEnabled(cdsEnabled);
|
||||
|
||||
// Configure sweep
|
||||
FPGA::SetNumberOfPoints(internalPoints);
|
||||
FPGA::SetNumberOfPoints(settings.points);
|
||||
uint32_t samplesPerPoint = (HW::getADCRate() / s.if_bandwidth);
|
||||
// round up to next multiple of 16 (16 samples are spread across 5 IF2 periods)
|
||||
if(samplesPerPoint%16) {
|
||||
|
|
@ -324,33 +309,11 @@ bool VNA::Setup(Protocol::SweepSettings s) {
|
|||
needs_halt = true;
|
||||
}
|
||||
|
||||
// Write sweep config(s) for this user point
|
||||
if(cdsPhaseCount >= 2) {
|
||||
// CDS enabled: write N configs with different phases
|
||||
// Extract Source M from PLL registers for phase calculation
|
||||
uint32_t* sourceRegs = Source.GetRegisters();
|
||||
uint16_t Source_M = (sourceRegs[1] & 0x00007FF8) >> 3;
|
||||
|
||||
for(uint8_t k = 0; k < cdsPhaseCount; k++) {
|
||||
uint16_t internalPointNum = i * cdsPhaseCount + k;
|
||||
// Calculate phase: sourcePhase = M * k / N
|
||||
// This gives phase = k * 360 / N degrees
|
||||
uint16_t sourcePhase = (uint16_t)((uint32_t)Source_M * k / cdsPhaseCount);
|
||||
|
||||
// Only halt on first CDS phase of each point (if needed)
|
||||
bool pointHalt = (k == 0) ? needs_halt : false;
|
||||
|
||||
FPGA::WriteSweepConfig(internalPointNum, lowband, sourceRegs,
|
||||
LO1.GetRegisters(), attenuator, freq,
|
||||
FPGA::Samples::SPPRegister, pointHalt, FPGA::LowpassFilter::Auto,
|
||||
sourcePhase);
|
||||
}
|
||||
} else {
|
||||
// No CDS: single config per point
|
||||
FPGA::WriteSweepConfig(i, lowband, Source.GetRegisters(),
|
||||
LO1.GetRegisters(), attenuator, freq,
|
||||
FPGA::Samples::SPPRegister, needs_halt);
|
||||
}
|
||||
// Write sweep config for this point
|
||||
// CDS is handled by FPGA internally - it takes 2 measurements per point when enabled
|
||||
FPGA::WriteSweepConfig(i, lowband, Source.GetRegisters(),
|
||||
LO1.GetRegisters(), attenuator, freq,
|
||||
FPGA::Samples::SPPRegister, needs_halt);
|
||||
last_lowband = lowband;
|
||||
}
|
||||
// reset a possibly changed PLL reference index
|
||||
|
|
@ -428,19 +391,18 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
|
|||
if(!active) {
|
||||
return false;
|
||||
}
|
||||
if(result.pointNum != pointCnt || result.stageNum != stageCnt) {
|
||||
LOG_WARN("Indicated point does not match (%u != %u, %d != %d)", result.pointNum, pointCnt, result.stageNum, stageCnt);
|
||||
FPGA::AbortSweep();
|
||||
return false;
|
||||
}
|
||||
|
||||
if(cdsPhaseCount >= 2) {
|
||||
// CDS mode: accumulate weighted samples per stage
|
||||
// Internal point mapping: user_point = pointCnt / cdsPhaseCount
|
||||
// cds_phase = pointCnt % cdsPhaseCount
|
||||
uint16_t userPoint = pointCnt / cdsPhaseCount;
|
||||
uint8_t cdsPhase = pointCnt % cdsPhaseCount;
|
||||
float weight = cdsWeights[cdsPhase];
|
||||
if(cdsEnabled) {
|
||||
// CDS mode: FPGA returns 2 measurements per point (cdsPhase 0 and 1)
|
||||
// Verify point and stage numbers match expected values
|
||||
if(result.pointNum != pointCnt || result.stageNum != stageCnt) {
|
||||
LOG_WARN("Indicated point does not match (%u != %u, %d != %d)", result.pointNum, pointCnt, result.stageNum, stageCnt);
|
||||
FPGA::AbortSweep();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Weight: +1.0 for phase 0 (0°), -1.0 for phase 1 (180°)
|
||||
float weight = result.cdsPhase == 0 ? 1.0f : -1.0f;
|
||||
|
||||
// Accumulate weighted values into per-stage accumulators
|
||||
cdsAccumP1I[stageCnt] += result.P1I * weight;
|
||||
|
|
@ -450,24 +412,24 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
|
|||
cdsAccumRefI[stageCnt] += result.RefI * weight;
|
||||
cdsAccumRefQ[stageCnt] += result.RefQ * weight;
|
||||
|
||||
// Check if all stages for this internal point are complete
|
||||
// Check if all stages for this CDS phase are complete
|
||||
stageCnt++;
|
||||
if(stageCnt > settings.stages) {
|
||||
stageCnt = 0;
|
||||
|
||||
// Check if this was the last CDS phase for this user point
|
||||
if(cdsPhase == cdsPhaseCount - 1) {
|
||||
// All CDS phases complete - add combined values to data
|
||||
// Check if this was the second CDS phase (phase 1 = 180°)
|
||||
if(result.cdsPhase == 1) {
|
||||
// Both CDS phases complete - add combined values to data
|
||||
for(uint8_t stg = 0; stg <= settings.stages; stg++) {
|
||||
data.addValue((int64_t)cdsAccumP1I[stg], (int64_t)cdsAccumP1Q[stg], stg, (int) Protocol::Source::Port1);
|
||||
data.addValue((int64_t)cdsAccumP2I[stg], (int64_t)cdsAccumP2Q[stg], stg, (int) Protocol::Source::Port2);
|
||||
data.addValue((int64_t)cdsAccumRefI[stg], (int64_t)cdsAccumRefQ[stg], stg, (int) Protocol::Source::Port1 | (int) Protocol::Source::Port2 | (int) Protocol::Source::Reference);
|
||||
// Reset accumulators for next user point
|
||||
// Reset accumulators for next point
|
||||
cdsAccumP1I[stg] = cdsAccumP1Q[stg] = 0;
|
||||
cdsAccumP2I[stg] = cdsAccumP2Q[stg] = 0;
|
||||
cdsAccumRefI[stg] = cdsAccumRefQ[stg] = 0;
|
||||
}
|
||||
data.pointNum = userPoint;
|
||||
data.pointNum = pointCnt;
|
||||
|
||||
if(zerospan) {
|
||||
uint64_t timestamp = HW::getLastISRTimestamp();
|
||||
|
|
@ -479,24 +441,31 @@ bool VNA::MeasurementDone(const FPGA::SamplingResult &result) {
|
|||
data.us = timestamp - firstPointTime;
|
||||
}
|
||||
} else {
|
||||
data.frequency = getPointFrequency(userPoint);
|
||||
data.cdBm = settings.cdbm_excitation_start + (settings.cdbm_excitation_stop - settings.cdbm_excitation_start) * userPoint / (settings.points - 1);
|
||||
data.frequency = getPointFrequency(pointCnt);
|
||||
data.cdBm = settings.cdbm_excitation_start + (settings.cdbm_excitation_stop - settings.cdbm_excitation_start) * pointCnt / (settings.points - 1);
|
||||
}
|
||||
|
||||
// Send data for this user point
|
||||
// Send data for this point
|
||||
STM::DispatchToInterrupt(PassOnData);
|
||||
|
||||
// Check if sweep is complete
|
||||
if(userPoint >= settings.points - 1) {
|
||||
if(pointCnt >= settings.points - 1) {
|
||||
pointCnt = 0;
|
||||
return true; // End of sweep
|
||||
}
|
||||
pointCnt++;
|
||||
}
|
||||
pointCnt++;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-CDS mode: verify point matches
|
||||
if(result.pointNum != pointCnt || result.stageNum != stageCnt) {
|
||||
LOG_WARN("Indicated point does not match (%u != %u, %d != %d)", result.pointNum, pointCnt, result.stageNum, stageCnt);
|
||||
FPGA::AbortSweep();
|
||||
return false;
|
||||
}
|
||||
|
||||
// Non-CDS mode: original behavior
|
||||
data.addValue(result.P1I, result.P1Q, stageCnt, (int) Protocol::Source::Port1);
|
||||
data.addValue(result.P2I, result.P2Q, stageCnt, (int) Protocol::Source::Port2);
|
||||
|
|
|
|||
Loading…
Reference in a new issue