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;
|
SYNC_MASTER : out STD_LOGIC;
|
||||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||||
PORT2_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;
|
PORT1_EN : out STD_LOGIC;
|
||||||
PORT2_EN : out STD_LOGIC;
|
PORT2_EN : out STD_LOGIC;
|
||||||
REF_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 5 => ADC_PHASEINC <= spi_buf_out(11 downto 0);
|
||||||
when 6 => STAGES <= spi_buf_out(15 downto 13);
|
when 6 => STAGES <= spi_buf_out(15 downto 13);
|
||||||
SYNC_ENABLED <= spi_buf_out(12);
|
SYNC_ENABLED <= spi_buf_out(12);
|
||||||
|
SUBSTAGE_BITS <= spi_buf_out(8 downto 6);
|
||||||
PORT1_STAGE <= spi_buf_out(5 downto 3);
|
PORT1_STAGE <= spi_buf_out(5 downto 3);
|
||||||
PORT2_STAGE <= spi_buf_out(2 downto 0);
|
PORT2_STAGE <= spi_buf_out(2 downto 0);
|
||||||
when 7 => SPI_OVERWRITE_ENABLED <= spi_buf_out(15);
|
when 7 => SPI_OVERWRITE_ENABLED <= spi_buf_out(15);
|
||||||
|
|||||||
@@ -77,7 +77,11 @@ entity Sweep is
|
|||||||
STAGES : in STD_LOGIC_VECTOR (2 downto 0);
|
STAGES : in STD_LOGIC_VECTOR (2 downto 0);
|
||||||
PORT1_STAGE : 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);
|
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;
|
PORT1_ACTIVE : out STD_LOGIC;
|
||||||
PORT2_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 stage_cnt : unsigned (2 downto 0);
|
||||||
signal config_reg : std_logic_vector(95 downto 0);
|
signal config_reg : std_logic_vector(95 downto 0);
|
||||||
signal source_active : std_logic;
|
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
|
begin
|
||||||
|
|
||||||
CONFIG_ADDRESS <= std_logic_vector(point_cnt);
|
CONFIG_ADDRESS <= std_logic_vector(point_cnt);
|
||||||
@@ -195,13 +221,13 @@ begin
|
|||||||
when Settling =>
|
when Settling =>
|
||||||
NEW_DATA <= '0';
|
NEW_DATA <= '0';
|
||||||
source_active <= '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';
|
PORT1_ACTIVE <= '1';
|
||||||
source_active <= '1';
|
source_active <= '1';
|
||||||
else
|
else
|
||||||
PORT1_ACTIVE <= '0';
|
PORT1_ACTIVE <= '0';
|
||||||
end if;
|
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';
|
PORT2_ACTIVE <= '1';
|
||||||
source_active <= '1';
|
source_active <= '1';
|
||||||
else
|
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);
|
STAGES : in STD_LOGIC_VECTOR (2 downto 0);
|
||||||
PORT1_STAGE : 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);
|
PORT2_STAGE : in STD_LOGIC_VECTOR (2 downto 0);
|
||||||
|
SUBSTAGE_BITS : in STD_LOGIC_VECTOR (2 downto 0);
|
||||||
|
|
||||||
PORT1_ACTIVE : out STD_LOGIC;
|
PORT1_ACTIVE : out STD_LOGIC;
|
||||||
PORT2_ACTIVE : out STD_LOGIC;
|
PORT2_ACTIVE : out STD_LOGIC;
|
||||||
@@ -262,6 +263,7 @@ architecture Behavioral of top is
|
|||||||
SYNC_MASTER : out STD_LOGIC;
|
SYNC_MASTER : out STD_LOGIC;
|
||||||
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
PORT1_STAGE : out STD_LOGIC_VECTOR (2 downto 0);
|
||||||
PORT2_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;
|
PORT1_EN : out STD_LOGIC;
|
||||||
PORT2_EN : out STD_LOGIC;
|
PORT2_EN : out STD_LOGIC;
|
||||||
REF_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_sync_master : STD_LOGIC;
|
||||||
signal sweep_port1_stage : STD_LOGIC_VECTOR (2 downto 0);
|
signal sweep_port1_stage : STD_LOGIC_VECTOR (2 downto 0);
|
||||||
signal sweep_port2_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_data : std_logic_vector(95 downto 0);
|
||||||
signal sweep_config_address : std_logic_vector(12 downto 0);
|
signal sweep_config_address : std_logic_vector(12 downto 0);
|
||||||
signal sweep_source_filter : std_logic_vector(1 downto 0);
|
signal sweep_source_filter : std_logic_vector(1 downto 0);
|
||||||
@@ -731,6 +734,7 @@ begin
|
|||||||
STAGES => sweep_stages,
|
STAGES => sweep_stages,
|
||||||
PORT1_STAGE => sweep_port1_stage,
|
PORT1_STAGE => sweep_port1_stage,
|
||||||
PORT2_STAGE => sweep_port2_stage,
|
PORT2_STAGE => sweep_port2_stage,
|
||||||
|
SUBSTAGE_BITS => sweep_substage_bits,
|
||||||
|
|
||||||
PORT1_ACTIVE => sweep_excite_port1,
|
PORT1_ACTIVE => sweep_excite_port1,
|
||||||
PORT2_ACTIVE => sweep_excite_port2,
|
PORT2_ACTIVE => sweep_excite_port2,
|
||||||
@@ -815,6 +819,7 @@ begin
|
|||||||
SYNC_MASTER => sweep_sync_master,
|
SYNC_MASTER => sweep_sync_master,
|
||||||
PORT1_STAGE => sweep_port1_stage,
|
PORT1_STAGE => sweep_port1_stage,
|
||||||
PORT2_STAGE => sweep_port2_stage,
|
PORT2_STAGE => sweep_port2_stage,
|
||||||
|
SUBSTAGE_BITS => sweep_substage_bits,
|
||||||
SPI_OVERWRITE_ENABLED => HW_overwrite_enabled,
|
SPI_OVERWRITE_ENABLED => HW_overwrite_enabled,
|
||||||
SPI_OVERWRITE_DATA => HW_overwrite_data,
|
SPI_OVERWRITE_DATA => HW_overwrite_data,
|
||||||
DFT_BIN1_PHASEINC => dft_bin1_phaseinc,
|
DFT_BIN1_PHASEINC => dft_bin1_phaseinc,
|
||||||
|
|||||||
+3
-1
@@ -143,12 +143,14 @@ void FPGA::SetSettlingTime(uint16_t us) {
|
|||||||
WriteRegister(Reg::SettlingTimeHigh, value >> 16);
|
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;
|
uint16_t value = 0x0000;
|
||||||
value |= (uint16_t) (stages & 0x07) << 13;
|
value |= (uint16_t) (stages & 0x07) << 13;
|
||||||
if(synchronize) {
|
if(synchronize) {
|
||||||
value |= 0x1000;
|
value |= 0x1000;
|
||||||
}
|
}
|
||||||
|
// Bits 11..9 of this register are still unused.
|
||||||
|
value |= (uint16_t) (substage_bits & 0x07) << 6;
|
||||||
value |= (port1_stage & 0x07) << 3;
|
value |= (port1_stage & 0x07) << 3;
|
||||||
value |= (port2_stage & 0x07) << 0;
|
value |= (port2_stage & 0x07) << 0;
|
||||||
WriteRegister(Reg::SweepSetup, value);
|
WriteRegister(Reg::SweepSetup, value);
|
||||||
|
|||||||
+5
-1
@@ -110,7 +110,11 @@ void WriteRegister(FPGA::Reg reg, uint16_t value);
|
|||||||
void SetNumberOfPoints(uint16_t npoints);
|
void SetNumberOfPoints(uint16_t npoints);
|
||||||
void SetSamplesPerPoint(uint32_t nsamples);
|
void SetSamplesPerPoint(uint32_t nsamples);
|
||||||
void SetSettlingTime(uint16_t us);
|
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 Enable(Periphery p, bool enable = true);
|
||||||
void Disable(Periphery p);
|
void Disable(Periphery p);
|
||||||
bool IsEnabled(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::LO1Chip);
|
||||||
FPGA::Enable(FPGA::Periphery::LO1RF);
|
FPGA::Enable(FPGA::Periphery::LO1RF);
|
||||||
#endif
|
#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);
|
Trigger::SetMode((Trigger::Mode) s.syncMode);
|
||||||
FPGA::Enable(FPGA::Periphery::PortSwitch);
|
FPGA::Enable(FPGA::Periphery::PortSwitch);
|
||||||
pointCnt = 0;
|
pointCnt = 0;
|
||||||
|
|||||||
@@ -3,6 +3,21 @@
|
|||||||
Здесь всё, что нужно, чтобы пересобрать прошивки из [`device_firmware/`](../) с нуля.
|
Здесь всё, что нужно, чтобы пересобрать прошивки из [`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
|
## Отличия от апстрима v1.6.5
|
||||||
|
|
||||||
Всего 12 файлов. Всё остальное дерево совпадает с апстримом байт в байт.
|
Аппаратный триггер и external-LO slave — 13 файлов (не 12: в списке не значился
|
||||||
|
`PC_Application/LibreVNA-GUI/Traces/trace.cpp`, правка в GUI, к прошивкам
|
||||||
|
отношения не имеющая). Переключение стимула внутри точки добавило ещё 5
|
||||||
|
изменённых файлов и один новый. Всё остальное дерево совпадает с апстримом байт
|
||||||
|
в байт.
|
||||||
|
|
||||||
### STM32 — поддержка аппаратного триггера (это и есть съём без USB)
|
### 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`,
|
Новые файлы: `BuildConfig.vhd`, `BuildConfig_external_lo_slave.vhd`,
|
||||||
`VNA_external_lo_slave.xise`; в `VNA.xise` добавлена ссылка на `BuildConfig.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`
|
**`AssembleFirmware.py`** — переписан: добавлены `--bitstream`, `--mcu`, `--output`
|
||||||
|
|||||||
@@ -15,5 +15,5 @@ c0e69bbcadd9fac20de10acbe1875ceba39cc7ce35a7c64ed54f44150560ef75 *LibreVNA/FPGA/
|
|||||||
ba729999be7849187704cf4ccbc4ae4a8877b0860eee1b44225c0585537bb091 *LibreVNA/Software/VNA_embedded/build/VNA_embedded.bin
|
ba729999be7849187704cf4ccbc4ae4a8877b0860eee1b44225c0585537bb091 *LibreVNA/Software/VNA_embedded/build/VNA_embedded.bin
|
||||||
56654a0344556d24ecca10f119e0226b8297a2bb89d1aa4217280854e9ffa7bd *LibreVNA/Software/VNA_embedded/build_external_lo_slave/VNA_embedded.bin
|
56654a0344556d24ecca10f119e0226b8297a2bb89d1aa4217280854e9ffa7bd *LibreVNA/Software/VNA_embedded/build_external_lo_slave/VNA_embedded.bin
|
||||||
# комплект
|
# комплект
|
||||||
993e3809f3935b8ca8100d1b3910eea0382c298bd085a656b64d641ee0971108 *README.md
|
a3c88be8abbff988650d7fe20f4a93745d46c347c9d0936aa5b43a1ff29dc70b *README.md
|
||||||
be686c2000094acb7703727a3eb9249f7ae8a3517dc7d8630fe97f394aadb849 *build.sh
|
be686c2000094acb7703727a3eb9249f7ae8a3517dc7d8630fe97f394aadb849 *build.sh
|
||||||
|
|||||||
@@ -56,49 +56,39 @@
|
|||||||
|
|
||||||
## FPGA мастера
|
## FPGA мастера
|
||||||
|
|
||||||
|
Сделано в [`multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/`](multi_libreVNA_firmware/reproduce/LibreVNA/FPGA/VNA/),
|
||||||
|
осталось собрать битстрим в ISE.
|
||||||
|
|
||||||
### `Sweep.vhd`
|
### `Sweep.vhd`
|
||||||
|
|
||||||
Сейчас порт включается по точному совпадению, поэтому излучает ровно в одной
|
Порт включался по точному совпадению, поэтому излучал ровно в одной стадии, и во
|
||||||
стадии, и во второй под-стадии зондирующего сигнала просто не будет:
|
второй под-стадии зондирующего сигнала просто не было. Сравнение вынесено в
|
||||||
|
функцию `port_excites`: младшие `SUBSTAGE_BITS` бит счётчика стадий — положение
|
||||||
|
ключа, старшие — номер порта. Сдвиг на переменное число бит синтезируется в
|
||||||
|
трёхбитный barrel shifter.
|
||||||
|
|
||||||
```vhdl
|
```vhdl
|
||||||
if std_logic_vector(stage_cnt) = PORT1_STAGE then
|
if unsigned(port_stage) > shift_right(unsigned(last_stage), to_integer(unsigned(substage_width))) then
|
||||||
```
|
return false;
|
||||||
|
|
||||||
Меняется на сравнение по старшим битам плюс явное «порт выключен». Сдвиг на
|
|
||||||
переменное число бит синтезируется в трёхбитный 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';
|
|
||||||
end if;
|
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 — сдвиг нулевой, сравнение
|
Значение выше последней порт-стадии никогда не совпадает — так хост глушит
|
||||||
вырождается в исходное. Стоковое поведение сохраняется бит в бит.
|
излучение слейвов, посылая им 7. Старый хост слал «одну за последней», это тоже
|
||||||
* Слейв с `port_stage = 7`: `7 <= 1` ложно → порт выключен. Старый хост слал 2 —
|
глушит.
|
||||||
`2 <= 1` тоже ложно, так что новый битстрим работает и со старым хостом.
|
|
||||||
* `SUBSTAGE_BITS = 1`, `STAGES = 3`: порт 0 активен в стадиях 0 и 1, порт 1 — в
|
|
||||||
2 и 3.
|
|
||||||
|
|
||||||
### `SPIConfig.vhd`
|
### `SPIConfig.vhd` и `top.vhd`
|
||||||
|
|
||||||
В регистре `SweepSetup` (адрес 6) заняты биты 15:13, 12, 5:3 и 2:0 — **биты 11:6
|
В регистре `SweepSetup` (адрес 6) заняты биты 15:13, 12, 5:3 и 2:0 — **биты 11:6
|
||||||
свободны**. Берём три из них:
|
свободны**. Заняты три из них:
|
||||||
|
|
||||||
```vhdl
|
```vhdl
|
||||||
when 6 => STAGES <= spi_buf_out(15 downto 13);
|
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);
|
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
|
## Прошивка STM32 LibreVNA
|
||||||
|
|
||||||
Для самих стадий менять нечего: `stageCnt` уже считается до `settings.stages`,
|
Сделано, собирается. Для самих стадий менять было нечего: `stageCnt` уже считается
|
||||||
значения тегируются номером стадии, `VNADatapoint<32>` вмещает 4 стадии × 3
|
до `settings.stages`, значения тегируются номером стадии, `VNADatapoint<32>`
|
||||||
значения. Нужно только протащить новое поле:
|
вмещает 4 стадии × 3 значения. Протащено только новое поле:
|
||||||
|
|
||||||
```c
|
```c
|
||||||
// FPGA.cpp
|
// FPGA.cpp — параметр со значением по умолчанию, чтобы не трогать вызовы из
|
||||||
|
// ручного режима и анализатора спектра
|
||||||
void FPGA::SetupSweep(uint8_t stages, uint8_t port1_stage, uint8_t port2_stage,
|
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;
|
value |= (uint16_t) (substage_bits & 0x07) << 6;
|
||||||
|
|
||||||
// VNA.cpp
|
// VNA.cpp — ширина под-стадии приезжает в port3Stage: порта 3 у этого железа нет,
|
||||||
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.port3Stage,
|
// поле свободно, формат пакета не меняется
|
||||||
s.syncMode != 0, s.syncMaster);
|
FPGA::SetupSweep(s.stages, s.port1Stage, s.port2Stage, s.syncMode != 0,
|
||||||
|
s.syncMaster, s.port3Stage);
|
||||||
```
|
```
|
||||||
|
|
||||||
Собирать так же, как описано в [`reproduce/README.md`](multi_libreVNA_firmware/reproduce/README.md).
|
Собирать так же, как описано в [`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`:
|
Уже сделано. Включается одним полем в `run_config.json`:
|
||||||
|
|||||||
Reference in New Issue
Block a user