diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/SPIConfig.vhd b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/SPIConfig.vhd index 4703728..4bb4666 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/SPIConfig.vhd +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/SPIConfig.vhd @@ -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); diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Sweep.vhd b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Sweep.vhd index c2408ef..208950e 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Sweep.vhd +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Sweep.vhd @@ -77,7 +77,11 @@ 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 diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Test_Sweep.vhd b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Test_Sweep.vhd new file mode 100644 index 0000000..ed890e0 --- /dev/null +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/Test_Sweep.vhd @@ -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; diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/top.vhd b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/top.vhd index 2d23cae..8f6a9c1 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/top.vhd +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/top.vhd @@ -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, diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.cpp b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.cpp index 32de720..bf512a9 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.cpp +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.cpp @@ -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); diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.hpp b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.hpp index 25a34b4..93526ee 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.hpp +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/Drivers/FPGA/FPGA.hpp @@ -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); diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/VNA.cpp b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/VNA.cpp index c7536bb..347bf44 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/VNA.cpp +++ b/device_firmware/multi_libreVNA_firmware/reproduce/LibreVNA/Software/VNA_embedded/Application/VNA.cpp @@ -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; diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/README.md b/device_firmware/multi_libreVNA_firmware/reproduce/README.md index ee3f321..9ca444c 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/README.md +++ b/device_firmware/multi_libreVNA_firmware/reproduce/README.md @@ -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` diff --git a/device_firmware/multi_libreVNA_firmware/reproduce/SHA256SUMS b/device_firmware/multi_libreVNA_firmware/reproduce/SHA256SUMS index 2b4f292..2d2ee91 100644 --- a/device_firmware/multi_libreVNA_firmware/reproduce/SHA256SUMS +++ b/device_firmware/multi_libreVNA_firmware/reproduce/SHA256SUMS @@ -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 diff --git a/device_firmware/per_point_switching.md b/device_firmware/per_point_switching.md index a80e496..acd0f8f 100644 --- a/device_firmware/per_point_switching.md +++ b/device_firmware/per_point_switching.md @@ -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`: