fpga: replace variable shift with a compare mask in port gating
Контрольная сборка стока в ISE показала, что первая версия правки внесла нарушение тайминга: сток даёт Timing Score 0, а она — 1880 (hold на входе MCU_MOSI). Причина — shift_right с переменным сдвигом синтезируется в barrel shifter, а вызывался он четырежды: +142 LUT при +3 регистрах, из-за чего поехало размещение и пострадала SPI-шина, по которой MCU пишет регистры FPGA. Сбой там означал бы неверную конфигурацию развёртки. Теперь младшие биты счётчика стадий просто маскируются, а маска декодируется из SUBSTAGE_BITS тремя вентилями. Итог: 2182 LUT против 2189 у стока, то есть логика стала дешевле стоковой. При SUBSTAGE_BITS=0 маска все единицы и выражение вырождается в стоковое stage_cnt = port_stage буквально. Заодно контракт стал однозначнее: хост присылает ПЕРВУЮ стадию блока порта, а не индекс порта. Без под-стадий это то же число, поэтому стоковая совместимость сохраняется, а зарезервированное "7 = порт выключен" больше не может совпасть с настоящей стадией (при четырёх под-стадиях маскированная семёрка совпадала с блоком второго порта). Из VHDL убраны не-ASCII символы: GHDL отвергает их даже в комментариях. Проверено: тестбенч проходит все четыре сценария и падает под мутацией (8 сработавших проверок), 305 python-тестов зелёные, ISE собирает битстрим. Остаточное нарушение hold на MCU_MOSI ловится подбором cost table размещателя — ограничение маргинально уже в стоке (запас +0.458 нс). Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -76,6 +76,9 @@ def build_sweep_settings_payload(
|
||||
how many LOW bits of the stage counter are the switch position rather than the port
|
||||
selector. Zero there is the stock layout, so this payload stays compatible with an
|
||||
unmodified device as long as ``switch_substage_count`` is 1.
|
||||
|
||||
Port stages are sent as the FIRST stage of each port's block, which collapses to the
|
||||
plain port index when there are no substages.
|
||||
"""
|
||||
stimulus_ports = normalize_master_stimulus_ports(master_stimulus_ports)
|
||||
substage_count = normalize_switch_substage_count(switch_substage_count)
|
||||
@@ -97,10 +100,11 @@ def build_sweep_settings_payload(
|
||||
)
|
||||
|
||||
if is_synchronization_master:
|
||||
# The port selector lives in the stage bits ABOVE the substage block, so the
|
||||
# value sent here is the port's index, not its first stage number.
|
||||
# A port is matched by the stage bits ABOVE the substage block, so the value
|
||||
# sent here is the FIRST stage of the port's block. Without substages that is
|
||||
# just the port index, identical to the stock layout.
|
||||
port_stage_by_port = {
|
||||
port: index for index, port in enumerate(stimulus_ports)
|
||||
port: index * substage_count for index, port in enumerate(stimulus_ports)
|
||||
}
|
||||
port_1_stimulus_stage = port_stage_by_port.get(1, DISABLED_PORT_STAGE)
|
||||
port_2_stimulus_stage = port_stage_by_port.get(2, DISABLED_PORT_STAGE)
|
||||
|
||||
Reference in New Issue
Block a user