mirror of
https://github.com/NawfalMotii79/PLFM_RADAR.git
synced 2026-06-11 07:51:17 +00:00
feat(fpga,mcu,gui): PR-AB.b — drift-free dwell sync via DIG_6 frame_pulse + AGC always-on policy
FPGA (Phase 1+2): - gpio_dig6 (PD14) now carries chirp_scheduler frame_pulse, FPGA-stretched to ~100 ns so the STM32 EXTI on PD14 can latch reliably. - gpio_dig7 (PD15) returns to its pre-PR-AB.b role: control-fault OR (range_decim_watchdog | CDC overrun); MCU stuck-high sampler unchanged. - rx_range_decim_watchdog gains a sticky in source clock domain so a slow status poll cannot miss a 1-cycle assertion (Phase 1). - New tb_dig6_frame_pulse.v (13 checks); tb_status_words_stickies.v extended with DIG_7 fault-OR coverage (14 checks); retired tb_audit_s10_gpio_split.v. - Port comments in radar_system_top.v / _50t.v and XDC roles refreshed. MCU (Phase 3): - PD14 reconfigured to GPIO_MODE_IT_RISING + GPIO_PULLDOWN; new EXTI15_10_IRQHandler in stm32f7xx_it.c dispatches to HAL_GPIO_EXTI_Callback that bumps a volatile g_frame_pulse_count. - runRadarPulseSequence dwell loop replaces 3x HAL_Delay(8) with waitForFramePulse(20) — per-pattern dwell now tracks the actual mask-aware ladder length (drift-free, mask-aware), with a 20 ms timeout safety net. - AGC outer loop is ALWAYS-ON in production (compile-time policy); bench builds compile the body out via -DMCU_AGC_FORCE_DISABLED. The runtime enable/debounce + DIG_6 polling that previously gated AGC are removed. - main.h adds FPGA_FRAME_PULSE_* aliases pointing at FPGA_DIG6_*. GUI (Phase 4): - Settings tab gains a Bench / Diagnostics group with a BENCH-MODE checkbox (off by default, persisted via QSettings). - AGC group header swaps between a green "AGC: ALWAYS-ON" badge (production) and Enable/Disable AGC buttons (bench), pinned to the top of the group. The redundant 0/1 spinbox row for opcode 0x28 is removed — buttons send the same opcode and cannot accept invalid input. - Both the FPGA Control AGC Status box and the AGC Monitor strip share a helper that honours bench-mode in production (always shows ALWAYS-ON in green so the two views never disagree with the badge). - _add_fpga_param_row uses setFixedWidth on label and Set button + explicit stretch=1 on the hint, so all rows align column-wise whether they sit directly in a QVBoxLayout or inside a wrapper QWidget. Regression: FPGA 42/0/0 (PR-M.4 baseline) - MCU 34/34 - GPS extended 51/51 - GUI v7 150/150 - BENCH-MODE flip behaviorally verified. Hardware-blocked steps deferred: bench-scope verify (PD14 dwell pulse, counter advance, PD15 stuck-high recovery still triggers). Closes #182.
This commit is contained in:
@@ -212,6 +212,25 @@ void OTG_FS_IRQHandler(void)
|
|||||||
/* USER CODE END OTG_FS_IRQn 1 */
|
/* USER CODE END OTG_FS_IRQn 1 */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief This function handles EXTI lines [15:10] interrupts.
|
||||||
|
*
|
||||||
|
* PR-AB.b: PD14 (FPGA frame_pulse / DIG_6) is the only EXTI source on lines
|
||||||
|
* 10-15. HAL_GPIO_EXTI_IRQHandler clears the pending bit and dispatches to
|
||||||
|
* HAL_GPIO_EXTI_Callback in main.cpp, which increments g_frame_pulse_count
|
||||||
|
* to release the runRadarPulseSequence dwell loop.
|
||||||
|
*/
|
||||||
|
void EXTI15_10_IRQHandler(void)
|
||||||
|
{
|
||||||
|
/* USER CODE BEGIN EXTI15_10_IRQn 0 */
|
||||||
|
|
||||||
|
/* USER CODE END EXTI15_10_IRQn 0 */
|
||||||
|
HAL_GPIO_EXTI_IRQHandler(GPIO_PIN_14);
|
||||||
|
/* USER CODE BEGIN EXTI15_10_IRQn 1 */
|
||||||
|
|
||||||
|
/* USER CODE END EXTI15_10_IRQn 1 */
|
||||||
|
}
|
||||||
|
|
||||||
/* USER CODE BEGIN 1 */
|
/* USER CODE BEGIN 1 */
|
||||||
|
|
||||||
/* USER CODE END 1 */
|
/* USER CODE END 1 */
|
||||||
|
|||||||
@@ -300,6 +300,25 @@ void systemPowerUpSequence();
|
|||||||
void systemPowerDownSequence();
|
void systemPowerDownSequence();
|
||||||
void initializeBeamMatrices();
|
void initializeBeamMatrices();
|
||||||
void runRadarPulseSequence();
|
void runRadarPulseSequence();
|
||||||
|
|
||||||
|
/* PR-AB.b: FPGA chirp_scheduler frame_pulse (DIG_6 / PD14, ~100 ns stretched)
|
||||||
|
* is wired to EXTI15_10. Each rising edge bumps g_frame_pulse_count from the
|
||||||
|
* ISR; the dwell loop in runRadarPulseSequence waits for the counter to
|
||||||
|
* advance instead of HAL_Delay-padding, so per-pattern dwell tracks the
|
||||||
|
* actual mask-aware ladder length (drift-free, mask-aware). The ladder is
|
||||||
|
* ~8 ms; we use a 20 ms timeout so a missed pulse cannot stall the loop. */
|
||||||
|
static volatile uint32_t g_frame_pulse_count = 0;
|
||||||
|
|
||||||
|
static inline void waitForFramePulse(uint32_t timeout_ms)
|
||||||
|
{
|
||||||
|
uint32_t prev = g_frame_pulse_count;
|
||||||
|
uint32_t t0 = HAL_GetTick();
|
||||||
|
while (g_frame_pulse_count == prev) {
|
||||||
|
if ((HAL_GetTick() - t0) >= timeout_ms) {
|
||||||
|
return; // timeout — caller continues; missed-pulse degrades to wall-clock cadence
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
/* F-2.1: executeChirpSequence() removed. The MCU is no longer in the chirp
|
/* F-2.1: executeChirpSequence() removed. The MCU is no longer in the chirp
|
||||||
* dispatch path -- production runs FPGA mode 2'b01 (auto-scan) where
|
* dispatch path -- production runs FPGA mode 2'b01 (auto-scan) where
|
||||||
* chirp_scheduler.v owns chirp timing and adar_tr_x. See runRadarPulseSequence
|
* chirp_scheduler.v owns chirp timing and adar_tr_x. See runRadarPulseSequence
|
||||||
@@ -376,6 +395,17 @@ extern "C" {
|
|||||||
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &usb_rx_buffer[0]);
|
USBD_CDC_SetRxBuffer(&hUsbDeviceFS, &usb_rx_buffer[0]);
|
||||||
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
|
USBD_CDC_ReceivePacket(&hUsbDeviceFS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// PR-AB.b: EXTI callback for FPGA frame_pulse on PD14 (DIG_6).
|
||||||
|
// EXTI15_10_IRQHandler in stm32f7xx_it.c calls HAL_GPIO_EXTI_IRQHandler,
|
||||||
|
// which dispatches here. Bumping g_frame_pulse_count releases the dwell
|
||||||
|
// loop's waitForFramePulse spin. PD15 (DIG_7 fault flag) stays polled
|
||||||
|
// by checkSystemHealth — no EXTI on that line.
|
||||||
|
void HAL_GPIO_EXTI_Callback(uint16_t GPIO_Pin) {
|
||||||
|
if (GPIO_Pin == FPGA_FRAME_PULSE_Pin) {
|
||||||
|
g_frame_pulse_count++;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void systemPowerUpSequence() {
|
void systemPowerUpSequence() {
|
||||||
@@ -577,25 +607,26 @@ void runRadarPulseSequence() {
|
|||||||
* reference; if multiple per-pos broadside frames are ever needed
|
* reference; if multiple per-pos broadside frames are ever needed
|
||||||
* they should re-enter behind a runtime switch.
|
* they should re-enter behind a runtime switch.
|
||||||
*
|
*
|
||||||
* Per-pattern dwell:
|
* Per-pattern dwell tracks the actual chirp_scheduler ladder length
|
||||||
|
* (drift-free, mask-aware) by waiting on the FPGA frame_pulse on
|
||||||
|
* DIG_6 / PD14 — see waitForFramePulse + HAL_GPIO_EXTI_Callback above.
|
||||||
|
* Nominal ladder is:
|
||||||
* 16 × PRI_SHORT (175 us) +
|
* 16 × PRI_SHORT (175 us) +
|
||||||
* 16 × PRI_MEDIUM (161 us) +
|
* 16 × PRI_MEDIUM (161 us) +
|
||||||
* 16 × PRI_LONG (167 us) = 8048 us per ladder, rounded to 8 ms.
|
* 16 × PRI_LONG (167 us) = 8048 us
|
||||||
* HAL_Delay yields to SysTick / IRQs, so unlike the removed
|
* but with subframe_enable masking a single PRI it can drop to ~2.7 ms,
|
||||||
* executeChirpSequence busy-loop the MCU stays responsive to USB CDC,
|
* which a fixed HAL_Delay(8) used to over-pad. The 20 ms timeout below
|
||||||
* UART, and I2C peripherals during the dwell. For drift-immune sync
|
* is a safety floor — if a frame_pulse ever goes missing (FPGA reset,
|
||||||
* we'd wait on an FPGA `subframe_pulse` GPIO instead — DIG_7
|
* PD14 disconnected) the loop degrades to wall-clock cadence rather
|
||||||
* (H12→PD15) is wired in the schematic but currently driven by the
|
* than hanging. */
|
||||||
* F-6.4 watchdog OR; reassigning it is a tracked follow-up
|
static const uint32_t FRAME_PULSE_TIMEOUT_MS = 20u;
|
||||||
* (PR-AB.b). */
|
|
||||||
static const uint32_t BEAM_PATTERN_DWELL_MS = 8u;
|
|
||||||
|
|
||||||
// One broadside (vector_0) reference frame per azimuth — replaces the
|
// One broadside (vector_0) reference frame per azimuth — replaces the
|
||||||
// 15 in-loop fires that PR-AB.a removed.
|
// 15 in-loop fires that PR-AB.a removed.
|
||||||
DIAG("SYS", "Broadside reference (vector_0) — 1× per azimuth");
|
DIAG("SYS", "Broadside reference (vector_0) — 1× per azimuth");
|
||||||
adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::TX);
|
adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::TX);
|
||||||
adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::RX);
|
adarManager.setCustomBeamPattern16(vector_0, ADAR1000Manager::BeamDirection::RX);
|
||||||
HAL_Delay(BEAM_PATTERN_DWELL_MS);
|
waitForFramePulse(FRAME_PULSE_TIMEOUT_MS);
|
||||||
m += m_max/2;
|
m += m_max/2;
|
||||||
|
|
||||||
for(int beam_pos = 0; beam_pos < 15; beam_pos++) {
|
for(int beam_pos = 0; beam_pos < 15; beam_pos++) {
|
||||||
@@ -604,13 +635,13 @@ void runRadarPulseSequence() {
|
|||||||
// Pattern 1: matrix1 (negative-θ scan, peak at -62°..-3°)
|
// Pattern 1: matrix1 (negative-θ scan, peak at -62°..-3°)
|
||||||
adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::TX);
|
adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::TX);
|
||||||
adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::RX);
|
adarManager.setCustomBeamPattern16(matrix1[beam_pos], ADAR1000Manager::BeamDirection::RX);
|
||||||
HAL_Delay(BEAM_PATTERN_DWELL_MS);
|
waitForFramePulse(FRAME_PULSE_TIMEOUT_MS);
|
||||||
m += m_max/2;
|
m += m_max/2;
|
||||||
|
|
||||||
// Pattern 2: matrix2 (positive-θ scan, peak at +3°..+62°)
|
// Pattern 2: matrix2 (positive-θ scan, peak at +3°..+62°)
|
||||||
adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::TX);
|
adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::TX);
|
||||||
adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::RX);
|
adarManager.setCustomBeamPattern16(matrix2[beam_pos], ADAR1000Manager::BeamDirection::RX);
|
||||||
HAL_Delay(BEAM_PATTERN_DWELL_MS);
|
waitForFramePulse(FRAME_PULSE_TIMEOUT_MS);
|
||||||
m += m_max/2;
|
m += m_max/2;
|
||||||
|
|
||||||
// Reset chirp counter if needed
|
// Reset chirp counter if needed
|
||||||
@@ -1718,6 +1749,17 @@ int main(void)
|
|||||||
DIAG("SYS", "DWT cycle counter initialized, TIM1 started");
|
DIAG("SYS", "DWT cycle counter initialized, TIM1 started");
|
||||||
DIAG("SYS", "HAL tick at init start: %lu ms", (unsigned long)HAL_GetTick());
|
DIAG("SYS", "HAL tick at init start: %lu ms", (unsigned long)HAL_GetTick());
|
||||||
|
|
||||||
|
/* PR-AB.b: production AGC policy is ALWAYS-ON. Bench builds compile this
|
||||||
|
* out via -DMCU_AGC_FORCE_DISABLED so engineers can drive the ADAR VGA
|
||||||
|
* by hand. Default class state is enabled=false (see ADAR1000_AGC ctor),
|
||||||
|
* so we have to flip it here for the production polling loop to do work. */
|
||||||
|
#ifndef MCU_AGC_FORCE_DISABLED
|
||||||
|
outerAgc.enabled = true;
|
||||||
|
DIAG("AGC", "Outer-loop AGC ALWAYS-ON (production policy)");
|
||||||
|
#else
|
||||||
|
DIAG("AGC", "Outer-loop AGC compiled out (MCU_AGC_FORCE_DISABLED bench build)");
|
||||||
|
#endif
|
||||||
|
|
||||||
/* MCU-A4: skip the full 180 s OCXO warmup on warm restart. BKPSRAM
|
/* MCU-A4: skip the full 180 s OCXO warmup on warm restart. BKPSRAM
|
||||||
* survives MCU-only resets (IWDG, SYSRESETREQ, brown-out) so the warmup
|
* survives MCU-only resets (IWDG, SYSRESETREQ, brown-out) so the warmup
|
||||||
* flag from the previous boot tells us the OCXO oven is still hot and
|
* flag from the previous boot tells us the OCXO oven is still hot and
|
||||||
@@ -2582,30 +2624,23 @@ int main(void)
|
|||||||
|
|
||||||
runRadarPulseSequence();
|
runRadarPulseSequence();
|
||||||
|
|
||||||
/* [AGC] Outer-loop AGC: sync enable from FPGA via DIG_6 (PD14),
|
/* [AGC] Outer-loop AGC: read saturation flag (DIG_5 / PD13) once per
|
||||||
* then read saturation flag (DIG_5 / PD13) and adjust ADAR1000 VGA
|
* radar frame and adjust the ADAR1000 VGA common gain. PR-AB.b
|
||||||
* common gain once per radar frame (~258 ms).
|
* repurposed DIG_6 to carry the FPGA frame_pulse, so the runtime
|
||||||
* FPGA register host_agc_enable is the single source of truth —
|
* enable/debounce path is gone — production firmware runs AGC
|
||||||
* DIG_6 propagates it to MCU every frame.
|
* unconditionally (always-on policy). Bench builds compile this body
|
||||||
* 2-frame confirmation debounce: only change outerAgc.enabled when
|
* out by defining MCU_AGC_FORCE_DISABLED, which lets engineers drive
|
||||||
* two consecutive frames read the same DIG_6 value. Prevents a
|
* the ADAR VGA manually via adarManager / GUI gain widgets without
|
||||||
* single-sample glitch from causing a spurious AGC state transition.
|
* the AGC fighting them. The host opcode-0x28 register stays plumbed
|
||||||
* Added latency: 1 extra frame (~258 ms), acceptable for control plane. */
|
* for telemetry display; the MCU just no longer reads it. */
|
||||||
|
#ifndef MCU_AGC_FORCE_DISABLED
|
||||||
{
|
{
|
||||||
bool dig6_now = (HAL_GPIO_ReadPin(FPGA_DIG6_GPIO_Port,
|
|
||||||
FPGA_DIG6_Pin) == GPIO_PIN_SET);
|
|
||||||
static bool dig6_prev = false; // matches boot default (AGC off)
|
|
||||||
if (dig6_now == dig6_prev) {
|
|
||||||
outerAgc.enabled = dig6_now;
|
|
||||||
}
|
|
||||||
dig6_prev = dig6_now;
|
|
||||||
}
|
|
||||||
if (outerAgc.enabled) {
|
|
||||||
bool sat = HAL_GPIO_ReadPin(FPGA_DIG5_SAT_GPIO_Port,
|
bool sat = HAL_GPIO_ReadPin(FPGA_DIG5_SAT_GPIO_Port,
|
||||||
FPGA_DIG5_SAT_Pin) == GPIO_PIN_SET;
|
FPGA_DIG5_SAT_Pin) == GPIO_PIN_SET;
|
||||||
outerAgc.update(sat);
|
outerAgc.update(sat);
|
||||||
outerAgc.applyGain(adarManager);
|
outerAgc.applyGain(adarManager);
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
/* [GAP-3 FIX 2] Kick hardware watchdog — if we don't reach here within
|
/* [GAP-3 FIX 2] Kick hardware watchdog — if we don't reach here within
|
||||||
* ~4 s, the IWDG resets the MCU automatically. */
|
* ~4 s, the IWDG resets the MCU automatically. */
|
||||||
@@ -3143,12 +3178,30 @@ static void MX_GPIO_Init(void)
|
|||||||
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
GPIO_InitStruct.Speed = GPIO_SPEED_FREQ_LOW;
|
||||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
||||||
|
|
||||||
/*Configure GPIO pins : PD13 PD14 PD15 */
|
/*Configure GPIO pins : PD13 PD15 — polled inputs
|
||||||
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_14|GPIO_PIN_15;
|
* PD13 (FPGA_DIG5_SAT): saturation flag for AGC outer loop
|
||||||
|
* PD15 (FPGA_DIG7): control-fault OR, polled by checkSystemHealth */
|
||||||
|
GPIO_InitStruct.Pin = GPIO_PIN_13|GPIO_PIN_15;
|
||||||
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
||||||
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
GPIO_InitStruct.Pull = GPIO_NOPULL;
|
||||||
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
HAL_GPIO_Init(GPIOD, &GPIO_InitStruct);
|
||||||
|
|
||||||
|
/*Configure GPIO pin : PD14 — PR-AB.b FPGA frame_pulse, EXTI rising edge.
|
||||||
|
* Pulldown so a disconnected FPGA can't ring the line and starve the dwell
|
||||||
|
* loop with phantom pulses (the FPGA-side stretcher drives 3.3V CMOS active
|
||||||
|
* high for ~100 ns). NVIC enable below routes EXTI15_10_IRQn to the
|
||||||
|
* handler in stm32f7xx_it.c. */
|
||||||
|
GPIO_InitStruct.Pin = FPGA_FRAME_PULSE_Pin;
|
||||||
|
GPIO_InitStruct.Mode = GPIO_MODE_IT_RISING;
|
||||||
|
GPIO_InitStruct.Pull = GPIO_PULLDOWN;
|
||||||
|
HAL_GPIO_Init(FPGA_FRAME_PULSE_GPIO_Port, &GPIO_InitStruct);
|
||||||
|
|
||||||
|
/* Priority below USB_OTG_FS (0) and SysTick — frame_pulse ISR is short
|
||||||
|
* (one volatile increment) and a couple of ms of jitter is invisible to
|
||||||
|
* the dwell loop, so it doesn't need top priority. */
|
||||||
|
HAL_NVIC_SetPriority(FPGA_FRAME_PULSE_EXTI_IRQn, 5, 0);
|
||||||
|
HAL_NVIC_EnableIRQ(FPGA_FRAME_PULSE_EXTI_IRQn);
|
||||||
|
|
||||||
/*Configure GPIO pins : ADF4382_RX_LKDET_Pin ADF4382_TX_LKDET_Pin */
|
/*Configure GPIO pins : ADF4382_RX_LKDET_Pin ADF4382_TX_LKDET_Pin */
|
||||||
GPIO_InitStruct.Pin = ADF4382_RX_LKDET_Pin|ADF4382_TX_LKDET_Pin;
|
GPIO_InitStruct.Pin = ADF4382_RX_LKDET_Pin|ADF4382_TX_LKDET_Pin;
|
||||||
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
GPIO_InitStruct.Mode = GPIO_MODE_INPUT;
|
||||||
|
|||||||
@@ -142,7 +142,14 @@ void Error_Handler(void);
|
|||||||
#define EN_DIS_COOLING_Pin GPIO_PIN_7
|
#define EN_DIS_COOLING_Pin GPIO_PIN_7
|
||||||
#define EN_DIS_COOLING_GPIO_Port GPIOD
|
#define EN_DIS_COOLING_GPIO_Port GPIOD
|
||||||
|
|
||||||
/* FPGA digital I/O (directly connected GPIOs) */
|
/* FPGA digital I/O (directly connected GPIOs)
|
||||||
|
* DIG_5 (PD13): signal-saturation flag — input, polled by AGC outer loop
|
||||||
|
* (gated by !MCU_AGC_FORCE_DISABLED).
|
||||||
|
* DIG_6 (PD14): chirp_scheduler frame_pulse, ~100 ns FPGA-stretched —
|
||||||
|
* EXTI rising + pulldown; ISR increments g_frame_pulse_count
|
||||||
|
* (PR-AB.b drift-free dwell sync).
|
||||||
|
* DIG_7 (PD15): control-fault OR (range_decim_watchdog | CIC→FIR overrun) —
|
||||||
|
* polled by checkSystemHealth stuck-high sampler. */
|
||||||
#define FPGA_DIG5_SAT_Pin GPIO_PIN_13
|
#define FPGA_DIG5_SAT_Pin GPIO_PIN_13
|
||||||
#define FPGA_DIG5_SAT_GPIO_Port GPIOD
|
#define FPGA_DIG5_SAT_GPIO_Port GPIOD
|
||||||
#define FPGA_DIG6_Pin GPIO_PIN_14
|
#define FPGA_DIG6_Pin GPIO_PIN_14
|
||||||
@@ -150,6 +157,11 @@ void Error_Handler(void);
|
|||||||
#define FPGA_DIG7_Pin GPIO_PIN_15
|
#define FPGA_DIG7_Pin GPIO_PIN_15
|
||||||
#define FPGA_DIG7_GPIO_Port GPIOD
|
#define FPGA_DIG7_GPIO_Port GPIOD
|
||||||
|
|
||||||
|
/* PR-AB.b: alias names that match the post-Phase-3 role of DIG_6. */
|
||||||
|
#define FPGA_FRAME_PULSE_Pin FPGA_DIG6_Pin
|
||||||
|
#define FPGA_FRAME_PULSE_GPIO_Port FPGA_DIG6_GPIO_Port
|
||||||
|
#define FPGA_FRAME_PULSE_EXTI_IRQn EXTI15_10_IRQn
|
||||||
|
|
||||||
#define ADF4382_RX_CE_Pin GPIO_PIN_9
|
#define ADF4382_RX_CE_Pin GPIO_PIN_9
|
||||||
#define ADF4382_RX_CE_GPIO_Port GPIOG
|
#define ADF4382_RX_CE_GPIO_Port GPIOG
|
||||||
#define ADF4382_RX_CS_Pin GPIO_PIN_10
|
#define ADF4382_RX_CS_Pin GPIO_PIN_10
|
||||||
|
|||||||
@@ -50,7 +50,7 @@ static void test_defaults()
|
|||||||
assert(agc.min_gain == 0);
|
assert(agc.min_gain == 0);
|
||||||
assert(agc.max_gain == 127);
|
assert(agc.max_gain == 127);
|
||||||
assert(agc.holdoff_frames == 4);
|
assert(agc.holdoff_frames == 4);
|
||||||
assert(agc.enabled == false); // disabled by default — FPGA DIG_6 is source of truth
|
assert(agc.enabled == false); // class default; main.cpp flips this on under !MCU_AGC_FORCE_DISABLED
|
||||||
assert(agc.holdoff_counter == 0);
|
assert(agc.holdoff_counter == 0);
|
||||||
assert(agc.last_saturated == false);
|
assert(agc.last_saturated == false);
|
||||||
assert(agc.saturation_event_count == 0);
|
assert(agc.saturation_event_count == 0);
|
||||||
|
|||||||
@@ -219,8 +219,8 @@ set_property IOSTANDARD LVCMOS18 [get_ports {stm32_*_1v8}]
|
|||||||
# ============================================================================
|
# ============================================================================
|
||||||
# STM32 CONTROL INTERFACE (DIG bus, Bank 15, VCCO=3.3V)
|
# STM32 CONTROL INTERFACE (DIG bus, Bank 15, VCCO=3.3V)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# DIG_0..DIG_4 are STM32 outputs (PD8-PD12) → FPGA inputs
|
# DIG_0..DIG_4 are STM32 outputs (PD8-PD12) → FPGA inputs (control)
|
||||||
# DIG_5..DIG_7 are STM32 inputs (PD13-PD15) ← FPGA outputs (unused in RTL)
|
# DIG_5..DIG_7 are STM32 inputs (PD13-PD15) ← FPGA outputs (status / sync)
|
||||||
|
|
||||||
set_property PACKAGE_PIN F13 [get_ports {stm32_new_chirp}] ;# DIG_0 (PD8)
|
set_property PACKAGE_PIN F13 [get_ports {stm32_new_chirp}] ;# DIG_0 (PD8)
|
||||||
set_property PACKAGE_PIN E16 [get_ports {stm32_new_elevation}] ;# DIG_1 (PD9)
|
set_property PACKAGE_PIN E16 [get_ports {stm32_new_elevation}] ;# DIG_1 (PD9)
|
||||||
@@ -230,10 +230,13 @@ set_property IOSTANDARD LVCMOS33 [get_ports {stm32_new_*}]
|
|||||||
set_property IOSTANDARD LVCMOS33 [get_ports {stm32_mixers_enable}]
|
set_property IOSTANDARD LVCMOS33 [get_ports {stm32_mixers_enable}]
|
||||||
# reset_n is DIG_4 (PD12) — constrained above in the RESET section
|
# reset_n is DIG_4 (PD12) — constrained above in the RESET section
|
||||||
|
|
||||||
# DIG_5 = H11, DIG_6 = G12, DIG_7 = H12 — FPGA→STM32 status outputs
|
# DIG_5 = H11, DIG_6 = G12, DIG_7 = H12 — FPGA→STM32 status / sync outputs
|
||||||
# DIG_5: AGC saturation flag (PD13 on STM32)
|
# DIG_5 (PD13): signal-chain saturation flag — drives MCU outer-loop AGC.
|
||||||
# DIG_6: AGC enable flag (PD14) — mirrors FPGA host_agc_enable to STM32
|
# DIG_6 (PD14): stretched chirp_scheduler frame_pulse (~100 ns) — PR-AB.b
|
||||||
# DIG_7: reserved (PD15)
|
# STM32 EXTI rising edge for drift-free dwell sync.
|
||||||
|
# DIG_7 (PD15): control-chain fault OR — F-6.4 range_decim_watchdog
|
||||||
|
# | F-1.2 CIC→FIR CDC overrun. MCU PD15 stuck-high sampler
|
||||||
|
# triggers attemptErrorRecovery(ERROR_FPGA_DSP_STALL).
|
||||||
set_property PACKAGE_PIN H11 [get_ports {gpio_dig5}]
|
set_property PACKAGE_PIN H11 [get_ports {gpio_dig5}]
|
||||||
set_property PACKAGE_PIN G12 [get_ports {gpio_dig6}]
|
set_property PACKAGE_PIN G12 [get_ports {gpio_dig6}]
|
||||||
set_property PACKAGE_PIN H12 [get_ports {gpio_dig7}]
|
set_property PACKAGE_PIN H12 [get_ports {gpio_dig7}]
|
||||||
|
|||||||
@@ -135,8 +135,8 @@ module radar_system_top (
|
|||||||
// FPGA→STM32 GPIO outputs (DIG_5..DIG_7 on 50T board)
|
// FPGA→STM32 GPIO outputs (DIG_5..DIG_7 on 50T board)
|
||||||
// Used by STM32 outer AGC loop to read saturation state without USB polling.
|
// Used by STM32 outer AGC loop to read saturation state without USB polling.
|
||||||
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag (1=clipping detected)
|
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag (1=clipping detected)
|
||||||
output wire gpio_dig6, // DIG_6 (G12→PD14): AGC enable flag (mirrors host_agc_enable)
|
output wire gpio_dig6, // DIG_6 (G12→PD14): stretched chirp_scheduler frame_pulse (~100 ns) for STM32 dwell sync
|
||||||
output wire gpio_dig7 // DIG_7 (H12→PD15): reserved (tied low)
|
output wire gpio_dig7 // DIG_7 (H12→PD15): control-fault OR (F-6.4 watchdog | F-1.2 CDC overrun)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -227,10 +227,29 @@ wire rx_ddc_overflow_any;
|
|||||||
wire [2:0] rx_ddc_saturation_count;
|
wire [2:0] rx_ddc_saturation_count;
|
||||||
// MTI saturation count (audit F-6.3). OR'd into gpio_dig5 for MCU visibility.
|
// MTI saturation count (audit F-6.3). OR'd into gpio_dig5 for MCU visibility.
|
||||||
wire [7:0] rx_mti_saturation_count;
|
wire [7:0] rx_mti_saturation_count;
|
||||||
// Range-bin decimator watchdog (audit F-6.4). High = decimator stalled.
|
// Range-bin decimator watchdog (audit F-6.4). 1-cycle pulse from
|
||||||
|
// range_bin_decimator (~10 ns @ 100 MHz). Drives gpio_dig7 directly so
|
||||||
|
// the MCU can see the raw fault edge.
|
||||||
wire rx_range_decim_watchdog;
|
wire rx_range_decim_watchdog;
|
||||||
|
// PR-AB.b Step 1: sticky latch over the watchdog pulse so a slow host
|
||||||
|
// status poll never misses the event. The 100 MHz → ft_clk synchronizer
|
||||||
|
// inside usb_data_interface_ft2232h cannot reliably capture a 10 ns
|
||||||
|
// pulse, so we register the level here in the source (clk) domain and
|
||||||
|
// feed the level — not the pulse — into status_words[5][5]. Cleared
|
||||||
|
// only by reset_n: the FPGA exposes no host-driven clear opcode today.
|
||||||
|
// See project_aeris10_clear_monitors_opcode_future.md for the bundled-PR
|
||||||
|
// shape that would activate a host clear here and across the DDC + AD9484
|
||||||
|
// overrange stickies in radar_receiver_final.v.
|
||||||
|
reg rx_range_decim_watchdog_sticky;
|
||||||
|
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||||
|
if (!sys_reset_n)
|
||||||
|
rx_range_decim_watchdog_sticky <= 1'b0;
|
||||||
|
else if (rx_range_decim_watchdog)
|
||||||
|
rx_range_decim_watchdog_sticky <= 1'b1;
|
||||||
|
end
|
||||||
// CIC→FIR CDC overrun sticky (audit F-1.2). High = at least one baseband
|
// CIC→FIR CDC overrun sticky (audit F-1.2). High = at least one baseband
|
||||||
// sample has been silently dropped between the 400 MHz CIC and 100 MHz FIR.
|
// sample has been silently dropped between the 400 MHz CIC and 100 MHz FIR.
|
||||||
|
// Already sticky in source (ddc_400m.v:697-702), no extra latch needed.
|
||||||
wire rx_ddc_cic_fir_overrun;
|
wire rx_ddc_cic_fir_overrun;
|
||||||
|
|
||||||
// Data packing for USB
|
// Data packing for USB
|
||||||
@@ -906,8 +925,11 @@ if (USB_MODE == 0) begin : gen_ft601
|
|||||||
.status_agc_enable(host_agc_enable),
|
.status_agc_enable(host_agc_enable),
|
||||||
|
|
||||||
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
|
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
|
||||||
// for host-side observability (paired with gpio_dig7 split)
|
// for host-side observability (paired with gpio_dig7 split).
|
||||||
.status_range_decim_watchdog(rx_range_decim_watchdog),
|
// PR-AB.b Step 1: feed the sticky version of the F-6.4 watchdog
|
||||||
|
// (level signal) so the ft_clk synchronizer cannot miss a 10 ns
|
||||||
|
// source-domain pulse. F-1.2 overrun is already sticky in source.
|
||||||
|
.status_range_decim_watchdog(rx_range_decim_watchdog_sticky),
|
||||||
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
|
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
|
||||||
);
|
);
|
||||||
|
|
||||||
@@ -996,8 +1018,11 @@ end else begin : gen_ft2232h
|
|||||||
.status_agc_enable(host_agc_enable),
|
.status_agc_enable(host_agc_enable),
|
||||||
|
|
||||||
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
|
// AUDIT-S10: control-fault flags exposed in status_words[5][6:5]
|
||||||
// for host-side observability (paired with gpio_dig7 split)
|
// for host-side observability (paired with gpio_dig7 split).
|
||||||
.status_range_decim_watchdog(rx_range_decim_watchdog),
|
// PR-AB.b Step 1: feed the sticky version of the F-6.4 watchdog
|
||||||
|
// (level signal) so the ft_clk synchronizer cannot miss a 10 ns
|
||||||
|
// source-domain pulse. F-1.2 overrun is already sticky in source.
|
||||||
|
.status_range_decim_watchdog(rx_range_decim_watchdog_sticky),
|
||||||
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun),
|
.status_ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun),
|
||||||
|
|
||||||
// PR-G: 2-tier CFAR telemetry (status_words[6])
|
// PR-G: 2-tier CFAR telemetry (status_words[6])
|
||||||
@@ -1238,7 +1263,7 @@ end
|
|||||||
assign system_status = status_reg;
|
assign system_status = status_reg;
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// FPGA→STM32 GPIO OUTPUTS (DIG_5, DIG_6, DIG_7) — AUDIT-S10 SPLIT
|
// FPGA→STM32 GPIO OUTPUTS (DIG_5, DIG_6, DIG_7) — AUDIT-S10 SPLIT + PR-AB.b
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags (signal-saturation
|
// AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags (signal-saturation
|
||||||
// AND control-faults), so the MCU outer-loop AGC could not distinguish
|
// AND control-faults), so the MCU outer-loop AGC could not distinguish
|
||||||
@@ -1251,19 +1276,49 @@ assign system_status = status_reg;
|
|||||||
// usb_data_interface[*_ft2232h].v so the host can graph each fault class
|
// usb_data_interface[*_ft2232h].v so the host can graph each fault class
|
||||||
// regardless of MCU consumption.
|
// regardless of MCU consumption.
|
||||||
//
|
//
|
||||||
|
// PR-AB.b: gpio_dig6 is reassigned from a host_agc_enable mirror to the
|
||||||
|
// chirp_scheduler frame_pulse, stretched to ~100 ns for clean STM32 EXTI
|
||||||
|
// capture on PD14. The STM32 firmware swaps HAL_Delay(BEAM_PATTERN_DWELL_MS)
|
||||||
|
// for an EXTI-driven semaphore acquire so per-pattern dwell tracks the
|
||||||
|
// actual ladder length — drift-free, mask-aware (a 2-subframe 3 km variant
|
||||||
|
// runs at the shorter cadence automatically because chirp_scheduler.frame_pulse
|
||||||
|
// fires at the wrap of whatever subframes are enabled). Outer-loop AGC moves
|
||||||
|
// to always-on in production firmware (saturation handling shouldn't be
|
||||||
|
// optional); host_agc_enable register stays for status display + bench build
|
||||||
|
// flag MCU_AGC_FORCE_DISABLED keeps a manual-gain path for ADAR1000
|
||||||
|
// calibration / level checks / RX gain sweeps.
|
||||||
|
//
|
||||||
// DIG_5 (PD13): Signal-chain saturation — outer-loop AGC reduces RF gain.
|
// DIG_5 (PD13): Signal-chain saturation — outer-loop AGC reduces RF gain.
|
||||||
// Asserts on AGC clipping, DDC mixer/filter overflow, or MTI
|
// Asserts on AGC clipping, DDC mixer/filter overflow, or MTI
|
||||||
// 2-pulse saturation (audit F-6.1/F-6.3).
|
// 2-pulse saturation (audit F-6.1/F-6.3).
|
||||||
// DIG_6 (PD14): AGC enable mirror (host_agc_enable) — single source of truth.
|
// DIG_6 (PD14): Stretched chirp_scheduler frame_pulse for STM32 dwell sync.
|
||||||
|
// 1-cycle source pulse @ 100 MHz, stretched to 10 cycles
|
||||||
|
// (100 ns). Period tracks the enabled-subframe ladder
|
||||||
|
// (8.05 ms full 3-PRI, 5.38 ms SHORT+MEDIUM, 2.80 ms SHORT-only).
|
||||||
// DIG_7 (PD15): Control-chain fault — MCU should log + consider FPGA reset.
|
// DIG_7 (PD15): Control-chain fault — MCU should log + consider FPGA reset.
|
||||||
// Asserts on range-decimator watchdog (audit F-6.4) or CIC→FIR
|
// Asserts on range-decimator watchdog (audit F-6.4) or CIC→FIR
|
||||||
// CDC overrun (audit F-1.2). MCU consumption is a tracked
|
// CDC overrun (audit F-1.2). PD15 stuck-high sampler in MCU
|
||||||
// follow-up; until then the host telemetry path covers it.
|
// main loop fires attemptErrorRecovery(ERROR_FPGA_DSP_STALL).
|
||||||
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0)
|
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0)
|
||||||
| rx_ddc_overflow_any
|
| rx_ddc_overflow_any
|
||||||
| (rx_ddc_saturation_count != 3'd0)
|
| (rx_ddc_saturation_count != 3'd0)
|
||||||
| (rx_mti_saturation_count != 8'd0);
|
| (rx_mti_saturation_count != 8'd0);
|
||||||
assign gpio_dig6 = host_agc_enable;
|
|
||||||
|
// PR-AB.b: 10-cycle stretcher on sched_frame_pulse. STM32 EXTI on PD14 needs
|
||||||
|
// ≥2 APB clocks (~12 ns @ 168 MHz APB) to latch the rising edge; 100 ns gives
|
||||||
|
// generous margin while staying well below the shortest dwell (2.80 ms =
|
||||||
|
// SHORT-only) so back-to-back pulses never overlap.
|
||||||
|
reg [3:0] frame_pulse_stretch_count;
|
||||||
|
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||||
|
if (!sys_reset_n)
|
||||||
|
frame_pulse_stretch_count <= 4'd0;
|
||||||
|
else if (sched_frame_pulse)
|
||||||
|
frame_pulse_stretch_count <= 4'd10; // 10 cycles = 100 ns @ 100 MHz
|
||||||
|
else if (frame_pulse_stretch_count != 4'd0)
|
||||||
|
frame_pulse_stretch_count <= frame_pulse_stretch_count - 4'd1;
|
||||||
|
end
|
||||||
|
assign gpio_dig6 = (frame_pulse_stretch_count != 4'd0);
|
||||||
|
|
||||||
assign gpio_dig7 = rx_range_decim_watchdog // audit F-6.4
|
assign gpio_dig7 = rx_range_decim_watchdog // audit F-6.4
|
||||||
| rx_ddc_cic_fir_overrun; // audit F-1.2
|
| rx_ddc_cic_fir_overrun; // audit F-1.2
|
||||||
|
|
||||||
|
|||||||
@@ -84,8 +84,8 @@ module radar_system_top_50t (
|
|||||||
|
|
||||||
// ===== FPGA→STM32 GPIO (Bank 15: 3.3V) =====
|
// ===== FPGA→STM32 GPIO (Bank 15: 3.3V) =====
|
||||||
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag
|
output wire gpio_dig5, // DIG_5 (H11→PD13): AGC saturation flag
|
||||||
output wire gpio_dig6, // DIG_6 (G12→PD14): reserved
|
output wire gpio_dig6, // DIG_6 (G12→PD14): stretched chirp_scheduler frame_pulse (PR-AB.b dwell sync)
|
||||||
output wire gpio_dig7 // DIG_7 (H12→PD15): reserved
|
output wire gpio_dig7 // DIG_7 (H12→PD15): control-fault OR (F-6.4 watchdog | F-1.2 CDC overrun)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
|
||||||
|
|||||||
@@ -566,9 +566,13 @@ run_test "ADC PWDN opcode 0x32 (AUDIT-S25)" \
|
|||||||
tb/tb_adc_pwdn_opcode.vvp \
|
tb/tb_adc_pwdn_opcode.vvp \
|
||||||
tb/tb_adc_pwdn_opcode.v
|
tb/tb_adc_pwdn_opcode.v
|
||||||
|
|
||||||
run_test "GPIO dig5/dig7 split (AUDIT-S10)" \
|
run_test "Status-word stickies CDC + DIG7 fault-OR (AUDIT-S10 + PR-AB.b Step 1)" \
|
||||||
tb/tb_audit_s10_gpio_split.vvp \
|
tb/tb_status_words_stickies.vvp \
|
||||||
tb/tb_audit_s10_gpio_split.v
|
tb/tb_status_words_stickies.v
|
||||||
|
|
||||||
|
run_test "DIG6 frame-pulse stretcher (PR-AB.b)" \
|
||||||
|
tb/tb_dig6_frame_pulse.vvp \
|
||||||
|
tb/tb_dig6_frame_pulse.v
|
||||||
|
|
||||||
run_test "NUM_CELLS sizing 50T (AUDIT-C16)" \
|
run_test "NUM_CELLS sizing 50T (AUDIT-C16)" \
|
||||||
tb/tb_audit_c16_num_cells_50t.vvp \
|
tb/tb_audit_c16_num_cells_50t.vvp \
|
||||||
|
|||||||
@@ -0,0 +1,205 @@
|
|||||||
|
// ============================================================================
|
||||||
|
// tb_dig6_frame_pulse.v
|
||||||
|
//
|
||||||
|
// PR-AB.b: gpio_dig6 (PD14) carries the chirp_scheduler frame_pulse, stretched
|
||||||
|
// to ~100 ns so the STM32 EXTI on PD14 can latch the rising edge reliably.
|
||||||
|
// The MCU dwell loop (runRadarPulseSequence) replaces
|
||||||
|
// HAL_Delay(BEAM_PATTERN_DWELL_MS) with osSemaphoreAcquire so per-pattern
|
||||||
|
// dwell tracks the actual ladder length — drift-free, mask-aware.
|
||||||
|
//
|
||||||
|
// Companion to tb_status_words_stickies.v which covers the gpio_dig7 fault-OR
|
||||||
|
// (watchdog | CDC overrun) semantic and the status_words[5][6:5] CDC packing.
|
||||||
|
//
|
||||||
|
// This TB mirrors the production stretcher fragment from radar_system_top.v
|
||||||
|
// and asserts:
|
||||||
|
//
|
||||||
|
// T1 Reset → count=0, dig6=0
|
||||||
|
// T2 Single 1-cycle pulse → dig6 high for exactly 10 cycles, low on 11
|
||||||
|
// T3 Pulse during stretch → counter reloads to 10 (longer total high time)
|
||||||
|
// T4 Two pulses spaced > 10 cycles → two clean rising edges
|
||||||
|
// T5 Pulse asserted continuously for many cycles → counter pinned high
|
||||||
|
// T6 No pulse activity → dig6 stays low forever
|
||||||
|
// T7 Reset mid-stretch → counter and dig6 drop immediately
|
||||||
|
// ============================================================================
|
||||||
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
|
module tb_dig6_frame_pulse;
|
||||||
|
|
||||||
|
reg clk_100m = 1'b0;
|
||||||
|
reg sys_reset_n = 1'b0;
|
||||||
|
reg frame_pulse = 1'b0;
|
||||||
|
wire dig6;
|
||||||
|
|
||||||
|
frame_pulse_stretcher_block dut (
|
||||||
|
.clk_100m_buf (clk_100m),
|
||||||
|
.sys_reset_n (sys_reset_n),
|
||||||
|
.frame_pulse_in(frame_pulse),
|
||||||
|
.gpio_dig6 (dig6)
|
||||||
|
);
|
||||||
|
|
||||||
|
// 100 MHz clock (10 ns period)
|
||||||
|
always #5 clk_100m = ~clk_100m;
|
||||||
|
|
||||||
|
integer pass = 0;
|
||||||
|
integer fail = 0;
|
||||||
|
|
||||||
|
task check (input [127:0] label, input expected);
|
||||||
|
begin
|
||||||
|
#1;
|
||||||
|
if (dig6 === expected) begin
|
||||||
|
$display(" [PASS] %0s: dig6=%b", label, dig6);
|
||||||
|
pass = pass + 1;
|
||||||
|
end else begin
|
||||||
|
$display(" [FAIL] %0s: dig6=%b (exp %b)", label, dig6, expected);
|
||||||
|
fail = fail + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
task fire_pulse;
|
||||||
|
begin
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b1;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b0;
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
|
integer i;
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
$display("============================================================");
|
||||||
|
$display("PR-AB.b: gpio_dig6 = stretched(frame_pulse)");
|
||||||
|
$display("============================================================");
|
||||||
|
|
||||||
|
// ---- T1: reset state ----
|
||||||
|
sys_reset_n = 1'b0;
|
||||||
|
repeat (4) @(posedge clk_100m);
|
||||||
|
check("T1 reset asserted, dig6 low", 1'b0);
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
sys_reset_n = 1'b1;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
check("T1b after deassert, dig6 still low", 1'b0);
|
||||||
|
|
||||||
|
// ---- T2: single pulse → exactly 10 cycles high ----
|
||||||
|
fire_pulse();
|
||||||
|
// After fire_pulse, frame_pulse was high for 1 clk; the always block
|
||||||
|
// sampled it on that posedge and loaded count=10. dig6 is high while
|
||||||
|
// count != 0. Count decrements each cycle: 10,9,...,1,0. So dig6 is
|
||||||
|
// high for 10 posedges starting on the cycle the load took effect.
|
||||||
|
for (i = 0; i < 10; i = i + 1) begin
|
||||||
|
#1;
|
||||||
|
if (dig6 !== 1'b1) begin
|
||||||
|
$display(" [FAIL] T2 cycle %0d: dig6=%b (exp 1)", i, dig6);
|
||||||
|
fail = fail + 1;
|
||||||
|
end
|
||||||
|
@(posedge clk_100m);
|
||||||
|
end
|
||||||
|
// 11th cycle: count has wrapped to 0, dig6 should be low.
|
||||||
|
#1;
|
||||||
|
check("T2 dig6 low on 11th cycle", 1'b0);
|
||||||
|
// Make sure it stays low.
|
||||||
|
repeat (5) @(posedge clk_100m);
|
||||||
|
check("T2 dig6 stays low after stretch", 1'b0);
|
||||||
|
|
||||||
|
// ---- T3: pulse during stretch reloads counter ----
|
||||||
|
fire_pulse();
|
||||||
|
@(posedge clk_100m); #1; // count = 9
|
||||||
|
@(posedge clk_100m); #1; // count = 8
|
||||||
|
@(posedge clk_100m); #1; // count = 7
|
||||||
|
// Reload by firing another pulse mid-stretch.
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b1;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b0;
|
||||||
|
// Counter is now reloaded to 10. dig6 stays high for another 10 cycles.
|
||||||
|
for (i = 0; i < 10; i = i + 1) begin
|
||||||
|
#1;
|
||||||
|
if (dig6 !== 1'b1) begin
|
||||||
|
$display(" [FAIL] T3 reload cycle %0d: dig6=%b (exp 1)", i, dig6);
|
||||||
|
fail = fail + 1;
|
||||||
|
end
|
||||||
|
@(posedge clk_100m);
|
||||||
|
end
|
||||||
|
#1;
|
||||||
|
check("T3 dig6 low after reloaded stretch", 1'b0);
|
||||||
|
|
||||||
|
// ---- T4: spaced pulses produce two distinct rising edges ----
|
||||||
|
fire_pulse();
|
||||||
|
repeat (15) @(posedge clk_100m); // > 10 cycles, dig6 must be 0
|
||||||
|
check("T4a dig6 low between pulses", 1'b0);
|
||||||
|
fire_pulse();
|
||||||
|
#1;
|
||||||
|
check("T4b dig6 high after second pulse", 1'b1);
|
||||||
|
repeat (10) @(posedge clk_100m);
|
||||||
|
#1;
|
||||||
|
check("T4c dig6 low after second stretch", 1'b0);
|
||||||
|
|
||||||
|
// ---- T5: continuous pulse pins counter at 10 ----
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b1;
|
||||||
|
repeat (50) @(posedge clk_100m);
|
||||||
|
#1;
|
||||||
|
check("T5 dig6 high under continuous pulse", 1'b1);
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
frame_pulse = 1'b0;
|
||||||
|
// After deassert, dig6 should drain over 10 cycles.
|
||||||
|
repeat (10) @(posedge clk_100m);
|
||||||
|
#1;
|
||||||
|
check("T5 dig6 drains after pulse deassert", 1'b0);
|
||||||
|
|
||||||
|
// ---- T6: idle line stays low ----
|
||||||
|
repeat (200) @(posedge clk_100m);
|
||||||
|
check("T6 dig6 idle stays low", 1'b0);
|
||||||
|
|
||||||
|
// ---- T7: reset mid-stretch drops dig6 immediately ----
|
||||||
|
fire_pulse();
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
check("T7a dig6 high pre-reset", 1'b1);
|
||||||
|
sys_reset_n = 1'b0;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
check("T7b reset drops dig6", 1'b0);
|
||||||
|
sys_reset_n = 1'b1;
|
||||||
|
@(posedge clk_100m); #1;
|
||||||
|
|
||||||
|
$display("============================================================");
|
||||||
|
$display("DIG6 FRAME PULSE RESULTS: pass=%0d fail=%0d", pass, fail);
|
||||||
|
$display("============================================================");
|
||||||
|
if (fail == 0) $display("[OVERALL] PASS");
|
||||||
|
else $display("[OVERALL] FAIL");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
initial begin
|
||||||
|
#1_000_000;
|
||||||
|
$display("[FATAL] timeout");
|
||||||
|
$finish;
|
||||||
|
end
|
||||||
|
|
||||||
|
endmodule
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// frame_pulse_stretcher_block — mirrors the production stretcher in
|
||||||
|
// radar_system_top.v (PR-AB.b). 1-cycle frame_pulse_in loads count=10;
|
||||||
|
// counter decrements each clock; gpio_dig6 = (count != 0). reset_n async
|
||||||
|
// clear. Reloads on subsequent pulses. Result: dig6 high for 10 clk_100m
|
||||||
|
// cycles = 100 ns starting on the cycle after the input pulse is sampled.
|
||||||
|
// ============================================================================
|
||||||
|
module frame_pulse_stretcher_block (
|
||||||
|
input wire clk_100m_buf,
|
||||||
|
input wire sys_reset_n,
|
||||||
|
input wire frame_pulse_in,
|
||||||
|
output wire gpio_dig6
|
||||||
|
);
|
||||||
|
reg [3:0] frame_pulse_stretch_count;
|
||||||
|
always @(posedge clk_100m_buf or negedge sys_reset_n) begin
|
||||||
|
if (!sys_reset_n)
|
||||||
|
frame_pulse_stretch_count <= 4'd0;
|
||||||
|
else if (frame_pulse_in)
|
||||||
|
frame_pulse_stretch_count <= 4'd10;
|
||||||
|
else if (frame_pulse_stretch_count != 4'd0)
|
||||||
|
frame_pulse_stretch_count <= frame_pulse_stretch_count - 4'd1;
|
||||||
|
end
|
||||||
|
assign gpio_dig6 = (frame_pulse_stretch_count != 4'd0);
|
||||||
|
endmodule
|
||||||
+85
-171
@@ -1,68 +1,35 @@
|
|||||||
// ============================================================================
|
// ============================================================================
|
||||||
// tb_audit_s10_gpio_split.v
|
// tb_status_words_stickies.v
|
||||||
//
|
//
|
||||||
// AUDIT-S10: gpio_dig5 previously OR'd six unrelated flags — four signal-
|
// AUDIT-S10 + PR-AB.b: status_words[5][6:5] CDC packing for the two
|
||||||
// saturation classes (AGC, DDC overflow, DDC saturation, MTI saturation) and
|
// control-fault flags (range-decimator watchdog F-6.4, CIC->FIR CDC overrun
|
||||||
// two control-fault classes (range-decimator watchdog, CIC->FIR CDC overrun)
|
// F-1.2) AND the gpio_dig7 fault-OR semantic. PR-AB.b Step 1 made the F-6.4
|
||||||
// — into the single MCU-visible bit at PD13. The MCU outer-loop AGC reduces
|
// half sticky in the source clock domain so a slow host poll cannot miss
|
||||||
// RF gain on PD13 assertion, which is the wrong response to a watchdog or
|
// the event; F-1.2 is already sticky inside ddc_400m.v. PR-AB.b drives
|
||||||
// CDC stall. gpio_dig7 (PD15) was tied 1'b0 (reserved).
|
// gpio_dig7 = (watchdog | cic_fir_overrun) so the MCU stuck-high sampler
|
||||||
|
// (main.cpp:880-1000) can fire attemptErrorRecovery(ERROR_FPGA_DSP_STALL).
|
||||||
|
// status_words[5][7] is reserved (must stay 0).
|
||||||
//
|
//
|
||||||
// Fix: split the OR-network so gpio_dig5 carries only signal-saturation flags
|
// Replaces the GROUP B tests in the retired tb_audit_s10_gpio_split.v and
|
||||||
// (AGC continues to react correctly) and gpio_dig7 carries control-fault
|
// also restores the GROUP A dig7 fault-OR coverage (the dig5 saturation
|
||||||
// flags (MCU follow-up will log + reset; until then host telemetry covers).
|
// portion of GROUP A lives in the radar_receiver_final TBs). gpio_dig6
|
||||||
// Status words[5][6:5] expose the two control-fault classes so host-side
|
// stretcher (chirp_scheduler frame_pulse) coverage is in
|
||||||
// can graph them regardless of MCU consumption.
|
// tb_dig6_frame_pulse.v.
|
||||||
//
|
//
|
||||||
// This TB mirrors the production fragments from radar_system_top.v and
|
// T1 Reset state -> sync regs 0, status[6:5]=0, dig7=0
|
||||||
// usb_data_interface[*_ft2232h].v and asserts:
|
// T2 Watchdog asserted -> after 2 ft_clk edges, status[5]=1, dig7=1
|
||||||
//
|
// T3 CIC overrun asserted -> after 2 ft_clk edges, status[6]=1, dig7=1
|
||||||
// GROUP A GPIO split (combinational)
|
// T4a Both asserted -> status[6:5]=11, dig7=1
|
||||||
// T1 All inputs 0 -> dig5=0, dig7=0
|
// T4b Both cleared -> status[6:5]=00, dig7=0
|
||||||
// T2 Each signal-sat input -> dig5=1, dig7=0 (no cross-route to dig7)
|
// T5 status_words[5][7] stays 0 (reserved bit not stomped by sync); dig7=1
|
||||||
// T3 Each control-fault -> dig5=0, dig7=1 (no cross-route to dig5)
|
// T6 status_words[5][4:0] (self_test_flags) pass through; dig7=0
|
||||||
// T4 Mixed sat + fault -> dig5=1, dig7=1 (independent)
|
|
||||||
// T5 AGC count >0 (boundary) -> dig5=1
|
|
||||||
// T6 DDC count =0 (boundary) -> dig5=0
|
|
||||||
//
|
|
||||||
// GROUP B Status-word CDC packing (sequential)
|
|
||||||
// T7 Reset state -> sync regs 0, status_word_bits 0
|
|
||||||
// T8 Watchdog asserted in src -> after 2 ft_clk edges, status[5]=1
|
|
||||||
// T9 CIC overrun asserted in src -> after 2 ft_clk edges, status[6]=1
|
|
||||||
// T10 Both asserted, both cleared -> status[6:5] tracks (sticky in src;
|
|
||||||
// TB drives explicit clears)
|
|
||||||
// T11 status_words[5][7] stays 0 (reserved bit, not stomped by sync)
|
|
||||||
// T12 status_words[5][4:0] (self_test_flags) stays = src input
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
`timescale 1ns/1ps
|
`timescale 1ns/1ps
|
||||||
|
|
||||||
module tb_audit_s10_gpio_split;
|
module tb_status_words_stickies;
|
||||||
|
|
||||||
// ===== GROUP A: GPIO split inputs/outputs =====
|
|
||||||
reg [7:0] agc_saturation_count;
|
|
||||||
reg ddc_overflow_any;
|
|
||||||
reg [2:0] ddc_saturation_count;
|
|
||||||
reg [7:0] mti_saturation_count;
|
|
||||||
reg range_decim_watchdog;
|
|
||||||
reg ddc_cic_fir_overrun;
|
|
||||||
|
|
||||||
wire dig5;
|
|
||||||
wire dig7;
|
|
||||||
|
|
||||||
gpio_split_block gpio_dut (
|
|
||||||
.agc_saturation_count (agc_saturation_count),
|
|
||||||
.ddc_overflow_any (ddc_overflow_any),
|
|
||||||
.ddc_saturation_count (ddc_saturation_count),
|
|
||||||
.mti_saturation_count (mti_saturation_count),
|
|
||||||
.range_decim_watchdog (range_decim_watchdog),
|
|
||||||
.ddc_cic_fir_overrun (ddc_cic_fir_overrun),
|
|
||||||
.gpio_dig5 (dig5),
|
|
||||||
.gpio_dig7 (dig7)
|
|
||||||
);
|
|
||||||
|
|
||||||
// ===== GROUP B: status-word CDC packing =====
|
|
||||||
reg clk_src = 1'b0; // 100 MHz radar domain
|
reg clk_src = 1'b0; // 100 MHz radar domain
|
||||||
reg ft_clk = 1'b0; // 60/100 MHz USB domain
|
reg ft_clk = 1'b0; // 60 MHz USB domain
|
||||||
reg reset_n = 1'b0;
|
reg reset_n = 1'b0;
|
||||||
reg src_watchdog;
|
reg src_watchdog;
|
||||||
reg src_overrun;
|
reg src_overrun;
|
||||||
@@ -70,6 +37,7 @@ module tb_audit_s10_gpio_split;
|
|||||||
reg status_req_pulse;
|
reg status_req_pulse;
|
||||||
|
|
||||||
wire [31:0] status_word_5;
|
wire [31:0] status_word_5;
|
||||||
|
wire gpio_dig7; // PR-AB.b: production OR of fault flags
|
||||||
|
|
||||||
status_packing_block status_dut (
|
status_packing_block status_dut (
|
||||||
.clk (clk_src),
|
.clk (clk_src),
|
||||||
@@ -82,29 +50,20 @@ module tb_audit_s10_gpio_split;
|
|||||||
.status_word_5 (status_word_5)
|
.status_word_5 (status_word_5)
|
||||||
);
|
);
|
||||||
|
|
||||||
// 100 MHz src clock
|
// Mirrors production combinational OR in radar_system_top.v:
|
||||||
|
// assign gpio_dig7 = rx_range_decim_watchdog | rx_ddc_cic_fir_overrun;
|
||||||
|
gpio_dig7_or_block dig7_dut (
|
||||||
|
.watchdog (src_watchdog),
|
||||||
|
.overrun (src_overrun),
|
||||||
|
.gpio_dig7(gpio_dig7)
|
||||||
|
);
|
||||||
|
|
||||||
always #5 clk_src = ~clk_src;
|
always #5 clk_src = ~clk_src;
|
||||||
// 60 MHz ft_clk (~16.67 ns)
|
|
||||||
always #8 ft_clk = ~ft_clk;
|
always #8 ft_clk = ~ft_clk;
|
||||||
|
|
||||||
// ----- bookkeeping -----
|
|
||||||
integer pass = 0;
|
integer pass = 0;
|
||||||
integer fail = 0;
|
integer fail = 0;
|
||||||
|
|
||||||
task check_dig (input [127:0] label, input expected_dig5, input expected_dig7);
|
|
||||||
begin
|
|
||||||
#1; // settle combinational
|
|
||||||
if (dig5 === expected_dig5 && dig7 === expected_dig7) begin
|
|
||||||
$display(" [PASS] %0s: dig5=%b dig7=%b", label, dig5, dig7);
|
|
||||||
pass = pass + 1;
|
|
||||||
end else begin
|
|
||||||
$display(" [FAIL] %0s: dig5=%b (exp %b) dig7=%b (exp %b)",
|
|
||||||
label, dig5, expected_dig5, dig7, expected_dig7);
|
|
||||||
fail = fail + 1;
|
|
||||||
end
|
|
||||||
end
|
|
||||||
endtask
|
|
||||||
|
|
||||||
task check_status (input [127:0] label, input [31:0] mask, input [31:0] expected);
|
task check_status (input [127:0] label, input [31:0] mask, input [31:0] expected);
|
||||||
begin
|
begin
|
||||||
if ((status_word_5 & mask) === (expected & mask)) begin
|
if ((status_word_5 & mask) === (expected & mask)) begin
|
||||||
@@ -119,155 +78,117 @@ module tb_audit_s10_gpio_split;
|
|||||||
end
|
end
|
||||||
endtask
|
endtask
|
||||||
|
|
||||||
|
task check_dig7 (input [127:0] label, input expected);
|
||||||
|
begin
|
||||||
|
if (gpio_dig7 === expected) begin
|
||||||
|
$display(" [PASS] %0s: dig7=%b", label, gpio_dig7);
|
||||||
|
pass = pass + 1;
|
||||||
|
end else begin
|
||||||
|
$display(" [FAIL] %0s: dig7=%b (exp %b)", label, gpio_dig7, expected);
|
||||||
|
fail = fail + 1;
|
||||||
|
end
|
||||||
|
end
|
||||||
|
endtask
|
||||||
|
|
||||||
task pulse_status_req;
|
task pulse_status_req;
|
||||||
begin
|
begin
|
||||||
@(posedge ft_clk); #1;
|
@(posedge ft_clk); #1;
|
||||||
status_req_pulse = 1'b1;
|
status_req_pulse = 1'b1;
|
||||||
@(posedge ft_clk); #1;
|
@(posedge ft_clk); #1;
|
||||||
status_req_pulse = 1'b0;
|
status_req_pulse = 1'b0;
|
||||||
// Allow the registered status_words update to land.
|
|
||||||
@(posedge ft_clk); #1;
|
@(posedge ft_clk); #1;
|
||||||
end
|
end
|
||||||
endtask
|
endtask
|
||||||
|
|
||||||
initial begin
|
initial begin
|
||||||
$display("============================================================");
|
$display("============================================================");
|
||||||
$display("AUDIT-S10: gpio_dig split + status_words[5][6:5] visibility");
|
$display("status_words[5][6:5] CDC + gpio_dig7 fault-OR (AUDIT-S10 + PR-AB.b)");
|
||||||
$display("============================================================");
|
$display("============================================================");
|
||||||
|
|
||||||
// ---- GROUP A: GPIO split ----
|
|
||||||
agc_saturation_count = 8'd0;
|
|
||||||
ddc_overflow_any = 1'b0;
|
|
||||||
ddc_saturation_count = 3'd0;
|
|
||||||
mti_saturation_count = 8'd0;
|
|
||||||
range_decim_watchdog = 1'b0;
|
|
||||||
ddc_cic_fir_overrun = 1'b0;
|
|
||||||
|
|
||||||
// T1
|
|
||||||
check_dig("T1 all zero", 1'b0, 1'b0);
|
|
||||||
|
|
||||||
// T2 each signal-sat individually
|
|
||||||
agc_saturation_count = 8'd1;
|
|
||||||
check_dig("T2a agc_sat>0", 1'b1, 1'b0);
|
|
||||||
agc_saturation_count = 8'd0;
|
|
||||||
|
|
||||||
ddc_overflow_any = 1'b1;
|
|
||||||
check_dig("T2b ddc_overflow", 1'b1, 1'b0);
|
|
||||||
ddc_overflow_any = 1'b0;
|
|
||||||
|
|
||||||
ddc_saturation_count = 3'd1;
|
|
||||||
check_dig("T2c ddc_sat>0", 1'b1, 1'b0);
|
|
||||||
ddc_saturation_count = 3'd0;
|
|
||||||
|
|
||||||
mti_saturation_count = 8'd1;
|
|
||||||
check_dig("T2d mti_sat>0", 1'b1, 1'b0);
|
|
||||||
mti_saturation_count = 8'd0;
|
|
||||||
|
|
||||||
// T3 each control-fault individually
|
|
||||||
range_decim_watchdog = 1'b1;
|
|
||||||
check_dig("T3a watchdog", 1'b0, 1'b1);
|
|
||||||
range_decim_watchdog = 1'b0;
|
|
||||||
|
|
||||||
ddc_cic_fir_overrun = 1'b1;
|
|
||||||
check_dig("T3b cic_fir_overrun", 1'b0, 1'b1);
|
|
||||||
ddc_cic_fir_overrun = 1'b0;
|
|
||||||
|
|
||||||
// T4 mixed
|
|
||||||
agc_saturation_count = 8'd5;
|
|
||||||
range_decim_watchdog = 1'b1;
|
|
||||||
check_dig("T4 mixed sat+fault", 1'b1, 1'b1);
|
|
||||||
agc_saturation_count = 8'd0;
|
|
||||||
range_decim_watchdog = 1'b0;
|
|
||||||
|
|
||||||
// T5 boundary: largest agc count
|
|
||||||
agc_saturation_count = 8'hFF;
|
|
||||||
check_dig("T5 agc_sat=FF", 1'b1, 1'b0);
|
|
||||||
agc_saturation_count = 8'd0;
|
|
||||||
|
|
||||||
// T6 boundary: ddc_sat=0 stays low
|
|
||||||
ddc_saturation_count = 3'd0;
|
|
||||||
check_dig("T6 ddc_sat=0", 1'b0, 1'b0);
|
|
||||||
|
|
||||||
// ---- GROUP B: status-word CDC packing ----
|
|
||||||
src_watchdog = 1'b0;
|
src_watchdog = 1'b0;
|
||||||
src_overrun = 1'b0;
|
src_overrun = 1'b0;
|
||||||
src_self_test_flags = 5'b00000;
|
src_self_test_flags = 5'b00000;
|
||||||
status_req_pulse = 1'b0;
|
status_req_pulse = 1'b0;
|
||||||
|
|
||||||
// Apply reset
|
|
||||||
reset_n = 1'b0;
|
reset_n = 1'b0;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
reset_n = 1'b1;
|
reset_n = 1'b1;
|
||||||
repeat (3) @(posedge ft_clk);
|
repeat (3) @(posedge ft_clk);
|
||||||
|
|
||||||
// T7 reset state
|
// T1 reset state
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T7 reset state",
|
check_status("T1 reset state",
|
||||||
32'h000000E0, // [7:5]
|
32'h000000E0, // [7:5]
|
||||||
32'h00000000);
|
32'h00000000);
|
||||||
|
check_dig7("T1 dig7 idle low", 1'b0);
|
||||||
|
|
||||||
// T8 watchdog asserted only
|
// T2 watchdog asserted only
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b1;
|
src_watchdog = 1'b1;
|
||||||
// give 4 ft_clk for sync chain to settle
|
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T8 watchdog asserted",
|
check_status("T2 watchdog asserted",
|
||||||
32'h00000060, // [6:5]
|
32'h00000060, // [6:5]
|
||||||
32'h00000020); // [5]=1
|
32'h00000020); // [5]=1
|
||||||
|
check_dig7("T2 dig7 watchdog -> high", 1'b1);
|
||||||
|
|
||||||
// T9 cic_fir_overrun asserted only (clear watchdog first)
|
// T3 cic_fir_overrun asserted only (clear watchdog first)
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b0;
|
src_watchdog = 1'b0;
|
||||||
src_overrun = 1'b1;
|
src_overrun = 1'b1;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T9 cic_fir_overrun asserted",
|
check_status("T3 cic_fir_overrun asserted",
|
||||||
32'h00000060,
|
32'h00000060,
|
||||||
32'h00000040); // [6]=1
|
32'h00000040); // [6]=1
|
||||||
|
check_dig7("T3 dig7 overrun -> high", 1'b1);
|
||||||
|
|
||||||
// T10 both, then both cleared
|
// T4 both, then both cleared
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b1;
|
src_watchdog = 1'b1;
|
||||||
src_overrun = 1'b1;
|
src_overrun = 1'b1;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T10a both asserted",
|
check_status("T4a both asserted",
|
||||||
32'h00000060,
|
32'h00000060,
|
||||||
32'h00000060); // [6:5]=11
|
32'h00000060); // [6:5]=11
|
||||||
|
check_dig7("T4a dig7 both -> high", 1'b1);
|
||||||
|
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b0;
|
src_watchdog = 1'b0;
|
||||||
src_overrun = 1'b0;
|
src_overrun = 1'b0;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T10b both cleared",
|
check_status("T4b both cleared",
|
||||||
32'h00000060,
|
32'h00000060,
|
||||||
32'h00000000);
|
32'h00000000);
|
||||||
|
check_dig7("T4b dig7 both cleared -> low", 1'b0);
|
||||||
|
|
||||||
// T11 reserved bit [7] stays 0 even when neighbours are 1
|
// T5 reserved bit [7] stays 0 even when neighbours are 1
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b1;
|
src_watchdog = 1'b1;
|
||||||
src_overrun = 1'b1;
|
src_overrun = 1'b1;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T11 [7] reserved stays 0",
|
check_status("T5 [7] reserved stays 0",
|
||||||
32'h00000080, // [7] only
|
32'h00000080,
|
||||||
32'h00000000);
|
32'h00000000);
|
||||||
|
check_dig7("T5 dig7 both asserted -> high", 1'b1);
|
||||||
|
|
||||||
// T12 self_test_flags pass through unchanged
|
// T6 self_test_flags pass through unchanged
|
||||||
@(posedge clk_src); #1;
|
@(posedge clk_src); #1;
|
||||||
src_watchdog = 1'b0;
|
src_watchdog = 1'b0;
|
||||||
src_overrun = 1'b0;
|
src_overrun = 1'b0;
|
||||||
src_self_test_flags = 5'b10110;
|
src_self_test_flags = 5'b10110;
|
||||||
repeat (5) @(posedge ft_clk);
|
repeat (5) @(posedge ft_clk);
|
||||||
pulse_status_req();
|
pulse_status_req();
|
||||||
check_status("T12 self_test_flags untouched",
|
check_status("T6 self_test_flags untouched",
|
||||||
32'h0000001F,
|
32'h0000001F,
|
||||||
32'h00000016);
|
32'h00000016);
|
||||||
|
check_dig7("T6 dig7 cleared -> low", 1'b0);
|
||||||
|
|
||||||
$display("============================================================");
|
$display("============================================================");
|
||||||
$display("AUDIT-S10 RESULTS: pass=%0d fail=%0d", pass, fail);
|
$display("STATUS STICKIES + DIG7 OR RESULTS: pass=%0d fail=%0d", pass, fail);
|
||||||
$display("============================================================");
|
$display("============================================================");
|
||||||
if (fail == 0) $display("[OVERALL] PASS");
|
if (fail == 0) $display("[OVERALL] PASS");
|
||||||
else $display("[OVERALL] FAIL");
|
else $display("[OVERALL] FAIL");
|
||||||
@@ -282,31 +203,6 @@ module tb_audit_s10_gpio_split;
|
|||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// gpio_split_block — mirrors the production fragment from radar_system_top.v
|
|
||||||
// post AUDIT-S10. Two combinational ORs:
|
|
||||||
// gpio_dig5 = signal-saturation classes (AGC + DDC + MTI)
|
|
||||||
// gpio_dig7 = control-fault classes (range-decimator watchdog + CIC->FIR
|
|
||||||
// CDC overrun)
|
|
||||||
// ============================================================================
|
|
||||||
module gpio_split_block (
|
|
||||||
input wire [7:0] agc_saturation_count,
|
|
||||||
input wire ddc_overflow_any,
|
|
||||||
input wire [2:0] ddc_saturation_count,
|
|
||||||
input wire [7:0] mti_saturation_count,
|
|
||||||
input wire range_decim_watchdog,
|
|
||||||
input wire ddc_cic_fir_overrun,
|
|
||||||
output wire gpio_dig5,
|
|
||||||
output wire gpio_dig7
|
|
||||||
);
|
|
||||||
assign gpio_dig5 = (agc_saturation_count != 8'd0)
|
|
||||||
| ddc_overflow_any
|
|
||||||
| (ddc_saturation_count != 3'd0)
|
|
||||||
| (mti_saturation_count != 8'd0);
|
|
||||||
assign gpio_dig7 = range_decim_watchdog
|
|
||||||
| ddc_cic_fir_overrun;
|
|
||||||
endmodule
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// status_packing_block — mirrors the production CDC fragment from
|
// status_packing_block — mirrors the production CDC fragment from
|
||||||
// usb_data_interface.v (and usb_data_interface_ft2232h.v) for the AUDIT-S10
|
// usb_data_interface.v (and usb_data_interface_ft2232h.v) for the AUDIT-S10
|
||||||
@@ -355,3 +251,21 @@ module status_packing_block (
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
endmodule
|
endmodule
|
||||||
|
|
||||||
|
// ============================================================================
|
||||||
|
// gpio_dig7_or_block — mirrors the production combinational OR in
|
||||||
|
// radar_system_top.v:
|
||||||
|
//
|
||||||
|
// assign gpio_dig7 = rx_range_decim_watchdog | rx_ddc_cic_fir_overrun;
|
||||||
|
//
|
||||||
|
// MCU's PD15 stuck-high sampler at main.cpp:880-1000 fires
|
||||||
|
// attemptErrorRecovery(ERROR_FPGA_DSP_STALL) → bitstream reload on either
|
||||||
|
// fault class.
|
||||||
|
// ============================================================================
|
||||||
|
module gpio_dig7_or_block (
|
||||||
|
input wire watchdog,
|
||||||
|
input wire overrun,
|
||||||
|
output wire gpio_dig7
|
||||||
|
);
|
||||||
|
assign gpio_dig7 = watchdog | overrun;
|
||||||
|
endmodule
|
||||||
@@ -40,7 +40,7 @@ from PyQt6.QtWidgets import (
|
|||||||
QTableWidget, QTableWidgetItem, QHeaderView,
|
QTableWidget, QTableWidgetItem, QHeaderView,
|
||||||
QPlainTextEdit, QStatusBar, QMessageBox,
|
QPlainTextEdit, QStatusBar, QMessageBox,
|
||||||
)
|
)
|
||||||
from PyQt6.QtCore import Qt, QLocale, QTimer, pyqtSignal, pyqtSlot, QObject
|
from PyQt6.QtCore import Qt, QLocale, QTimer, QSettings, pyqtSignal, pyqtSlot, QObject
|
||||||
from PyQt6.QtGui import QColor
|
from PyQt6.QtGui import QColor
|
||||||
|
|
||||||
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
from matplotlib.backends.backend_qtagg import FigureCanvasQTAgg
|
||||||
@@ -208,6 +208,18 @@ class RadarDashboard(QMainWindow):
|
|||||||
# FPGA control parameter widgets
|
# FPGA control parameter widgets
|
||||||
self._param_spins: dict = {} # opcode_hex -> QSpinBox
|
self._param_spins: dict = {} # opcode_hex -> QSpinBox
|
||||||
|
|
||||||
|
# PR-AB.b: BENCH-MODE persistent flag (advanced settings checkbox).
|
||||||
|
# OFF (default, production): AGC is ALWAYS-ON in MCU firmware; AGC
|
||||||
|
# Enable spinbox + Enable/Disable AGC buttons are hidden so an operator
|
||||||
|
# cannot send opcode 0x28 and confuse themselves about whether the MCU
|
||||||
|
# is honouring the register (it doesn't — see main.cpp #ifndef
|
||||||
|
# MCU_AGC_FORCE_DISABLED). A coloured "ALWAYS-ON" badge replaces them.
|
||||||
|
# ON: bench / debug build assumed, AGC controls are exposed; the user
|
||||||
|
# is expected to know the MCU is compiled with MCU_AGC_FORCE_DISABLED
|
||||||
|
# and that opcode 0x28 only sets the FPGA register (display-only).
|
||||||
|
self._qsettings = QSettings("AERIS-10", "RadarDashboardV7")
|
||||||
|
self._bench_mode: bool = bool(self._qsettings.value("bench_mode", False, type=bool))
|
||||||
|
|
||||||
# AGC visualization history (ring buffers)
|
# AGC visualization history (ring buffers)
|
||||||
self._agc_history_len = 256
|
self._agc_history_len = 256
|
||||||
self._agc_gain_history: deque[int] = deque(maxlen=self._agc_history_len)
|
self._agc_gain_history: deque[int] = deque(maxlen=self._agc_history_len)
|
||||||
@@ -375,6 +387,11 @@ class RadarDashboard(QMainWindow):
|
|||||||
self._create_diagnostics_tab()
|
self._create_diagnostics_tab()
|
||||||
self._create_settings_tab()
|
self._create_settings_tab()
|
||||||
|
|
||||||
|
# PR-AB.b: apply persisted BENCH-MODE state to AGC widget visibility.
|
||||||
|
# Has to run AFTER _create_fpga_control_tab (creates the AGC widgets)
|
||||||
|
# AND _create_settings_tab (creates the checkbox).
|
||||||
|
self._apply_bench_mode_visibility()
|
||||||
|
|
||||||
# -----------------------------------------------------------------
|
# -----------------------------------------------------------------
|
||||||
# TAB 1: Main View
|
# TAB 1: Main View
|
||||||
# -----------------------------------------------------------------
|
# -----------------------------------------------------------------
|
||||||
@@ -816,11 +833,48 @@ class RadarDashboard(QMainWindow):
|
|||||||
right_layout.addWidget(grp_cfar)
|
right_layout.addWidget(grp_cfar)
|
||||||
|
|
||||||
# ── AGC (Automatic Gain Control) ──────────────────────────────
|
# ── AGC (Automatic Gain Control) ──────────────────────────────
|
||||||
|
# PR-AB.b: The MCU's outer-loop AGC is ALWAYS-ON in production builds
|
||||||
|
# (compile-time policy, see main.cpp #ifndef MCU_AGC_FORCE_DISABLED).
|
||||||
|
# Bench/debug builds compile the AGC body out, and the runtime enable
|
||||||
|
# bit becomes meaningful only as an FPGA-side display register. We
|
||||||
|
# therefore hide the AGC Enable spinbox + Enable/Disable buttons in
|
||||||
|
# production (BENCH-MODE OFF) and show a "ALWAYS-ON" badge instead.
|
||||||
|
# Bench engineers tick the BENCH-MODE checkbox in Settings to expose
|
||||||
|
# the controls.
|
||||||
grp_agc = QGroupBox("AGC (Auto Gain)")
|
grp_agc = QGroupBox("AGC (Auto Gain)")
|
||||||
agc_layout = QVBoxLayout(grp_agc)
|
agc_layout = QVBoxLayout(grp_agc)
|
||||||
|
|
||||||
|
# Header row — exactly one of these is visible at a time:
|
||||||
|
# production: ALWAYS-ON badge.
|
||||||
|
# bench: Enable/Disable AGC buttons (send opcode 0x28 with 0|1;
|
||||||
|
# the bit's only valid values, so a spinbox would add
|
||||||
|
# nothing but typo-risk).
|
||||||
|
# Tuning knobs sit below regardless, so the AGC group's "header"
|
||||||
|
# control is always at the top of the box.
|
||||||
|
self._agc_always_on_badge = QLabel(
|
||||||
|
"AGC: ALWAYS-ON (production policy — MCU runs every frame)"
|
||||||
|
)
|
||||||
|
self._agc_always_on_badge.setStyleSheet(
|
||||||
|
f"background-color: {DARK_SUCCESS}; color: white; "
|
||||||
|
"padding: 6px; font-weight: bold; border-radius: 3px;"
|
||||||
|
)
|
||||||
|
self._agc_always_on_badge.setWordWrap(True)
|
||||||
|
agc_layout.addWidget(self._agc_always_on_badge)
|
||||||
|
|
||||||
|
self._agc_toggle_container = QWidget()
|
||||||
|
agc_toggle_inner = QHBoxLayout(self._agc_toggle_container)
|
||||||
|
agc_toggle_inner.setContentsMargins(0, 0, 0, 0)
|
||||||
|
self._btn_agc_on = QPushButton("Enable AGC")
|
||||||
|
self._btn_agc_on.clicked.connect(lambda: self._send_fpga_cmd(0x28, 1))
|
||||||
|
agc_toggle_inner.addWidget(self._btn_agc_on)
|
||||||
|
self._btn_agc_off = QPushButton("Disable AGC")
|
||||||
|
self._btn_agc_off.clicked.connect(lambda: self._send_fpga_cmd(0x28, 0))
|
||||||
|
agc_toggle_inner.addWidget(self._btn_agc_off)
|
||||||
|
agc_layout.addWidget(self._agc_toggle_container)
|
||||||
|
|
||||||
|
# Tuning knobs — always visible. Target/attack/decay/holdoff drive the
|
||||||
|
# FPGA inner-loop register fields regardless of the MCU AGC build flag.
|
||||||
agc_params = [
|
agc_params = [
|
||||||
("AGC Enable", 0x28, 0, 1, "0=manual, 1=auto"),
|
|
||||||
("AGC Target", 0x29, 200, 8, "0-255, peak target"),
|
("AGC Target", 0x29, 200, 8, "0-255, peak target"),
|
||||||
("AGC Attack", 0x2A, 1, 4, "0-15, atten step"),
|
("AGC Attack", 0x2A, 1, 4, "0-15, atten step"),
|
||||||
("AGC Decay", 0x2B, 1, 4, "0-15, gain-up step"),
|
("AGC Decay", 0x2B, 1, 4, "0-15, gain-up step"),
|
||||||
@@ -829,16 +883,6 @@ class RadarDashboard(QMainWindow):
|
|||||||
for label, opcode, default, bits, hint in agc_params:
|
for label, opcode, default, bits, hint in agc_params:
|
||||||
self._add_fpga_param_row(agc_layout, label, opcode, default, bits, hint)
|
self._add_fpga_param_row(agc_layout, label, opcode, default, bits, hint)
|
||||||
|
|
||||||
# AGC quick toggles
|
|
||||||
agc_row = QHBoxLayout()
|
|
||||||
btn_agc_on = QPushButton("Enable AGC")
|
|
||||||
btn_agc_on.clicked.connect(lambda: self._send_fpga_cmd(0x28, 1))
|
|
||||||
agc_row.addWidget(btn_agc_on)
|
|
||||||
btn_agc_off = QPushButton("Disable AGC")
|
|
||||||
btn_agc_off.clicked.connect(lambda: self._send_fpga_cmd(0x28, 0))
|
|
||||||
agc_row.addWidget(btn_agc_off)
|
|
||||||
agc_layout.addLayout(agc_row)
|
|
||||||
|
|
||||||
# AGC status readback labels
|
# AGC status readback labels
|
||||||
agc_st_group = QGroupBox("AGC Status")
|
agc_st_group = QGroupBox("AGC Status")
|
||||||
agc_st_layout = QVBoxLayout(agc_st_group)
|
agc_st_layout = QVBoxLayout(agc_st_group)
|
||||||
@@ -927,23 +971,38 @@ class RadarDashboard(QMainWindow):
|
|||||||
row = QHBoxLayout()
|
row = QHBoxLayout()
|
||||||
|
|
||||||
lbl = QLabel(label)
|
lbl = QLabel(label)
|
||||||
lbl.setMinimumWidth(140)
|
# PR-AB.b: setFixedWidth (not min) so labels line up across rows
|
||||||
|
# regardless of whether the row sits directly in the group's
|
||||||
|
# QVBoxLayout or inside an intermediate QWidget container (the AGC
|
||||||
|
# Enable row uses _agc_enable_container so it can be hidden in
|
||||||
|
# production — its inner layout reflows differently and the
|
||||||
|
# minimum-width hint was being ignored, leaving the spinbox start
|
||||||
|
# ~67 px to the left of its peers).
|
||||||
|
lbl.setFixedWidth(140)
|
||||||
row.addWidget(lbl)
|
row.addWidget(lbl)
|
||||||
|
|
||||||
max_val = (1 << bits) - 1
|
max_val = (1 << bits) - 1
|
||||||
spin = QSpinBox()
|
spin = QSpinBox()
|
||||||
spin.setRange(0, max_val)
|
spin.setRange(0, max_val)
|
||||||
spin.setValue(default)
|
spin.setValue(default)
|
||||||
spin.setMinimumWidth(80)
|
# PR-AB.b: setFixedWidth (not min/max) — QHBoxLayout would otherwise
|
||||||
|
# squeeze the spinbox toward its minimum on rows where the hint is
|
||||||
|
# longer than its peers (the AGC Enable hint is ~3× longer than the
|
||||||
|
# others and was rendering at ~90 px while siblings hit ~160).
|
||||||
|
spin.setFixedWidth(120)
|
||||||
row.addWidget(spin)
|
row.addWidget(spin)
|
||||||
self._param_spins[f"0x{opcode:02X}"] = spin
|
self._param_spins[f"0x{opcode:02X}"] = spin
|
||||||
|
|
||||||
|
# PR-AB.b: hint is the only flex element in the row — explicit stretch=1
|
||||||
|
# so it absorbs leftover space instead of competing with the Set button.
|
||||||
|
# Without this, a long hint (e.g. AGC Enable's "0=manual, 1=auto …")
|
||||||
|
# would shrink the Set button below its 60 px cap on the same row.
|
||||||
hint_lbl = QLabel(hint)
|
hint_lbl = QLabel(hint)
|
||||||
hint_lbl.setStyleSheet(f"color: {DARK_INFO}; font-size: 10px;")
|
hint_lbl.setStyleSheet(f"color: {DARK_INFO}; font-size: 10px;")
|
||||||
row.addWidget(hint_lbl)
|
row.addWidget(hint_lbl, 1)
|
||||||
|
|
||||||
btn = QPushButton("Set")
|
btn = QPushButton("Set")
|
||||||
btn.setMaximumWidth(60)
|
btn.setFixedWidth(60)
|
||||||
# Capture opcode and spin by value
|
# Capture opcode and spin by value
|
||||||
btn.clicked.connect(lambda _, op=opcode, sp=spin, b=bits:
|
btn.clicked.connect(lambda _, op=opcode, sp=spin, b=bits:
|
||||||
self._send_fpga_validated(op, sp.value(), b))
|
self._send_fpga_validated(op, sp.value(), b))
|
||||||
@@ -1245,6 +1304,41 @@ class RadarDashboard(QMainWindow):
|
|||||||
|
|
||||||
layout.addWidget(proc_group)
|
layout.addWidget(proc_group)
|
||||||
|
|
||||||
|
# ---- Bench / Diagnostics ------------------------------------------
|
||||||
|
# PR-AB.b: tucked-away toggle that only matters when the MCU is built
|
||||||
|
# with -DMCU_AGC_FORCE_DISABLED (bench / debug build). Production
|
||||||
|
# operators should leave this OFF — the AGC runs always-on at the
|
||||||
|
# firmware level and exposing the Enable/Disable buttons would let
|
||||||
|
# someone send opcode 0x28 without changing observable behaviour,
|
||||||
|
# which would just create confusion.
|
||||||
|
bench_group = QGroupBox("Bench / Diagnostics")
|
||||||
|
bench_layout = QVBoxLayout(bench_group)
|
||||||
|
|
||||||
|
self._bench_mode_check = QCheckBox(
|
||||||
|
"BENCH-MODE: expose AGC Enable controls (debug-build firmware only)"
|
||||||
|
)
|
||||||
|
self._bench_mode_check.setChecked(self._bench_mode)
|
||||||
|
self._bench_mode_check.setToolTip(
|
||||||
|
"OFF (default): production firmware runs AGC every frame. "
|
||||||
|
"AGC Enable buttons are hidden so opcode 0x28 cannot be sent.\n"
|
||||||
|
"ON: bench / debug firmware (built with -DMCU_AGC_FORCE_DISABLED) "
|
||||||
|
"is assumed. AGC Enable buttons become visible — they only set "
|
||||||
|
"the FPGA-side display register, the MCU does not honour them."
|
||||||
|
)
|
||||||
|
self._bench_mode_check.toggled.connect(self._on_bench_mode_toggled)
|
||||||
|
bench_layout.addWidget(self._bench_mode_check)
|
||||||
|
|
||||||
|
bench_note = QLabel(
|
||||||
|
"<i>Future: this checkbox will go away once the MCU broadcasts "
|
||||||
|
"a one-shot USB-CDC boot announce identifying production vs "
|
||||||
|
"bench firmware build.</i>"
|
||||||
|
)
|
||||||
|
bench_note.setStyleSheet(f"color: {DARK_INFO}; font-size: 10px;")
|
||||||
|
bench_note.setWordWrap(True)
|
||||||
|
bench_layout.addWidget(bench_note)
|
||||||
|
|
||||||
|
layout.addWidget(bench_group)
|
||||||
|
|
||||||
# ---- About group ---------------------------------------------------
|
# ---- About group ---------------------------------------------------
|
||||||
about_group = QGroupBox("About")
|
about_group = QGroupBox("About")
|
||||||
about_layout = QVBoxLayout(about_group)
|
about_layout = QVBoxLayout(about_group)
|
||||||
@@ -1868,13 +1962,10 @@ class RadarDashboard(QMainWindow):
|
|||||||
self._st_labels["t4"].setText(
|
self._st_labels["t4"].setText(
|
||||||
f"T4 ADC: {'PASS' if flags & 0x10 else 'FAIL'}")
|
f"T4 ADC: {'PASS' if flags & 0x10 else 'FAIL'}")
|
||||||
|
|
||||||
# AGC status readback
|
# AGC status readback. The 'enable' line is owned by
|
||||||
|
# _refresh_agc_mode_labels so production stays honest with the badge.
|
||||||
if hasattr(self, '_agc_labels'):
|
if hasattr(self, '_agc_labels'):
|
||||||
agc_str = "AUTO" if st.agc_enable else "MANUAL"
|
self._refresh_agc_mode_labels(st)
|
||||||
agc_color = DARK_SUCCESS if st.agc_enable else DARK_INFO
|
|
||||||
self._agc_labels["enable"].setStyleSheet(
|
|
||||||
f"color: {agc_color}; font-weight: bold;")
|
|
||||||
self._agc_labels["enable"].setText(f"AGC: {agc_str}")
|
|
||||||
self._agc_labels["gain"].setText(
|
self._agc_labels["gain"].setText(
|
||||||
f"Gain: {st.agc_current_gain}")
|
f"Gain: {st.agc_current_gain}")
|
||||||
self._agc_labels["peak"].setText(
|
self._agc_labels["peak"].setText(
|
||||||
@@ -1903,12 +1994,9 @@ class RadarDashboard(QMainWindow):
|
|||||||
self._agc_peak_history.append(st.agc_peak_magnitude)
|
self._agc_peak_history.append(st.agc_peak_magnitude)
|
||||||
self._agc_sat_history.append(st.agc_saturation_count)
|
self._agc_sat_history.append(st.agc_saturation_count)
|
||||||
|
|
||||||
# Update indicator labels (cheap Qt calls)
|
# The mode label honours bench-mode in production — same shared helper
|
||||||
agc_str = "AUTO" if st.agc_enable else "MANUAL"
|
# the FPGA Control tab uses, so the two views can never disagree.
|
||||||
agc_color = DARK_SUCCESS if st.agc_enable else DARK_INFO
|
self._refresh_agc_mode_labels(st)
|
||||||
self._agc_mode_lbl.setStyleSheet(
|
|
||||||
f"color: {agc_color}; font-size: 16px; font-weight: bold;")
|
|
||||||
self._agc_mode_lbl.setText(f"AGC: {agc_str}")
|
|
||||||
self._agc_gain_lbl.setText(f"Gain: {st.agc_current_gain}")
|
self._agc_gain_lbl.setText(f"Gain: {st.agc_current_gain}")
|
||||||
self._agc_peak_lbl.setText(f"Peak: {st.agc_peak_magnitude}")
|
self._agc_peak_lbl.setText(f"Peak: {st.agc_peak_magnitude}")
|
||||||
|
|
||||||
@@ -2001,6 +2089,51 @@ class RadarDashboard(QMainWindow):
|
|||||||
f"Failed to apply DSP settings: {e}")
|
f"Failed to apply DSP settings: {e}")
|
||||||
logger.error(f"DSP config error: {e}")
|
logger.error(f"DSP config error: {e}")
|
||||||
|
|
||||||
|
def _on_bench_mode_toggled(self, checked: bool):
|
||||||
|
"""Persist BENCH-MODE flag and refresh AGC widget visibility."""
|
||||||
|
self._bench_mode = bool(checked)
|
||||||
|
self._qsettings.setValue("bench_mode", self._bench_mode)
|
||||||
|
self._apply_bench_mode_visibility()
|
||||||
|
logger.info(f"BENCH-MODE {'ON' if self._bench_mode else 'OFF'}")
|
||||||
|
|
||||||
|
def _apply_bench_mode_visibility(self):
|
||||||
|
"""Show or hide AGC Enable controls based on self._bench_mode and
|
||||||
|
re-sync the AGC mode labels so they don't contradict the badge."""
|
||||||
|
production = not self._bench_mode
|
||||||
|
self._agc_always_on_badge.setVisible(production)
|
||||||
|
self._agc_toggle_container.setVisible(self._bench_mode)
|
||||||
|
# Push current bench-mode state through the AGC mode labels — uses
|
||||||
|
# the last StatusResponse if any, otherwise the static defaults.
|
||||||
|
self._refresh_agc_mode_labels(self._last_status)
|
||||||
|
|
||||||
|
def _refresh_agc_mode_labels(self, st: "StatusResponse | None"):
|
||||||
|
"""Update the AGC enable text on both the FPGA Control Status box
|
||||||
|
(self._agc_labels['enable']) and the AGC Monitor strip
|
||||||
|
(self._agc_mode_lbl). In production the firmware ignores the FPGA
|
||||||
|
register and runs AGC every frame, so both labels show 'ALWAYS-ON'
|
||||||
|
in green — keeps them honest with the production badge. In bench
|
||||||
|
the labels follow the StatusResponse register, falling back to '--'
|
||||||
|
before the first status arrives."""
|
||||||
|
if self._bench_mode:
|
||||||
|
if st is None:
|
||||||
|
text, color = "AGC: --", DARK_INFO
|
||||||
|
elif st.agc_enable:
|
||||||
|
text, color = "AGC: AUTO", DARK_SUCCESS
|
||||||
|
else:
|
||||||
|
text, color = "AGC: MANUAL", DARK_INFO
|
||||||
|
else:
|
||||||
|
text, color = "AGC: ALWAYS-ON", DARK_SUCCESS
|
||||||
|
|
||||||
|
if hasattr(self, "_agc_labels") and "enable" in self._agc_labels:
|
||||||
|
self._agc_labels["enable"].setStyleSheet(
|
||||||
|
f"color: {color}; font-weight: bold;")
|
||||||
|
self._agc_labels["enable"].setText(text)
|
||||||
|
|
||||||
|
if hasattr(self, "_agc_mode_lbl"):
|
||||||
|
self._agc_mode_lbl.setStyleSheet(
|
||||||
|
f"color: {color}; font-size: 16px; font-weight: bold;")
|
||||||
|
self._agc_mode_lbl.setText(text)
|
||||||
|
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
# Periodic GUI refresh (100 ms timer)
|
# Periodic GUI refresh (100 ms timer)
|
||||||
# =====================================================================
|
# =====================================================================
|
||||||
|
|||||||
Reference in New Issue
Block a user