Compare commits
11 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| a8aefc4f61 | |||
| 5b84af68f6 | |||
| 846a0debe8 | |||
| e979363730 | |||
| 2e9a848908 | |||
| 3366ac6417 | |||
| 607399ec28 | |||
| f48448970b | |||
| ebd96c90ce | |||
| db80baf34d | |||
| f895c0244c |
@@ -24,6 +24,7 @@ ADAR1000_AGC::ADAR1000_AGC()
|
|||||||
, saturation_event_count(0)
|
, saturation_event_count(0)
|
||||||
{
|
{
|
||||||
memset(cal_offset, 0, sizeof(cal_offset));
|
memset(cal_offset, 0, sizeof(cal_offset));
|
||||||
|
if (holdoff_frames == 0) holdoff_frames = 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------------------------------------------------------------------------
|
// ---------------------------------------------------------------------------
|
||||||
|
|||||||
@@ -163,10 +163,8 @@ void ADAR1000Manager::switchToTXMode() {
|
|||||||
DIAG("BF", "Step 3: PA bias ON");
|
DIAG("BF", "Step 3: PA bias ON");
|
||||||
setPABias(true);
|
setPABias(true);
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
// Step 4 (former setADTR1107Control(true)) removed: TR pin is FPGA-owned.
|
DIAG("BF", "Step 4: ADTR1107 -> TX");
|
||||||
// Chip follows adar_tr_x; TX path is asserted by the FPGA chirp FSM, not
|
setADTR1107Control(true);
|
||||||
// by SPI here. Write per-channel TX enables so the FPGA TR override has
|
|
||||||
// something to gate.
|
|
||||||
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF);
|
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF);
|
||||||
@@ -187,7 +185,8 @@ void ADAR1000Manager::switchToRXMode() {
|
|||||||
DIAG("BF", "Step 2: Disable PA supplies");
|
DIAG("BF", "Step 2: Disable PA supplies");
|
||||||
disablePASupplies();
|
disablePASupplies();
|
||||||
delayUs(10);
|
delayUs(10);
|
||||||
// Step 3 (former setADTR1107Control(false)) removed: FPGA owns TR pin.
|
DIAG("BF", "Step 3: ADTR1107 -> RX");
|
||||||
|
setADTR1107Control(false);
|
||||||
DIAG("BF", "Step 4: Enable LNA supplies");
|
DIAG("BF", "Step 4: Enable LNA supplies");
|
||||||
enableLNASupplies();
|
enableLNASupplies();
|
||||||
delayUs(50);
|
delayUs(50);
|
||||||
@@ -205,11 +204,39 @@ void ADAR1000Manager::switchToRXMode() {
|
|||||||
DIAG("BF", "switchToRXMode() complete");
|
DIAG("BF", "switchToRXMode() complete");
|
||||||
}
|
}
|
||||||
|
|
||||||
// fastTXMode, fastRXMode, pulseTXMode, pulseRXMode: REMOVED.
|
void ADAR1000Manager::fastTXMode() {
|
||||||
// The chirp hot path owns T/R switching via the FPGA adar_tr_x pins
|
DIAG("BF", "fastTXMode(): ADTR1107 -> TX (no bias sequencing)");
|
||||||
// (see 9_Firmware/9_2_FPGA/plfm_chirp_controller.v). The old SPI-RMW per
|
setADTR1107Control(true);
|
||||||
// chirp was architecturally redundant, raced the FPGA, and toggled the
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
// wrong bit of REG_SW_CONTROL (TR_SOURCE instead of TR_SPI).
|
adarWrite(dev, REG_RX_ENABLES, 0x00, BROADCAST_OFF);
|
||||||
|
adarWrite(dev, REG_TX_ENABLES, 0x0F, BROADCAST_OFF);
|
||||||
|
devices_[dev]->current_mode = BeamDirection::TX;
|
||||||
|
}
|
||||||
|
current_mode_ = BeamDirection::TX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADAR1000Manager::fastRXMode() {
|
||||||
|
DIAG("BF", "fastRXMode(): ADTR1107 -> RX (no bias sequencing)");
|
||||||
|
setADTR1107Control(false);
|
||||||
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
|
adarWrite(dev, REG_TX_ENABLES, 0x00, BROADCAST_OFF);
|
||||||
|
adarWrite(dev, REG_RX_ENABLES, 0x0F, BROADCAST_OFF);
|
||||||
|
devices_[dev]->current_mode = BeamDirection::RX;
|
||||||
|
}
|
||||||
|
current_mode_ = BeamDirection::RX;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADAR1000Manager::pulseTXMode() {
|
||||||
|
DIAG("BF", "pulseTXMode(): TR switch only");
|
||||||
|
setADTR1107Control(true);
|
||||||
|
last_switch_time_us_ = HAL_GetTick() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADAR1000Manager::pulseRXMode() {
|
||||||
|
DIAG("BF", "pulseRXMode(): TR switch only");
|
||||||
|
setADTR1107Control(false);
|
||||||
|
last_switch_time_us_ = HAL_GetTick() * 1000;
|
||||||
|
}
|
||||||
|
|
||||||
// Beam Steering
|
// Beam Steering
|
||||||
bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction) {
|
bool ADAR1000Manager::setBeamAngle(float angle_degrees, BeamDirection direction) {
|
||||||
@@ -341,10 +368,25 @@ void ADAR1000Manager::writeRegister(uint8_t deviceIndex, uint32_t address, uint8
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
// setSwitchSettlingTime, setFastSwitchMode: REMOVED.
|
void ADAR1000Manager::setSwitchSettlingTime(uint32_t us) {
|
||||||
// Their only reader was the deleted setADTR1107Control; setFastSwitchMode(true)
|
switch_settling_time_us_ = us;
|
||||||
// also violated the ADTR1107 datasheet bias sequence (PA + LNA biased to
|
}
|
||||||
// operational simultaneously). Per-chirp T/R is FPGA-owned now.
|
|
||||||
|
void ADAR1000Manager::setFastSwitchMode(bool enable) {
|
||||||
|
DIAG("BF", "setFastSwitchMode(%s)", enable ? "ON" : "OFF");
|
||||||
|
fast_switch_mode_ = enable;
|
||||||
|
if (enable) {
|
||||||
|
switch_settling_time_us_ = 10;
|
||||||
|
DIAG("BF", " settling time = 10 us, enabling PA+LNA supplies and bias simultaneously");
|
||||||
|
enablePASupplies();
|
||||||
|
enableLNASupplies();
|
||||||
|
setPABias(true);
|
||||||
|
setLNABias(true);
|
||||||
|
} else {
|
||||||
|
switch_settling_time_us_ = 50;
|
||||||
|
DIAG("BF", " settling time = 50 us");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::setBeamDwellTime(uint32_t ms) {
|
void ADAR1000Manager::setBeamDwellTime(uint32_t ms) {
|
||||||
beam_dwell_time_ms_ = ms;
|
beam_dwell_time_ms_ = ms;
|
||||||
@@ -386,30 +428,15 @@ bool ADAR1000Manager::initializeSingleDevice(uint8_t deviceIndex) {
|
|||||||
DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex);
|
DIAG("BF", " dev[%u] set RAM bypass (bias+beam)", deviceIndex);
|
||||||
adarSetRamBypass(deviceIndex, BROADCAST_OFF);
|
adarSetRamBypass(deviceIndex, BROADCAST_OFF);
|
||||||
|
|
||||||
// Hand per-chirp T/R switching to the FPGA.
|
|
||||||
// Set TR_SOURCE (REG_SW_CONTROL bit 2) = 1 so the chip's internal
|
|
||||||
// RX_EN_OVERRIDE / TX_EN_OVERRIDE follow the external TR pin (driven by
|
|
||||||
// plfm_chirp_controller's adar_tr_x output). See ADAR1000 datasheet
|
|
||||||
// "Theory of Operation" -- SPI Control vs TR Pin Control.
|
|
||||||
// Without this write, the FPGA's TR pin is ignored and the chip stays
|
|
||||||
// in RX state (TR_SPI POR default).
|
|
||||||
DIAG("BF", " dev[%u] SW_CONTROL: TR_SOURCE=1 (FPGA owns TR pin)", deviceIndex);
|
|
||||||
adarWrite(deviceIndex, REG_SW_CONTROL, (1 << 2), BROADCAST_OFF);
|
|
||||||
|
|
||||||
// Initialize ADC
|
// Initialize ADC
|
||||||
DIAG("BF", " dev[%u] enable ADC (2MHz clk)", deviceIndex);
|
DIAG("BF", " dev[%u] enable ADC (2MHz clk)", deviceIndex);
|
||||||
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF);
|
adarWrite(deviceIndex, REG_ADC_CONTROL, ADAR1000_ADC_2MHZ_CLK | ADAR1000_ADC_EN, BROADCAST_OFF);
|
||||||
|
|
||||||
// Verify communication with scratchpad test
|
// Verify communication with scratchpad test
|
||||||
// Audit F-4.4: on SPI failure, previously marked the device initialized
|
|
||||||
// anyway, so downstream (e.g. PA enable) could drive PA gates out-of-spec
|
|
||||||
// on a dead bus. Now propagate the failure so initializeAllDevices aborts.
|
|
||||||
DIAG("BF", " dev[%u] verifying SPI communication...", deviceIndex);
|
DIAG("BF", " dev[%u] verifying SPI communication...", deviceIndex);
|
||||||
bool comms_ok = verifyDeviceCommunication(deviceIndex);
|
bool comms_ok = verifyDeviceCommunication(deviceIndex);
|
||||||
if (!comms_ok) {
|
if (!comms_ok) {
|
||||||
DIAG_ERR("BF", " dev[%u] scratchpad verify FAILED -- device NOT marked initialized", deviceIndex);
|
DIAG_WARN("BF", " dev[%u] scratchpad verify FAILED but marking initialized anyway", deviceIndex);
|
||||||
devices_[deviceIndex]->initialized = false;
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
devices_[deviceIndex]->initialized = true;
|
devices_[deviceIndex]->initialized = true;
|
||||||
@@ -437,11 +464,9 @@ bool ADAR1000Manager::initializeADTR1107Sequence() {
|
|||||||
HAL_GPIO_WritePin(EN_P_3V3_SW_GPIO_Port, EN_P_3V3_SW_Pin, GPIO_PIN_SET);
|
HAL_GPIO_WritePin(EN_P_3V3_SW_GPIO_Port, EN_P_3V3_SW_Pin, GPIO_PIN_SET);
|
||||||
HAL_Delay(1);
|
HAL_Delay(1);
|
||||||
|
|
||||||
// Step 4: CTRL_SW safe-default is RX.
|
// Step 4: Set CTRL_SW to RX mode initially via GPIO
|
||||||
// FPGA-owned path: with TR_SOURCE=1 (set in initializeSingleDevice) the
|
DIAG("BF", "Step 4: CTRL_SW -> RX (initial safe mode)");
|
||||||
// chip follows adar_tr_x, which is 0 in the FPGA FSM's IDLE state = RX.
|
setADTR1107Control(false); // RX mode
|
||||||
// No SPI write needed here.
|
|
||||||
DIAG("BF", "Step 4: CTRL_SW -> RX (FPGA adar_tr_x idle-low == RX)");
|
|
||||||
HAL_Delay(1);
|
HAL_Delay(1);
|
||||||
|
|
||||||
// Step 5: Set VGG_LNA to 0
|
// Step 5: Set VGG_LNA to 0
|
||||||
@@ -543,7 +568,7 @@ bool ADAR1000Manager::setAllDevicesRXMode() {
|
|||||||
void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
||||||
if (direction == BeamDirection::TX) {
|
if (direction == BeamDirection::TX) {
|
||||||
DIAG_SECTION("ADTR1107 -> TX MODE");
|
DIAG_SECTION("ADTR1107 -> TX MODE");
|
||||||
// setADTR1107Control(true) removed: TR pin is FPGA-driven.
|
setADTR1107Control(true); // TX mode
|
||||||
|
|
||||||
// Step 1: Disable LNA power first
|
// Step 1: Disable LNA power first
|
||||||
DIAG("BF", " Disable LNA supplies");
|
DIAG("BF", " Disable LNA supplies");
|
||||||
@@ -573,11 +598,10 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
|||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 5: TR switch state is FPGA-driven. TR_SOURCE=1 is set once in
|
// Step 5: Set TR switch to TX mode
|
||||||
// initializeSingleDevice, so the chip already follows adar_tr_x.
|
DIAG("BF", " TR switch -> TX (TR_SOURCE=1, BIAS_EN)");
|
||||||
// Only BIAS_EN needs to be asserted here.
|
|
||||||
DIAG("BF", " BIAS_EN (TR source still = FPGA adar_tr_x)");
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
|
adarSetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF); // TR_SOURCE = 1 (TX)
|
||||||
adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF); // BIAS_EN
|
adarSetBit(dev, REG_MISC_ENABLES, 5, BROADCAST_OFF); // BIAS_EN
|
||||||
}
|
}
|
||||||
DIAG("BF", " ADTR1107 TX mode complete");
|
DIAG("BF", " ADTR1107 TX mode complete");
|
||||||
@@ -585,7 +609,7 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
|||||||
} else {
|
} else {
|
||||||
// RECEIVE MODE: Enable LNA, Disable PA
|
// RECEIVE MODE: Enable LNA, Disable PA
|
||||||
DIAG_SECTION("ADTR1107 -> RX MODE");
|
DIAG_SECTION("ADTR1107 -> RX MODE");
|
||||||
// setADTR1107Control(false) removed: TR pin is FPGA-driven.
|
setADTR1107Control(false); // RX mode
|
||||||
|
|
||||||
// Step 1: Disable PA power first
|
// Step 1: Disable PA power first
|
||||||
DIAG("BF", " Disable PA supplies");
|
DIAG("BF", " Disable PA supplies");
|
||||||
@@ -616,21 +640,34 @@ void ADAR1000Manager::setADTR1107Mode(BeamDirection direction) {
|
|||||||
}
|
}
|
||||||
HAL_Delay(5);
|
HAL_Delay(5);
|
||||||
|
|
||||||
// Step 5: TR switch state is FPGA-driven (TR_SOURCE left at 1).
|
// Step 5: Set TR switch to RX mode
|
||||||
// Only LNA_BIAS_OUT_EN needs to be asserted here.
|
DIAG("BF", " TR switch -> RX (TR_SOURCE=0, LNA_BIAS_OUT_EN)");
|
||||||
DIAG("BF", " LNA_BIAS_OUT_EN (TR source still = FPGA adar_tr_x)");
|
|
||||||
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
|
adarResetBit(dev, REG_SW_CONTROL, 2, BROADCAST_OFF); // TR_SOURCE = 0 (RX)
|
||||||
adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF); // LNA_BIAS_OUT_EN
|
adarSetBit(dev, REG_MISC_ENABLES, 4, BROADCAST_OFF); // LNA_BIAS_OUT_EN
|
||||||
}
|
}
|
||||||
DIAG("BF", " ADTR1107 RX mode complete");
|
DIAG("BF", " ADTR1107 RX mode complete");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// setADTR1107Control, setTRSwitchPosition: REMOVED.
|
void ADAR1000Manager::setADTR1107Control(bool tx_mode) {
|
||||||
// The per-device SPI RMW of REG_SW_CONTROL bit 2 (TR_SOURCE) was both wrong
|
DIAG("BF", "setADTR1107Control(%s): setting TR switch on all %u devices, settling %lu us",
|
||||||
// (it toggled the *control source*, not the TX/RX state -- TR_SPI is bit 1)
|
tx_mode ? "TX" : "RX", (unsigned)devices_.size(), (unsigned long)switch_settling_time_us_);
|
||||||
// and redundant with the FPGA's plfm_chirp_controller adar_tr_x output.
|
for (uint8_t dev = 0; dev < devices_.size(); ++dev) {
|
||||||
// TR_SOURCE is now set to 1 exactly once in initializeSingleDevice.
|
setTRSwitchPosition(dev, tx_mode);
|
||||||
|
}
|
||||||
|
delayUs(switch_settling_time_us_);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ADAR1000Manager::setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode) {
|
||||||
|
if (tx_mode) {
|
||||||
|
// TX mode: Set TR_SOURCE = 1
|
||||||
|
adarSetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
|
||||||
|
} else {
|
||||||
|
// RX mode: Set TR_SOURCE = 0
|
||||||
|
adarResetBit(deviceIndex, REG_SW_CONTROL, 2, BROADCAST_OFF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Add the new public method
|
// Add the new public method
|
||||||
bool ADAR1000Manager::setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction) {
|
bool ADAR1000Manager::setCustomBeamPattern16(const uint8_t phase_pattern[16], BeamDirection direction) {
|
||||||
@@ -693,21 +730,10 @@ void ADAR1000Manager::setLNABias(bool enable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::delayUs(uint32_t microseconds) {
|
void ADAR1000Manager::delayUs(uint32_t microseconds) {
|
||||||
// Audit F-4.7: the prior implementation was a calibrated __NOP() busy-loop
|
// Simple implementation - for F7 @ 216MHz, each loop ~7 cycles ≈ 0.032us
|
||||||
// that silently drifted with compiler optimization, cache state, and flash
|
volatile uint32_t cycles = microseconds * 10; // Adjust this multiplier for your clock
|
||||||
// wait-states. The ADAR1000 PLL/TX settling times require a real clock, so
|
while (cycles--) {
|
||||||
// we poll the DWT cycle counter instead. One-time TRCENA/CYCCNTENA enable
|
__NOP();
|
||||||
// is idempotent; subsequent calls skip the init branch via DWT->CTRL read.
|
|
||||||
if ((DWT->CTRL & DWT_CTRL_CYCCNTENA_Msk) == 0U) {
|
|
||||||
CoreDebug->DEMCR |= CoreDebug_DEMCR_TRCENA_Msk;
|
|
||||||
DWT->CYCCNT = 0U;
|
|
||||||
DWT->CTRL |= DWT_CTRL_CYCCNTENA_Msk;
|
|
||||||
}
|
|
||||||
const uint32_t cycles_per_us = SystemCoreClock / 1000000U;
|
|
||||||
const uint32_t start = DWT->CYCCNT;
|
|
||||||
const uint32_t target = microseconds * cycles_per_us;
|
|
||||||
while ((DWT->CYCCNT - start) < target) {
|
|
||||||
/* CYCCNT wraps cleanly modulo 2^32 — subtraction stays correct. */
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -769,25 +795,14 @@ void ADAR1000Manager::setChipSelect(uint8_t deviceIndex, bool state) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast) {
|
void ADAR1000Manager::adarWrite(uint8_t deviceIndex, uint32_t mem_addr, uint8_t data, uint8_t broadcast) {
|
||||||
// Audit F-4.1: the broadcast SPI opcode path (`instruction[0] = 0x08`)
|
uint8_t instruction[3];
|
||||||
// has never been exercised on silicon and is structurally questionable —
|
|
||||||
// setChipSelect() only toggles ONE device's CS line, so even if a caller
|
if (broadcast) {
|
||||||
// opts into the broadcast opcode today, only the single selected chip
|
instruction[0] = 0x08;
|
||||||
// actually sees the frame. Until a HIL test confirms multi-CS semantics,
|
} else {
|
||||||
// route every broadcast write through a per-device unicast loop. This
|
instruction[0] = ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
||||||
// preserves caller intent (all four devices take the write) and makes
|
|
||||||
// the dead opcode-0x08 path unreachable at runtime.
|
|
||||||
if (broadcast == BROADCAST_ON) {
|
|
||||||
DIAG_WARN("BF", "adarWrite: broadcast=1 lowered to per-device unicast (addr=0x%03lX data=0x%02X)",
|
|
||||||
(unsigned long)mem_addr, data);
|
|
||||||
for (uint8_t d = 0; d < devices_.size(); ++d) {
|
|
||||||
adarWrite(d, mem_addr, data, BROADCAST_OFF);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
uint8_t instruction[3];
|
|
||||||
instruction[0] = ((devices_[deviceIndex]->dev_addr & 0x03) << 5);
|
|
||||||
instruction[0] |= (0x1F00 & mem_addr) >> 8;
|
instruction[0] |= (0x1F00 & mem_addr) >> 8;
|
||||||
instruction[1] = (0xFF & mem_addr);
|
instruction[1] = (0xFF & mem_addr);
|
||||||
instruction[2] = data;
|
instruction[2] = data;
|
||||||
@@ -820,26 +835,12 @@ uint8_t ADAR1000Manager::adarRead(uint8_t deviceIndex, uint32_t mem_addr) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
||||||
// Audit F-4.2: broadcast-RMW is unsafe. The read samples a single device
|
|
||||||
// but the write fans out to all four, overwriting the other three with
|
|
||||||
// deviceIndex's state. Reject and surface the mistake.
|
|
||||||
if (broadcast == BROADCAST_ON) {
|
|
||||||
DIAG_ERR("BF", "adarSetBit: broadcast RMW is unsafe, ignored (dev=%u addr=0x%03lX bit=%u)",
|
|
||||||
deviceIndex, (unsigned long)mem_addr, bit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
||||||
uint8_t data = temp | (1 << bit);
|
uint8_t data = temp | (1 << bit);
|
||||||
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
void ADAR1000Manager::adarResetBit(uint8_t deviceIndex, uint32_t mem_addr, uint8_t bit, uint8_t broadcast) {
|
||||||
// Audit F-4.2: see adarSetBit.
|
|
||||||
if (broadcast == BROADCAST_ON) {
|
|
||||||
DIAG_ERR("BF", "adarResetBit: broadcast RMW is unsafe, ignored (dev=%u addr=0x%03lX bit=%u)",
|
|
||||||
deviceIndex, (unsigned long)mem_addr, bit);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
uint8_t temp = adarRead(deviceIndex, mem_addr);
|
||||||
uint8_t data = temp & ~(1 << bit);
|
uint8_t data = temp & ~(1 << bit);
|
||||||
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
adarWrite(deviceIndex, mem_addr, data, broadcast);
|
||||||
@@ -903,7 +904,7 @@ void ADAR1000Manager::adarSetTxPhase(uint8_t deviceIndex, uint8_t channel, uint8
|
|||||||
|
|
||||||
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_i, i_val, broadcast);
|
||||||
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
adarWrite(deviceIndex, mem_addr_q, q_val, broadcast);
|
||||||
adarWrite(deviceIndex, REG_LOAD_WORKING, LD_WRK_REGS_LDTX_OVERRIDE, broadcast);
|
adarWrite(deviceIndex, REG_LOAD_WORKING, 0x1, broadcast);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
void ADAR1000Manager::adarSetRxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast) {
|
||||||
|
|||||||
@@ -48,11 +48,10 @@ public:
|
|||||||
// Mode Switching
|
// Mode Switching
|
||||||
void switchToTXMode();
|
void switchToTXMode();
|
||||||
void switchToRXMode();
|
void switchToRXMode();
|
||||||
// fastTXMode/fastRXMode/pulseTXMode/pulseRXMode were removed: per-chirp T/R
|
void fastTXMode();
|
||||||
// switching is owned by the FPGA (plfm_chirp_controller -> adar_tr_x pins,
|
void fastRXMode();
|
||||||
// requires TR_SOURCE=1 in REG_SW_CONTROL, set in initializeSingleDevice).
|
void pulseTXMode();
|
||||||
// The old SPI RMW path was architecturally redundant and also toggled the
|
void pulseRXMode();
|
||||||
// wrong bit (TR_SOURCE instead of TR_SPI). See PR for details.
|
|
||||||
|
|
||||||
// Beam Steering
|
// Beam Steering
|
||||||
bool setBeamAngle(float angle_degrees, BeamDirection direction);
|
bool setBeamAngle(float angle_degrees, BeamDirection direction);
|
||||||
@@ -70,8 +69,7 @@ public:
|
|||||||
bool setAllDevicesTXMode();
|
bool setAllDevicesTXMode();
|
||||||
bool setAllDevicesRXMode();
|
bool setAllDevicesRXMode();
|
||||||
void setADTR1107Mode(BeamDirection direction);
|
void setADTR1107Mode(BeamDirection direction);
|
||||||
// setADTR1107Control removed -- it only wrapped the now-deleted
|
void setADTR1107Control(bool tx_mode);
|
||||||
// setTRSwitchPosition SPI path. FPGA drives the TR pin directly.
|
|
||||||
|
|
||||||
// Monitoring and Diagnostics
|
// Monitoring and Diagnostics
|
||||||
float readTemperature(uint8_t deviceIndex);
|
float readTemperature(uint8_t deviceIndex);
|
||||||
@@ -80,11 +78,8 @@ public:
|
|||||||
void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value);
|
void writeRegister(uint8_t deviceIndex, uint32_t address, uint8_t value);
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
// setSwitchSettlingTime / setFastSwitchMode removed: their only reader was
|
void setSwitchSettlingTime(uint32_t us);
|
||||||
// the deleted setADTR1107Control SPI path, and setFastSwitchMode(true)
|
void setFastSwitchMode(bool enable);
|
||||||
// also bundled a datasheet-violating PA+LNA-biased-simultaneously side
|
|
||||||
// effect. Per-chirp settling is now FPGA-owned. Callers that need a
|
|
||||||
// warm-up bias state should use switchToTXMode / switchToRXMode instead.
|
|
||||||
void setBeamDwellTime(uint32_t ms);
|
void setBeamDwellTime(uint32_t ms);
|
||||||
|
|
||||||
// Getters
|
// Getters
|
||||||
@@ -105,8 +100,8 @@ public:
|
|||||||
};
|
};
|
||||||
|
|
||||||
// Configuration
|
// Configuration
|
||||||
// fast_switch_mode_ / switch_settling_time_us_ removed: both had no
|
bool fast_switch_mode_ = false;
|
||||||
// readers after the FPGA-owned TR refactor.
|
uint32_t switch_settling_time_us_ = 50;
|
||||||
uint32_t beam_dwell_time_ms_ = 100;
|
uint32_t beam_dwell_time_ms_ = 100;
|
||||||
uint32_t last_switch_time_us_ = 0;
|
uint32_t last_switch_time_us_ = 0;
|
||||||
|
|
||||||
@@ -172,7 +167,7 @@ public:
|
|||||||
void adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast);
|
void adarSetTxVgaGain(uint8_t deviceIndex, uint8_t channel, uint8_t gain, uint8_t broadcast);
|
||||||
void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast);
|
void adarSetTxBias(uint8_t deviceIndex, uint8_t broadcast);
|
||||||
uint8_t adarAdcRead(uint8_t deviceIndex, uint8_t broadcast);
|
uint8_t adarAdcRead(uint8_t deviceIndex, uint8_t broadcast);
|
||||||
// setTRSwitchPosition removed -- FPGA owns TR pin. See PR.
|
void setTRSwitchPosition(uint8_t deviceIndex, bool tx_mode);
|
||||||
|
|
||||||
private:
|
private:
|
||||||
|
|
||||||
|
|||||||
@@ -13,6 +13,7 @@ void USBHandler::reset() {
|
|||||||
start_flag_received = false;
|
start_flag_received = false;
|
||||||
buffer_index = 0;
|
buffer_index = 0;
|
||||||
current_settings.resetToDefaults();
|
current_settings.resetToDefaults();
|
||||||
|
fault_ack_received = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void USBHandler::processUSBData(const uint8_t* data, uint32_t length) {
|
void USBHandler::processUSBData(const uint8_t* data, uint32_t length) {
|
||||||
@@ -23,6 +24,18 @@ void USBHandler::processUSBData(const uint8_t* data, uint32_t length) {
|
|||||||
|
|
||||||
DIAG("USB", "processUSBData: %lu bytes, state=%d", (unsigned long)length, (int)current_state);
|
DIAG("USB", "processUSBData: %lu bytes, state=%d", (unsigned long)length, (int)current_state);
|
||||||
|
|
||||||
|
// FAULT_ACK: host sends exactly 4 bytes [0x40, 0x00, 0x00, 0x00].
|
||||||
|
// Requires exact 4-byte packet length: settings packets are always
|
||||||
|
// >= 82 bytes, so a lone 4-byte payload is unambiguous. Scanning
|
||||||
|
// inside larger packets would false-trigger on the IEEE 754
|
||||||
|
// encoding of 2.0 (0x4000000000000000) embedded in settings doubles.
|
||||||
|
static const uint8_t FAULT_ACK_SEQ[4] = {0x40, 0x00, 0x00, 0x00};
|
||||||
|
if (length == 4 && memcmp(data, FAULT_ACK_SEQ, 4) == 0) {
|
||||||
|
fault_ack_received = true;
|
||||||
|
DIAG("USB", "FAULT_ACK received");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
switch (current_state) {
|
switch (current_state) {
|
||||||
case USBState::WAITING_FOR_START:
|
case USBState::WAITING_FOR_START:
|
||||||
processStartFlag(data, length);
|
processStartFlag(data, length);
|
||||||
|
|||||||
@@ -29,6 +29,11 @@ public:
|
|||||||
// Reset USB handler
|
// Reset USB handler
|
||||||
void reset();
|
void reset();
|
||||||
|
|
||||||
|
// Fault-acknowledgement: host sends FAULT_ACK (0x40) to clear
|
||||||
|
// system_emergency_state and exit the safe-mode blink loop.
|
||||||
|
bool isFaultAckReceived() const { return fault_ack_received; }
|
||||||
|
void clearFaultAck() { fault_ack_received = false; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
RadarSettings current_settings;
|
RadarSettings current_settings;
|
||||||
USBState current_state;
|
USBState current_state;
|
||||||
@@ -38,6 +43,7 @@ private:
|
|||||||
static constexpr uint32_t MAX_BUFFER_SIZE = 256;
|
static constexpr uint32_t MAX_BUFFER_SIZE = 256;
|
||||||
uint8_t usb_buffer[MAX_BUFFER_SIZE];
|
uint8_t usb_buffer[MAX_BUFFER_SIZE];
|
||||||
uint32_t buffer_index;
|
uint32_t buffer_index;
|
||||||
|
bool fault_ack_received;
|
||||||
|
|
||||||
void processStartFlag(const uint8_t* data, uint32_t length);
|
void processStartFlag(const uint8_t* data, uint32_t length);
|
||||||
void processSettingsData(const uint8_t* data, uint32_t length);
|
void processSettingsData(const uint8_t* data, uint32_t length);
|
||||||
|
|||||||
@@ -483,14 +483,11 @@ void executeChirpSequence(int num_chirps, float T1, float PRI1, float T2, float
|
|||||||
DIAG("SYS", "executeChirpSequence: num_chirps=%d T1=%.2f PRI1=%.2f T2=%.2f PRI2=%.2f",
|
DIAG("SYS", "executeChirpSequence: num_chirps=%d T1=%.2f PRI1=%.2f T2=%.2f PRI2=%.2f",
|
||||||
num_chirps, T1, PRI1, T2, PRI2);
|
num_chirps, T1, PRI1, T2, PRI2);
|
||||||
// First chirp sequence (microsecond timing)
|
// First chirp sequence (microsecond timing)
|
||||||
// T/R switching is owned by the FPGA plfm_chirp_controller: its chirp
|
|
||||||
// FSM drives adar_tr_x high during LONG_CHIRP/SHORT_CHIRP and low during
|
|
||||||
// listen/guard. new_chirp (GPIOD_8) triggers the FSM out of IDLE.
|
|
||||||
// The MCU's old pulseTXMode/pulseRXMode SPI path was redundant and raced
|
|
||||||
// the FPGA -- removed.
|
|
||||||
for(int i = 0; i < num_chirps; i++) {
|
for(int i = 0; i < num_chirps; i++) {
|
||||||
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
|
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
|
||||||
|
adarManager.pulseTXMode();
|
||||||
delay_us((uint32_t)T1);
|
delay_us((uint32_t)T1);
|
||||||
|
adarManager.pulseRXMode();
|
||||||
delay_us((uint32_t)(PRI1 - T1));
|
delay_us((uint32_t)(PRI1 - T1));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -499,8 +496,11 @@ void executeChirpSequence(int num_chirps, float T1, float PRI1, float T2, float
|
|||||||
// Second chirp sequence (nanosecond timing)
|
// Second chirp sequence (nanosecond timing)
|
||||||
for(int i = 0; i < num_chirps; i++) {
|
for(int i = 0; i < num_chirps; i++) {
|
||||||
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
|
HAL_GPIO_TogglePin(GPIOD, GPIO_PIN_8); // New chirp signal to FPGA
|
||||||
|
adarManager.pulseTXMode();
|
||||||
delay_ns((uint32_t)(T2 * 1000));
|
delay_ns((uint32_t)(T2 * 1000));
|
||||||
|
adarManager.pulseRXMode();
|
||||||
delay_ns((uint32_t)((PRI2 - T2) * 1000));
|
delay_ns((uint32_t)((PRI2 - T2) * 1000));
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -513,9 +513,9 @@ void runRadarPulseSequence() {
|
|||||||
DIAG("SYS", "runRadarPulseSequence #%d: m_max=%d n_max=%d y_max=%d",
|
DIAG("SYS", "runRadarPulseSequence #%d: m_max=%d n_max=%d y_max=%d",
|
||||||
sequence_count, m_max, n_max, y_max);
|
sequence_count, m_max, n_max, y_max);
|
||||||
|
|
||||||
// Fast per-chirp switching is now FPGA-owned (plfm_chirp_controller
|
// Configure for fast switching
|
||||||
// adar_tr_x), not MCU-driven. setFastSwitchMode(true) call removed.
|
DIAG("BF", "Enabling fast-switch mode for beam sweep");
|
||||||
DIAG("BF", "Beam sweep start (FPGA owns per-chirp T/R switching)");
|
adarManager.setFastSwitchMode(true);
|
||||||
|
|
||||||
int m = 1; // Chirp counter
|
int m = 1; // Chirp counter
|
||||||
int n = 1; // Beam Elevation position counter
|
int n = 1; // Beam Elevation position counter
|
||||||
@@ -627,7 +627,7 @@ typedef enum {
|
|||||||
|
|
||||||
static SystemError_t last_error = ERROR_NONE;
|
static SystemError_t last_error = ERROR_NONE;
|
||||||
static uint32_t error_count = 0;
|
static uint32_t error_count = 0;
|
||||||
static bool system_emergency_state = false;
|
static volatile bool system_emergency_state = false;
|
||||||
|
|
||||||
// Error handler function
|
// Error handler function
|
||||||
SystemError_t checkSystemHealth(void) {
|
SystemError_t checkSystemHealth(void) {
|
||||||
@@ -2054,6 +2054,10 @@ int main(void)
|
|||||||
HAL_GPIO_TogglePin(LED_3_GPIO_Port, LED_3_Pin);
|
HAL_GPIO_TogglePin(LED_3_GPIO_Port, LED_3_Pin);
|
||||||
HAL_GPIO_TogglePin(LED_4_GPIO_Port, LED_4_Pin);
|
HAL_GPIO_TogglePin(LED_4_GPIO_Port, LED_4_Pin);
|
||||||
HAL_Delay(250);
|
HAL_Delay(250);
|
||||||
|
if (usbHandler.isFaultAckReceived()) {
|
||||||
|
system_emergency_state = false;
|
||||||
|
usbHandler.clearFaultAck();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
DIAG("SYS", "Exited safe mode blink loop -- system_emergency_state cleared");
|
DIAG("SYS", "Exited safe mode blink loop -- system_emergency_state cleared");
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -70,7 +70,8 @@ TESTS_STANDALONE := test_bug12_pa_cal_loop_inverted \
|
|||||||
test_gap3_idq_periodic_reread \
|
test_gap3_idq_periodic_reread \
|
||||||
test_gap3_emergency_state_ordering \
|
test_gap3_emergency_state_ordering \
|
||||||
test_gap3_overtemp_emergency_stop \
|
test_gap3_overtemp_emergency_stop \
|
||||||
test_gap3_health_watchdog_cold_start
|
test_gap3_health_watchdog_cold_start \
|
||||||
|
test_gap3_fault_ack_clears_emergency
|
||||||
|
|
||||||
# Tests that need platform_noos_stm32.o + mocks
|
# Tests that need platform_noos_stm32.o + mocks
|
||||||
TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
|
TESTS_WITH_PLATFORM := test_bug11_platform_spi_transmit_only
|
||||||
@@ -178,6 +179,9 @@ test_gap3_overtemp_emergency_stop: test_gap3_overtemp_emergency_stop.c
|
|||||||
test_gap3_health_watchdog_cold_start: test_gap3_health_watchdog_cold_start.c
|
test_gap3_health_watchdog_cold_start: test_gap3_health_watchdog_cold_start.c
|
||||||
$(CC) $(CFLAGS) $< -o $@
|
$(CC) $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
|
test_gap3_fault_ack_clears_emergency: test_gap3_fault_ack_clears_emergency.c
|
||||||
|
$(CC) $(CFLAGS) $< -o $@
|
||||||
|
|
||||||
# Tests that need platform_noos_stm32.o + mocks
|
# Tests that need platform_noos_stm32.o + mocks
|
||||||
$(TESTS_WITH_PLATFORM): %: %.c $(MOCK_OBJS) $(PLATFORM_OBJ)
|
$(TESTS_WITH_PLATFORM): %: %.c $(MOCK_OBJS) $(PLATFORM_OBJ)
|
||||||
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) $(PLATFORM_OBJ) -o $@
|
$(CC) $(CFLAGS) $(INCLUDES) $< $(MOCK_OBJS) $(PLATFORM_OBJ) -o $@
|
||||||
|
|||||||
@@ -406,11 +406,3 @@ static int mock_spi_init_stub(void) { return 0; }
|
|||||||
const struct no_os_spi_platform_ops stm32_spi_ops = {
|
const struct no_os_spi_platform_ops stm32_spi_ops = {
|
||||||
.init = mock_spi_init_stub,
|
.init = mock_spi_init_stub,
|
||||||
};
|
};
|
||||||
|
|
||||||
/* ========================= CMSIS-Core stub storage ======================= */
|
|
||||||
/* See stm32_hal_mock.h for rationale. SystemCoreClock = 0 forces delayUs() to
|
|
||||||
* return immediately under host test builds. DWT->CTRL pre-enabled so the
|
|
||||||
* one-time-init branch is skipped deterministically. */
|
|
||||||
struct _DWT_Mock_Type _dwt_mock = { .CTRL = DWT_CTRL_CYCCNTENA_Msk, .CYCCNT = 0 };
|
|
||||||
struct _CoreDebug_Mock_Type _coredebug_mock = { .DEMCR = 0 };
|
|
||||||
uint32_t SystemCoreClock = 0U;
|
|
||||||
|
|||||||
@@ -242,26 +242,6 @@ uint8_t ADS7830_Measure_SingleEnded(ADC_HandleTypeDef *hadc, uint8_t channel);
|
|||||||
* if desired via a global flag. */
|
* if desired via a global flag. */
|
||||||
extern int mock_printf_enabled;
|
extern int mock_printf_enabled;
|
||||||
|
|
||||||
/* ========================= CMSIS-Core stubs ======================= */
|
|
||||||
/* Minimum surface to let F-4.7's DWT-based delayUs() in ADAR1000_Manager.cpp
|
|
||||||
* compile under the host mock build. SystemCoreClock is intentionally 0 so
|
|
||||||
* target = microseconds * (SystemCoreClock / 1000000) is also 0, making the
|
|
||||||
* busy-wait loop exit immediately regardless of argument. Pre-setting
|
|
||||||
* DWT->CTRL with CYCCNTENA also skips the one-time init branch. */
|
|
||||||
|
|
||||||
#define DWT_CTRL_CYCCNTENA_Msk (1UL << 0)
|
|
||||||
#define CoreDebug_DEMCR_TRCENA_Msk (1UL << 24)
|
|
||||||
|
|
||||||
struct _DWT_Mock_Type { uint32_t CTRL; uint32_t CYCCNT; };
|
|
||||||
struct _CoreDebug_Mock_Type { uint32_t DEMCR; };
|
|
||||||
|
|
||||||
extern struct _DWT_Mock_Type _dwt_mock;
|
|
||||||
extern struct _CoreDebug_Mock_Type _coredebug_mock;
|
|
||||||
extern uint32_t SystemCoreClock;
|
|
||||||
|
|
||||||
#define DWT (&_dwt_mock)
|
|
||||||
#define CoreDebug (&_coredebug_mock)
|
|
||||||
|
|
||||||
#ifdef __cplusplus
|
#ifdef __cplusplus
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -0,0 +1,121 @@
|
|||||||
|
/*******************************************************************************
|
||||||
|
* test_gap3_fault_ack_clears_emergency.c
|
||||||
|
*
|
||||||
|
* Verifies the FAULT_ACK clear path for system_emergency_state:
|
||||||
|
* - USBHandler detects exactly [0x40, 0x00, 0x00, 0x00] in a 4-byte packet
|
||||||
|
* - Detection is false-positive-free: larger packets (settings data) carrying
|
||||||
|
* the same bytes as a subsequence must NOT trigger the ack
|
||||||
|
* - Main-loop blink logic clears system_emergency_state on receipt
|
||||||
|
*
|
||||||
|
* Logic extracted from USBHandler.cpp + main.cpp to mirror the actual code
|
||||||
|
* paths without requiring HAL headers.
|
||||||
|
******************************************************************************/
|
||||||
|
#include <assert.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
|
||||||
|
/* ── Simulated USBHandler state ─────────────────────────────────────────── */
|
||||||
|
static bool fault_ack_received = false;
|
||||||
|
static volatile bool system_emergency_state = false;
|
||||||
|
|
||||||
|
static const uint8_t FAULT_ACK_SEQ[4] = {0x40, 0x00, 0x00, 0x00};
|
||||||
|
|
||||||
|
/* Mirrors USBHandler::processUSBData() detection logic */
|
||||||
|
static void sim_processUSBData(const uint8_t *data, uint32_t length)
|
||||||
|
{
|
||||||
|
if (data == NULL || length == 0) return;
|
||||||
|
if (length == 4 && memcmp(data, FAULT_ACK_SEQ, 4) == 0) {
|
||||||
|
fault_ack_received = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
/* (normal state machine omitted — not under test here) */
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Mirrors one iteration of the blink loop in main.cpp */
|
||||||
|
static void sim_blink_iteration(void)
|
||||||
|
{
|
||||||
|
/* HAL_GPIO_TogglePin + HAL_Delay omitted */
|
||||||
|
if (fault_ack_received) {
|
||||||
|
system_emergency_state = false;
|
||||||
|
fault_ack_received = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int main(void)
|
||||||
|
{
|
||||||
|
printf("=== Gap-3 FAULT_ACK clears system_emergency_state ===\n");
|
||||||
|
|
||||||
|
/* Test 1: exact 4-byte FAULT_ACK packet sets the flag */
|
||||||
|
printf(" Test 1: exact FAULT_ACK packet detected... ");
|
||||||
|
fault_ack_received = false;
|
||||||
|
const uint8_t ack_pkt[4] = {0x40, 0x00, 0x00, 0x00};
|
||||||
|
sim_processUSBData(ack_pkt, 4);
|
||||||
|
assert(fault_ack_received == true);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 2: flag cleared and system_emergency_state exits blink loop */
|
||||||
|
printf(" Test 2: blink loop exits on FAULT_ACK... ");
|
||||||
|
system_emergency_state = true;
|
||||||
|
fault_ack_received = true;
|
||||||
|
sim_blink_iteration();
|
||||||
|
assert(system_emergency_state == false);
|
||||||
|
assert(fault_ack_received == false);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 3: blink loop does NOT exit without ack */
|
||||||
|
printf(" Test 3: blink loop holds without ack... ");
|
||||||
|
system_emergency_state = true;
|
||||||
|
fault_ack_received = false;
|
||||||
|
sim_blink_iteration();
|
||||||
|
assert(system_emergency_state == true);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 4: settings-sized packet carrying [0x40,0x00,0x00,0x00] as first
|
||||||
|
* 4 bytes does NOT trigger ack (IEEE 754 double 2.0 = 0x4000000000000000) */
|
||||||
|
printf(" Test 4: settings packet with 2.0 double does not false-trigger... ");
|
||||||
|
fault_ack_received = false;
|
||||||
|
uint8_t settings_pkt[82];
|
||||||
|
memset(settings_pkt, 0, sizeof(settings_pkt));
|
||||||
|
/* First 4 bytes look like FAULT_ACK but packet length is 82 */
|
||||||
|
settings_pkt[0] = 0x40; settings_pkt[1] = 0x00;
|
||||||
|
settings_pkt[2] = 0x00; settings_pkt[3] = 0x00;
|
||||||
|
sim_processUSBData(settings_pkt, sizeof(settings_pkt));
|
||||||
|
assert(fault_ack_received == false);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 5: 3-byte packet (truncated) does not trigger */
|
||||||
|
printf(" Test 5: truncated 3-byte packet ignored... ");
|
||||||
|
fault_ack_received = false;
|
||||||
|
const uint8_t short_pkt[3] = {0x40, 0x00, 0x00};
|
||||||
|
sim_processUSBData(short_pkt, 3);
|
||||||
|
assert(fault_ack_received == false);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 6: wrong opcode byte in 4-byte packet does not trigger */
|
||||||
|
printf(" Test 6: wrong opcode (0x28 AGC_ENABLE) not detected as FAULT_ACK... ");
|
||||||
|
fault_ack_received = false;
|
||||||
|
const uint8_t agc_pkt[4] = {0x28, 0x00, 0x00, 0x01};
|
||||||
|
sim_processUSBData(agc_pkt, 4);
|
||||||
|
assert(fault_ack_received == false);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
/* Test 7: multiple blink iterations — loop stays active until ack */
|
||||||
|
printf(" Test 7: loop stays active across multiple iterations until ack... ");
|
||||||
|
system_emergency_state = true;
|
||||||
|
fault_ack_received = false;
|
||||||
|
sim_blink_iteration();
|
||||||
|
assert(system_emergency_state == true);
|
||||||
|
sim_blink_iteration();
|
||||||
|
assert(system_emergency_state == true);
|
||||||
|
/* Now ack arrives */
|
||||||
|
sim_processUSBData(ack_pkt, 4);
|
||||||
|
assert(fault_ack_received == true);
|
||||||
|
sim_blink_iteration();
|
||||||
|
assert(system_emergency_state == false);
|
||||||
|
printf("PASS\n");
|
||||||
|
|
||||||
|
printf("\n=== Gap-3 FAULT_ACK: ALL 7 TESTS PASSED ===\n\n");
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
@@ -4,11 +4,6 @@ module ad9484_interface_400m (
|
|||||||
input wire [7:0] adc_d_n, // ADC Data N
|
input wire [7:0] adc_d_n, // ADC Data N
|
||||||
input wire adc_dco_p, // Data Clock Output P (400MHz)
|
input wire adc_dco_p, // Data Clock Output P (400MHz)
|
||||||
input wire adc_dco_n, // Data Clock Output N (400MHz)
|
input wire adc_dco_n, // Data Clock Output N (400MHz)
|
||||||
// Audit F-0.1: AD9484 OR (overrange) LVDS pair, DDR like data.
|
|
||||||
// Routed on the 50T main board to bank 14 pins M6/N6. Asserts for any
|
|
||||||
// sample whose absolute value exceeds full-scale.
|
|
||||||
input wire adc_or_p,
|
|
||||||
input wire adc_or_n,
|
|
||||||
|
|
||||||
// System Interface
|
// System Interface
|
||||||
input wire sys_clk, // 100MHz system clock (for control only)
|
input wire sys_clk, // 100MHz system clock (for control only)
|
||||||
@@ -17,10 +12,7 @@ module ad9484_interface_400m (
|
|||||||
// Output at 400MHz domain
|
// Output at 400MHz domain
|
||||||
output wire [7:0] adc_data_400m, // ADC data at 400MHz
|
output wire [7:0] adc_data_400m, // ADC data at 400MHz
|
||||||
output wire adc_data_valid_400m, // Valid at 400MHz
|
output wire adc_data_valid_400m, // Valid at 400MHz
|
||||||
output wire adc_dco_bufg, // Buffered 400MHz DCO clock for downstream use
|
output wire adc_dco_bufg // Buffered 400MHz DCO clock for downstream use
|
||||||
// Audit F-0.1: OR flag, clk_400m domain. High on any sample in the
|
|
||||||
// current 400 MHz cycle where the ADC reports overrange.
|
|
||||||
output wire adc_overrange_400m
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// LVDS to single-ended conversion
|
// LVDS to single-ended conversion
|
||||||
@@ -174,54 +166,4 @@ end
|
|||||||
assign adc_data_400m = adc_data_400m_reg;
|
assign adc_data_400m = adc_data_400m_reg;
|
||||||
assign adc_data_valid_400m = adc_data_valid_400m_reg;
|
assign adc_data_valid_400m = adc_data_valid_400m_reg;
|
||||||
|
|
||||||
// ============================================================================
|
|
||||||
// Audit F-0.1: AD9484 OR (overrange) capture
|
|
||||||
// OR is a DDR LVDS pair (same as data). Buffer it, capture both edges with an
|
|
||||||
// IDDR in the BUFIO domain, then OR the two phases into a single clk_400m
|
|
||||||
// flag. Register once for stability. No latching — downstream is expected to
|
|
||||||
// stickify in its own domain.
|
|
||||||
// ============================================================================
|
|
||||||
wire adc_or_raw;
|
|
||||||
IBUFDS #(
|
|
||||||
.DIFF_TERM("FALSE"),
|
|
||||||
.IOSTANDARD("DEFAULT")
|
|
||||||
) ibufds_or (
|
|
||||||
.O(adc_or_raw),
|
|
||||||
.I(adc_or_p),
|
|
||||||
.IB(adc_or_n)
|
|
||||||
);
|
|
||||||
|
|
||||||
wire adc_or_rise;
|
|
||||||
wire adc_or_fall;
|
|
||||||
IDDR #(
|
|
||||||
.DDR_CLK_EDGE("SAME_EDGE_PIPELINED"),
|
|
||||||
.INIT_Q1(1'b0),
|
|
||||||
.INIT_Q2(1'b0),
|
|
||||||
.SRTYPE("SYNC")
|
|
||||||
) iddr_or (
|
|
||||||
.Q1(adc_or_rise),
|
|
||||||
.Q2(adc_or_fall),
|
|
||||||
.C(adc_dco_bufio),
|
|
||||||
.CE(1'b1),
|
|
||||||
.D(adc_or_raw),
|
|
||||||
.R(1'b0),
|
|
||||||
.S(1'b0)
|
|
||||||
);
|
|
||||||
|
|
||||||
reg adc_or_rise_bufg;
|
|
||||||
reg adc_or_fall_bufg;
|
|
||||||
always @(posedge adc_dco_buffered) begin
|
|
||||||
adc_or_rise_bufg <= adc_or_rise;
|
|
||||||
adc_or_fall_bufg <= adc_or_fall;
|
|
||||||
end
|
|
||||||
|
|
||||||
reg adc_overrange_r;
|
|
||||||
always @(posedge adc_dco_buffered or negedge reset_n_400m) begin
|
|
||||||
if (!reset_n_400m)
|
|
||||||
adc_overrange_r <= 1'b0;
|
|
||||||
else
|
|
||||||
adc_overrange_r <= adc_or_rise_bufg | adc_or_fall_bufg;
|
|
||||||
end
|
|
||||||
assign adc_overrange_400m = adc_overrange_r;
|
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
@@ -17,12 +17,7 @@ module cdc_adc_to_processing #(
|
|||||||
input wire [WIDTH-1:0] src_data,
|
input wire [WIDTH-1:0] src_data,
|
||||||
input wire src_valid,
|
input wire src_valid,
|
||||||
output wire [WIDTH-1:0] dst_data,
|
output wire [WIDTH-1:0] dst_data,
|
||||||
output wire dst_valid,
|
output wire dst_valid
|
||||||
// Audit F-1.2: overrun pulse in src_clk domain. Asserts for 1 src cycle
|
|
||||||
// whenever src_valid fires while the previous sample has not yet been
|
|
||||||
// acknowledged by the destination edge-detector (i.e., the transaction
|
|
||||||
// the CDC is silently dropping). Hold/count externally.
|
|
||||||
output wire overrun
|
|
||||||
`ifdef FORMAL
|
`ifdef FORMAL
|
||||||
,output wire [WIDTH-1:0] fv_src_data_reg,
|
,output wire [WIDTH-1:0] fv_src_data_reg,
|
||||||
output wire [1:0] fv_src_toggle
|
output wire [1:0] fv_src_toggle
|
||||||
@@ -135,36 +130,6 @@ module cdc_adc_to_processing #(
|
|||||||
assign dst_data = dst_data_reg;
|
assign dst_data = dst_data_reg;
|
||||||
assign dst_valid = dst_valid_reg;
|
assign dst_valid = dst_valid_reg;
|
||||||
|
|
||||||
// ------------------------------------------------------------------
|
|
||||||
// Audit F-1.2: overrun detection
|
|
||||||
//
|
|
||||||
// The src-side `src_toggle` counter flips on each latched src_valid.
|
|
||||||
// We feed back a 1-bit "ack" toggle from the dst domain (flipped each
|
|
||||||
// time dst_valid fires) through a STAGES-deep synchronizer into the
|
|
||||||
// src domain. If a new src_valid arrives while src_toggle[0] already
|
|
||||||
// differs from the acked value, the previous sample is still in flight
|
|
||||||
// and this new latch drops it. Emit a 1-cycle overrun pulse.
|
|
||||||
// ------------------------------------------------------------------
|
|
||||||
reg dst_ack_toggle;
|
|
||||||
always @(posedge dst_clk) begin
|
|
||||||
if (!dst_reset_n) dst_ack_toggle <= 1'b0;
|
|
||||||
else if (dst_valid_reg) dst_ack_toggle <= ~dst_ack_toggle;
|
|
||||||
end
|
|
||||||
|
|
||||||
(* ASYNC_REG = "TRUE" *) reg [STAGES-1:0] ack_sync_chain;
|
|
||||||
always @(posedge src_clk) begin
|
|
||||||
if (!src_reset_n) ack_sync_chain <= {STAGES{1'b0}};
|
|
||||||
else ack_sync_chain <= {ack_sync_chain[STAGES-2:0], dst_ack_toggle};
|
|
||||||
end
|
|
||||||
wire ack_in_src = ack_sync_chain[STAGES-1];
|
|
||||||
|
|
||||||
reg overrun_r;
|
|
||||||
always @(posedge src_clk) begin
|
|
||||||
if (!src_reset_n) overrun_r <= 1'b0;
|
|
||||||
else overrun_r <= src_valid && (src_toggle[0] != ack_in_src);
|
|
||||||
end
|
|
||||||
assign overrun = overrun_r;
|
|
||||||
|
|
||||||
`ifdef FORMAL
|
`ifdef FORMAL
|
||||||
assign fv_src_data_reg = src_data_reg;
|
assign fv_src_data_reg = src_data_reg;
|
||||||
assign fv_src_toggle = src_toggle;
|
assign fv_src_toggle = src_toggle;
|
||||||
|
|||||||
@@ -74,7 +74,7 @@ localparam COMB_WIDTH = 28;
|
|||||||
// DSP output) = 4 cycles at 400 MHz = 10 ns.
|
// DSP output) = 4 cycles at 400 MHz = 10 ns.
|
||||||
// Negligible vs system reset assertion duration.
|
// Negligible vs system reset assertion duration.
|
||||||
// ----------------------------------------------------------------------------
|
// ----------------------------------------------------------------------------
|
||||||
(* max_fanout = 25 *) reg reset_h = 1'b1; // INIT=1'b1: registers start in reset state on power-up
|
(* max_fanout = 50 *) reg reset_h = 1'b1; // INIT=1'b1: registers start in reset state on power-up
|
||||||
always @(posedge clk) reset_h <= ~reset_n;
|
always @(posedge clk) reset_h <= ~reset_n;
|
||||||
|
|
||||||
// Sign-extended input for integrator_0 C port (48-bit)
|
// Sign-extended input for integrator_0 C port (48-bit)
|
||||||
|
|||||||
@@ -33,10 +33,10 @@
|
|||||||
# (one period) to ensure the tools verify the transfer fits within one cycle
|
# (one period) to ensure the tools verify the transfer fits within one cycle
|
||||||
# without over-constraining with full inter-clock setup/hold analysis.
|
# without over-constraining with full inter-clock setup/hold analysis.
|
||||||
set_max_delay -datapath_only -from [get_clocks adc_dco_p] \
|
set_max_delay -datapath_only -from [get_clocks adc_dco_p] \
|
||||||
-to [get_clocks clk_mmcm_out0] 2.700
|
-to [get_clocks clk_mmcm_out0] 2.500
|
||||||
|
|
||||||
set_max_delay -datapath_only -from [get_clocks clk_mmcm_out0] \
|
set_max_delay -datapath_only -from [get_clocks clk_mmcm_out0] \
|
||||||
-to [get_clocks adc_dco_p] 2.700
|
-to [get_clocks adc_dco_p] 2.500
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# CDC: MMCM output domain ↔ other clock domains
|
# CDC: MMCM output domain ↔ other clock domains
|
||||||
@@ -47,12 +47,8 @@ set_max_delay -datapath_only -from [get_clocks clk_mmcm_out0] \
|
|||||||
set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_mmcm_out0]
|
set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_mmcm_out0]
|
||||||
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks clk_100m]
|
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks clk_100m]
|
||||||
|
|
||||||
# Audit F-0.6: the USB-domain clock name differs per board
|
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks ft601_clk_in]
|
||||||
# (50T: ft_clkout, 200T: ft601_clk_in). XDC files only support a
|
set_false_path -from [get_clocks ft601_clk_in] -to [get_clocks clk_mmcm_out0]
|
||||||
# restricted Tcl subset — `foreach`/`unset` trigger CRITICAL WARNING
|
|
||||||
# [Designutils 20-1307]. The clk_mmcm_out0 ↔ USB-clock false paths
|
|
||||||
# are declared in the per-board XDC (xc7a50t_ftg256.xdc and
|
|
||||||
# xc7a200t_fbg484.xdc) where the USB clock name is already known.
|
|
||||||
|
|
||||||
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks clk_120m_dac]
|
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks clk_120m_dac]
|
||||||
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_mmcm_out0]
|
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_mmcm_out0]
|
||||||
@@ -63,10 +59,7 @@ set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_mmcm_out0]
|
|||||||
# LOCKED is not a valid timing startpoint (it's a combinational output of the
|
# LOCKED is not a valid timing startpoint (it's a combinational output of the
|
||||||
# MMCM primitive). Use -through instead of -from to waive all paths that pass
|
# MMCM primitive). Use -through instead of -from to waive all paths that pass
|
||||||
# through the LOCKED net. This avoids the CRITICAL WARNING from Build 19/20.
|
# through the LOCKED net. This avoids the CRITICAL WARNING from Build 19/20.
|
||||||
# Audit F-0.7: the literal hierarchical path was missing the `u_core/`
|
set_false_path -through [get_pins rx_inst/adc/mmcm_inst/mmcm_adc_400m/LOCKED]
|
||||||
# prefix and silently matched no pins. Use a hierarchical wildcard to
|
|
||||||
# catch the MMCM LOCKED pin regardless of wrapper hierarchy.
|
|
||||||
set_false_path -through [get_pins -hierarchical -filter {REF_PIN_NAME == LOCKED}]
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Hold waiver for source-synchronous ADC capture (BUFIO-clocked IDDR)
|
# Hold waiver for source-synchronous ADC capture (BUFIO-clocked IDDR)
|
||||||
@@ -89,19 +82,14 @@ set_false_path -through [get_pins -hierarchical -filter {REF_PIN_NAME == LOCKED}
|
|||||||
#
|
#
|
||||||
# Waiving hold on these 8 paths (adc_d_p[0..7] → IDDR) is standard practice
|
# Waiving hold on these 8 paths (adc_d_p[0..7] → IDDR) is standard practice
|
||||||
# for source-synchronous LVDS ADC interfaces using BUFIO capture.
|
# for source-synchronous LVDS ADC interfaces using BUFIO capture.
|
||||||
# adc_or_p (AD9484 overrange, audit F-0.1) shares the same IBUFDS→BUFIO
|
set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p]
|
||||||
# source-synchronous capture topology as adc_d_p[*] — same ~1.9 ns STA hold
|
|
||||||
# violation for the same reason (BUFIO clock insertion ~4 ns vs data IBUFDS
|
|
||||||
# ~0.9 ns), resolved by the same external-timing argument.
|
|
||||||
set_false_path -hold -from [get_ports {adc_d_p[*] adc_or_p}] -to [get_clocks adc_dco_p]
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Timing margin for 400 MHz critical paths
|
# Timing margin for 400 MHz critical paths
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Extra setup uncertainty forces Vivado to leave margin for temperature/voltage/
|
# Extra setup uncertainty forces Vivado to leave margin for temperature/voltage/
|
||||||
# aging variation. 150 ps absolute covers the built-in jitter-based value
|
# aging variation. Reduced from 200 ps to 100 ps after NCO→mixer pipeline
|
||||||
# (~53 ps) plus ~100 ps temperature/voltage/aging guardband.
|
# register fix eliminated the dominant timing bottleneck (WNS went from +0.002ns
|
||||||
# NOTE: Vivado's set_clock_uncertainty does NOT accept -add; prior use of
|
# to comfortable margin). 100 ps still provides ~4% guardband on the 2.5ns period.
|
||||||
# -add 0.100 was silently rejected as a CRITICAL WARNING, so no guardband
|
# This is additive to the existing jitter-based uncertainty (~53 ps).
|
||||||
# was applied. Use an absolute value. (audit finding F-0.8)
|
set_clock_uncertainty -setup -add 0.100 [get_clocks clk_mmcm_out0]
|
||||||
set_clock_uncertainty -setup 0.150 [get_clocks clk_mmcm_out0]
|
|
||||||
|
|||||||
@@ -134,22 +134,6 @@ set_property IOSTANDARD LVDS_25 [get_ports {adc_d_p[*]}]
|
|||||||
set_property IOSTANDARD LVDS_25 [get_ports {adc_d_n[*]}]
|
set_property IOSTANDARD LVDS_25 [get_ports {adc_d_n[*]}]
|
||||||
set_property DIFF_TERM TRUE [get_ports {adc_d_p[*]}]
|
set_property DIFF_TERM TRUE [get_ports {adc_d_p[*]}]
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# Audit F-0.1: AD9484 OR (overrange) LVDS pair
|
|
||||||
# The 50T main board schematic routes ADC_OR_P/N to bank-14 pins M6/N6 on
|
|
||||||
# xc7a50t-ftg256. The 200T dev-board schematic has NOT been checked yet;
|
|
||||||
# adc_or_p/n are declared as top-level ports so the 50T build anchors them
|
|
||||||
# cleanly, but the 200T anchor below is a TODO placeholder — synth/impl will
|
|
||||||
# error on unplaced IO until the 200T schematic is verified and the PACKAGE_PIN
|
|
||||||
# values are set. IOSTANDARD/DIFF_TERM properties stay as-is (same class as
|
|
||||||
# adc_d_p).
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
set_property IOSTANDARD LVDS_25 [get_ports {adc_or_p}]
|
|
||||||
set_property IOSTANDARD LVDS_25 [get_ports {adc_or_n}]
|
|
||||||
set_property DIFF_TERM TRUE [get_ports {adc_or_p}]
|
|
||||||
# TODO(F-0.1): set_property PACKAGE_PIN <?> [get_ports {adc_or_p}] after 200T schematic audit
|
|
||||||
# TODO(F-0.1): set_property PACKAGE_PIN <?> [get_ports {adc_or_n}] after 200T schematic audit
|
|
||||||
|
|
||||||
# ADC Power Down — single-ended, Bank 14 (LVCMOS25 matches bank VCCO)
|
# ADC Power Down — single-ended, Bank 14 (LVCMOS25 matches bank VCCO)
|
||||||
# Pin: P20 = IO_0_14
|
# Pin: P20 = IO_0_14
|
||||||
set_property PACKAGE_PIN P20 [get_ports {adc_pwdn}]
|
set_property PACKAGE_PIN P20 [get_ports {adc_pwdn}]
|
||||||
@@ -637,10 +621,6 @@ set_false_path -from [get_clocks ft601_clk_in] -to [get_clocks clk_120m_dac]
|
|||||||
set_false_path -from [get_clocks adc_dco_p] -to [get_clocks ft601_clk_in]
|
set_false_path -from [get_clocks adc_dco_p] -to [get_clocks ft601_clk_in]
|
||||||
set_false_path -from [get_clocks ft601_clk_in] -to [get_clocks adc_dco_p]
|
set_false_path -from [get_clocks ft601_clk_in] -to [get_clocks adc_dco_p]
|
||||||
|
|
||||||
# MMCM 400 MHz domain ↔ FT601 USB clock (see adc_clk_mmcm.xdc for rationale)
|
|
||||||
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks ft601_clk_in]
|
|
||||||
set_false_path -from [get_clocks ft601_clk_in] -to [get_clocks clk_mmcm_out0]
|
|
||||||
|
|
||||||
# Generated clock cross-domain paths:
|
# Generated clock cross-domain paths:
|
||||||
# dac_clk_fwd and ft601_clk_fwd are generated from their respective source
|
# dac_clk_fwd and ft601_clk_fwd are generated from their respective source
|
||||||
# clocks. Vivado automatically inherits the source clock false paths for
|
# clocks. Vivado automatically inherits the source clock false paths for
|
||||||
|
|||||||
@@ -107,15 +107,8 @@ set_property PACKAGE_PIN C4 [get_ports {ft_clkout}]
|
|||||||
set_property IOSTANDARD LVCMOS33 [get_ports {ft_clkout}]
|
set_property IOSTANDARD LVCMOS33 [get_ports {ft_clkout}]
|
||||||
create_clock -name ft_clkout -period 16.667 [get_ports {ft_clkout}]
|
create_clock -name ft_clkout -period 16.667 [get_ports {ft_clkout}]
|
||||||
set_input_jitter [get_clocks ft_clkout] 0.2
|
set_input_jitter [get_clocks ft_clkout] 0.2
|
||||||
# N-type MRCC pin requires dedicated route override (Place 30-876).
|
# N-type MRCC pin requires dedicated route override (Place 30-876)
|
||||||
# Audit F-0.4: the literal net name `ft_clkout_IBUF` exists post-synth but
|
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
|
||||||
# the XDC scan happens before synthesis, when the IBUF net does not yet
|
|
||||||
# exist — Vivado reported `No nets matched 'ft_clkout_IBUF'` + CRITICAL
|
|
||||||
# WARNING. Use -hierarchical -filter + -quiet so the constraint matches
|
|
||||||
# post-synth without warning during pre-synth XDC scan. The TCL duplicate
|
|
||||||
# at scripts/50t/build_50t.tcl:119 remains as belt-and-suspenders.
|
|
||||||
set_property -quiet CLOCK_DEDICATED_ROUTE FALSE \
|
|
||||||
[get_nets -quiet -hierarchical -filter {NAME =~ *ft_clkout_IBUF}]
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# RESET (Active-Low)
|
# RESET (Active-Low)
|
||||||
@@ -290,22 +283,6 @@ set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 [get_ports {adc_d_p[*]}]
|
|||||||
set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
|
set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
|
||||||
set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
|
set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
# Audit F-0.1: AD9484 OR (overrange) LVDS pair (Bank 14)
|
|
||||||
# Schematic RADAR_Main_Board.sch: ADC_OR_P → U42 IO_L19P_T3_A10_D26_14 (M6)
|
|
||||||
# ADC_OR_N → U42 IO_L19N_T3_A09_D25_VREF_14 (N6)
|
|
||||||
# DDR-sourced by adc_dco_p, same timing class as adc_d_p[*].
|
|
||||||
# --------------------------------------------------------------------------
|
|
||||||
set_property PACKAGE_PIN M6 [get_ports {adc_or_p}]
|
|
||||||
set_property PACKAGE_PIN N6 [get_ports {adc_or_n}]
|
|
||||||
set_property IOSTANDARD LVDS_25 [get_ports {adc_or_p}]
|
|
||||||
set_property IOSTANDARD LVDS_25 [get_ports {adc_or_n}]
|
|
||||||
set_property DIFF_TERM TRUE [get_ports {adc_or_p}]
|
|
||||||
set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 [get_ports {adc_or_p}]
|
|
||||||
set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 [get_ports {adc_or_p}]
|
|
||||||
set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 -clock_fall [get_ports {adc_or_p}] -add_delay
|
|
||||||
set_input_delay -clock [get_clocks adc_dco_p] -min 0.2 -clock_fall [get_ports {adc_or_p}] -add_delay
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# FT2232H USB 2.0 INTERFACE (Bank 35, VCCO=3.3V)
|
# FT2232H USB 2.0 INTERFACE (Bank 35, VCCO=3.3V)
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
@@ -359,46 +336,40 @@ set_property DRIVE 8 [get_ports {ft_data[*]}]
|
|||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# FT2232H Source-Synchronous Timing Constraints
|
# FT2232H Source-Synchronous Timing Constraints
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# FT2232H 245 Synchronous FIFO mode timing (60 MHz, period = 16.667 ns).
|
# FT2232H 245 Synchronous FIFO mode timing (60 MHz, period = 16.667 ns):
|
||||||
# Values per FTDI TN_167 "FT2232H Synchronous FIFO Bus Bridge" — verify
|
|
||||||
# against the exact app-note revision before shipping.
|
|
||||||
#
|
#
|
||||||
# FPGA Read Path (FT2232H drives data/RXF#/TXE#, FPGA samples on CLKOUT↑):
|
# FPGA Read Path (FT2232H drives data, FPGA samples):
|
||||||
# - t_co (CLKOUT↑ → data valid) max = 10.0 ns
|
# - Data valid before CLKOUT rising edge: t_vr(max) = 7.0 ns
|
||||||
# - t_coh (CLKOUT↑ → data hold) min = 0.5 ns
|
# - Data hold after CLKOUT rising edge: t_hr(min) = 0.0 ns
|
||||||
# - set_input_delay -max = t_co, -min = t_coh
|
# - Input delay max = period - t_vr = 16.667 - 7.0 = 9.667 ns
|
||||||
|
# - Input delay min = t_hr = 0.0 ns
|
||||||
#
|
#
|
||||||
# FPGA Write Path (FPGA drives data/WR#/RD#/OE#, FT2232H samples on CLKOUT↑):
|
# FPGA Write Path (FPGA drives data, FT2232H samples):
|
||||||
# - t_su (data setup before CLKOUT↑) min = 3.5 ns (NOT 5 ns — prior
|
# - Data setup before next CLKOUT rising: t_su = 5.0 ns
|
||||||
# constraint used a synthetic period-based back-calculation)
|
# - Data hold after CLKOUT rising: t_hd = 0.0 ns
|
||||||
# - t_h (data hold after CLKOUT↑) min = 1.0 ns (NOT 0 — a 0 ns hold
|
# - Output delay max = period - t_su = 16.667 - 5.0 = 11.667 ns
|
||||||
# constraint produced no hold check at all)
|
# - Output delay min = t_hd = 0.0 ns
|
||||||
# - set_output_delay -max = t_su, -min = -t_h (Vivado convention)
|
|
||||||
#
|
|
||||||
# Audit F-2026-04-20 Option B: the previous output_delay = 11.667 ns
|
|
||||||
# (= period − 5) over-constrained launch by ~8 ns vs the actual datasheet
|
|
||||||
# figure. Relaxing to 3.5 ns matches the chip's real setup requirement.
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
|
|
||||||
# Input delays: FT2232H → FPGA (data bus and status signals)
|
# Input delays: FT2232H → FPGA (data bus and status signals)
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -max 10.0 [get_ports {ft_data[*]}]
|
set_input_delay -clock [get_clocks ft_clkout] -max 9.667 [get_ports {ft_data[*]}]
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -min 0.5 [get_ports {ft_data[*]}]
|
set_input_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_data[*]}]
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -max 10.0 [get_ports {ft_rxf_n}]
|
set_input_delay -clock [get_clocks ft_clkout] -max 9.667 [get_ports {ft_rxf_n}]
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -min 0.5 [get_ports {ft_rxf_n}]
|
set_input_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_rxf_n}]
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -max 10.0 [get_ports {ft_txe_n}]
|
set_input_delay -clock [get_clocks ft_clkout] -max 9.667 [get_ports {ft_txe_n}]
|
||||||
set_input_delay -clock [get_clocks ft_clkout] -min 0.5 [get_ports {ft_txe_n}]
|
set_input_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_txe_n}]
|
||||||
|
|
||||||
# Output delays: FPGA → FT2232H (control strobes and data bus when writing)
|
# Output delays: FPGA → FT2232H (control strobes and data bus when writing)
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -max 3.5 [get_ports {ft_data[*]}]
|
set_output_delay -clock [get_clocks ft_clkout] -max 11.667 [get_ports {ft_data[*]}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -min -1.0 [get_ports {ft_data[*]}]
|
set_output_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_data[*]}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -max 3.5 [get_ports {ft_rd_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -max 11.667 [get_ports {ft_rd_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -min -1.0 [get_ports {ft_rd_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_rd_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -max 3.5 [get_ports {ft_wr_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -max 11.667 [get_ports {ft_wr_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -min -1.0 [get_ports {ft_wr_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_wr_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -max 3.5 [get_ports {ft_oe_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -max 11.667 [get_ports {ft_oe_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -min -1.0 [get_ports {ft_oe_n}]
|
set_output_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_oe_n}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -max 3.5 [get_ports {ft_siwu}]
|
set_output_delay -clock [get_clocks ft_clkout] -max 11.667 [get_ports {ft_siwu}]
|
||||||
set_output_delay -clock [get_clocks ft_clkout] -min -1.0 [get_ports {ft_siwu}]
|
set_output_delay -clock [get_clocks ft_clkout] -min 0.0 [get_ports {ft_siwu}]
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# STATUS / DEBUG OUTPUTS — NO PHYSICAL CONNECTIONS
|
# STATUS / DEBUG OUTPUTS — NO PHYSICAL CONNECTIONS
|
||||||
@@ -437,17 +408,7 @@ set_false_path -from [get_ports {stm32_mixers_enable}]
|
|||||||
# - Reset deassertion order is not functionally critical — all registers
|
# - Reset deassertion order is not functionally critical — all registers
|
||||||
# come out of reset within a few cycles of each other
|
# come out of reset within a few cycles of each other
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Audit F-0.5: the literal cell name `reset_sync_reg[*]` does not match any
|
set_false_path -from [get_cells reset_sync_reg[*]] -to [get_pins -filter {REF_PIN_NAME == CLR} -of_objects [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ REGISTER.*.*}]]
|
||||||
# cell in the post-synth netlist. The actual sync regs are
|
|
||||||
# `u_core/reset_sync_reg[0..1]`, `u_core/rx_inst/ddc/reset_sync_400m_reg[*]`,
|
|
||||||
# `u_core/gen_ft2232h.usb_inst/ft_reset_sync_reg[*]`, and peers under
|
|
||||||
# `u_core/reset_sync_120m_reg[*]`, `u_core/reset_sync_ft601_reg[*]`,
|
|
||||||
# `u_core/rx_inst/adc/reset_sync_400m_reg[*]`. The waiver below covers all
|
|
||||||
# of them by matching any register whose name contains `reset_sync`.
|
|
||||||
# Without this, STA runs recovery/removal on the fanout of each sync-chain
|
|
||||||
# output register (up to ~1000 loads pre-PR#113 replication).
|
|
||||||
set_false_path -from [get_cells -hierarchical -filter {NAME =~ *reset_sync*_reg*}] \
|
|
||||||
-to [get_pins -hierarchical -filter {REF_PIN_NAME == CLR || REF_PIN_NAME == PRE}]
|
|
||||||
|
|
||||||
# --------------------------------------------------------------------------
|
# --------------------------------------------------------------------------
|
||||||
# Clock Domain Crossing false paths
|
# Clock Domain Crossing false paths
|
||||||
@@ -469,10 +430,6 @@ set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_100m]
|
|||||||
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks ft_clkout]
|
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks ft_clkout]
|
||||||
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_120m_dac]
|
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_120m_dac]
|
||||||
|
|
||||||
# MMCM 400 MHz domain ↔ FT2232H USB clock (see adc_clk_mmcm.xdc for rationale)
|
|
||||||
set_false_path -from [get_clocks clk_mmcm_out0] -to [get_clocks ft_clkout]
|
|
||||||
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_mmcm_out0]
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# PHYSICAL CONSTRAINTS
|
# PHYSICAL CONSTRAINTS
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -25,10 +25,7 @@ module ddc_400m_enhanced (
|
|||||||
input wire reset_monitors,
|
input wire reset_monitors,
|
||||||
output wire [31:0] debug_sample_count,
|
output wire [31:0] debug_sample_count,
|
||||||
output wire [17:0] debug_internal_i,
|
output wire [17:0] debug_internal_i,
|
||||||
output wire [17:0] debug_internal_q,
|
output wire [17:0] debug_internal_q
|
||||||
// Audit F-1.2: sticky CIC→FIR CDC overrun flag (clk_400m domain). Goes
|
|
||||||
// high on the first dropped sample and stays high until reset_monitors.
|
|
||||||
output wire cdc_cic_fir_overrun
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Parameters for numerical precision
|
// Parameters for numerical precision
|
||||||
@@ -151,20 +148,22 @@ always @(posedge clk_400m or negedge reset_n) begin
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
// CDC synchronization for control signals (2-stage synchronizers).
|
// CDC synchronization for control signals (2-stage synchronizers)
|
||||||
// Audit F-1.3: the mixers_enable synchronizer was dead — its _sync output
|
(* ASYNC_REG = "TRUE" *) reg [1:0] mixers_enable_sync_chain;
|
||||||
// was never consumed (the NCO phase_valid uses the raw port), and the only
|
|
||||||
// caller (radar_receiver_final.v) ties the port to 1'b1. Removed.
|
|
||||||
(* ASYNC_REG = "TRUE" *) reg [1:0] force_saturation_sync_chain;
|
(* ASYNC_REG = "TRUE" *) reg [1:0] force_saturation_sync_chain;
|
||||||
|
wire mixers_enable_sync;
|
||||||
wire force_saturation_sync;
|
wire force_saturation_sync;
|
||||||
|
assign mixers_enable_sync = mixers_enable_sync_chain[1];
|
||||||
assign force_saturation_sync = force_saturation_sync_chain[1];
|
assign force_saturation_sync = force_saturation_sync_chain[1];
|
||||||
|
|
||||||
// Sync reset via reset_400m (replicated, max_fanout=50). Was async on
|
// Sync reset via reset_400m (replicated, max_fanout=50). Was async on
|
||||||
// reset_n_400m — see "400 MHz RESET DISTRIBUTION" comment above.
|
// reset_n_400m — see "400 MHz RESET DISTRIBUTION" comment above.
|
||||||
always @(posedge clk_400m) begin
|
always @(posedge clk_400m) begin
|
||||||
if (reset_400m) begin
|
if (reset_400m) begin
|
||||||
|
mixers_enable_sync_chain <= 2'b00;
|
||||||
force_saturation_sync_chain <= 2'b00;
|
force_saturation_sync_chain <= 2'b00;
|
||||||
end else begin
|
end else begin
|
||||||
|
mixers_enable_sync_chain <= {mixers_enable_sync_chain[0], mixers_enable};
|
||||||
force_saturation_sync_chain <= {force_saturation_sync_chain[0], force_saturation};
|
force_saturation_sync_chain <= {force_saturation_sync_chain[0], force_saturation};
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
@@ -602,9 +601,6 @@ wire fir_in_valid_i, fir_in_valid_q;
|
|||||||
wire fir_valid_i, fir_valid_q;
|
wire fir_valid_i, fir_valid_q;
|
||||||
wire fir_i_ready, fir_q_ready;
|
wire fir_i_ready, fir_q_ready;
|
||||||
wire [17:0] fir_d_in_i, fir_d_in_q;
|
wire [17:0] fir_d_in_i, fir_d_in_q;
|
||||||
// Audit F-1.2: per-lane CIC→FIR CDC overrun pulses (clk_400m domain)
|
|
||||||
wire cdc_fir_i_overrun;
|
|
||||||
wire cdc_fir_q_overrun;
|
|
||||||
|
|
||||||
cdc_adc_to_processing #(
|
cdc_adc_to_processing #(
|
||||||
.WIDTH(18),
|
.WIDTH(18),
|
||||||
@@ -617,8 +613,7 @@ cdc_adc_to_processing #(
|
|||||||
.src_data(cic_i_out),
|
.src_data(cic_i_out),
|
||||||
.src_valid(cic_valid_i),
|
.src_valid(cic_valid_i),
|
||||||
.dst_data(fir_d_in_i),
|
.dst_data(fir_d_in_i),
|
||||||
.dst_valid(fir_in_valid_i),
|
.dst_valid(fir_in_valid_i)
|
||||||
.overrun(cdc_fir_i_overrun)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
cdc_adc_to_processing #(
|
cdc_adc_to_processing #(
|
||||||
@@ -632,30 +627,13 @@ cdc_adc_to_processing #(
|
|||||||
.src_data(cic_q_out),
|
.src_data(cic_q_out),
|
||||||
.src_valid(cic_valid_q),
|
.src_valid(cic_valid_q),
|
||||||
.dst_data(fir_d_in_q),
|
.dst_data(fir_d_in_q),
|
||||||
.dst_valid(fir_in_valid_q),
|
.dst_valid(fir_in_valid_q)
|
||||||
.overrun(cdc_fir_q_overrun)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Audit F-1.2: sticky-latch the two per-lane overrun pulses in the 400 MHz
|
|
||||||
// domain and expose a single module-level flag. Cleared only by
|
|
||||||
// reset_monitors (or reset_n via reset_400m), matching the other DDC
|
|
||||||
// diagnostic latches (overflow/saturation).
|
|
||||||
reg cdc_cic_fir_overrun_sticky;
|
|
||||||
always @(posedge clk_400m) begin
|
|
||||||
if (reset_400m || reset_monitors) cdc_cic_fir_overrun_sticky <= 1'b0;
|
|
||||||
else if (cdc_fir_i_overrun || cdc_fir_q_overrun) cdc_cic_fir_overrun_sticky <= 1'b1;
|
|
||||||
end
|
|
||||||
assign cdc_cic_fir_overrun = cdc_cic_fir_overrun_sticky;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// FIR Filter Instances
|
// FIR Filter Instances
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
// FIR overflow flags (audit F-6.2 — previously dangling, now OR'd into
|
|
||||||
// module-level filter_overflow so the receiver can see FIR arithmetic overflow)
|
|
||||||
wire fir_i_overflow;
|
|
||||||
wire fir_q_overflow;
|
|
||||||
|
|
||||||
// FIR I channel
|
// FIR I channel
|
||||||
fir_lowpass_parallel_enhanced fir_i_inst (
|
fir_lowpass_parallel_enhanced fir_i_inst (
|
||||||
.clk(clk_100m),
|
.clk(clk_100m),
|
||||||
@@ -665,7 +643,7 @@ fir_lowpass_parallel_enhanced fir_i_inst (
|
|||||||
.data_out(fir_i_out),
|
.data_out(fir_i_out),
|
||||||
.data_out_valid(fir_valid_i),
|
.data_out_valid(fir_valid_i),
|
||||||
.fir_ready(fir_i_ready),
|
.fir_ready(fir_i_ready),
|
||||||
.filter_overflow(fir_i_overflow)
|
.filter_overflow()
|
||||||
);
|
);
|
||||||
|
|
||||||
// FIR Q channel
|
// FIR Q channel
|
||||||
@@ -677,11 +655,10 @@ fir_lowpass_parallel_enhanced fir_q_inst (
|
|||||||
.data_out(fir_q_out),
|
.data_out(fir_q_out),
|
||||||
.data_out_valid(fir_valid_q),
|
.data_out_valid(fir_valid_q),
|
||||||
.fir_ready(fir_q_ready),
|
.fir_ready(fir_q_ready),
|
||||||
.filter_overflow(fir_q_overflow)
|
.filter_overflow()
|
||||||
);
|
);
|
||||||
|
|
||||||
assign fir_valid = fir_valid_i & fir_valid_q;
|
assign fir_valid = fir_valid_i & fir_valid_q;
|
||||||
assign filter_overflow = fir_i_overflow | fir_q_overflow;
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// Enhanced Output Stage
|
// Enhanced Output Stage
|
||||||
|
|||||||
@@ -58,12 +58,7 @@ module mti_canceller #(
|
|||||||
input wire mti_enable, // 1=MTI active, 0=pass-through
|
input wire mti_enable, // 1=MTI active, 0=pass-through
|
||||||
|
|
||||||
// ========== STATUS ==========
|
// ========== STATUS ==========
|
||||||
output reg mti_first_chirp, // 1 during first chirp (output muted)
|
output reg mti_first_chirp // 1 during first chirp (output muted)
|
||||||
|
|
||||||
// Audit F-6.3: count of saturated samples since last reset. Saturation
|
|
||||||
// here produces spurious Doppler harmonics (phantom targets at ±fs/2)
|
|
||||||
// and was previously invisible to the MCU. Saturates at 0xFF.
|
|
||||||
output reg [7:0] mti_saturation_count
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -109,11 +104,6 @@ assign diff_q_sat = (diff_q_full > $signed({{2{1'b0}}, {(DATA_WIDTH-1){1'b1}}}))
|
|||||||
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}})
|
? $signed({1'b1, {(DATA_WIDTH-1){1'b0}}})
|
||||||
: diff_q_full[DATA_WIDTH-1:0];
|
: diff_q_full[DATA_WIDTH-1:0];
|
||||||
|
|
||||||
// Saturation detection (F-6.3): the top two bits of the DATA_WIDTH+1 signed
|
|
||||||
// difference disagree iff the value exceeds the DATA_WIDTH signed range.
|
|
||||||
wire diff_i_overflow = (diff_i_full[DATA_WIDTH] != diff_i_full[DATA_WIDTH-1]);
|
|
||||||
wire diff_q_overflow = (diff_q_full[DATA_WIDTH] != diff_q_full[DATA_WIDTH-1]);
|
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
// MAIN LOGIC
|
// MAIN LOGIC
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -125,14 +115,7 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
range_bin_out <= 6'd0;
|
range_bin_out <= 6'd0;
|
||||||
has_previous <= 1'b0;
|
has_previous <= 1'b0;
|
||||||
mti_first_chirp <= 1'b1;
|
mti_first_chirp <= 1'b1;
|
||||||
mti_saturation_count <= 8'd0;
|
|
||||||
end else begin
|
end else begin
|
||||||
// Count saturated MTI-active samples (F-6.3). Clamp at 0xFF.
|
|
||||||
if (range_valid_in && mti_enable && has_previous
|
|
||||||
&& (diff_i_overflow || diff_q_overflow)
|
|
||||||
&& (mti_saturation_count != 8'hFF)) begin
|
|
||||||
mti_saturation_count <= mti_saturation_count + 8'd1;
|
|
||||||
end
|
|
||||||
// Default: no valid output
|
// Default: no valid output
|
||||||
range_valid_out <= 1'b0;
|
range_valid_out <= 1'b0;
|
||||||
|
|
||||||
|
|||||||
@@ -9,9 +9,6 @@ module radar_receiver_final (
|
|||||||
input wire [7:0] adc_d_n, // ADC Data N (LVDS)
|
input wire [7:0] adc_d_n, // ADC Data N (LVDS)
|
||||||
input wire adc_dco_p, // Data Clock Output P (400MHz LVDS)
|
input wire adc_dco_p, // Data Clock Output P (400MHz LVDS)
|
||||||
input wire adc_dco_n, // Data Clock Output N (400MHz LVDS)
|
input wire adc_dco_n, // Data Clock Output N (400MHz LVDS)
|
||||||
// Audit F-0.1: AD9484 OR (overrange) LVDS pair
|
|
||||||
input wire adc_or_p,
|
|
||||||
input wire adc_or_n,
|
|
||||||
output wire adc_pwdn,
|
output wire adc_pwdn,
|
||||||
|
|
||||||
// Chirp counter from transmitter (for matched filter indexing)
|
// Chirp counter from transmitter (for matched filter indexing)
|
||||||
@@ -77,28 +74,7 @@ module radar_receiver_final (
|
|||||||
// AGC status outputs (for status readback / STM32 outer loop)
|
// AGC status outputs (for status readback / STM32 outer loop)
|
||||||
output wire [7:0] agc_saturation_count, // Per-frame clipped sample count
|
output wire [7:0] agc_saturation_count, // Per-frame clipped sample count
|
||||||
output wire [7:0] agc_peak_magnitude, // Per-frame peak (upper 8 bits)
|
output wire [7:0] agc_peak_magnitude, // Per-frame peak (upper 8 bits)
|
||||||
output wire [3:0] agc_current_gain, // Effective gain_shift encoding
|
output wire [3:0] agc_current_gain // Effective gain_shift encoding
|
||||||
|
|
||||||
// DDC overflow diagnostics (audit F-6.1 — previously deleted at boundary).
|
|
||||||
// Not yet plumbed into the USB status packet (protocol contract is frozen);
|
|
||||||
// exposed here for gpio aggregation and ILA mark_debug visibility.
|
|
||||||
output wire ddc_overflow_any,
|
|
||||||
output wire [2:0] ddc_saturation_count,
|
|
||||||
|
|
||||||
// MTI 2-pulse canceller saturation count (audit F-6.3).
|
|
||||||
output wire [7:0] mti_saturation_count_out,
|
|
||||||
|
|
||||||
// Range-bin decimator watchdog (audit F-6.4 — previously tied off
|
|
||||||
// with an ILA-only note). A high pulse here means the decimator
|
|
||||||
// FSM has not seen the expected number of input samples within
|
|
||||||
// its timeout window, i.e. the upstream FIR/CDC has stalled.
|
|
||||||
output wire range_decim_watchdog,
|
|
||||||
|
|
||||||
// Audit F-1.2: sticky CIC→FIR CDC overrun flag. Asserts on the first
|
|
||||||
// silent sample drop between the 400 MHz CIC output and the 100 MHz
|
|
||||||
// FIR input; stays high until the next reset. OR'd into the GPIO
|
|
||||||
// diagnostic bit at the top level.
|
|
||||||
output wire ddc_cic_fir_overrun
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== INTERNAL SIGNALS ==========
|
// ========== INTERNAL SIGNALS ==========
|
||||||
@@ -209,43 +185,18 @@ wire adc_valid; // Data valid signal
|
|||||||
// ADC power-down control (directly tie low = ADC always on)
|
// ADC power-down control (directly tie low = ADC always on)
|
||||||
assign adc_pwdn = 1'b0;
|
assign adc_pwdn = 1'b0;
|
||||||
|
|
||||||
wire adc_overrange_400m;
|
|
||||||
ad9484_interface_400m adc (
|
ad9484_interface_400m adc (
|
||||||
.adc_d_p(adc_d_p),
|
.adc_d_p(adc_d_p),
|
||||||
.adc_d_n(adc_d_n),
|
.adc_d_n(adc_d_n),
|
||||||
.adc_dco_p(adc_dco_p),
|
.adc_dco_p(adc_dco_p),
|
||||||
.adc_dco_n(adc_dco_n),
|
.adc_dco_n(adc_dco_n),
|
||||||
.adc_or_p(adc_or_p),
|
|
||||||
.adc_or_n(adc_or_n),
|
|
||||||
.sys_clk(clk),
|
.sys_clk(clk),
|
||||||
.reset_n(reset_n),
|
.reset_n(reset_n),
|
||||||
.adc_data_400m(adc_data_cmos),
|
.adc_data_400m(adc_data_cmos),
|
||||||
.adc_data_valid_400m(adc_valid),
|
.adc_data_valid_400m(adc_valid),
|
||||||
.adc_dco_bufg(clk_400m),
|
.adc_dco_bufg(clk_400m)
|
||||||
.adc_overrange_400m(adc_overrange_400m)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Audit F-0.1: stickify the 400 MHz OR pulse, then CDC to clk_100m via 2FF.
|
|
||||||
// Same reasoning as ddc_cic_fir_overrun: single-bit, low→high-only once
|
|
||||||
// latched, so a 2FF sync is sufficient for a GPIO-class diagnostic. Cleared
|
|
||||||
// only by global reset_n.
|
|
||||||
reg adc_overrange_sticky_400m;
|
|
||||||
always @(posedge clk_400m or negedge reset_n) begin
|
|
||||||
if (!reset_n)
|
|
||||||
adc_overrange_sticky_400m <= 1'b0;
|
|
||||||
else if (adc_overrange_400m)
|
|
||||||
adc_overrange_sticky_400m <= 1'b1;
|
|
||||||
end
|
|
||||||
|
|
||||||
(* ASYNC_REG = "TRUE" *) reg [1:0] adc_overrange_sync_100m;
|
|
||||||
always @(posedge clk or negedge reset_n) begin
|
|
||||||
if (!reset_n)
|
|
||||||
adc_overrange_sync_100m <= 2'b00;
|
|
||||||
else
|
|
||||||
adc_overrange_sync_100m <= {adc_overrange_sync_100m[0], adc_overrange_sticky_400m};
|
|
||||||
end
|
|
||||||
wire adc_overrange_100m = adc_overrange_sync_100m[1];
|
|
||||||
|
|
||||||
// NOTE: The cdc_adc_to_processing instance that was here used src_clk=dst_clk=clk_400m
|
// NOTE: The cdc_adc_to_processing instance that was here used src_clk=dst_clk=clk_400m
|
||||||
// (same clock domain — no crossing). Gray-code CDC on same-clock with fast-changing
|
// (same clock domain — no crossing). Gray-code CDC on same-clock with fast-changing
|
||||||
// ADC data corrupts samples because Gray coding only guarantees safe transfer of
|
// ADC data corrupts samples because Gray coding only guarantees safe transfer of
|
||||||
@@ -260,16 +211,6 @@ wire signed [17:0] ddc_out_q;
|
|||||||
wire ddc_valid_i;
|
wire ddc_valid_i;
|
||||||
wire ddc_valid_q;
|
wire ddc_valid_q;
|
||||||
|
|
||||||
// DDC diagnostic signals (audit F-6.1 — all outputs previously unconnected)
|
|
||||||
wire [1:0] ddc_status_w;
|
|
||||||
wire [7:0] ddc_diagnostics_w;
|
|
||||||
wire ddc_mixer_saturation;
|
|
||||||
wire ddc_filter_overflow;
|
|
||||||
|
|
||||||
(* mark_debug = "true" *) wire ddc_mixer_saturation_dbg = ddc_mixer_saturation;
|
|
||||||
(* mark_debug = "true" *) wire ddc_filter_overflow_dbg = ddc_filter_overflow;
|
|
||||||
(* mark_debug = "true" *) wire [7:0] ddc_diagnostics_dbg = ddc_diagnostics_w;
|
|
||||||
|
|
||||||
ddc_400m_enhanced ddc(
|
ddc_400m_enhanced ddc(
|
||||||
.clk_400m(clk_400m), // 400MHz clock from ADC DCO
|
.clk_400m(clk_400m), // 400MHz clock from ADC DCO
|
||||||
.clk_100m(clk), // 100MHz system clock //used by the 2 FIR
|
.clk_100m(clk), // 100MHz system clock //used by the 2 FIR
|
||||||
@@ -281,28 +222,9 @@ ddc_400m_enhanced ddc(
|
|||||||
.baseband_q(ddc_out_q), // Q output at 100MHz
|
.baseband_q(ddc_out_q), // Q output at 100MHz
|
||||||
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
|
.baseband_valid_i(ddc_valid_i), // Valid at 100MHz
|
||||||
.baseband_valid_q(ddc_valid_q),
|
.baseband_valid_q(ddc_valid_q),
|
||||||
.mixers_enable(1'b1),
|
.mixers_enable(1'b1)
|
||||||
// Diagnostics (audit F-6.1) — previously all unconnected
|
|
||||||
.ddc_status(ddc_status_w),
|
|
||||||
.ddc_diagnostics(ddc_diagnostics_w),
|
|
||||||
.mixer_saturation(ddc_mixer_saturation),
|
|
||||||
.filter_overflow(ddc_filter_overflow),
|
|
||||||
// Test/debug inputs — explicit tie-low (were floating)
|
|
||||||
.test_mode(2'b00),
|
|
||||||
.test_phase_inc(16'h0000),
|
|
||||||
.force_saturation(1'b0),
|
|
||||||
.reset_monitors(1'b0),
|
|
||||||
.debug_sample_count(),
|
|
||||||
.debug_internal_i(),
|
|
||||||
.debug_internal_q(),
|
|
||||||
.cdc_cic_fir_overrun(ddc_cic_fir_overrun)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Audit F-0.1: AD9484 overrange aggregated here so a single gpio_dig bit
|
|
||||||
// covers DDC-internal saturation, FIR overflow, AND raw ADC clipping.
|
|
||||||
assign ddc_overflow_any = ddc_mixer_saturation | ddc_filter_overflow | adc_overrange_100m;
|
|
||||||
assign ddc_saturation_count = ddc_diagnostics_w[7:5];
|
|
||||||
|
|
||||||
ddc_input_interface ddc_if (
|
ddc_input_interface ddc_if (
|
||||||
.clk(clk),
|
.clk(clk),
|
||||||
.reset_n(reset_n),
|
.reset_n(reset_n),
|
||||||
@@ -447,7 +369,7 @@ range_bin_decimator #(
|
|||||||
.range_bin_index(decimated_range_bin),
|
.range_bin_index(decimated_range_bin),
|
||||||
.decimation_mode(2'b01), // Peak detection mode
|
.decimation_mode(2'b01), // Peak detection mode
|
||||||
.start_bin(10'd0),
|
.start_bin(10'd0),
|
||||||
.watchdog_timeout(range_decim_watchdog) // Audit F-6.4 — plumbed out
|
.watchdog_timeout() // Diagnostic — unconnected (monitored via ILA if needed)
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== MTI CANCELLER (Ground Clutter Removal) ==========
|
// ========== MTI CANCELLER (Ground Clutter Removal) ==========
|
||||||
@@ -469,8 +391,7 @@ mti_canceller #(
|
|||||||
.range_valid_out(mti_range_valid),
|
.range_valid_out(mti_range_valid),
|
||||||
.range_bin_out(mti_range_bin),
|
.range_bin_out(mti_range_bin),
|
||||||
.mti_enable(host_mti_enable),
|
.mti_enable(host_mti_enable),
|
||||||
.mti_first_chirp(mti_first_chirp),
|
.mti_first_chirp(mti_first_chirp)
|
||||||
.mti_saturation_count(mti_saturation_count_out)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ========== FRAME SYNC FROM TRANSMITTER ==========
|
// ========== FRAME SYNC FROM TRANSMITTER ==========
|
||||||
|
|||||||
@@ -67,9 +67,6 @@ module radar_system_top (
|
|||||||
input wire [7:0] adc_d_n, // ADC Data N (LVDS)
|
input wire [7:0] adc_d_n, // ADC Data N (LVDS)
|
||||||
input wire adc_dco_p, // Data Clock Output P (400MHz LVDS)
|
input wire adc_dco_p, // Data Clock Output P (400MHz LVDS)
|
||||||
input wire adc_dco_n, // Data Clock Output N (400MHz LVDS)
|
input wire adc_dco_n, // Data Clock Output N (400MHz LVDS)
|
||||||
// Audit F-0.1: AD9484 OR (overrange) LVDS pair
|
|
||||||
input wire adc_or_p,
|
|
||||||
input wire adc_or_n,
|
|
||||||
output wire adc_pwdn, // ADC Power Down
|
output wire adc_pwdn, // ADC Power Down
|
||||||
|
|
||||||
// ========== STM32 CONTROL INTERFACES ==========
|
// ========== STM32 CONTROL INTERFACES ==========
|
||||||
@@ -201,19 +198,6 @@ wire [7:0] rx_agc_saturation_count;
|
|||||||
wire [7:0] rx_agc_peak_magnitude;
|
wire [7:0] rx_agc_peak_magnitude;
|
||||||
wire [3:0] rx_agc_current_gain;
|
wire [3:0] rx_agc_current_gain;
|
||||||
|
|
||||||
// DDC overflow diagnostics (audit F-6.1) — plumbed out of receiver so the
|
|
||||||
// DDC mixer_saturation / filter_overflow ports are no longer deleted at
|
|
||||||
// the boundary. Aggregated into gpio_dig5 alongside AGC saturation.
|
|
||||||
wire rx_ddc_overflow_any;
|
|
||||||
wire [2:0] rx_ddc_saturation_count;
|
|
||||||
// MTI saturation count (audit F-6.3). OR'd into gpio_dig5 for MCU visibility.
|
|
||||||
wire [7:0] rx_mti_saturation_count;
|
|
||||||
// Range-bin decimator watchdog (audit F-6.4). High = decimator stalled.
|
|
||||||
wire rx_range_decim_watchdog;
|
|
||||||
// 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.
|
|
||||||
wire rx_ddc_cic_fir_overrun;
|
|
||||||
|
|
||||||
// Data packing for USB
|
// Data packing for USB
|
||||||
wire [31:0] usb_range_profile;
|
wire [31:0] usb_range_profile;
|
||||||
wire usb_range_valid;
|
wire usb_range_valid;
|
||||||
@@ -529,8 +513,6 @@ radar_receiver_final rx_inst (
|
|||||||
.adc_d_n(adc_d_n),
|
.adc_d_n(adc_d_n),
|
||||||
.adc_dco_p(adc_dco_p),
|
.adc_dco_p(adc_dco_p),
|
||||||
.adc_dco_n(adc_dco_n),
|
.adc_dco_n(adc_dco_n),
|
||||||
.adc_or_p(adc_or_p),
|
|
||||||
.adc_or_n(adc_or_n),
|
|
||||||
.adc_pwdn(adc_pwdn),
|
.adc_pwdn(adc_pwdn),
|
||||||
|
|
||||||
// Doppler Outputs
|
// Doppler Outputs
|
||||||
@@ -580,15 +562,7 @@ radar_receiver_final rx_inst (
|
|||||||
// AGC status outputs
|
// AGC status outputs
|
||||||
.agc_saturation_count(rx_agc_saturation_count),
|
.agc_saturation_count(rx_agc_saturation_count),
|
||||||
.agc_peak_magnitude(rx_agc_peak_magnitude),
|
.agc_peak_magnitude(rx_agc_peak_magnitude),
|
||||||
.agc_current_gain(rx_agc_current_gain),
|
.agc_current_gain(rx_agc_current_gain)
|
||||||
// DDC overflow diagnostics (audit F-6.1)
|
|
||||||
.ddc_overflow_any(rx_ddc_overflow_any),
|
|
||||||
.ddc_saturation_count(rx_ddc_saturation_count),
|
|
||||||
// MTI saturation count (audit F-6.3)
|
|
||||||
.mti_saturation_count_out(rx_mti_saturation_count),
|
|
||||||
// Range-bin decimator watchdog (audit F-6.4)
|
|
||||||
.range_decim_watchdog(rx_range_decim_watchdog),
|
|
||||||
.ddc_cic_fir_overrun(rx_ddc_cic_fir_overrun)
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
@@ -897,19 +871,6 @@ endgenerate
|
|||||||
// we simply sample them in clk_100m when the CDC'd pulse arrives.
|
// we simply sample them in clk_100m when the CDC'd pulse arrives.
|
||||||
|
|
||||||
// Step 1: Toggle on cmd_valid pulse (ft601_clk domain)
|
// Step 1: Toggle on cmd_valid pulse (ft601_clk domain)
|
||||||
//
|
|
||||||
// CDC INVARIANT (audit F-1.1): usb_cmd_opcode / usb_cmd_addr / usb_cmd_value
|
|
||||||
// / usb_cmd_data MUST be driven to their final values BEFORE usb_cmd_valid
|
|
||||||
// asserts, and held stable for at least (STAGES + 1) clk_100m cycles after
|
|
||||||
// (i.e., until cmd_valid_100m has pulsed in the destination domain). These
|
|
||||||
// buses cross from ft601_clk to clk_100m as quasi-static data, NOT through
|
|
||||||
// a synchronizer — only the toggle bit above is CDC'd. If a future edit
|
|
||||||
// moves the cmd_* register write to the SAME cycle as the toggle flip, or
|
|
||||||
// drops the stability hold, the clk_100m sampler at the command decoder
|
|
||||||
// will latch metastable bits and dispatch on a garbage opcode.
|
|
||||||
// The source-side FSM in usb_data_interface_ft2232h.v / usb_data_interface.v
|
|
||||||
// currently satisfies this by assigning the cmd_* buses several cycles
|
|
||||||
// before pulsing cmd_valid and leaving them stable until the next command.
|
|
||||||
reg cmd_valid_toggle_ft601;
|
reg cmd_valid_toggle_ft601;
|
||||||
always @(posedge ft601_clk_buf or negedge sys_reset_ft601_n) begin
|
always @(posedge ft601_clk_buf or negedge sys_reset_ft601_n) begin
|
||||||
if (!sys_reset_ft601_n)
|
if (!sys_reset_ft601_n)
|
||||||
@@ -1079,15 +1040,7 @@ assign system_status = status_reg;
|
|||||||
// DIG_6: AGC enable flag — mirrors host_agc_enable so STM32 outer-loop AGC
|
// DIG_6: AGC enable flag — mirrors host_agc_enable so STM32 outer-loop AGC
|
||||||
// tracks the FPGA register as single source of truth.
|
// tracks the FPGA register as single source of truth.
|
||||||
// DIG_7: Reserved (tied low for future use).
|
// DIG_7: Reserved (tied low for future use).
|
||||||
// gpio_dig5: "signal-chain clipped" — asserts on AGC saturation, DDC mixer/FIR
|
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0);
|
||||||
// overflow, or MTI 2-pulse saturation. Audit F-6.1/F-6.3: these were all
|
|
||||||
// previously invisible to the MCU.
|
|
||||||
assign gpio_dig5 = (rx_agc_saturation_count != 8'd0)
|
|
||||||
| rx_ddc_overflow_any
|
|
||||||
| (rx_ddc_saturation_count != 3'd0)
|
|
||||||
| (rx_mti_saturation_count != 8'd0)
|
|
||||||
| rx_range_decim_watchdog // audit F-6.4
|
|
||||||
| rx_ddc_cic_fir_overrun; // audit F-1.2
|
|
||||||
assign gpio_dig6 = host_agc_enable;
|
assign gpio_dig6 = host_agc_enable;
|
||||||
assign gpio_dig7 = 1'b0;
|
assign gpio_dig7 = 1'b0;
|
||||||
|
|
||||||
|
|||||||
@@ -60,8 +60,6 @@ module radar_system_top_50t (
|
|||||||
input wire [7:0] adc_d_n,
|
input wire [7:0] adc_d_n,
|
||||||
input wire adc_dco_p,
|
input wire adc_dco_p,
|
||||||
input wire adc_dco_n,
|
input wire adc_dco_n,
|
||||||
input wire adc_or_p,
|
|
||||||
input wire adc_or_n,
|
|
||||||
output wire adc_pwdn,
|
output wire adc_pwdn,
|
||||||
|
|
||||||
// ===== STM32 Control (Bank 15: 3.3V) =====
|
// ===== STM32 Control (Bank 15: 3.3V) =====
|
||||||
@@ -173,8 +171,6 @@ module radar_system_top_50t (
|
|||||||
.adc_d_n (adc_d_n),
|
.adc_d_n (adc_d_n),
|
||||||
.adc_dco_p (adc_dco_p),
|
.adc_dco_p (adc_dco_p),
|
||||||
.adc_dco_n (adc_dco_n),
|
.adc_dco_n (adc_dco_n),
|
||||||
.adc_or_p (adc_or_p),
|
|
||||||
.adc_or_n (adc_or_n),
|
|
||||||
.adc_pwdn (adc_pwdn),
|
.adc_pwdn (adc_pwdn),
|
||||||
|
|
||||||
// ----- STM32 Control -----
|
// ----- STM32 Control -----
|
||||||
|
|||||||
@@ -169,11 +169,11 @@ endfunction
|
|||||||
// =========================================================================
|
// =========================================================================
|
||||||
// Clamp a wider signed value to [-7, +7]
|
// Clamp a wider signed value to [-7, +7]
|
||||||
function signed [3:0] clamp_gain;
|
function signed [3:0] clamp_gain;
|
||||||
input signed [4:0] val; // 5-bit to handle overflow from add
|
input signed [5:0] val; // 6-bit: covers [-22,+22] (max |gain|+step = 7+15)
|
||||||
begin
|
begin
|
||||||
if (val > 5'sd7)
|
if (val > 6'sd7)
|
||||||
clamp_gain = 4'sd7;
|
clamp_gain = 4'sd7;
|
||||||
else if (val < -5'sd7)
|
else if (val < -6'sd7)
|
||||||
clamp_gain = -4'sd7;
|
clamp_gain = -4'sd7;
|
||||||
else
|
else
|
||||||
clamp_gain = val[3:0];
|
clamp_gain = val[3:0];
|
||||||
@@ -246,15 +246,15 @@ always @(posedge clk or negedge reset_n) begin
|
|||||||
// Use inclusive counts/peaks (accounting for simultaneous valid_in)
|
// Use inclusive counts/peaks (accounting for simultaneous valid_in)
|
||||||
if (wire_frame_sat_incr || frame_sat_count > 8'd0) begin
|
if (wire_frame_sat_incr || frame_sat_count > 8'd0) begin
|
||||||
// Clipping detected: reduce gain immediately (attack)
|
// Clipping detected: reduce gain immediately (attack)
|
||||||
agc_gain <= clamp_gain($signed({agc_gain[3], agc_gain}) -
|
agc_gain <= clamp_gain($signed({agc_gain[3], agc_gain[3], agc_gain}) -
|
||||||
$signed({1'b0, agc_attack}));
|
$signed({2'b00, agc_attack}));
|
||||||
holdoff_counter <= agc_holdoff; // Reset holdoff
|
holdoff_counter <= agc_holdoff; // Reset holdoff
|
||||||
end else if ((wire_frame_peak_update ? max_iq[14:7] : frame_peak[14:7])
|
end else if ((wire_frame_peak_update ? max_iq[14:7] : frame_peak[14:7])
|
||||||
< agc_target) begin
|
< agc_target) begin
|
||||||
// Signal too weak: increase gain after holdoff expires
|
// Signal too weak: increase gain after holdoff expires
|
||||||
if (holdoff_counter == 4'd0) begin
|
if (holdoff_counter == 4'd0) begin
|
||||||
agc_gain <= clamp_gain($signed({agc_gain[3], agc_gain}) +
|
agc_gain <= clamp_gain($signed({agc_gain[3], agc_gain[3], agc_gain}) +
|
||||||
$signed({1'b0, agc_decay}));
|
$signed({2'b00, agc_decay}));
|
||||||
end else begin
|
end else begin
|
||||||
holdoff_counter <= holdoff_counter - 4'd1;
|
holdoff_counter <= holdoff_counter - 4'd1;
|
||||||
end
|
end
|
||||||
|
|||||||
@@ -19,10 +19,6 @@ module ad9484_interface_400m (
|
|||||||
input wire [7:0] adc_d_n,
|
input wire [7:0] adc_d_n,
|
||||||
input wire adc_dco_p,
|
input wire adc_dco_p,
|
||||||
input wire adc_dco_n,
|
input wire adc_dco_n,
|
||||||
// Audit F-0.1: AD9484 OR (overrange) LVDS pair — stub treats adc_or_p as
|
|
||||||
// the single-ended overrange flag, adc_or_n is ignored.
|
|
||||||
input wire adc_or_p,
|
|
||||||
input wire adc_or_n,
|
|
||||||
|
|
||||||
// System Interface
|
// System Interface
|
||||||
input wire sys_clk,
|
input wire sys_clk,
|
||||||
@@ -31,8 +27,7 @@ module ad9484_interface_400m (
|
|||||||
// Output at 400MHz domain
|
// Output at 400MHz domain
|
||||||
output wire [7:0] adc_data_400m,
|
output wire [7:0] adc_data_400m,
|
||||||
output wire adc_data_valid_400m,
|
output wire adc_data_valid_400m,
|
||||||
output wire adc_dco_bufg,
|
output wire adc_dco_bufg
|
||||||
output wire adc_overrange_400m
|
|
||||||
);
|
);
|
||||||
|
|
||||||
// Pass-through clock (no BUFG needed in simulation)
|
// Pass-through clock (no BUFG needed in simulation)
|
||||||
@@ -55,15 +50,4 @@ end
|
|||||||
assign adc_data_400m = adc_data_400m_reg;
|
assign adc_data_400m = adc_data_400m_reg;
|
||||||
assign adc_data_valid_400m = adc_data_valid_400m_reg;
|
assign adc_data_valid_400m = adc_data_valid_400m_reg;
|
||||||
|
|
||||||
// Audit F-0.1: 1-cycle pipeline of adc_or_p to match the real IDDR+register
|
|
||||||
// capture path. TB drives adc_or_p directly with the overrange flag.
|
|
||||||
reg adc_overrange_400m_reg;
|
|
||||||
always @(posedge adc_dco_p or negedge reset_n) begin
|
|
||||||
if (!reset_n)
|
|
||||||
adc_overrange_400m_reg <= 1'b0;
|
|
||||||
else
|
|
||||||
adc_overrange_400m_reg <= adc_or_p;
|
|
||||||
end
|
|
||||||
assign adc_overrange_400m = adc_overrange_400m_reg;
|
|
||||||
|
|
||||||
endmodule
|
endmodule
|
||||||
|
|||||||
@@ -487,8 +487,6 @@ radar_system_top #(
|
|||||||
.adc_d_n(adc_d_n),
|
.adc_d_n(adc_d_n),
|
||||||
.adc_dco_p(adc_dco_p),
|
.adc_dco_p(adc_dco_p),
|
||||||
.adc_dco_n(adc_dco_n),
|
.adc_dco_n(adc_dco_n),
|
||||||
.adc_or_p(1'b0),
|
|
||||||
.adc_or_n(1'b1),
|
|
||||||
.adc_pwdn(adc_pwdn),
|
.adc_pwdn(adc_pwdn),
|
||||||
|
|
||||||
// STM32 Control
|
// STM32 Control
|
||||||
|
|||||||
@@ -64,11 +64,9 @@ module tb_ddc_cosim;
|
|||||||
|
|
||||||
// Scenario selector (set via +define)
|
// Scenario selector (set via +define)
|
||||||
reg [255:0] scenario_name;
|
reg [255:0] scenario_name;
|
||||||
// Widened to 4 kbits (512 bytes) so fuzz-runner temp paths
|
reg [1023:0] hex_file_path;
|
||||||
// (e.g. /private/var/folders/.../pytest-of-...) fit without MSB truncation.
|
reg [1023:0] csv_out_path;
|
||||||
reg [4095:0] hex_file_path;
|
reg [1023:0] csv_cic_path;
|
||||||
reg [4095:0] csv_out_path;
|
|
||||||
reg [4095:0] csv_cic_path;
|
|
||||||
|
|
||||||
// ── Clock generation ──────────────────────────────────────
|
// ── Clock generation ──────────────────────────────────────
|
||||||
// 400 MHz clock
|
// 400 MHz clock
|
||||||
@@ -154,16 +152,7 @@ module tb_ddc_cosim;
|
|||||||
// ── Select scenario ───────────────────────────────────
|
// ── Select scenario ───────────────────────────────────
|
||||||
// Default to DC scenario for fastest validation
|
// Default to DC scenario for fastest validation
|
||||||
// Override with: +define+SCENARIO_SINGLE, +define+SCENARIO_MULTI, etc.
|
// Override with: +define+SCENARIO_SINGLE, +define+SCENARIO_MULTI, etc.
|
||||||
`ifdef SCENARIO_FUZZ
|
`ifdef SCENARIO_SINGLE
|
||||||
// Audit F-3.2: fuzz runner provides +hex and +csv paths plus a
|
|
||||||
// scenario tag. Any missing plusarg falls back to the DC vector.
|
|
||||||
if (!$value$plusargs("hex=%s", hex_file_path))
|
|
||||||
hex_file_path = "tb/cosim/adc_dc.hex";
|
|
||||||
if (!$value$plusargs("csv=%s", csv_out_path))
|
|
||||||
csv_out_path = "tb/cosim/rtl_bb_fuzz.csv";
|
|
||||||
if (!$value$plusargs("tag=%s", scenario_name))
|
|
||||||
scenario_name = "fuzz";
|
|
||||||
`elsif SCENARIO_SINGLE
|
|
||||||
hex_file_path = "tb/cosim/adc_single_target.hex";
|
hex_file_path = "tb/cosim/adc_single_target.hex";
|
||||||
csv_out_path = "tb/cosim/rtl_bb_single_target.csv";
|
csv_out_path = "tb/cosim/rtl_bb_single_target.csv";
|
||||||
scenario_name = "single_target";
|
scenario_name = "single_target";
|
||||||
|
|||||||
@@ -139,8 +139,6 @@ radar_receiver_final dut (
|
|||||||
// ADC "LVDS" -- stub treats adc_d_p as single-ended data
|
// ADC "LVDS" -- stub treats adc_d_p as single-ended data
|
||||||
.adc_d_p(adc_data),
|
.adc_d_p(adc_data),
|
||||||
.adc_d_n(~adc_data), // Complement (ignored by stub)
|
.adc_d_n(~adc_data), // Complement (ignored by stub)
|
||||||
.adc_or_p(1'b0), // F-0.1: no overrange stimulus in this TB
|
|
||||||
.adc_or_n(1'b1),
|
|
||||||
.adc_dco_p(clk_400m), // 400 MHz clock
|
.adc_dco_p(clk_400m), // 400 MHz clock
|
||||||
.adc_dco_n(~clk_400m), // Complement (ignored by stub)
|
.adc_dco_n(~clk_400m), // Complement (ignored by stub)
|
||||||
.adc_pwdn(),
|
.adc_pwdn(),
|
||||||
|
|||||||
@@ -427,8 +427,6 @@ radar_system_top #(
|
|||||||
.adc_d_n(adc_d_n),
|
.adc_d_n(adc_d_n),
|
||||||
.adc_dco_p(adc_dco_p),
|
.adc_dco_p(adc_dco_p),
|
||||||
.adc_dco_n(adc_dco_n),
|
.adc_dco_n(adc_dco_n),
|
||||||
.adc_or_p(1'b0),
|
|
||||||
.adc_or_n(1'b1),
|
|
||||||
.adc_pwdn(adc_pwdn),
|
.adc_pwdn(adc_pwdn),
|
||||||
|
|
||||||
.stm32_new_chirp(stm32_new_chirp),
|
.stm32_new_chirp(stm32_new_chirp),
|
||||||
@@ -940,106 +938,6 @@ initial begin
|
|||||||
|
|
||||||
$display("");
|
$display("");
|
||||||
|
|
||||||
// ================================================================
|
|
||||||
// GROUP 9B: Adversarial reset sweep (audit F-2.2)
|
|
||||||
// ================================================================
|
|
||||||
// Drive the same auto-scan pipeline, then inject reset at four distinct
|
|
||||||
// offsets relative to a known-good start of operation. For each offset
|
|
||||||
// the system must:
|
|
||||||
// (a) present system_status == 0 while held in reset
|
|
||||||
// (b) produce at least one additional new_chirp_frame within the
|
|
||||||
// observation window after reset release
|
|
||||||
// (c) advance obs_range_valid_count (confirms full DDC+MF chain resumes)
|
|
||||||
// The four offsets are chosen to hit mid-chirp, mid-listen, and around
|
|
||||||
// the short/long chirp boundary, which covers the interesting FSM and
|
|
||||||
// CDC transitions in the pipeline.
|
|
||||||
$display("--- Group 9B: Adversarial reset sweep (F-2.2) ---");
|
|
||||||
begin : reset_sweep
|
|
||||||
integer sweep_i;
|
|
||||||
integer sweep_baseline_range;
|
|
||||||
integer sweep_baseline_chirp;
|
|
||||||
integer sweep_offsets [0:3];
|
|
||||||
integer sweep_holds [0:3];
|
|
||||||
reg sweep_ok;
|
|
||||||
|
|
||||||
// Reset injection offsets (ns) after the last auto-scan reconfigure.
|
|
||||||
// 3 us / 7 us / 12 us / 18 us — sprayed across a short-chirp burst.
|
|
||||||
sweep_offsets[0] = 3000;
|
|
||||||
sweep_offsets[1] = 7000;
|
|
||||||
sweep_offsets[2] = 12000;
|
|
||||||
sweep_offsets[3] = 18000;
|
|
||||||
// Reset-assert durations mix short (~20 clk_100m) and long (~120)
|
|
||||||
sweep_holds[0] = 200;
|
|
||||||
sweep_holds[1] = 1200;
|
|
||||||
sweep_holds[2] = 400;
|
|
||||||
sweep_holds[3] = 800;
|
|
||||||
|
|
||||||
for (sweep_i = 0; sweep_i < 4; sweep_i = sweep_i + 1) begin
|
|
||||||
// Re-seed auto-scan from a clean base each iteration
|
|
||||||
reset_n = 0;
|
|
||||||
bfm_rx_wr_ptr = 0;
|
|
||||||
bfm_rx_rd_ptr = 0;
|
|
||||||
#200;
|
|
||||||
reset_n = 1;
|
|
||||||
#500;
|
|
||||||
stm32_mixers_enable = 1;
|
|
||||||
ft601_txe = 0;
|
|
||||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001);
|
|
||||||
#500;
|
|
||||||
bfm_send_cmd(8'h01, 8'h00, 16'h0001);
|
|
||||||
bfm_send_cmd(8'h10, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h11, 8'h00, 16'd200);
|
|
||||||
bfm_send_cmd(8'h12, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h13, 8'h00, 16'd20);
|
|
||||||
bfm_send_cmd(8'h14, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h15, 8'h00, 16'd4);
|
|
||||||
|
|
||||||
// Let the pipeline reach steady-state and capture a baseline
|
|
||||||
#30000;
|
|
||||||
sweep_baseline_range = obs_range_valid_count;
|
|
||||||
sweep_baseline_chirp = obs_chirp_frame_count;
|
|
||||||
|
|
||||||
// Wait out the configured offset, then assert reset asynchronously
|
|
||||||
#(sweep_offsets[sweep_i]);
|
|
||||||
reset_n = 0;
|
|
||||||
#(sweep_holds[sweep_i]);
|
|
||||||
sweep_ok = (system_status == 4'b0000);
|
|
||||||
check(sweep_ok,
|
|
||||||
"G9B.a: system_status drops to 0 during injected reset");
|
|
||||||
|
|
||||||
// Release reset, re-configure (regs are cleared), allow recovery
|
|
||||||
reset_n = 1;
|
|
||||||
#500;
|
|
||||||
stm32_mixers_enable = 1;
|
|
||||||
ft601_txe = 0;
|
|
||||||
bfm_send_cmd(8'h04, 8'h00, 16'h0001);
|
|
||||||
#500;
|
|
||||||
bfm_send_cmd(8'h01, 8'h00, 16'h0001);
|
|
||||||
bfm_send_cmd(8'h10, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h11, 8'h00, 16'd200);
|
|
||||||
bfm_send_cmd(8'h12, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h13, 8'h00, 16'd20);
|
|
||||||
bfm_send_cmd(8'h14, 8'h00, 16'd100);
|
|
||||||
bfm_send_cmd(8'h15, 8'h00, 16'd4);
|
|
||||||
|
|
||||||
sweep_baseline_range = obs_range_valid_count;
|
|
||||||
sweep_baseline_chirp = obs_chirp_frame_count;
|
|
||||||
#60000; // 60 us — two+ short-chirp frames
|
|
||||||
|
|
||||||
check(obs_chirp_frame_count > sweep_baseline_chirp,
|
|
||||||
"G9B.b: new_chirp_frame resumes after injected reset");
|
|
||||||
check(obs_range_valid_count > sweep_baseline_range,
|
|
||||||
"G9B.c: range pipeline resumes after injected reset");
|
|
||||||
|
|
||||||
$display(" [F-2.2] iter=%0d offset=%0dns hold=%0dns chirps=+%0d ranges=+%0d",
|
|
||||||
sweep_i, sweep_offsets[sweep_i], sweep_holds[sweep_i],
|
|
||||||
obs_chirp_frame_count - sweep_baseline_chirp,
|
|
||||||
obs_range_valid_count - sweep_baseline_range);
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
$display("");
|
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
// GROUP 10: STREAM CONTROL (Gap 2)
|
// GROUP 10: STREAM CONTROL (Gap 2)
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|||||||
@@ -103,6 +103,15 @@ class Opcode(IntEnum):
|
|||||||
STATUS_REQUEST = 0xFF
|
STATUS_REQUEST = 0xFF
|
||||||
|
|
||||||
|
|
||||||
|
# MCU-only commands — NOT dispatched to the FPGA opcode switch.
|
||||||
|
# These values have no corresponding case in radar_system_top.v.
|
||||||
|
# Listed here so the GUI can build and send them via build_command().
|
||||||
|
# contract_parser.py filters MCU_ONLY_OPCODES out of the Python/Verilog
|
||||||
|
# bidirectional check.
|
||||||
|
FAULT_ACK = 0x40 # Exact 4-byte CDC packet; clears system_emergency_state
|
||||||
|
MCU_ONLY_OPCODES: frozenset[int] = frozenset({0x40})
|
||||||
|
|
||||||
|
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
# Data Structures
|
# Data Structures
|
||||||
# ============================================================================
|
# ============================================================================
|
||||||
|
|||||||
@@ -586,6 +586,135 @@ class TestSoftwareFPGA(unittest.TestCase):
|
|||||||
self.assertEqual(fpga.agc_holdoff, 0x0F)
|
self.assertEqual(fpga.agc_holdoff, 0x0F)
|
||||||
|
|
||||||
|
|
||||||
|
# ============================================================================
|
||||||
|
# Test: live vs replay physical-unit parity — regression guard for unit drift
|
||||||
|
#
|
||||||
|
# Uses AST parse of workers.py (not inspect.getsource / import) so the test
|
||||||
|
# runs in headless CI without PyQt6 — v7.workers imports PyQt6 unconditionally
|
||||||
|
# at workers.py:24, and other worker tests here already use skipUnless(
|
||||||
|
# _pyqt6_available()). Contract enforcement must not be gated on GUI deps.
|
||||||
|
#
|
||||||
|
# Asserts on AST nodes (Call / Attribute / BinOp), not source substrings, so
|
||||||
|
# false-pass on comments or docstring wording is impossible.
|
||||||
|
# ============================================================================
|
||||||
|
|
||||||
|
|
||||||
|
class TestLiveReplayPhysicalUnitsParity(unittest.TestCase):
|
||||||
|
"""Contract: live path (RadarDataWorker._run_host_dsp) and replay path
|
||||||
|
(ReplayWorker._emit_frame) both derive bin-to-physical conversion from
|
||||||
|
WaveformConfig — same source of truth, identical (range_m, velocity_ms)
|
||||||
|
for identical detections.
|
||||||
|
|
||||||
|
Regression context: before the fix, live path used
|
||||||
|
RadarSettings.velocity_resolution (default 1.0 in models.py:113) while
|
||||||
|
replay used WaveformConfig.velocity_resolution_mps (~5.343). Live GUI
|
||||||
|
therefore under-reported velocity by factor ~5.34x vs replay for
|
||||||
|
identical frames. See test_v7.py:449 for the WaveformConfig pin.
|
||||||
|
"""
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _parse_method(class_name: str, method_name: str):
|
||||||
|
"""Return AST FunctionDef for class_name.method_name from workers.py,
|
||||||
|
without importing v7.workers (PyQt6-independent)."""
|
||||||
|
import ast
|
||||||
|
from pathlib import Path
|
||||||
|
path = Path(__file__).parent / "v7" / "workers.py"
|
||||||
|
tree = ast.parse(path.read_text(encoding="utf-8"))
|
||||||
|
for node in tree.body:
|
||||||
|
if isinstance(node, ast.ClassDef) and node.name == class_name:
|
||||||
|
for item in node.body:
|
||||||
|
if isinstance(item, ast.FunctionDef) and item.name == method_name:
|
||||||
|
return item
|
||||||
|
raise RuntimeError(f"{class_name}.{method_name} not found in workers.py")
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_attribute_chain(tree, chain):
|
||||||
|
"""True if AST tree contains a dotted attribute access matching chain.
|
||||||
|
|
||||||
|
Chain ('self', '_settings', 'range_resolution') matches
|
||||||
|
``self._settings.range_resolution`` exactly.
|
||||||
|
"""
|
||||||
|
import ast
|
||||||
|
for n in ast.walk(tree):
|
||||||
|
if isinstance(n, ast.Attribute):
|
||||||
|
parts = [n.attr]
|
||||||
|
cur = n.value
|
||||||
|
while isinstance(cur, ast.Attribute):
|
||||||
|
parts.append(cur.attr)
|
||||||
|
cur = cur.value
|
||||||
|
if isinstance(cur, ast.Name):
|
||||||
|
parts.append(cur.id)
|
||||||
|
parts.reverse()
|
||||||
|
if tuple(parts) == tuple(chain):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_call_to(tree, func_name):
|
||||||
|
"""True if AST tree contains a call to a bare name (func_name())."""
|
||||||
|
import ast
|
||||||
|
for n in ast.walk(tree):
|
||||||
|
if (isinstance(n, ast.Call) and isinstance(n.func, ast.Name)
|
||||||
|
and n.func.id == func_name):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _has_dbin_minus(tree, literal):
|
||||||
|
"""True if AST tree contains ``dbin - <literal>`` binary op."""
|
||||||
|
import ast
|
||||||
|
for n in ast.walk(tree):
|
||||||
|
if (isinstance(n, ast.BinOp) and isinstance(n.op, ast.Sub)
|
||||||
|
and isinstance(n.left, ast.Name) and n.left.id == "dbin"
|
||||||
|
and isinstance(n.right, ast.Constant)
|
||||||
|
and n.right.value == literal):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_live_path_uses_waveform_config(self):
|
||||||
|
"""RadarDataWorker.__init__ must instantiate WaveformConfig() into
|
||||||
|
self._waveform; _run_host_dsp must read self._waveform.range_resolution_m
|
||||||
|
/ velocity_resolution_mps — not self._settings equivalents."""
|
||||||
|
init = self._parse_method("RadarDataWorker", "__init__")
|
||||||
|
self.assertTrue(self._has_call_to(init, "WaveformConfig"),
|
||||||
|
"RadarDataWorker.__init__ must instantiate WaveformConfig() into self._waveform.")
|
||||||
|
method = self._parse_method("RadarDataWorker", "_run_host_dsp")
|
||||||
|
self.assertTrue(
|
||||||
|
self._has_attribute_chain(method, ("self", "_waveform", "range_resolution_m")),
|
||||||
|
"Live path must read self._waveform.range_resolution_m.")
|
||||||
|
self.assertTrue(
|
||||||
|
self._has_attribute_chain(method, ("self", "_waveform", "velocity_resolution_mps")),
|
||||||
|
"Live path must read self._waveform.velocity_resolution_mps. "
|
||||||
|
"RadarSettings.velocity_resolution default 1.0 caused ~5.34x "
|
||||||
|
"underreport vs replay (test_v7.py:449 pins ~5.343).")
|
||||||
|
self.assertFalse(self._has_attribute_chain(
|
||||||
|
method, ("self", "_settings", "range_resolution")),
|
||||||
|
"Live path still reads stale RadarSettings.range_resolution.")
|
||||||
|
self.assertFalse(self._has_attribute_chain(
|
||||||
|
method, ("self", "_settings", "velocity_resolution")),
|
||||||
|
"Live path still reads stale RadarSettings.velocity_resolution.")
|
||||||
|
|
||||||
|
def test_live_path_doppler_center_not_hardcoded(self):
|
||||||
|
"""_run_host_dsp must derive doppler_center from frame shape, not
|
||||||
|
use hardcoded ``dbin - 16`` — mirrors processing.py:520."""
|
||||||
|
method = self._parse_method("RadarDataWorker", "_run_host_dsp")
|
||||||
|
self.assertFalse(self._has_dbin_minus(method, 16),
|
||||||
|
"Hardcoded doppler_center=16 breaks if frame shape changes. "
|
||||||
|
"Use frame.detections.shape[1] // 2 like processing.py:520.")
|
||||||
|
|
||||||
|
def test_replay_path_still_uses_waveform_config(self):
|
||||||
|
"""Parity half: replay path (ReplayWorker._emit_frame) must keep
|
||||||
|
reading self._waveform.range_resolution_m / velocity_resolution_mps —
|
||||||
|
guards against someone breaking the replay side of the invariant."""
|
||||||
|
method = self._parse_method("ReplayWorker", "_emit_frame")
|
||||||
|
self.assertTrue(self._has_attribute_chain(
|
||||||
|
method, ("self", "_waveform", "range_resolution_m")),
|
||||||
|
"Replay path lost WaveformConfig range source of truth.")
|
||||||
|
self.assertTrue(self._has_attribute_chain(
|
||||||
|
method, ("self", "_waveform", "velocity_resolution_mps")),
|
||||||
|
"Replay path lost WaveformConfig velocity source of truth.")
|
||||||
|
|
||||||
|
|
||||||
class TestSoftwareFPGASignalChain(unittest.TestCase):
|
class TestSoftwareFPGASignalChain(unittest.TestCase):
|
||||||
"""SoftwareFPGA.process_chirps with real co-sim data."""
|
"""SoftwareFPGA.process_chirps with real co-sim data."""
|
||||||
|
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ import numpy as np
|
|||||||
|
|
||||||
from PyQt6.QtCore import QThread, QObject, QTimer, pyqtSignal
|
from PyQt6.QtCore import QThread, QObject, QTimer, pyqtSignal
|
||||||
|
|
||||||
from .models import RadarTarget, GPSData, RadarSettings
|
from .models import RadarTarget, GPSData, RadarSettings, WaveformConfig
|
||||||
from .hardware import (
|
from .hardware import (
|
||||||
RadarAcquisition,
|
RadarAcquisition,
|
||||||
RadarFrame,
|
RadarFrame,
|
||||||
@@ -84,6 +84,7 @@ class RadarDataWorker(QThread):
|
|||||||
self._recorder = recorder
|
self._recorder = recorder
|
||||||
self._gps = gps_data_ref
|
self._gps = gps_data_ref
|
||||||
self._settings = settings or RadarSettings()
|
self._settings = settings or RadarSettings()
|
||||||
|
self._waveform = WaveformConfig()
|
||||||
self._running = False
|
self._running = False
|
||||||
|
|
||||||
# Frame queue for production RadarAcquisition → this thread
|
# Frame queue for production RadarAcquisition → this thread
|
||||||
@@ -97,6 +98,9 @@ class RadarDataWorker(QThread):
|
|||||||
self._byte_count = 0
|
self._byte_count = 0
|
||||||
self._error_count = 0
|
self._error_count = 0
|
||||||
|
|
||||||
|
def set_waveform(self, wf: "WaveformConfig") -> None:
|
||||||
|
self._waveform = wf
|
||||||
|
|
||||||
def stop(self):
|
def stop(self):
|
||||||
self._running = False
|
self._running = False
|
||||||
if self._acquisition:
|
if self._acquisition:
|
||||||
@@ -169,8 +173,8 @@ class RadarDataWorker(QThread):
|
|||||||
The FPGA already does: FFT, MTI, CFAR, DC notch.
|
The FPGA already does: FFT, MTI, CFAR, DC notch.
|
||||||
Host-side DSP adds: clustering, tracking, geo-coordinate mapping.
|
Host-side DSP adds: clustering, tracking, geo-coordinate mapping.
|
||||||
|
|
||||||
Bin-to-physical conversion uses RadarSettings.range_resolution
|
Bin-to-physical conversion uses self._waveform (WaveformConfig) to keep
|
||||||
and velocity_resolution (should be calibrated to actual waveform).
|
live and replay units aligned. Override via set_waveform() if needed.
|
||||||
"""
|
"""
|
||||||
targets: list[RadarTarget] = []
|
targets: list[RadarTarget] = []
|
||||||
|
|
||||||
@@ -180,8 +184,11 @@ class RadarDataWorker(QThread):
|
|||||||
|
|
||||||
# Extract detections from FPGA CFAR flags
|
# Extract detections from FPGA CFAR flags
|
||||||
det_indices = np.argwhere(frame.detections > 0)
|
det_indices = np.argwhere(frame.detections > 0)
|
||||||
r_res = self._settings.range_resolution
|
r_res = self._waveform.range_resolution_m
|
||||||
v_res = self._settings.velocity_resolution
|
v_res = self._waveform.velocity_resolution_mps
|
||||||
|
n_doppler = (frame.detections.shape[1] if frame.detections.ndim == 2
|
||||||
|
else self._waveform.n_doppler_bins)
|
||||||
|
doppler_center = n_doppler // 2
|
||||||
|
|
||||||
for idx in det_indices:
|
for idx in det_indices:
|
||||||
rbin, dbin = idx
|
rbin, dbin = idx
|
||||||
@@ -190,8 +197,9 @@ class RadarDataWorker(QThread):
|
|||||||
|
|
||||||
# Convert bin indices to physical units
|
# Convert bin indices to physical units
|
||||||
range_m = float(rbin) * r_res
|
range_m = float(rbin) * r_res
|
||||||
# Doppler: centre bin (16) = 0 m/s; positive bins = approaching
|
# Doppler: centre bin = 0 m/s; positive bins = approaching.
|
||||||
velocity_ms = float(dbin - 16) * v_res
|
# Derived from frame shape — mirrors processing.py:520.
|
||||||
|
velocity_ms = float(dbin - doppler_center) * v_res
|
||||||
|
|
||||||
# Apply pitch correction if GPS data available
|
# Apply pitch correction if GPS data available
|
||||||
raw_elev = 0.0 # FPGA doesn't send elevation per-detection
|
raw_elev = 0.0 # FPGA doesn't send elevation per-detection
|
||||||
|
|||||||
@@ -108,12 +108,23 @@ class ConcatWidth:
|
|||||||
|
|
||||||
def parse_python_opcodes(filepath: Path | None = None) -> dict[int, OpcodeEntry]:
|
def parse_python_opcodes(filepath: Path | None = None) -> dict[int, OpcodeEntry]:
|
||||||
"""Parse the Opcode enum from radar_protocol.py.
|
"""Parse the Opcode enum from radar_protocol.py.
|
||||||
Returns {opcode_value: OpcodeEntry}.
|
Returns {opcode_value: OpcodeEntry}, excluding MCU_ONLY_OPCODES.
|
||||||
|
MCU-only opcodes have no FPGA case statement and must not appear in
|
||||||
|
the bidirectional Python/Verilog contract check.
|
||||||
"""
|
"""
|
||||||
if filepath is None:
|
if filepath is None:
|
||||||
filepath = GUI_DIR / "radar_protocol.py"
|
filepath = GUI_DIR / "radar_protocol.py"
|
||||||
text = filepath.read_text()
|
text = filepath.read_text()
|
||||||
|
|
||||||
|
# Extract MCU_ONLY_OPCODES set so we can exclude those values below.
|
||||||
|
mcu_only: set[int] = set()
|
||||||
|
m_set = re.search(r'MCU_ONLY_OPCODES[^=]*=\s*frozenset\(\{([^}]*)\}\)', text)
|
||||||
|
if m_set:
|
||||||
|
for tok in m_set.group(1).split(','):
|
||||||
|
tok = tok.strip()
|
||||||
|
if tok.startswith(('0x', '0X')):
|
||||||
|
mcu_only.add(int(tok, 16))
|
||||||
|
|
||||||
# Find the Opcode class body
|
# Find the Opcode class body
|
||||||
match = re.search(r'class Opcode\b.*?(?=\nclass |\Z)', text, re.DOTALL)
|
match = re.search(r'class Opcode\b.*?(?=\nclass |\Z)', text, re.DOTALL)
|
||||||
if not match:
|
if not match:
|
||||||
@@ -123,6 +134,7 @@ def parse_python_opcodes(filepath: Path | None = None) -> dict[int, OpcodeEntry]
|
|||||||
for m in re.finditer(r'(\w+)\s*=\s*(0x[0-9a-fA-F]+)', match.group()):
|
for m in re.finditer(r'(\w+)\s*=\s*(0x[0-9a-fA-F]+)', match.group()):
|
||||||
name = m.group(1)
|
name = m.group(1)
|
||||||
value = int(m.group(2), 16)
|
value = int(m.group(2), 16)
|
||||||
|
if value not in mcu_only:
|
||||||
opcodes[value] = OpcodeEntry(name=name, value=value)
|
opcodes[value] = OpcodeEntry(name=name, value=value)
|
||||||
return opcodes
|
return opcodes
|
||||||
|
|
||||||
|
|||||||
@@ -1,185 +0,0 @@
|
|||||||
"""
|
|
||||||
DDC Cosim Fuzz Runner (audit F-3.2)
|
|
||||||
===================================
|
|
||||||
Parameterized seed sweep over the existing DDC cosim testbench.
|
|
||||||
|
|
||||||
For each seed the runner:
|
|
||||||
1. Generates a random plausible radar scene (1-4 targets, random range /
|
|
||||||
velocity / RCS, random noise level) via tb/cosim/radar_scene.py, using
|
|
||||||
the seed for full determinism.
|
|
||||||
2. Writes a temporary ADC hex file.
|
|
||||||
3. Compiles tb_ddc_cosim.v with -DSCENARIO_FUZZ (once, cached across seeds)
|
|
||||||
and runs vvp with +hex, +csv, +tag plusargs.
|
|
||||||
4. Parses the RTL output CSV and checks:
|
|
||||||
- non-empty output (the pipeline produced baseband samples)
|
|
||||||
- all I/Q values are within signed-18-bit range
|
|
||||||
- no NaN / parse errors
|
|
||||||
- sample count is within the expected bound from CIC decimation ratio
|
|
||||||
|
|
||||||
The intent is liveness / crash-fuzz, not bit-exact cross-check. Bit-exact
|
|
||||||
validation is covered by the static scenarios (single_target, multi_target,
|
|
||||||
etc) in the existing suite. Fuzz complements that by surfacing edge-case
|
|
||||||
corruption, saturation, or overflow on random-but-valid inputs.
|
|
||||||
|
|
||||||
Marks:
|
|
||||||
- The default fuzz sweep uses 8 seeds for fast CI.
|
|
||||||
- Use `-m slow` to unlock the full 100-seed sweep matched to the audit ask.
|
|
||||||
|
|
||||||
Compile + run times per seed on a laptop with iverilog 13: ~6 s. The default
|
|
||||||
8-seed sweep fits in a ~1 minute pytest run; the 100-seed sweep takes ~10-12
|
|
||||||
minutes.
|
|
||||||
"""
|
|
||||||
from __future__ import annotations
|
|
||||||
|
|
||||||
import os
|
|
||||||
import random
|
|
||||||
import subprocess
|
|
||||||
import sys
|
|
||||||
import tempfile
|
|
||||||
from pathlib import Path
|
|
||||||
|
|
||||||
import pytest
|
|
||||||
|
|
||||||
THIS_DIR = Path(__file__).resolve().parent
|
|
||||||
REPO_ROOT = THIS_DIR.parent.parent.parent
|
|
||||||
FPGA_DIR = REPO_ROOT / "9_Firmware" / "9_2_FPGA"
|
|
||||||
COSIM_DIR = FPGA_DIR / "tb" / "cosim"
|
|
||||||
|
|
||||||
sys.path.insert(0, str(COSIM_DIR))
|
|
||||||
import radar_scene # noqa: E402
|
|
||||||
|
|
||||||
FAST_SEEDS = list(range(8))
|
|
||||||
SLOW_SEEDS = list(range(100))
|
|
||||||
|
|
||||||
# Pipeline constants
|
|
||||||
N_ADC_SAMPLES = 16384
|
|
||||||
CIC_DECIMATION = 4
|
|
||||||
FIR_DECIMATION = 1
|
|
||||||
EXPECTED_BB_MIN = N_ADC_SAMPLES // (CIC_DECIMATION * 4) # pessimistic lower bound
|
|
||||||
EXPECTED_BB_MAX = N_ADC_SAMPLES // CIC_DECIMATION # upper bound before FIR drain
|
|
||||||
SIGNED_18_MIN = -(1 << 17)
|
|
||||||
SIGNED_18_MAX = (1 << 17) - 1
|
|
||||||
|
|
||||||
SOURCE_FILES = [
|
|
||||||
"tb/tb_ddc_cosim.v",
|
|
||||||
"ddc_400m.v",
|
|
||||||
"nco_400m_enhanced.v",
|
|
||||||
"cic_decimator_4x_enhanced.v",
|
|
||||||
"fir_lowpass.v",
|
|
||||||
"cdc_modules.v",
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.fixture(scope="module")
|
|
||||||
def compiled_fuzz_vvp(tmp_path_factory):
|
|
||||||
"""Compile tb_ddc_cosim.v once per pytest session with SCENARIO_FUZZ."""
|
|
||||||
iverilog = _iverilog_bin()
|
|
||||||
if not iverilog:
|
|
||||||
pytest.skip("iverilog not available on PATH")
|
|
||||||
|
|
||||||
out_dir = tmp_path_factory.mktemp("ddc_fuzz_build")
|
|
||||||
vvp = out_dir / "tb_ddc_cosim_fuzz.vvp"
|
|
||||||
sources = [str(FPGA_DIR / p) for p in SOURCE_FILES]
|
|
||||||
cmd = [
|
|
||||||
iverilog, "-g2001", "-DSIMULATION", "-DSCENARIO_FUZZ",
|
|
||||||
"-o", str(vvp), *sources,
|
|
||||||
]
|
|
||||||
res = subprocess.run(cmd, cwd=FPGA_DIR, capture_output=True, text=True, check=False)
|
|
||||||
if res.returncode != 0:
|
|
||||||
pytest.skip(f"iverilog compile failed:\n{res.stderr}")
|
|
||||||
return vvp
|
|
||||||
|
|
||||||
|
|
||||||
def _iverilog_bin() -> str | None:
|
|
||||||
from shutil import which
|
|
||||||
return which("iverilog")
|
|
||||||
|
|
||||||
|
|
||||||
def _random_scene(seed: int) -> list[radar_scene.Target]:
|
|
||||||
rng = random.Random(seed)
|
|
||||||
n = rng.randint(1, 4)
|
|
||||||
return [
|
|
||||||
radar_scene.Target(
|
|
||||||
range_m=rng.uniform(50, 1500),
|
|
||||||
velocity_mps=rng.uniform(-40, 40),
|
|
||||||
rcs_dbsm=rng.uniform(-10, 20),
|
|
||||||
phase_deg=rng.uniform(0, 360),
|
|
||||||
)
|
|
||||||
for _ in range(n)
|
|
||||||
]
|
|
||||||
|
|
||||||
|
|
||||||
def _run_seed(seed: int, vvp: Path, work: Path) -> tuple[int, list[tuple[int, int]]]:
|
|
||||||
"""Generate stimulus, run the DUT, return (bb_sample_count, [(i,q)...])."""
|
|
||||||
targets = _random_scene(seed)
|
|
||||||
noise = random.Random(seed ^ 0xA5A5).uniform(0.5, 6.0)
|
|
||||||
adc = radar_scene.generate_adc_samples(
|
|
||||||
targets, N_ADC_SAMPLES, noise_stddev=noise, seed=seed
|
|
||||||
)
|
|
||||||
|
|
||||||
hex_path = work / f"adc_fuzz_{seed:04d}.hex"
|
|
||||||
csv_path = work / f"rtl_bb_fuzz_{seed:04d}.csv"
|
|
||||||
radar_scene.write_hex_file(str(hex_path), adc, bits=8)
|
|
||||||
|
|
||||||
vvp_bin = _vvp_bin()
|
|
||||||
if not vvp_bin:
|
|
||||||
pytest.skip("vvp not available")
|
|
||||||
|
|
||||||
cmd = [
|
|
||||||
vvp_bin, str(vvp),
|
|
||||||
f"+hex={hex_path}",
|
|
||||||
f"+csv={csv_path}",
|
|
||||||
f"+tag=seed{seed:04d}",
|
|
||||||
]
|
|
||||||
res = subprocess.run(cmd, cwd=FPGA_DIR, capture_output=True, text=True, check=False, timeout=120)
|
|
||||||
assert res.returncode == 0, f"vvp exit={res.returncode}\nstdout:\n{res.stdout}\nstderr:\n{res.stderr}"
|
|
||||||
assert csv_path.exists(), (
|
|
||||||
f"vvp completed rc=0 but CSV was not produced at {csv_path}\n"
|
|
||||||
f"cmd: {cmd}\nstdout:\n{res.stdout[-2000:]}\nstderr:\n{res.stderr[-500:]}"
|
|
||||||
)
|
|
||||||
|
|
||||||
rows = []
|
|
||||||
with csv_path.open() as fh:
|
|
||||||
header = fh.readline()
|
|
||||||
assert "baseband_i" in header and "baseband_q" in header, f"unexpected CSV header: {header!r}"
|
|
||||||
for line in fh:
|
|
||||||
parts = line.strip().split(",")
|
|
||||||
if len(parts) != 3:
|
|
||||||
continue
|
|
||||||
_, i_str, q_str = parts
|
|
||||||
rows.append((int(i_str), int(q_str)))
|
|
||||||
return len(rows), rows
|
|
||||||
|
|
||||||
|
|
||||||
def _vvp_bin() -> str | None:
|
|
||||||
from shutil import which
|
|
||||||
return which("vvp")
|
|
||||||
|
|
||||||
|
|
||||||
def _fuzz_assertions(seed: int, rows: list[tuple[int, int]]) -> None:
|
|
||||||
n = len(rows)
|
|
||||||
assert EXPECTED_BB_MIN <= n <= EXPECTED_BB_MAX, (
|
|
||||||
f"seed {seed}: bb sample count {n} outside [{EXPECTED_BB_MIN},{EXPECTED_BB_MAX}]"
|
|
||||||
)
|
|
||||||
for idx, (i, q) in enumerate(rows):
|
|
||||||
assert SIGNED_18_MIN <= i <= SIGNED_18_MAX, (
|
|
||||||
f"seed {seed} row {idx}: baseband_i={i} out of signed-18 range"
|
|
||||||
)
|
|
||||||
assert SIGNED_18_MIN <= q <= SIGNED_18_MAX, (
|
|
||||||
f"seed {seed} row {idx}: baseband_q={q} out of signed-18 range"
|
|
||||||
)
|
|
||||||
all_zero = all(i == 0 and q == 0 for i, q in rows)
|
|
||||||
assert not all_zero, f"seed {seed}: all-zero baseband output — pipeline likely stalled"
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.parametrize("seed", FAST_SEEDS)
|
|
||||||
def test_ddc_fuzz_fast(seed: int, compiled_fuzz_vvp: Path, tmp_path: Path) -> None:
|
|
||||||
_, rows = _run_seed(seed, compiled_fuzz_vvp, tmp_path)
|
|
||||||
_fuzz_assertions(seed, rows)
|
|
||||||
|
|
||||||
|
|
||||||
@pytest.mark.slow
|
|
||||||
@pytest.mark.parametrize("seed", SLOW_SEEDS)
|
|
||||||
def test_ddc_fuzz_full(seed: int, compiled_fuzz_vvp: Path, tmp_path: Path) -> None:
|
|
||||||
_, rows = _run_seed(seed, compiled_fuzz_vvp, tmp_path)
|
|
||||||
_fuzz_assertions(seed, rows)
|
|
||||||
@@ -19,11 +19,6 @@ dev = [
|
|||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
# Ruff configuration
|
# Ruff configuration
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
[tool.pytest.ini_options]
|
|
||||||
markers = [
|
|
||||||
"slow: full-sweep tests (opt-in via -m slow); audit F-3.2 100-seed fuzz",
|
|
||||||
]
|
|
||||||
|
|
||||||
[tool.ruff]
|
[tool.ruff]
|
||||||
target-version = "py312"
|
target-version = "py312"
|
||||||
line-length = 100
|
line-length = 100
|
||||||
|
|||||||
@@ -1,216 +0,0 @@
|
|||||||
version = 1
|
|
||||||
revision = 1
|
|
||||||
requires-python = ">=3.12"
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "aeris-10-radar"
|
|
||||||
version = "1.0.0"
|
|
||||||
source = { virtual = "." }
|
|
||||||
|
|
||||||
[package.dev-dependencies]
|
|
||||||
dev = [
|
|
||||||
{ name = "h5py" },
|
|
||||||
{ name = "numpy" },
|
|
||||||
{ name = "pytest" },
|
|
||||||
{ name = "ruff" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[package.metadata]
|
|
||||||
|
|
||||||
[package.metadata.requires-dev]
|
|
||||||
dev = [
|
|
||||||
{ name = "h5py", specifier = ">=3.10" },
|
|
||||||
{ name = "numpy", specifier = ">=1.26" },
|
|
||||||
{ name = "pytest", specifier = ">=8" },
|
|
||||||
{ name = "ruff", specifier = ">=0.5" },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "colorama"
|
|
||||||
version = "0.4.6"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d8/53/6f443c9a4a8358a93a6792e2acffb9d9d5cb0a5cfd8802644b7b1c9a02e4/colorama-0.4.6.tar.gz", hash = "sha256:08695f5cb7ed6e0531a20572697297273c47b8cae5a63ffc6d6ed5c201be6e44", size = 27697 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/d6/3965ed04c63042e047cb6a3e6ed1a63a35087b6a609aa3a15ed8ac56c221/colorama-0.4.6-py2.py3-none-any.whl", hash = "sha256:4f1d9991f5acc0ca119f9d443620b77f9d6b33703e51011c16baf57afb285fc6", size = 25335 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "h5py"
|
|
||||||
version = "3.16.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "numpy" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/db/33/acd0ce6863b6c0d7735007df01815403f5589a21ff8c2e1ee2587a38f548/h5py-3.16.0.tar.gz", hash = "sha256:a0dbaad796840ccaa67a4c144a0d0c8080073c34c76d5a6941d6818678ef2738", size = 446526 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c8/c0/5d4119dba94093bbafede500d3defd2f5eab7897732998c04b54021e530b/h5py-3.16.0-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:c5313566f4643121a78503a473f0fb1e6dcc541d5115c44f05e037609c565c4d", size = 3685604 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/42/c84efcc1d4caebafb1ecd8be4643f39c85c47a80fe254d92b8b43b1eadaf/h5py-3.16.0-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:42b012933a83e1a558c673176676a10ce2fd3759976a0fedee1e672d1e04fc9d", size = 3061940 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/89/84/06281c82d4d1686fde1ac6b0f307c50918f1c0151062445ab3b6fa5a921d/h5py-3.16.0-cp312-cp312-manylinux_2_28_aarch64.whl", hash = "sha256:ff24039e2573297787c3063df64b60aab0591980ac898329a08b0320e0cf2527", size = 5198852 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9e/e9/1a19e42cd43cc1365e127db6aae85e1c671da1d9a5d746f4d34a50edb577/h5py-3.16.0-cp312-cp312-manylinux_2_28_x86_64.whl", hash = "sha256:dfc21898ff025f1e8e67e194965a95a8d4754f452f83454538f98f8a3fcb207e", size = 5405250 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b7/8e/9790c1655eabeb85b92b1ecab7d7e62a2069e53baefd58c98f0909c7a948/h5py-3.16.0-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:698dd69291272642ffda44a0ecd6cd3bda5faf9621452d255f57ce91487b9794", size = 5190108 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/51/d7/ab693274f1bd7e8c5f9fdd6c7003a88d59bedeaf8752716a55f532924fbb/h5py-3.16.0-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:2b2c02b0a160faed5fb33f1ba8a264a37ee240b22e049ecc827345d0d9043074", size = 5419216 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/c1/0976b235cf29ead553e22f2fb6385a8252b533715e00d0ae52ed7b900582/h5py-3.16.0-cp312-cp312-win_amd64.whl", hash = "sha256:96b422019a1c8975c2d5dadcf61d4ba6f01c31f92bbde6e4649607885fe502d6", size = 3182868 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/d9/866b7e570b39070f92d47b0ff1800f0f8239b6f9e45f02363d7112336c1f/h5py-3.16.0-cp312-cp312-win_arm64.whl", hash = "sha256:39c2838fb1e8d97bcf1755e60ad1f3dd76a7b2a475928dc321672752678b96db", size = 2653286 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0f/9e/6142ebfda0cb6e9349c091eae73c2e01a770b7659255248d637bec54a88b/h5py-3.16.0-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:370a845f432c2c9619db8eed334d1e610c6015796122b0e57aa46312c22617d9", size = 3671808 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b0/65/5e088a45d0f43cd814bc5bec521c051d42005a472e804b1a36c48dada09b/h5py-3.16.0-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:42108e93326c50c2810025aade9eac9d6827524cdccc7d4b75a546e5ab308edb", size = 3045837 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/da/1e/6172269e18cc5a484e2913ced33339aad588e02ba407fafd00d369e22ef3/h5py-3.16.0-cp313-cp313-manylinux_2_28_aarch64.whl", hash = "sha256:099f2525c9dcf28de366970a5fb34879aab20491589fa89ce2863a84218bb524", size = 5193860 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bd/98/ef2b6fe2903e377cbe870c3b2800d62552f1e3dbe81ce49e1923c53d1c5c/h5py-3.16.0-cp313-cp313-manylinux_2_28_x86_64.whl", hash = "sha256:9300ad32dea9dfc5171f94d5f6948e159ed93e4701280b0f508773b3f582f402", size = 5400417 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/81/5b62d760039eed64348c98129d17061fdfc7839fc9c04eaaad6dee1004e4/h5py-3.16.0-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:171038f23bccddfc23f344cadabdfc9917ff554db6a0d417180d2747fe4c75a7", size = 5185214 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/c4/532123bcd9080e250696779c927f2cb906c8bf3447df98f5ceb8dcded539/h5py-3.16.0-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:7e420b539fb6023a259a1b14d4c9f6df8cf50d7268f48e161169987a57b737ff", size = 5414598 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c3/d9/a27997f84341fc0dfcdd1fe4179b6ba6c32a7aa880fdb8c514d4dad6fba3/h5py-3.16.0-cp313-cp313-win_amd64.whl", hash = "sha256:18f2bbcd545e6991412253b98727374c356d67caa920e68dc79eab36bf5fedad", size = 3175509 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a5/23/bb8647521d4fd770c30a76cfc6cb6a2f5495868904054e92f2394c5a78ff/h5py-3.16.0-cp313-cp313-win_arm64.whl", hash = "sha256:656f00e4d903199a1d58df06b711cf3ca632b874b4207b7dbec86185b5c8c7d4", size = 2647362 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/48/3c/7fcd9b4c9eed82e91fb15568992561019ae7a829d1f696b2c844355d95dd/h5py-3.16.0-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:9c9d307c0ef862d1cd5714f72ecfafe0a5d7529c44845afa8de9f46e5ba8bd65", size = 3678608 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6a/b7/9366ed44ced9b7ef357ab48c94205280276db9d7f064aa3012a97227e966/h5py-3.16.0-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:8c1eff849cdd53cbc73c214c30ebdb6f1bb8b64790b4b4fc36acdb5e43570210", size = 3054773 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/a5/4964bc0e91e86340c2bbda83420225b2f770dcf1eb8a39464871ad769436/h5py-3.16.0-cp314-cp314-manylinux_2_28_aarch64.whl", hash = "sha256:e2c04d129f180019e216ee5f9c40b78a418634091c8782e1f723a6ca3658b965", size = 5198886 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f1/16/d905e7f53e661ce2c24686c38048d8e2b750ffc4350009d41c4e6c6c9826/h5py-3.16.0-cp314-cp314-manylinux_2_28_x86_64.whl", hash = "sha256:e4360f15875a532bc7b98196c7592ed4fc92672a57c0a621355961cafb17a6dd", size = 5404883 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4b/f2/58f34cb74af46d39f4cd18ea20909a8514960c5a3e5b92fd06a28161e0a8/h5py-3.16.0-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:3fae9197390c325e62e0a1aa977f2f62d994aa87aab182abbea85479b791197c", size = 5192039 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/ca/934a39c24ce2e2db017268c08da0537c20fa0be7e1549be3e977313fc8f5/h5py-3.16.0-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:43259303989ac8adacc9986695b31e35dba6fd1e297ff9c6a04b7da5542139cc", size = 5421526 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3e/14/615a450205e1b56d16c6783f5ccd116cde05550faad70ae077c955654a75/h5py-3.16.0-cp314-cp314-win_amd64.whl", hash = "sha256:fa48993a0b799737ba7fd21e2350fa0a60701e58180fae9f2de834bc39a147ab", size = 3183263 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/48/a6faef5ed632cae0c65ac6b214a6614a0b510c3183532c521bdb0055e117/h5py-3.16.0-cp314-cp314-win_arm64.whl", hash = "sha256:1897a771a7f40d05c262fc8f37376ec37873218544b70216872876c627640f63", size = 2663450 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5d/32/0c8bb8aedb62c772cf7c1d427c7d1951477e8c2835f872bc0a13d1f85f86/h5py-3.16.0-cp314-cp314t-macosx_10_15_x86_64.whl", hash = "sha256:15922e485844f77c0b9d275396d435db3baa58292a9c2176a386e072e0cf2491", size = 3760693 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/1d/1f/fcc5977d32d6387c5c9a694afee716a5e20658ac08b3ff24fdec79fb05f2/h5py-3.16.0-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:df02dd29bd247f98674634dfe41f89fd7c16ba3d7de8695ec958f58404a4e618", size = 3181305 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f5/a1/af87f64b9f986889884243643621ebbd4ac72472ba8ec8cec891ac8e2ca1/h5py-3.16.0-cp314-cp314t-manylinux_2_28_aarch64.whl", hash = "sha256:0f456f556e4e2cebeebd9d66adf8dc321770a42593494a0b6f0af54a7567b242", size = 5074061 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cc/d0/146f5eaff3dc246a9c7f6e5e4f42bd45cc613bce16693bcd4d1f7c958bf5/h5py-3.16.0-cp314-cp314t-manylinux_2_28_x86_64.whl", hash = "sha256:3e6cb3387c756de6a9492d601553dffea3fe11b5f22b443aac708c69f3f55e16", size = 5279216 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a1/9d/12a13424f1e604fc7df9497b73c0356fb78c2fb206abd7465ce47226e8fd/h5py-3.16.0-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:8389e13a1fd745ad2856873e8187fd10268b2d9677877bb667b41aebd771d8b7", size = 5070068 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/41/8c/bbe98f813722b4873818a8db3e15aa3e625b59278566905ac439725e8070/h5py-3.16.0-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:346df559a0f7dcb31cf8e44805319e2ab24b8957c45e7708ce503b2ec79ba725", size = 5300253 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/32/9e/87e6705b4d6890e7cecdf876e2a7d3e40654a2ae37482d79a6f1b87f7b92/h5py-3.16.0-cp314-cp314t-win_amd64.whl", hash = "sha256:4c6ab014ab704b4feaa719ae783b86522ed0bf1f82184704ed3c9e4e3228796e", size = 3381671 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/96/91/9fad90cfc5f9b2489c7c26ad897157bce82f0e9534a986a221b99760b23b/h5py-3.16.0-cp314-cp314t-win_arm64.whl", hash = "sha256:faca8fb4e4319c09d83337adc80b2ca7d5c5a343c2d6f1b6388f32cfecca13c1", size = 2740706 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "iniconfig"
|
|
||||||
version = "2.3.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/72/34/14ca021ce8e5dfedc35312d08ba8bf51fdd999c576889fc2c24cb97f4f10/iniconfig-2.3.0.tar.gz", hash = "sha256:c76315c77db068650d49c5b56314774a7804df16fee4402c1f19d6d15d8c4730", size = 20503 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cb/b1/3846dd7f199d53cb17f49cba7e651e9ce294d8497c8c150530ed11865bb8/iniconfig-2.3.0-py3-none-any.whl", hash = "sha256:f631c04d2c48c52b84d0d0549c99ff3859c98df65b3101406327ecc7d53fbf12", size = 7484 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "numpy"
|
|
||||||
version = "2.4.4"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/d7/9f/b8cef5bffa569759033adda9481211426f12f53299629b410340795c2514/numpy-2.4.4.tar.gz", hash = "sha256:2d390634c5182175533585cc89f3608a4682ccb173cc9bb940b2881c8d6f8fa0", size = 20731587 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/28/05/32396bec30fb2263770ee910142f49c1476d08e8ad41abf8403806b520ce/numpy-2.4.4-cp312-cp312-macosx_10_13_x86_64.whl", hash = "sha256:15716cfef24d3a9762e3acdf87e27f58dc823d1348f765bbea6bef8c639bfa1b", size = 16689272 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c5/f3/a983d28637bfcd763a9c7aafdb6d5c0ebf3d487d1e1459ffdb57e2f01117/numpy-2.4.4-cp312-cp312-macosx_11_0_arm64.whl", hash = "sha256:23cbfd4c17357c81021f21540da84ee282b9c8fba38a03b7b9d09ba6b951421e", size = 14699573 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9b/fd/e5ecca1e78c05106d98028114f5c00d3eddb41207686b2b7de3e477b0e22/numpy-2.4.4-cp312-cp312-macosx_14_0_arm64.whl", hash = "sha256:8b3b60bb7cba2c8c81837661c488637eee696f59a877788a396d33150c35d842", size = 5204782 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/de/2f/702a4594413c1a8632092beae8aba00f1d67947389369b3777aed783fdca/numpy-2.4.4-cp312-cp312-macosx_14_0_x86_64.whl", hash = "sha256:e4a010c27ff6f210ff4c6ef34394cd61470d01014439b192ec22552ee867f2a8", size = 6552038 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7f/37/eed308a8f56cba4d1fdf467a4fc67ef4ff4bf1c888f5fc980481890104b1/numpy-2.4.4-cp312-cp312-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:f9e75681b59ddaa5e659898085ae0eaea229d054f2ac0c7e563a62205a700121", size = 15670666 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/0a/0d/0e3ecece05b7a7e87ab9fb587855548da437a061326fff64a223b6dcb78a/numpy-2.4.4-cp312-cp312-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:81f4a14bee47aec54f883e0cad2d73986640c1590eb9bfaaba7ad17394481e6e", size = 16645480 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/49/f2312c154b82a286758ee2f1743336d50651f8b5195db18cdb63675ff649/numpy-2.4.4-cp312-cp312-musllinux_1_2_aarch64.whl", hash = "sha256:62d6b0f03b694173f9fcb1fb317f7222fd0b0b103e784c6549f5e53a27718c44", size = 17020036 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7b/e9/736d17bd77f1b0ec4f9901aaec129c00d59f5d84d5e79bba540ef12c2330/numpy-2.4.4-cp312-cp312-musllinux_1_2_x86_64.whl", hash = "sha256:fbc356aae7adf9e6336d336b9c8111d390a05df88f1805573ebb0807bd06fd1d", size = 18368643 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/f6/d417977c5f519b17c8a5c3bc9e8304b0908b0e21136fe43bf628a1343914/numpy-2.4.4-cp312-cp312-win32.whl", hash = "sha256:0d35aea54ad1d420c812bfa0385c71cd7cc5bcf7c65fed95fc2cd02fe8c79827", size = 5961117 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/2d/5b/e1deebf88ff431b01b7406ca3583ab2bbb90972bbe1c568732e49c844f7e/numpy-2.4.4-cp312-cp312-win_amd64.whl", hash = "sha256:b5f0362dc928a6ecd9db58868fca5e48485205e3855957bdedea308f8672ea4a", size = 12320584 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/89/e4e856ac82a68c3ed64486a544977d0e7bdd18b8da75b78a577ca31c4395/numpy-2.4.4-cp312-cp312-win_arm64.whl", hash = "sha256:846300f379b5b12cc769334464656bc882e0735d27d9726568bc932fdc49d5ec", size = 10221450 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/14/1d/d0a583ce4fefcc3308806a749a536c201ed6b5ad6e1322e227ee4848979d/numpy-2.4.4-cp313-cp313-macosx_10_13_x86_64.whl", hash = "sha256:08f2e31ed5e6f04b118e49821397f12767934cfdd12a1ce86a058f91e004ee50", size = 16684933 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c1/62/2b7a48fbb745d344742c0277f01286dead15f3f68e4f359fbfcf7b48f70f/numpy-2.4.4-cp313-cp313-macosx_11_0_arm64.whl", hash = "sha256:e823b8b6edc81e747526f70f71a9c0a07ac4e7ad13020aa736bb7c9d67196115", size = 14694532 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e5/87/499737bfba066b4a3bebff24a8f1c5b2dee410b209bc6668c9be692580f0/numpy-2.4.4-cp313-cp313-macosx_14_0_arm64.whl", hash = "sha256:4a19d9dba1a76618dd86b164d608566f393f8ec6ac7c44f0cc879011c45e65af", size = 5199661 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/cd/da/464d551604320d1491bc345efed99b4b7034143a85787aab78d5691d5a0e/numpy-2.4.4-cp313-cp313-macosx_14_0_x86_64.whl", hash = "sha256:d2a8490669bfe99a233298348acc2d824d496dee0e66e31b66a6022c2ad74a5c", size = 6547539 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7d/90/8d23e3b0dafd024bf31bdec225b3bb5c2dbfa6912f8a53b8659f21216cbf/numpy-2.4.4-cp313-cp313-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:45dbed2ab436a9e826e302fcdcbe9133f9b0006e5af7168afb8963a6520da103", size = 15668806 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d1/73/a9d864e42a01896bb5974475438f16086be9ba1f0d19d0bb7a07427c4a8b/numpy-2.4.4-cp313-cp313-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:c901b15172510173f5cb310eae652908340f8dede90fff9e3bf6c0d8dfd92f83", size = 16632682 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/34/fb/14570d65c3bde4e202a031210475ae9cde9b7686a2e7dc97ee67d2833b35/numpy-2.4.4-cp313-cp313-musllinux_1_2_aarch64.whl", hash = "sha256:99d838547ace2c4aace6c4f76e879ddfe02bb58a80c1549928477862b7a6d6ed", size = 17019810 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/8a/77/2ba9d87081fd41f6d640c83f26fb7351e536b7ce6dd9061b6af5904e8e46/numpy-2.4.4-cp313-cp313-musllinux_1_2_x86_64.whl", hash = "sha256:0aec54fd785890ecca25a6003fd9a5aed47ad607bbac5cd64f836ad8666f4959", size = 18357394 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/a2/23/52666c9a41708b0853fa3b1a12c90da38c507a3074883823126d4e9d5b30/numpy-2.4.4-cp313-cp313-win32.whl", hash = "sha256:07077278157d02f65c43b1b26a3886bce886f95d20aabd11f87932750dfb14ed", size = 5959556 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/57/fb/48649b4971cde70d817cf97a2a2fdc0b4d8308569f1dd2f2611959d2e0cf/numpy-2.4.4-cp313-cp313-win_amd64.whl", hash = "sha256:5c70f1cc1c4efbe316a572e2d8b9b9cc44e89b95f79ca3331553fbb63716e2bf", size = 12317311 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ba/d8/11490cddd564eb4de97b4579ef6bfe6a736cc07e94c1598590ae25415e01/numpy-2.4.4-cp313-cp313-win_arm64.whl", hash = "sha256:ef4059d6e5152fa1a39f888e344c73fdc926e1b2dd58c771d67b0acfbf2aa67d", size = 10222060 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/5d/dab4339177a905aad3e2221c915b35202f1ec30d750dd2e5e9d9a72b804b/numpy-2.4.4-cp313-cp313t-macosx_11_0_arm64.whl", hash = "sha256:4bbc7f303d125971f60ec0aaad5e12c62d0d2c925f0ab1273debd0e4ba37aba5", size = 14822302 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/eb/e4/0564a65e7d3d97562ed6f9b0fd0fb0a6f559ee444092f105938b50043876/numpy-2.4.4-cp313-cp313t-macosx_14_0_arm64.whl", hash = "sha256:4d6d57903571f86180eb98f8f0c839fa9ebbfb031356d87f1361be91e433f5b7", size = 5327407 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/29/8d/35a3a6ce5ad371afa58b4700f1c820f8f279948cca32524e0a695b0ded83/numpy-2.4.4-cp313-cp313t-macosx_14_0_x86_64.whl", hash = "sha256:4636de7fd195197b7535f231b5de9e4b36d2c440b6e566d2e4e4746e6af0ca93", size = 6647631 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/da/477731acbd5a58a946c736edfdabb2ac5b34c3d08d1ba1a7b437fa0884df/numpy-2.4.4-cp313-cp313t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:ad2e2ef14e0b04e544ea2fa0a36463f847f113d314aa02e5b402fdf910ef309e", size = 15727691 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e6/db/338535d9b152beabeb511579598418ba0212ce77cf9718edd70262cc4370/numpy-2.4.4-cp313-cp313t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:5a285b3b96f951841799528cd1f4f01cd70e7e0204b4abebac9463eecfcf2a40", size = 16681241 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e2/a9/ad248e8f58beb7a0219b413c9c7d8151c5d285f7f946c3e26695bdbbe2df/numpy-2.4.4-cp313-cp313t-musllinux_1_2_aarch64.whl", hash = "sha256:f8474c4241bc18b750be2abea9d7a9ec84f46ef861dbacf86a4f6e043401f79e", size = 17085767 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b5/1a/3b88ccd3694681356f70da841630e4725a7264d6a885c8d442a697e1146b/numpy-2.4.4-cp313-cp313t-musllinux_1_2_x86_64.whl", hash = "sha256:4e874c976154687c1f71715b034739b45c7711bec81db01914770373d125e392", size = 18403169 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c2/c9/fcfd5d0639222c6eac7f304829b04892ef51c96a75d479214d77e3ce6e33/numpy-2.4.4-cp313-cp313t-win32.whl", hash = "sha256:9c585a1790d5436a5374bac930dad6ed244c046ed91b2b2a3634eb2971d21008", size = 6083477 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d5/e3/3938a61d1c538aaec8ed6fd6323f57b0c2d2d2219512434c5c878db76553/numpy-2.4.4-cp313-cp313t-win_amd64.whl", hash = "sha256:93e15038125dc1e5345d9b5b68aa7f996ec33b98118d18c6ca0d0b7d6198b7e8", size = 12457487 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/97/6a/7e345032cc60501721ef94e0e30b60f6b0bd601f9174ebd36389a2b86d40/numpy-2.4.4-cp313-cp313t-win_arm64.whl", hash = "sha256:0dfd3f9d3adbe2920b68b5cd3d51444e13a10792ec7154cd0a2f6e74d4ab3233", size = 10292002 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/06/c54062f85f673dd5c04cbe2f14c3acb8c8b95e3384869bb8cc9bff8cb9df/numpy-2.4.4-cp314-cp314-macosx_10_15_x86_64.whl", hash = "sha256:f169b9a863d34f5d11b8698ead99febeaa17a13ca044961aa8e2662a6c7766a0", size = 16684353 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/4c/39/8a320264a84404c74cc7e79715de85d6130fa07a0898f67fb5cd5bd79908/numpy-2.4.4-cp314-cp314-macosx_11_0_arm64.whl", hash = "sha256:2483e4584a1cb3092da4470b38866634bafb223cbcd551ee047633fd2584599a", size = 14704914 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/91/fb/287076b2614e1d1044235f50f03748f31fa287e3dbe6abeb35cdfa351eca/numpy-2.4.4-cp314-cp314-macosx_14_0_arm64.whl", hash = "sha256:2d19e6e2095506d1736b7d80595e0f252d76b89f5e715c35e06e937679ea7d7a", size = 5210005 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/eb/fcc338595309910de6ecabfcef2419a9ce24399680bfb149421fa2df1280/numpy-2.4.4-cp314-cp314-macosx_14_0_x86_64.whl", hash = "sha256:6a246d5914aa1c820c9443ddcee9c02bec3e203b0c080349533fae17727dfd1b", size = 6544974 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/44/5d/e7e9044032a716cdfaa3fba27a8e874bf1c5f1912a1ddd4ed071bf8a14a6/numpy-2.4.4-cp314-cp314-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:989824e9faf85f96ec9c7761cd8d29c531ad857bfa1daa930cba85baaecf1a9a", size = 15684591 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/98/7c/21252050676612625449b4807d6b695b9ce8a7c9e1c197ee6216c8a65c7c/numpy-2.4.4-cp314-cp314-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:27a8d92cd10f1382a67d7cf4db7ce18341b66438bdd9f691d7b0e48d104c2a9d", size = 16637700 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/29/56d2bbef9465db24ef25393383d761a1af4f446a1df9b8cded4fe3a5a5d7/numpy-2.4.4-cp314-cp314-musllinux_1_2_aarch64.whl", hash = "sha256:e44319a2953c738205bf3354537979eaa3998ed673395b964c1176083dd46252", size = 17035781 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e3/2b/a35a6d7589d21f44cea7d0a98de5ddcbb3d421b2622a5c96b1edf18707c3/numpy-2.4.4-cp314-cp314-musllinux_1_2_x86_64.whl", hash = "sha256:e892aff75639bbef0d2a2cfd55535510df26ff92f63c92cd84ef8d4ba5a5557f", size = 18362959 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/64/c9/d52ec581f2390e0f5f85cbfd80fb83d965fc15e9f0e1aec2195faa142cde/numpy-2.4.4-cp314-cp314-win32.whl", hash = "sha256:1378871da56ca8943c2ba674530924bb8ca40cd228358a3b5f302ad60cf875fc", size = 6008768 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/fa/22/4cc31a62a6c7b74a8730e31a4274c5dc80e005751e277a2ce38e675e4923/numpy-2.4.4-cp314-cp314-win_amd64.whl", hash = "sha256:715d1c092715954784bc79e1174fc2a90093dc4dc84ea15eb14dad8abdcdeb74", size = 12449181 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/70/2e/14cda6f4d8e396c612d1bf97f22958e92148801d7e4f110cabebdc0eef4b/numpy-2.4.4-cp314-cp314-win_arm64.whl", hash = "sha256:2c194dd721e54ecad9ad387c1d35e63dce5c4450c6dc7dd5611283dda239aabb", size = 10496035 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/b1/e8/8fed8c8d848d7ecea092dc3469643f9d10bc3a134a815a3b033da1d2039b/numpy-2.4.4-cp314-cp314t-macosx_11_0_arm64.whl", hash = "sha256:2aa0613a5177c264ff5921051a5719d20095ea586ca88cc802c5c218d1c67d3e", size = 14824958 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/05/1a/d8007a5138c179c2bf33ef44503e83d70434d2642877ee8fbb230e7c0548/numpy-2.4.4-cp314-cp314t-macosx_14_0_arm64.whl", hash = "sha256:42c16925aa5a02362f986765f9ebabf20de75cdefdca827d14315c568dcab113", size = 5330020 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/99/64/ffb99ac6ae93faf117bcbd5c7ba48a7f45364a33e8e458545d3633615dda/numpy-2.4.4-cp314-cp314t-macosx_14_0_x86_64.whl", hash = "sha256:874f200b2a981c647340f841730fc3a2b54c9d940566a3c4149099591e2c4c3d", size = 6650758 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/6e/6e/795cc078b78a384052e73b2f6281ff7a700e9bf53bcce2ee579d4f6dd879/numpy-2.4.4-cp314-cp314t-manylinux_2_27_aarch64.manylinux_2_28_aarch64.whl", hash = "sha256:c9b39d38a9bd2ae1becd7eac1303d031c5c110ad31f2b319c6e7d98b135c934d", size = 15729948 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5f/86/2acbda8cc2af5f3d7bfc791192863b9e3e19674da7b5e533fded124d1299/numpy-2.4.4-cp314-cp314t-manylinux_2_27_x86_64.manylinux_2_28_x86_64.whl", hash = "sha256:b268594bccac7d7cf5844c7732e3f20c50921d94e36d7ec9b79e9857694b1b2f", size = 16679325 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/bc/59/cafd83018f4aa55e0ac6fa92aa066c0a1877b77a615ceff1711c260ffae8/numpy-2.4.4-cp314-cp314t-musllinux_1_2_aarch64.whl", hash = "sha256:ac6b31e35612a26483e20750126d30d0941f949426974cace8e6b5c58a3657b0", size = 17084883 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f0/85/a42548db84e65ece46ab2caea3d3f78b416a47af387fcbb47ec28e660dc2/numpy-2.4.4-cp314-cp314t-musllinux_1_2_x86_64.whl", hash = "sha256:8e3ed142f2728df44263aaf5fb1f5b0b99f4070c553a0d7f033be65338329150", size = 18403474 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ed/ad/483d9e262f4b831000062e5d8a45e342166ec8aaa1195264982bca267e62/numpy-2.4.4-cp314-cp314t-win32.whl", hash = "sha256:dddbbd259598d7240b18c9d87c56a9d2fb3b02fe266f49a7c101532e78c1d871", size = 6155500 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/c7/03/2fc4e14c7bd4ff2964b74ba90ecb8552540b6315f201df70f137faa5c589/numpy-2.4.4-cp314-cp314t-win_amd64.whl", hash = "sha256:a7164afb23be6e37ad90b2f10426149fd75aee07ca55653d2aa41e66c4ef697e", size = 12637755 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/58/78/548fb8e07b1a341746bfbecb32f2c268470f45fa028aacdbd10d9bc73aab/numpy-2.4.4-cp314-cp314t-win_arm64.whl", hash = "sha256:ba203255017337d39f89bdd58417f03c4426f12beed0440cfd933cb15f8669c7", size = 10566643 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "packaging"
|
|
||||||
version = "26.1"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/df/de/0d2b39fb4af88a0258f3bac87dfcbb48e73fbdea4a2ed0e2213f9a4c2f9a/packaging-26.1.tar.gz", hash = "sha256:f042152b681c4bfac5cae2742a55e103d27ab2ec0f3d88037136b6bfe7c9c5de", size = 215519 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/7a/c2/920ef838e2f0028c8262f16101ec09ebd5969864e5a64c4c05fad0617c56/packaging-26.1-py3-none-any.whl", hash = "sha256:5d9c0669c6285e491e0ced2eee587eaf67b670d94a19e94e3984a481aba6802f", size = 95831 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pluggy"
|
|
||||||
version = "1.6.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/f9/e2/3e91f31a7d2b083fe6ef3fa267035b518369d9511ffab804f839851d2779/pluggy-1.6.0.tar.gz", hash = "sha256:7dcc130b76258d33b90f61b658791dede3486c3e6bfb003ee5c9bfb396dd22f3", size = 69412 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/54/20/4d324d65cc6d9205fabedc306948156824eb9f0ee1633355a8f7ec5c66bf/pluggy-1.6.0-py3-none-any.whl", hash = "sha256:e920276dd6813095e9377c0bc5566d94c932c33b27a3e3945d8389c374dd4746", size = 20538 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pygments"
|
|
||||||
version = "2.20.0"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/c3/b2/bc9c9196916376152d655522fdcebac55e66de6603a76a02bca1b6414f6c/pygments-2.20.0.tar.gz", hash = "sha256:6757cd03768053ff99f3039c1a36d6c0aa0b263438fcab17520b30a303a82b5f", size = 4955991 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f4/7e/a72dd26f3b0f4f2bf1dd8923c85f7ceb43172af56d63c7383eb62b332364/pygments-2.20.0-py3-none-any.whl", hash = "sha256:81a9e26dd42fd28a23a2d169d86d7ac03b46e2f8b59ed4698fb4785f946d0176", size = 1231151 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "pytest"
|
|
||||||
version = "9.0.3"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
dependencies = [
|
|
||||||
{ name = "colorama", marker = "sys_platform == 'win32'" },
|
|
||||||
{ name = "iniconfig" },
|
|
||||||
{ name = "packaging" },
|
|
||||||
{ name = "pluggy" },
|
|
||||||
{ name = "pygments" },
|
|
||||||
]
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/7d/0d/549bd94f1a0a402dc8cf64563a117c0f3765662e2e668477624baeec44d5/pytest-9.0.3.tar.gz", hash = "sha256:b86ada508af81d19edeb213c681b1d48246c1a91d304c6c81a427674c17eb91c", size = 1572165 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d4/24/a372aaf5c9b7208e7112038812994107bc65a84cd00e0354a88c2c77a617/pytest-9.0.3-py3-none-any.whl", hash = "sha256:2c5efc453d45394fdd706ade797c0a81091eccd1d6e4bccfcd476e2b8e0ab5d9", size = 375249 },
|
|
||||||
]
|
|
||||||
|
|
||||||
[[package]]
|
|
||||||
name = "ruff"
|
|
||||||
version = "0.15.11"
|
|
||||||
source = { registry = "https://pypi.org/simple" }
|
|
||||||
sdist = { url = "https://files.pythonhosted.org/packages/e4/8d/192f3d7103816158dfd5ea50d098ef2aec19194e6cbccd4b3485bdb2eb2d/ruff-0.15.11.tar.gz", hash = "sha256:f092b21708bf0e7437ce9ada249dfe688ff9a0954fc94abab05dcea7dcd29c33", size = 4637264 }
|
|
||||||
wheels = [
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/1e/6aca3427f751295ab011828e15e9bf452200ac74484f1db4be0197b8170b/ruff-0.15.11-py3-none-linux_armv6l.whl", hash = "sha256:e927cfff503135c558eb581a0c9792264aae9507904eb27809cdcff2f2c847b7", size = 10607943 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e7/26/1341c262e74f36d4e84f3d6f4df0ac68cd53331a66bfc5080daa17c84c0b/ruff-0.15.11-py3-none-macosx_10_12_x86_64.whl", hash = "sha256:7a1b5b2938d8f890b76084d4fa843604d787a912541eae85fd7e233398bbb73e", size = 10988592 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/03/71/850b1d6ffa9564fbb6740429bad53df1094082fe515c8c1e74b6d8d05f18/ruff-0.15.11-py3-none-macosx_11_0_arm64.whl", hash = "sha256:d4176f3d194afbdaee6e41b9ccb1a2c287dba8700047df474abfbe773825d1cb", size = 10338501 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/f2/11/cc1284d3e298c45a817a6aadb6c3e1d70b45c9b36d8d9cce3387b495a03a/ruff-0.15.11-py3-none-manylinux_2_17_aarch64.manylinux2014_aarch64.whl", hash = "sha256:3b17c886fb88203ced3afe7f14e8d5ae96e9d2f4ccc0ee66aa19f2c2675a27e4", size = 10670693 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/ce/9e/f8288b034ab72b371513c13f9a41d9ba3effac54e24bfb467b007daee2ca/ruff-0.15.11-py3-none-manylinux_2_17_armv7l.manylinux2014_armv7l.whl", hash = "sha256:49fafa220220afe7758a487b048de4c8f9f767f37dfefad46b9dd06759d003eb", size = 10416177 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/85/71/504d79abfd3d92532ba6bbe3d1c19fada03e494332a59e37c7c2dabae427/ruff-0.15.11-py3-none-manylinux_2_17_i686.manylinux2014_i686.whl", hash = "sha256:f2ab8427e74a00d93b8bda1307b1e60970d40f304af38bccb218e056c220120d", size = 11221886 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/5a/947e6ab7a5ad603d65b474be15a4cbc6d29832db5d762cd142e4e3a74164/ruff-0.15.11-py3-none-manylinux_2_17_ppc64le.manylinux2014_ppc64le.whl", hash = "sha256:195072c0c8e1fc8f940652073df082e37a5d9cb43b4ab1e4d0566ab8977a13b7", size = 12075183 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/9f/a1/0b7bb6268775fdd3a0818aee8efd8f5b4e231d24dd4d528ced2534023182/ruff-0.15.11-py3-none-manylinux_2_17_s390x.manylinux2014_s390x.whl", hash = "sha256:a3a0996d486af3920dec930a2e7daed4847dfc12649b537a9335585ada163e9e", size = 11516575 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/30/c3/bb5168fc4d233cc06e95f482770d0f3c87945a0cd9f614b90ea8dc2f2833/ruff-0.15.11-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl", hash = "sha256:1bef2cb556d509259f1fe440bb9cd33c756222cf0a7afe90d15edf0866702431", size = 11306537 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/e4/92/4cfae6441f3967317946f3b788136eecf093729b94d6561f963ed810c82e/ruff-0.15.11-py3-none-manylinux_2_31_riscv64.whl", hash = "sha256:030d921a836d7d4a12cf6e8d984a88b66094ccb0e0f17ddd55067c331191bf19", size = 11296813 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/43/26/972784c5dde8313acde8ac71ba8ac65475b85db4a2352a76c9934361f9bc/ruff-0.15.11-py3-none-musllinux_1_2_aarch64.whl", hash = "sha256:0e783b599b4577788dbbb66b9addcef87e9a8832f4ce0c19e34bf55543a2f890", size = 10633136 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/5b/53/3985a4f185020c2f367f2e08a103032e12564829742a1b417980ce1514a0/ruff-0.15.11-py3-none-musllinux_1_2_armv7l.whl", hash = "sha256:ae90592246625ba4a34349d68ec28d4400d75182b71baa196ddb9f82db025ef5", size = 10424701 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/d3/57/bf0dfb32241b56c83bb663a826133da4bf17f682ba8c096973065f6e6a68/ruff-0.15.11-py3-none-musllinux_1_2_i686.whl", hash = "sha256:1f111d62e3c983ed20e0ca2e800f8d77433a5b1161947df99a5c2a3fb60514f0", size = 10873887 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/02/05/e48076b2a57dc33ee8c7a957296f97c744ca891a8ffb4ffb1aaa3b3f517d/ruff-0.15.11-py3-none-musllinux_1_2_x86_64.whl", hash = "sha256:06f483d6646f59eaffba9ae30956370d3a886625f511a3108994000480621d1c", size = 11404316 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/88/27/0195d15fe7a897cbcba0904792c4b7c9fdd958456c3a17d2ea6093716a9a/ruff-0.15.11-py3-none-win32.whl", hash = "sha256:476a2aa56b7da0b73a3ee80b6b2f0e19cce544245479adde7baa65466664d5f3", size = 10655535 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/3a/5e/c927b325bd4c1d3620211a4b96f47864633199feed60fa936025ab27e090/ruff-0.15.11-py3-none-win_amd64.whl", hash = "sha256:8b6756d88d7e234fb0c98c91511aae3cd519d5e3ed271cae31b20f39cb2a12a3", size = 11779692 },
|
|
||||||
{ url = "https://files.pythonhosted.org/packages/63/b6/aeadee5443e49baa2facd51131159fd6301cc4ccfc1541e4df7b021c37dd/ruff-0.15.11-py3-none-win_arm64.whl", hash = "sha256:063fed18cc1bbe0ee7393957284a6fe8b588c6a406a285af3ee3f46da2391ee4", size = 11032614 },
|
|
||||||
]
|
|
||||||
Reference in New Issue
Block a user