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:
Ayzen
2026-09-09 22:06:23 +03:00
co-authored by Claude Opus 5
parent 0db3dbe295
commit 7d1879ad79
10 changed files with 504 additions and 51 deletions
@@ -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);
@@ -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);
@@ -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;