per-point switching: FPGA and LibreVNA MCU changes
FPGA мастера: порт излучает не в одной стадии, а в блоке подряд идущих — младшие SUBSTAGE_BITS бит счётчика стадий стали положением выходного ключа, старшие выбирают порт. Вынесено в функцию port_excites в Sweep.vhd, новое поле приезжает из свободных битов 8:6 регистра SweepSetup через SPIConfig и top.vhd. Совместимость со стоком строгая: при SUBSTAGE_BITS=0 сдвиг нулевой, а добавленная проверка "порт-стадия выше последней" ничего не меняет, потому что счётчик стадий и так пробегает только 0..STAGES. Ручной режим и анализатор спектра, где STAGES=0, ведут себя ровно как раньше. MCU: у SetupSweep добавлен параметр substage_bits со значением по умолчанию 0, поэтому вызовы из Manual.cpp и SpectrumAnalyzer.cpp не тронуты. VNA.cpp берёт ширину под-стадии из port3Stage — порта 3 у этого железа нет, поле свободно, и формат пакета не меняется. Проверено: GHDL анализирует все три VHDL-файла чисто; новый самопроверяющийся Test_Sweep.vhd прогоняет четыре сценария (сток, два положения, приёмник без излучения, четыре положения) и проверен мутацией — с возвращённым стоковым сравнением сценарии с под-стадиями падают. Обе сборки MCU собираются без ошибок. Битовые позиции сверены сквозь всю цепочку: хост шлёт port3Stage=1 в слове 0x243, MCU пишет регистр 0x7041, FPGA читает SUBSTAGE_BITS=1. Слейвы перепрошивать не нужно: им шлётся порт-стадия 7, которая со счётчиком 0..3 не совпадает и в стоковом сравнении. Исходники теперь опережают лежащие рядом бинарники — предупреждение добавлено в начало reproduce/README.md, хеш README в SHA256SUMS обновлён. Порядок ввода в строй расписан в per_point_switching.md. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -56,6 +56,9 @@ entity SPICommands is
|
||||
SYNC_MASTER : out STD_LOGIC;
|
||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
-- How many LOW bits of the stage counter select the stimulus switch
|
||||
-- position instead of the excited port. Zero is the stock layout.
|
||||
SUBSTAGE_BITS : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_EN : out STD_LOGIC;
|
||||
PORT2_EN : out STD_LOGIC;
|
||||
REF_EN : out STD_LOGIC;
|
||||
@@ -288,6 +291,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);
|
||||
SUBSTAGE_BITS <= spi_buf_out(8 downto 6);
|
||||
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);
|
||||
|
||||
@@ -77,6 +77,10 @@ entity Sweep is
|
||||
STAGES : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_STAGE : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
-- Number of LOW bits of the stage counter that select the stimulus
|
||||
-- switch position rather than the excited port. Zero restores the
|
||||
-- stock behaviour of one stage per port.
|
||||
SUBSTAGE_BITS : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
|
||||
PORT1_ACTIVE : out STD_LOGIC;
|
||||
PORT2_ACTIVE : out STD_LOGIC;
|
||||
@@ -97,6 +101,28 @@ architecture Behavioral of Sweep is
|
||||
signal stage_cnt : unsigned (2 downto 0);
|
||||
signal config_reg : std_logic_vector(95 downto 0);
|
||||
signal source_active : std_logic;
|
||||
|
||||
-- A port excites during a BLOCK of consecutive stages instead of a single one:
|
||||
-- the low SUBSTAGE_BITS bits of the stage counter are the stimulus switch
|
||||
-- position and the bits above them select the port. With SUBSTAGE_BITS = 0 this
|
||||
-- reduces to the stock exact comparison, one stage per port.
|
||||
--
|
||||
-- A port number beyond the last port stage can never match, which is how the
|
||||
-- host silences a receive-only device (it sends 7). The stock "one past the last
|
||||
-- stage" sentinel cannot be used once the port comes from the upper bits: with
|
||||
-- four stages those bits only ever hold 0 or 1, leaving no free value.
|
||||
function port_excites(
|
||||
stage : unsigned(2 downto 0);
|
||||
port_stage : std_logic_vector(2 downto 0);
|
||||
substage_width : std_logic_vector(2 downto 0);
|
||||
last_stage : std_logic_vector(2 downto 0)
|
||||
) return boolean is
|
||||
begin
|
||||
if unsigned(port_stage) > shift_right(unsigned(last_stage), to_integer(unsigned(substage_width))) then
|
||||
return false;
|
||||
end if;
|
||||
return shift_right(stage, to_integer(unsigned(substage_width))) = unsigned(port_stage);
|
||||
end function;
|
||||
begin
|
||||
|
||||
CONFIG_ADDRESS <= std_logic_vector(point_cnt);
|
||||
@@ -195,13 +221,13 @@ begin
|
||||
when Settling =>
|
||||
NEW_DATA <= '0';
|
||||
source_active <= '0';
|
||||
if std_logic_vector(stage_cnt) = PORT1_STAGE then
|
||||
if port_excites(stage_cnt, PORT1_STAGE, SUBSTAGE_BITS, STAGES) then
|
||||
PORT1_ACTIVE <= '1';
|
||||
source_active <= '1';
|
||||
else
|
||||
PORT1_ACTIVE <= '0';
|
||||
end if;
|
||||
if std_logic_vector(stage_cnt) = PORT2_STAGE then
|
||||
if port_excites(stage_cnt, PORT2_STAGE, SUBSTAGE_BITS, STAGES) then
|
||||
PORT2_ACTIVE <= '1';
|
||||
source_active <= '1';
|
||||
else
|
||||
|
||||
@@ -0,0 +1,278 @@
|
||||
----------------------------------------------------------------------------------
|
||||
-- Self-checking testbench for the stage/port gating in Sweep.
|
||||
--
|
||||
-- Covers the per-point stimulus switching change (see
|
||||
-- device_firmware/per_point_switching.md): with SUBSTAGE_BITS > 0 a port must
|
||||
-- excite across a BLOCK of consecutive stages instead of a single one, while
|
||||
-- SUBSTAGE_BITS = 0 must behave exactly like the stock design.
|
||||
--
|
||||
-- Runs without a trigger loop (SYNC_ENABLED = '0'), so the sweep advances on its
|
||||
-- own and every stage is observed at the moment sampling starts.
|
||||
--
|
||||
-- ghdl -a --std=93 Sweep.vhd Test_Sweep.vhd
|
||||
-- ghdl -r --std=93 Test_Sweep --assert-level=error
|
||||
----------------------------------------------------------------------------------
|
||||
library IEEE;
|
||||
use IEEE.STD_LOGIC_1164.ALL;
|
||||
use IEEE.NUMERIC_STD.ALL;
|
||||
|
||||
entity Test_Sweep is
|
||||
end Test_Sweep;
|
||||
|
||||
architecture Behavioral of Test_Sweep is
|
||||
constant CLK_PERIOD : time := 10 ns;
|
||||
|
||||
signal clk : std_logic := '0';
|
||||
signal reset : std_logic := '1';
|
||||
signal simulation_done : boolean := false;
|
||||
|
||||
-- Sweep configuration driven per scenario.
|
||||
signal npoints : std_logic_vector(12 downto 0) := (others => '0');
|
||||
signal stages : std_logic_vector(2 downto 0) := "001";
|
||||
signal port1_stage : std_logic_vector(2 downto 0) := "000";
|
||||
signal port2_stage : std_logic_vector(2 downto 0) := "001";
|
||||
signal substage_bits : std_logic_vector(2 downto 0) := "000";
|
||||
|
||||
-- DUT interface.
|
||||
signal config_data : std_logic_vector(95 downto 0);
|
||||
signal sampling_busy : std_logic := '0';
|
||||
signal start_sampling : std_logic;
|
||||
signal reload_pll_regs : std_logic;
|
||||
signal pll_reload_done : std_logic := '1';
|
||||
signal port1_active : std_logic;
|
||||
signal port2_active : std_logic;
|
||||
signal source_ce : std_logic;
|
||||
|
||||
-- Expected (port1, port2) excitation per stage, indexed by stage number.
|
||||
type excitation is record
|
||||
port1 : std_logic;
|
||||
port2 : std_logic;
|
||||
end record;
|
||||
type excitation_list is array (natural range <>) of excitation;
|
||||
|
||||
-- Observation bookkeeping, shared between the checker and the stimulus.
|
||||
signal observed_stages : natural := 0;
|
||||
signal failures : natural := 0;
|
||||
signal expected_port1 : std_logic_vector(7 downto 0) := (others => '0');
|
||||
signal expected_port2 : std_logic_vector(7 downto 0) := (others => '0');
|
||||
signal stages_per_point : positive := 2;
|
||||
signal scenario_name : string(1 to 24) := (others => ' ');
|
||||
|
||||
-- config_reg(95) = 0 keeps the sweep from halting; bits 92..90 = "001" picks the
|
||||
-- shortest sample count so the simulation stays fast. Everything else is unused
|
||||
-- by the state machine under test.
|
||||
function make_config return std_logic_vector is
|
||||
variable value : std_logic_vector(95 downto 0) := (others => '0');
|
||||
begin
|
||||
value(90) := '1';
|
||||
return value;
|
||||
end function;
|
||||
begin
|
||||
config_data <= make_config;
|
||||
|
||||
clk <= not clk after CLK_PERIOD / 2 when not simulation_done else '0';
|
||||
|
||||
dut : entity work.Sweep
|
||||
port map (
|
||||
CLK => clk,
|
||||
RESET => reset,
|
||||
NPOINTS => npoints,
|
||||
CONFIG_ADDRESS => open,
|
||||
CONFIG_DATA => config_data,
|
||||
USER_NSAMPLES => std_logic_vector(to_unsigned(6, 13)),
|
||||
NSAMPLES => open,
|
||||
SETTLING_TIME => std_logic_vector(to_unsigned(3, 20)),
|
||||
SAMPLING_BUSY => sampling_busy,
|
||||
SAMPLING_DONE => '0',
|
||||
START_SAMPLING => start_sampling,
|
||||
BAND_SELECT => open,
|
||||
MAX2871_DEF_4 => (others => '0'),
|
||||
MAX2871_DEF_3 => (others => '0'),
|
||||
MAX2871_DEF_1 => (others => '0'),
|
||||
MAX2871_DEF_0 => (others => '0'),
|
||||
SOURCE_REG_4 => open,
|
||||
SOURCE_REG_3 => open,
|
||||
SOURCE_REG_1 => open,
|
||||
SOURCE_REG_0 => open,
|
||||
LO_REG_4 => open,
|
||||
LO_REG_3 => open,
|
||||
LO_REG_1 => open,
|
||||
LO_REG_0 => open,
|
||||
RELOAD_PLL_REGS => reload_pll_regs,
|
||||
PLL_RELOAD_DONE => pll_reload_done,
|
||||
PLL_LOCKED => '1',
|
||||
SWEEP_HALTED => open,
|
||||
SWEEP_RESUME => '0',
|
||||
SYNC_ENABLED => '0',
|
||||
SYNC_MASTER => '0',
|
||||
TRIGGER_IN => '0',
|
||||
TRIGGER_OUT => open,
|
||||
NEW_DATA => open,
|
||||
ATTENUATOR => open,
|
||||
SOURCE_FILTER => open,
|
||||
STAGES => stages,
|
||||
PORT1_STAGE => port1_stage,
|
||||
PORT2_STAGE => port2_stage,
|
||||
SUBSTAGE_BITS => substage_bits,
|
||||
PORT1_ACTIVE => port1_active,
|
||||
PORT2_ACTIVE => port2_active,
|
||||
SOURCE_CE => source_ce,
|
||||
DEBUG_STATUS => open,
|
||||
RESULT_INDEX => open
|
||||
);
|
||||
|
||||
-- Minimal ADC model: busy for a few cycles once sampling is requested.
|
||||
sampling_model : process(clk)
|
||||
variable countdown : natural := 0;
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if start_sampling = '1' and countdown = 0 then
|
||||
countdown := 4;
|
||||
sampling_busy <= '1';
|
||||
elsif countdown > 0 then
|
||||
countdown := countdown - 1;
|
||||
if countdown = 0 then
|
||||
sampling_busy <= '0';
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- Minimal PLL model: the reload takes a few cycles, then reports done.
|
||||
pll_model : process(clk)
|
||||
variable countdown : natural := 0;
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reload_pll_regs = '1' then
|
||||
pll_reload_done <= '0';
|
||||
countdown := 3;
|
||||
elsif countdown > 0 then
|
||||
countdown := countdown - 1;
|
||||
if countdown = 0 then
|
||||
pll_reload_done <= '1';
|
||||
end if;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
-- Checks the excitation pattern at the instant each stage starts sampling.
|
||||
-- The stage number is not read back from the DUT on purpose: counting sampling
|
||||
-- starts independently is what makes a wrong stage order visible.
|
||||
checker : process(clk)
|
||||
variable stage_index : natural;
|
||||
variable was_sampling : std_logic := '0';
|
||||
begin
|
||||
if rising_edge(clk) then
|
||||
if reset = '1' then
|
||||
was_sampling := '0';
|
||||
-- Owned solely by this process: a second driver from the stimulus
|
||||
-- would make the signal unresolved.
|
||||
observed_stages <= 0;
|
||||
else
|
||||
if start_sampling = '1' and was_sampling = '0' then
|
||||
stage_index := observed_stages mod stages_per_point;
|
||||
assert port1_active = expected_port1(stage_index)
|
||||
report scenario_name & ": stage " & integer'image(stage_index)
|
||||
& " port1_active=" & std_logic'image(port1_active)
|
||||
& ", expected " & std_logic'image(expected_port1(stage_index))
|
||||
severity error;
|
||||
assert port2_active = expected_port2(stage_index)
|
||||
report scenario_name & ": stage " & integer'image(stage_index)
|
||||
& " port2_active=" & std_logic'image(port2_active)
|
||||
& ", expected " & std_logic'image(expected_port2(stage_index))
|
||||
severity error;
|
||||
-- The source must be powered exactly when some port is excited.
|
||||
assert source_ce = (port1_active or port2_active)
|
||||
report scenario_name & ": stage " & integer'image(stage_index)
|
||||
& " source_ce disagrees with the active ports"
|
||||
severity error;
|
||||
if port1_active /= expected_port1(stage_index)
|
||||
or port2_active /= expected_port2(stage_index)
|
||||
or source_ce /= (port1_active or port2_active) then
|
||||
failures <= failures + 1;
|
||||
end if;
|
||||
observed_stages <= observed_stages + 1;
|
||||
end if;
|
||||
was_sampling := start_sampling;
|
||||
end if;
|
||||
end if;
|
||||
end process;
|
||||
|
||||
stimulus : process
|
||||
-- Applies one scenario and waits until the requested number of stages has
|
||||
-- been observed, so the test fails loudly on a stalled state machine.
|
||||
procedure run_scenario(
|
||||
name : string;
|
||||
cfg_npoints : natural;
|
||||
cfg_stages : natural;
|
||||
cfg_port1 : natural;
|
||||
cfg_port2 : natural;
|
||||
cfg_substage_bits : natural;
|
||||
pattern : excitation_list;
|
||||
stages_to_observe : positive
|
||||
) is
|
||||
variable deadline : natural := 0;
|
||||
begin
|
||||
reset <= '1';
|
||||
scenario_name <= (others => ' ');
|
||||
scenario_name(1 to name'length) <= name;
|
||||
npoints <= std_logic_vector(to_unsigned(cfg_npoints, 13));
|
||||
stages <= std_logic_vector(to_unsigned(cfg_stages, 3));
|
||||
port1_stage <= std_logic_vector(to_unsigned(cfg_port1, 3));
|
||||
port2_stage <= std_logic_vector(to_unsigned(cfg_port2, 3));
|
||||
substage_bits <= std_logic_vector(to_unsigned(cfg_substage_bits, 3));
|
||||
stages_per_point <= pattern'length;
|
||||
expected_port1 <= (others => '0');
|
||||
expected_port2 <= (others => '0');
|
||||
for index in pattern'range loop
|
||||
expected_port1(index) <= pattern(index).port1;
|
||||
expected_port2(index) <= pattern(index).port2;
|
||||
end loop;
|
||||
-- Held long enough for the checker to zero its own stage counter.
|
||||
wait for CLK_PERIOD * 4;
|
||||
reset <= '0';
|
||||
|
||||
while observed_stages < stages_to_observe loop
|
||||
wait for CLK_PERIOD;
|
||||
deadline := deadline + 1;
|
||||
assert deadline < 20000
|
||||
report name & ": sweep stalled, only " & integer'image(observed_stages)
|
||||
& " stage(s) observed"
|
||||
severity failure;
|
||||
end loop;
|
||||
report name & ": " & integer'image(observed_stages) & " stage(s) checked";
|
||||
end procedure;
|
||||
begin
|
||||
-- Stock layout: one stage per port, must be untouched by the change.
|
||||
run_scenario(
|
||||
"stock, no substages", 1, 1, 0, 1, 0,
|
||||
(0 => ('1', '0'), 1 => ('0', '1')),
|
||||
4); -- two points, so the stage counter is seen wrapping
|
||||
|
||||
-- Per-point switching: port 1 excites in stages 0..1, port 2 in stages 2..3.
|
||||
run_scenario(
|
||||
"two switch positions", 0, 3, 0, 1, 1,
|
||||
(0 => ('1', '0'), 1 => ('1', '0'), 2 => ('0', '1'), 3 => ('0', '1')),
|
||||
4);
|
||||
|
||||
-- Receive-only device: the host parks both ports at 7, nothing may excite.
|
||||
run_scenario(
|
||||
"receiver only", 0, 3, 7, 7, 1,
|
||||
(0 => ('0', '0'), 1 => ('0', '0'), 2 => ('0', '0'), 3 => ('0', '0')),
|
||||
4);
|
||||
|
||||
-- Four switch positions on one port: stages 0..3 all excite port 1.
|
||||
run_scenario(
|
||||
"four switch positions", 0, 7, 0, 1, 2,
|
||||
(0 => ('1', '0'), 1 => ('1', '0'), 2 => ('1', '0'), 3 => ('1', '0'),
|
||||
4 => ('0', '1'), 5 => ('0', '1'), 6 => ('0', '1'), 7 => ('0', '1')),
|
||||
8);
|
||||
|
||||
assert failures = 0
|
||||
report integer'image(failures) & " check(s) failed"
|
||||
severity failure;
|
||||
report "Test_Sweep: all scenarios passed" severity note;
|
||||
simulation_done <= true;
|
||||
wait;
|
||||
end process;
|
||||
end Behavioral;
|
||||
@@ -150,6 +150,7 @@ architecture Behavioral of top is
|
||||
STAGES : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_STAGE : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
SUBSTAGE_BITS : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
|
||||
PORT1_ACTIVE : out STD_LOGIC;
|
||||
PORT2_ACTIVE : out STD_LOGIC;
|
||||
@@ -262,6 +263,7 @@ architecture Behavioral of top is
|
||||
SYNC_MASTER : out STD_LOGIC;
|
||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT2_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
SUBSTAGE_BITS : out STD_LOGIC_VECTOR (2 downto 0);
|
||||
PORT1_EN : out STD_LOGIC;
|
||||
PORT2_EN : out STD_LOGIC;
|
||||
REF_EN : out STD_LOGIC;
|
||||
@@ -386,6 +388,7 @@ 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_substage_bits : STD_LOGIC_VECTOR (2 downto 0);
|
||||
signal sweep_config_data : std_logic_vector(95 downto 0);
|
||||
signal sweep_config_address : std_logic_vector(12 downto 0);
|
||||
signal sweep_source_filter : std_logic_vector(1 downto 0);
|
||||
@@ -731,6 +734,7 @@ begin
|
||||
STAGES => sweep_stages,
|
||||
PORT1_STAGE => sweep_port1_stage,
|
||||
PORT2_STAGE => sweep_port2_stage,
|
||||
SUBSTAGE_BITS => sweep_substage_bits,
|
||||
|
||||
PORT1_ACTIVE => sweep_excite_port1,
|
||||
PORT2_ACTIVE => sweep_excite_port2,
|
||||
@@ -815,6 +819,7 @@ begin
|
||||
SYNC_MASTER => sweep_sync_master,
|
||||
PORT1_STAGE => sweep_port1_stage,
|
||||
PORT2_STAGE => sweep_port2_stage,
|
||||
SUBSTAGE_BITS => sweep_substage_bits,
|
||||
SPI_OVERWRITE_ENABLED => HW_overwrite_enabled,
|
||||
SPI_OVERWRITE_DATA => HW_overwrite_data,
|
||||
DFT_BIN1_PHASEINC => dft_bin1_phaseinc,
|
||||
|
||||
+3
-1
@@ -143,12 +143,14 @@ void FPGA::SetSettlingTime(uint16_t us) {
|
||||
WriteRegister(Reg::SettlingTimeHigh, value >> 16);
|
||||
}
|
||||
|
||||
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize, bool syncMaster) {
|
||||
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize, bool syncMaster, uint8_t substage_bits) {
|
||||
uint16_t value = 0x0000;
|
||||
value |= (uint16_t) (stages & 0x07) << 13;
|
||||
if(synchronize) {
|
||||
value |= 0x1000;
|
||||
}
|
||||
// Bits 11..9 of this register are still unused.
|
||||
value |= (uint16_t) (substage_bits & 0x07) << 6;
|
||||
value |= (port1_stage & 0x07) << 3;
|
||||
value |= (port2_stage & 0x07) << 0;
|
||||
WriteRegister(Reg::SweepSetup, value);
|
||||
|
||||
+5
-1
@@ -110,7 +110,11 @@ void WriteRegister(FPGA::Reg reg, uint16_t value);
|
||||
void SetNumberOfPoints(uint16_t npoints);
|
||||
void SetSamplesPerPoint(uint32_t nsamples);
|
||||
void SetSettlingTime(uint16_t us);
|
||||
void SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize = false, bool syncMaster = false);
|
||||
// substage_bits is how many LOW bits of the stage counter select the stimulus
|
||||
// switch position instead of the excited port, letting a port excite across a
|
||||
// block of consecutive stages (per-point stimulus switching). Zero, the default,
|
||||
// is the stock layout of exactly one stage per port.
|
||||
void SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage, bool synchronize = false, bool syncMaster = false, uint8_t substage_bits = 0);
|
||||
void Enable(Periphery p, bool enable = true);
|
||||
void Disable(Periphery p);
|
||||
bool IsEnabled(Periphery p);
|
||||
|
||||
+5
-1
@@ -338,7 +338,11 @@ bool VNA::Setup(Protocol::SweepSettings s) {
|
||||
FPGA::Enable(FPGA::Periphery::LO1Chip);
|
||||
FPGA::Enable(FPGA::Periphery::LO1RF);
|
||||
#endif
|
||||
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0, s.syncMaster);
|
||||
// port3Stage carries the stimulus switch substage width: this hardware has no
|
||||
// port 3, so the field is free, and reusing it keeps the packet layout intact.
|
||||
// A host that does not know about per-point switching leaves it at zero, which
|
||||
// is the stock one-stage-per-port layout.
|
||||
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0, s.syncMaster, s.port3Stage);
|
||||
Trigger::SetMode((Trigger::Mode) s.syncMode);
|
||||
FPGA::Enable(FPGA::Periphery::PortSwitch);
|
||||
pointCnt = 0;
|
||||
|
||||
@@ -3,6 +3,21 @@
|
||||
Здесь всё, что нужно, чтобы пересобрать прошивки из [`device_firmware/`](../) с нуля.
|
||||
Проект лежит целиком — не патч, а полное дерево с уже внесёнными доработками.
|
||||
|
||||
> **Исходники опережают бинарники.** В дерево внесено переключение стимула
|
||||
> внутри частотной точки (см. [`../../per_point_switching.md`](../../per_point_switching.md)),
|
||||
> а лежащие рядом `.vnafw`, `extracted/` и `build*/` — это ПРЕДЫДУЩАЯ прошивка,
|
||||
> собранная до этой доработки и сейчас залитая в приборы. Поэтому:
|
||||
>
|
||||
> * `./build.sh` без аргументов по-прежнему даёт побайтовое совпадение — он
|
||||
> только переупаковывает готовые `.bin`, не трогая исходники;
|
||||
> * `./build.sh --stm32` и любая сборка FPGA дадут **другие** бинарники, уже с
|
||||
> новой логикой — это ожидаемо, а не поломка;
|
||||
> * исходники, из которых собраны текущие `.vnafw`, лежат в git-коммите
|
||||
> `d2338ff`.
|
||||
>
|
||||
> После сборки и проверки на железе обновите `.vnafw`, `extracted/`, `build*/`
|
||||
> и `SHA256SUMS`, и этот блок можно будет убрать.
|
||||
|
||||
## Что где
|
||||
|
||||
```
|
||||
@@ -70,7 +85,11 @@ tail -c +341737 combined.vnafw | head -c 85004 > VNA_embedded.bin # STM32
|
||||
|
||||
## Отличия от апстрима v1.6.5
|
||||
|
||||
Всего 12 файлов. Всё остальное дерево совпадает с апстримом байт в байт.
|
||||
Аппаратный триггер и external-LO slave — 13 файлов (не 12: в списке не значился
|
||||
`PC_Application/LibreVNA-GUI/Traces/trace.cpp`, правка в GUI, к прошивкам
|
||||
отношения не имеющая). Переключение стимула внутри точки добавило ещё 5
|
||||
изменённых файлов и один новый. Всё остальное дерево совпадает с апстримом байт
|
||||
в байт.
|
||||
|
||||
### STM32 — поддержка аппаратного триггера (это и есть съём без USB)
|
||||
|
||||
@@ -117,6 +136,39 @@ tail -c +341737 combined.vnafw | head -c 85004 > VNA_embedded.bin # STM32
|
||||
Новые файлы: `BuildConfig.vhd`, `BuildConfig_external_lo_slave.vhd`,
|
||||
`VNA_external_lo_slave.xise`; в `VNA.xise` добавлена ссылка на `BuildConfig.vhd`.
|
||||
|
||||
### Переключение стимула внутри частотной точки
|
||||
|
||||
Внесено позже остального, бинарников рядом ещё нет (см. блок в начале файла).
|
||||
Полное описание — [`../../per_point_switching.md`](../../per_point_switching.md).
|
||||
|
||||
**`FPGA/VNA/Sweep.vhd`** — порт излучает не в одной стадии, а в БЛОКЕ подряд
|
||||
идущих: младшие `SUBSTAGE_BITS` бит счётчика стадий стали положением выходного
|
||||
ключа, старшие выбирают порт. Вынесено в функцию `port_excites`. Значение
|
||||
порт-стадии выше последней никогда не совпадает — так хост глушит излучение
|
||||
слейвов, посылая им 7.
|
||||
|
||||
**`FPGA/VNA/SPIConfig.vhd`**, **`FPGA/VNA/top.vhd`** — новый вход `SUBSTAGE_BITS`
|
||||
из свободных битов 8:6 регистра `SweepSetup` (адрес 6) и его проброс в `Sweep`.
|
||||
|
||||
**`Application/Drivers/FPGA/FPGA.{hpp,cpp}`** — у `SetupSweep` добавлен параметр
|
||||
`substage_bits` со значением по умолчанию 0, чтобы вызовы из ручного режима и
|
||||
анализатора спектра не менялись.
|
||||
|
||||
**`Application/VNA.cpp`** — ширина под-стадии берётся из `port3Stage`: у этого
|
||||
железа нет порта 3, поле свободно, и его переиспользование не меняет формат
|
||||
пакета.
|
||||
|
||||
**При `SUBSTAGE_BITS = 0` логика бит в бит совпадает с апстримом** — это
|
||||
проверено симуляцией.
|
||||
|
||||
Новый файл `FPGA/VNA/Test_Sweep.vhd` — самопроверяющийся тестбенч на четыре
|
||||
сценария (сток, два положения ключа, приёмник без излучения, четыре положения).
|
||||
В проект ISE не добавлен, на сборку не влияет. Запуск любым VHDL-симулятором:
|
||||
|
||||
```bash
|
||||
ghdl -a --std=93 Sweep.vhd Test_Sweep.vhd && ghdl -r --std=93 Test_Sweep
|
||||
```
|
||||
|
||||
### Прочее
|
||||
|
||||
**`AssembleFirmware.py`** — переписан: добавлены `--bitstream`, `--mcu`, `--output`
|
||||
|
||||
@@ -15,5 +15,5 @@ c0e69bbcadd9fac20de10acbe1875ceba39cc7ce35a7c64ed54f44150560ef75 *LibreVNA/FPGA/
|
||||
ba729999be7849187704cf4ccbc4ae4a8877b0860eee1b44225c0585537bb091 *LibreVNA/Software/VNA_embedded/build/VNA_embedded.bin
|
||||
56654a0344556d24ecca10f119e0226b8297a2bb89d1aa4217280854e9ffa7bd *LibreVNA/Software/VNA_embedded/build_external_lo_slave/VNA_embedded.bin
|
||||
# комплект
|
||||
993e3809f3935b8ca8100d1b3910eea0382c298bd085a656b64d641ee0971108 *README.md
|
||||
a3c88be8abbff988650d7fe20f4a93745d46c347c9d0936aa5b43a1ff29dc70b *README.md
|
||||
be686c2000094acb7703727a3eb9249f7ae8a3517dc7d8630fe97f394aadb849 *build.sh
|
||||
|
||||
@@ -56,49 +56,39 @@
|
||||
|
||||
## FPGA мастера
|
||||
|
||||
Сделано в [`multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/`](multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/),
|
||||
осталось собрать битстрим в ISE.
|
||||
|
||||
### `Sweep.vhd`
|
||||
|
||||
Сейчас порт включается по точному совпадению, поэтому излучает ровно в одной
|
||||
стадии, и во второй под-стадии зондирующего сигнала просто не будет:
|
||||
Порт включался по точному совпадению, поэтому излучал ровно в одной стадии, и во
|
||||
второй под-стадии зондирующего сигнала просто не было. Сравнение вынесено в
|
||||
функцию `port_excites`: младшие `SUBSTAGE_BITS` бит счётчика стадий — положение
|
||||
ключа, старшие — номер порта. Сдвиг на переменное число бит синтезируется в
|
||||
трёхбитный barrel shifter.
|
||||
|
||||
```vhdl
|
||||
if std_logic_vector(stage_cnt) = PORT1_STAGE then
|
||||
```
|
||||
|
||||
Меняется на сравнение по старшим битам плюс явное «порт выключен». Сдвиг на
|
||||
переменное число бит синтезируется в трёхбитный barrel shifter:
|
||||
|
||||
```vhdl
|
||||
-- в entity
|
||||
SUBSTAGE_BITS : in STD_LOGIC_VECTOR (2 downto 0);
|
||||
|
||||
-- в architecture
|
||||
signal substage_shift : natural range 0 to 7;
|
||||
substage_shift <= to_integer(unsigned(SUBSTAGE_BITS));
|
||||
|
||||
-- в состоянии Settling, вместо каждой из двух проверок:
|
||||
if unsigned(PORT1_STAGE) <= unsigned(STAGES)
|
||||
and shift_right(stage_cnt, substage_shift) = unsigned(PORT1_STAGE) then
|
||||
PORT1_ACTIVE <= '1';
|
||||
source_active <= '1';
|
||||
else
|
||||
PORT1_ACTIVE <= '0';
|
||||
if unsigned(port_stage) > shift_right(unsigned(last_stage), to_integer(unsigned(substage_width))) then
|
||||
return false;
|
||||
end if;
|
||||
return shift_right(stage, to_integer(unsigned(substage_width))) = unsigned(port_stage);
|
||||
```
|
||||
|
||||
Проверка совместимости:
|
||||
Совместимость со стоком **строгая, а не приблизительная**. При `SUBSTAGE_BITS = 0`
|
||||
сдвиг нулевой и условие вырождается в `stage_cnt = port_stage` плюс проверка
|
||||
`port_stage <= STAGES`. Эта проверка не может ничего изменить: счётчик стадий
|
||||
пробегает только `0..STAGES`, поэтому при `port_stage > STAGES` совпадения не было
|
||||
бы и так. То есть для всех существующих вызовов поведение идентично — включая
|
||||
ручной режим и анализатор спектра, где `STAGES = 0`.
|
||||
|
||||
* `SUBSTAGE_BITS = 0`, `STAGES = 1`, порты 0 и 1 — сдвиг нулевой, сравнение
|
||||
вырождается в исходное. Стоковое поведение сохраняется бит в бит.
|
||||
* Слейв с `port_stage = 7`: `7 <= 1` ложно → порт выключен. Старый хост слал 2 —
|
||||
`2 <= 1` тоже ложно, так что новый битстрим работает и со старым хостом.
|
||||
* `SUBSTAGE_BITS = 1`, `STAGES = 3`: порт 0 активен в стадиях 0 и 1, порт 1 — в
|
||||
2 и 3.
|
||||
Значение выше последней порт-стадии никогда не совпадает — так хост глушит
|
||||
излучение слейвов, посылая им 7. Старый хост слал «одну за последней», это тоже
|
||||
глушит.
|
||||
|
||||
### `SPIConfig.vhd`
|
||||
### `SPIConfig.vhd` и `top.vhd`
|
||||
|
||||
В регистре `SweepSetup` (адрес 6) заняты биты 15:13, 12, 5:3 и 2:0 — **биты 11:6
|
||||
свободны**. Берём три из них:
|
||||
свободны**. Заняты три из них:
|
||||
|
||||
```vhdl
|
||||
when 6 => STAGES <= spi_buf_out(15 downto 13);
|
||||
@@ -108,27 +98,41 @@ when 6 => STAGES <= spi_buf_out(15 downto 13);
|
||||
PORT2_STAGE <= spi_buf_out(2 downto 0);
|
||||
```
|
||||
|
||||
### `top.vhd`
|
||||
В `top.vhd` добавлен сигнал `sweep_substage_bits` и проброшен от `SPIConfig` к
|
||||
`Sweep`.
|
||||
|
||||
Пробросить `SUBSTAGE_BITS` от `SPIConfig` к `Sweep` — новый сигнал рядом с
|
||||
`sweep_stages` / `sweep_port1_stage`.
|
||||
### Проверка
|
||||
|
||||
Рядом лежит самопроверяющийся тестбенч `Test_Sweep.vhd` на четыре сценария: сток,
|
||||
два положения ключа, приёмник без излучения, четыре положения. Он гоняет автомат
|
||||
без триггерной петли (`SYNC_ENABLED = '0'`) и сверяет `PORT1_ACTIVE`/`PORT2_ACTIVE`
|
||||
в момент старта каждой оцифровки.
|
||||
|
||||
```bash
|
||||
ghdl -a --std=93 Sweep.vhd Test_Sweep.vhd && ghdl -r --std=93 Test_Sweep
|
||||
```
|
||||
|
||||
Тест проверен мутацией: если вернуть стоковое точное сравнение, сценарии с
|
||||
под-стадиями падают, а стоковый продолжает проходить.
|
||||
|
||||
## Прошивка STM32 LibreVNA
|
||||
|
||||
Для самих стадий менять нечего: `stageCnt` уже считается до `settings.stages`,
|
||||
значения тегируются номером стадии, `VNADatapoint<32>` вмещает 4 стадии × 3
|
||||
значения. Нужно только протащить новое поле:
|
||||
Сделано, собирается. Для самих стадий менять было нечего: `stageCnt` уже считается
|
||||
до `settings.stages`, значения тегируются номером стадии, `VNADatapoint<32>`
|
||||
вмещает 4 стадии × 3 значения. Протащено только новое поле:
|
||||
|
||||
```c
|
||||
// FPGA.cpp
|
||||
// FPGA.cpp — параметр со значением по умолчанию, чтобы не трогать вызовы из
|
||||
// ручного режима и анализатора спектра
|
||||
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage,
|
||||
uint8_t substage_bits, bool synchronize, bool syncMaster) {
|
||||
bool synchronize, bool syncMaster, uint8_t substage_bits) {
|
||||
...
|
||||
value |= (uint16_t) (substage_bits & 0x07) << 6;
|
||||
|
||||
// VNA.cpp
|
||||
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.port3Stage,
|
||||
s.syncMode != 0, s.syncMaster);
|
||||
// VNA.cpp — ширина под-стадии приезжает в port3Stage: порта 3 у этого железа нет,
|
||||
// поле свободно, формат пакета не меняется
|
||||
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0,
|
||||
s.syncMaster, s.port3Stage);
|
||||
```
|
||||
|
||||
Собирать так же, как описано в [`reproduce/README.md`](multi_libreVNA_firmware/reproduce/README.md).
|
||||
@@ -168,6 +172,80 @@ STM32 прибора в Hi-Z на обеих линиях (`GPIO_MODE_INPUT`, `N
|
||||
паузы по скользящему окну с запасом, то есть подстраивается сам под любые
|
||||
настройки развёртки; отдельный провод для маркера начала развёртки не нужен.
|
||||
|
||||
## Порядок ввода в строй
|
||||
|
||||
### Кого перепрошивать
|
||||
|
||||
**Только мастер.** Слейвы остаются на текущей прошивке, и вот почему: хост
|
||||
выставляет им обе порт-стадии в 7, а стоковое сравнение `stage_cnt = PORTn_STAGE`
|
||||
при счётчике 0..3 с семёркой не совпадает никогда — то есть слейв не излучает и
|
||||
без доработки. Число стадий он берёт из того же поля `STAGES`, что и раньше, так
|
||||
что четыре стадии на точку отработает как есть, а тегирование значений номером
|
||||
стадии в стоковой прошивке уже общее.
|
||||
|
||||
Это заметно экономит силы: slave-битстрим пересобирать не надо, а он, напомню,
|
||||
побайтово невоспроизводим.
|
||||
|
||||
### 1. Битстрим FPGA мастера (Xilinx ISE 14.7)
|
||||
|
||||
1. Скопировать [`multi_libreVNA_firmware/reproduce/LibreVNA/FPGA`](multi_libreVNA_firmware/reproduce/LibreVNA/FPGA)
|
||||
в общую папку виртуалки (`C:\shared\LibreVNA\FPGA`).
|
||||
2. Открыть **`VNA.xise`** — проект мастера. Не `VNA_external_lo_slave.xise`.
|
||||
3. Process → **Generate Programming File**.
|
||||
4. Забрать `top.bin` **сразу** и переименовать в `top_master.bin`: оба проекта
|
||||
пишут в один и тот же файл, и следующая сборка его затрёт. Именно так был
|
||||
потерян исходный мастер-битстрим.
|
||||
|
||||
Битстрим невоспроизводим побайтово (place & route недетерминирован плюс
|
||||
timestamp внутри), так что новый файл будет отличаться от старого при любом
|
||||
исходе — это нормально.
|
||||
|
||||
### 2. Прошивка MCU мастера
|
||||
|
||||
```bash
|
||||
cd multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded
|
||||
make BINPATH="<путь к arm-none-eabi из CubeIDE>/tools/bin" -j4
|
||||
```
|
||||
|
||||
Без `EXTERNAL_MASTER_LO_SLAVE` — это и есть сборка мастера. Проверено: с этими
|
||||
правками собирается без ошибок.
|
||||
|
||||
### 3. Упаковка .vnafw
|
||||
|
||||
```bash
|
||||
cd multi_libreVNA_firmware/reproduce/LibreVNA
|
||||
python3 AssembleFirmware.py \
|
||||
--bitstream FPGA/VNA/top_master.bin \
|
||||
--mcu Software/VNA_embedded/build/VNA_embedded.bin \
|
||||
--output ../combined_per_point_switching.vnafw
|
||||
```
|
||||
|
||||
Имя намеренно новое: **не перезаписывайте `combined.vnafw`, пока не проверите
|
||||
всё на железе** — это ваш путь отката. После успешной проверки замените им
|
||||
`combined.vnafw`, обновите `extracted/`, `build/`, `SHA256SUMS` и уберите
|
||||
предупреждение в начале [`reproduce/README.md`](multi_libreVNA_firmware/reproduce/README.md).
|
||||
|
||||
Учтите: `./build.sh` сверяет результат со старым эталоном и теперь сообщит о
|
||||
расхождении. Это ожидаемо, а не поломка.
|
||||
|
||||
### 4. Заливка
|
||||
|
||||
Штатным LibreVNA-GUI: **Device → Firmware Update**, скормить `.vnafw` целиком —
|
||||
GUI сам разложит битстрим и прошивку MCU.
|
||||
|
||||
### 5. Включение режима и проверка
|
||||
|
||||
Только после того, как мастер поднялся на новой прошивке и обычный режим
|
||||
(`stimulus_switch_positions: 1`) работает как раньше — включать переключение.
|
||||
Порядок проверки на стенде:
|
||||
|
||||
1. Осциллографом на PB2 и PB5 контроллера: на одной частоте четыре стадии и два
|
||||
периода `A B A B`, PB5 молчит.
|
||||
2. По USB CDC контроллера (развёртку остановить): `parity corrections : 0` и
|
||||
`stages per point : 4`.
|
||||
3. Сверить трейсы: выходы 0 и 1 должны совпасть с тем, что даёт старый режим —
|
||||
это те же порт 1 и порт 2 в положении A.
|
||||
|
||||
## Хост
|
||||
|
||||
Уже сделано. Включается одним полем в `run_config.json`:
|
||||
|
||||
Reference in New Issue
Block a user