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,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