Merge pull request #46 from NawfalMotii79/develop

Merge the develop branch into main with the fixed changes.
This commit is contained in:
Jason
2026-04-07 21:45:17 +03:00
committed by GitHub
51 changed files with 4166 additions and 5310 deletions
+8 -4
View File
@@ -20,12 +20,16 @@ wire [7:0] adc_data;
wire adc_dco; wire adc_dco;
// IBUFDS for each data bit // IBUFDS for each data bit
// NOTE: IOSTANDARD and DIFF_TERM are set via XDC constraints, not RTL
// parameters, to support multiple FPGA targets with different bank voltages:
// - XC7A200T (FBG484): Bank 14 VCCO = 2.5V → LVDS_25
// - XC7A50T (FTG256): Bank 14 VCCO = 3.3V → LVDS_33
genvar i; genvar i;
generate generate
for (i = 0; i < 8; i = i + 1) begin : data_buffers for (i = 0; i < 8; i = i + 1) begin : data_buffers
IBUFDS #( IBUFDS #(
.DIFF_TERM("TRUE"), .DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property
.IOSTANDARD("LVDS_25") .IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property
) ibufds_data ( ) ibufds_data (
.O(adc_data[i]), .O(adc_data[i]),
.I(adc_d_p[i]), .I(adc_d_p[i]),
@@ -36,8 +40,8 @@ endgenerate
// IBUFDS for DCO // IBUFDS for DCO
IBUFDS #( IBUFDS #(
.DIFF_TERM("TRUE"), .DIFF_TERM("FALSE"), // Overridden by XDC DIFF_TERM property
.IOSTANDARD("LVDS_25") .IOSTANDARD("DEFAULT") // Overridden by XDC IOSTANDARD property
) ibufds_dco ( ) ibufds_dco (
.O(adc_dco), .O(adc_dco),
.I(adc_dco_p), .I(adc_dco_p),
+135 -25
View File
@@ -4,19 +4,19 @@
| File | Device | Package | Purpose | | File | Device | Package | Purpose |
|------|--------|---------|---------| |------|--------|---------|---------|
| `xc7a50t_ftg256.xdc` | XC7A50T-2FTG256I | FTG256 (256-ball BGA) | Upstream author's board (copy of `cntrt.xdc`) | | `xc7a50t_ftg256.xdc` | XC7A50T-2FTG256I | FTG256 (256-ball BGA) | 50T production board |
| `xc7a200t_fbg484.xdc` | XC7A200T-2FBG484I | FBG484 (484-ball BGA) | Production board (new PCB design) | | `xc7a200t_fbg484.xdc` | XC7A200T-2FBG484I | FBG484 (484-ball BGA) | 200T premium dev board |
| `te0712_te0701_minimal.xdc` | XC7A200T-2FBG484I | FBG484 (484-ball BGA) | Trenz dev split target (minimal clock/reset + LEDs/status) | | `te0712_te0701_minimal.xdc` | XC7A200T-2FBG484I | FBG484 (484-ball BGA) | Trenz dev split target (minimal clock/reset + LEDs/status) |
| `te0713_te0701_minimal.xdc` | XC7A200T-2FBG484C | FBG484 (484-ball BGA) | Trenz alternate SoM target (minimal clock + FMC status outputs) | | `te0713_te0701_minimal.xdc` | XC7A200T-2FBG484C | FBG484 (484-ball BGA) | Trenz alternate SoM target (minimal clock + FMC status outputs) |
## Why Four Files ## Why Four Files
The upstream prototype uses a smaller XC7A50T in an FTG256 package. The production The 50T production board uses an XC7A50T in an FTG256 package. The 200T premium
AERIS-10 radar migrates to the XC7A200T for more logic, BRAM, and DSP resources. dev board uses an XC7A200T for more logic, BRAM, and DSP resources. The two
The two devices have completely different packages and pin names, so each needs its devices have completely different packages and pin names, so each needs its own
own constraint file. constraint file.
The Trenz TE0712/TE0701 path uses the same FPGA part as production but different board The Trenz TE0712/TE0701 path uses the same FPGA part as the 200T but different board
pinout and peripherals. The dev target is split into its own top wrapper pinout and peripherals. The dev target is split into its own top wrapper
(`radar_system_top_te0712_dev.v`) and minimal constraints file to avoid accidental mixing (`radar_system_top_te0712_dev.v`) and minimal constraints file to avoid accidental mixing
of production pin assignments during bring-up. of production pin assignments during bring-up.
@@ -25,9 +25,83 @@ The Trenz TE0713/TE0701 path supports situations where TE0712 lead time is prohi
TE0713 uses XC7A200T-2FBG484C (commercial temp grade) and requires separate clock mapping, TE0713 uses XC7A200T-2FBG484C (commercial temp grade) and requires separate clock mapping,
so it has its own dev top and XDC. so it has its own dev top and XDC.
## USB Interface Architecture (USB_MODE)
The radar system supports two USB data interfaces, selected at **compile time** via
the `USB_MODE` parameter in `radar_system_top.v`:
| USB_MODE | Interface | Bus Width | Speed | Board Target |
|----------|-----------|-----------|-------|--------------|
| 0 (default) | FT601 (USB 3.0) | 32-bit | 100 MHz | 200T premium dev board |
| 1 | FT2232H (USB 2.0) | 8-bit | 60 MHz | 50T production board |
### How USB_MODE Works
`radar_system_top.v` contains a Verilog `generate` block that instantiates exactly
one USB interface module based on the `USB_MODE` parameter:
```
generate
if (USB_MODE == 0) begin : gen_ft601
usb_data_interface usb_inst (...) // FT601, 32-bit
// FT2232H ports tied off to inactive
end else begin : gen_ft2232h
usb_data_interface_ft2232h usb_inst (...) // FT2232H, 8-bit
// FT601 ports tied off to inactive
end
endgenerate
```
Both interfaces share the same internal radar data bus and host command interface.
The unused interface's I/O pins are tied to safe inactive states (active-low
signals high, active-high signals low, bidirectional buses high-Z).
### How USB_MODE Is Passed Per Board Target
The parameter is set via a **wrapper module** that overrides the default:
- **50T production**: `radar_system_top_50t.v` instantiates the core with
`.USB_MODE(1)` and maps the FT2232H's 60 MHz `CLKOUT` to the shared
`ft601_clk_in` port. FT601 inputs are tied inactive; outputs go to `_nc` wires.
```verilog
// In radar_system_top_50t.v:
radar_system_top #(
.USB_MODE(1)
) u_core ( ... );
```
- **200T dev board**: `radar_system_top` is used directly as the top module.
`USB_MODE` defaults to `0` (FT601). No wrapper needed.
### RTL Files by USB Interface
| File | Purpose |
|------|---------|
| `usb_data_interface.v` | FT601 USB 3.0 module (32-bit, USB_MODE=0) |
| `usb_data_interface_ft2232h.v` | FT2232H USB 2.0 module (8-bit, USB_MODE=1) |
| `radar_system_top.v` | Core module with USB_MODE generate block |
| `radar_system_top_50t.v` | 50T wrapper: sets USB_MODE=1, ties off FT601 |
### FT2232H Pin Map (50T, Bank 35, VCCO=3.3V)
All connections are direct between U6 (FT2232HQ) and U42 (XC7A50T). Only
Channel A is used (245 Synchronous FIFO mode). Channel B is unconnected.
| Signal | FT2232H Pin | FPGA Ball | Direction |
|--------|-------------|-----------|-----------|
| FT_D[7:0] | ADBUS[7:0] | K1,J3,H3,G4,F2,D1,C3,C1 | Bidirectional |
| FT_RXF# | ACBUS0 | A2 | Input (FIFO not empty) |
| FT_TXE# | ACBUS1 | B2 | Input (FIFO not full) |
| FT_RD# | ACBUS2 | A3 | Output (read strobe) |
| FT_WR# | ACBUS3 | A4 | Output (write strobe) |
| FT_SIWUA | ACBUS4 | A5 | Output (send immediate) |
| FT_CLKOUT | ACBUS5 | C4 (MRCC) | Input (60 MHz clock) |
| FT_OE# | ACBUS6 | B7 | Output (bus direction) |
## Bank Voltage Assignments ## Bank Voltage Assignments
### XC7A50T-FTG256 (Upstream) ### XC7A50T-FTG256 (50T Production)
| Bank | VCCO | Signals | | Bank | VCCO | Signals |
|------|------|---------| |------|------|---------|
@@ -35,9 +109,9 @@ so it has its own dev top and XDC.
| 14 | 3.3V | ADC LVDS (LVDS_33), SPI flash | | 14 | 3.3V | ADC LVDS (LVDS_33), SPI flash |
| 15 | 3.3V | DAC, clocks, STM32 3.3V SPI, DIG bus | | 15 | 3.3V | DAC, clocks, STM32 3.3V SPI, DIG bus |
| 34 | 1.8V | ADAR1000 control, SPI 1.8V side | | 34 | 1.8V | ADAR1000 control, SPI 1.8V side |
| 35 | 3.3V | Unused (no signal connections) | | 35 | 3.3V | FT2232H USB 2.0 (8-bit data + control, 15 signals) |
### XC7A200T-FBG484 (Production) ### XC7A200T-FBG484 (200T Premium Dev Board)
| Bank | VCCO | Used/Avail | Signals | | Bank | VCCO | Used/Avail | Signals |
|------|------|------------|---------| |------|------|------------|---------|
@@ -50,15 +124,43 @@ so it has its own dev top and XDC.
## Signal Differences Between Targets ## Signal Differences Between Targets
| Signal | Upstream (FTG256) | Production (FBG484) | | Signal | 50T Production (FTG256) | 200T Dev (FBG484) |
|--------|-------------------|---------------------| |--------|-------------------------|-------------------|
| FT601 USB | Unwired (chip placed, no nets) | Fully wired, Bank 16 | | USB interface | FT2232H USB 2.0 (8-bit, Bank 35) | FT601 USB 3.0 (32-bit, Bank 16) |
| USB_MODE | 1 (via `radar_system_top_50t` wrapper) | 0 (default in `radar_system_top`) |
| USB clock | 60 MHz from FT2232H CLKOUT | 100 MHz from FT601 |
| `dac_clk` | Not connected (DAC clocked by AD9523 directly) | Routed, FPGA drives DAC | | `dac_clk` | Not connected (DAC clocked by AD9523 directly) | Routed, FPGA drives DAC |
| `ft601_be` width | `[1:0]` in upstream RTL | `[3:0]` (RTL updated) | | `ft601_be` width | N/A (FT601 unused, tied off) | `[3:0]` (RTL updated) |
| ADC LVDS standard | LVDS_33 (3.3V bank) | LVDS_25 (2.5V bank, better quality) | | ADC LVDS standard | LVDS_33 (3.3V bank) | LVDS_25 (2.5V bank, better quality) |
| Status/debug outputs | No physical pins (commented out) | All routed to Banks 35 + 13 | | Status/debug outputs | No physical pins (commented out) | All routed to Banks 35 + 13 |
## How to Select in Vivado ## How to Build
### Quick Reference
```bash
# From the FPGA source directory (9_Firmware/9_2_FPGA):
# 50T production build (FT2232H, USB_MODE=1):
vivado -mode batch -source scripts/50t/build_50t.tcl 2>&1 | tee build_50t/vivado.log
# 200T dev build (FT601, USB_MODE=0):
vivado -mode batch -source scripts/200t/build_200t.tcl \
-log build/build.log -journal build/build.jou
```
The build scripts automatically select the correct top module and constraints:
| Build Script | Top Module | Constraints | USB_MODE |
|--------------|------------|-------------|----------|
| `scripts/50t/build_50t.tcl` | `radar_system_top_50t` | `xc7a50t_ftg256.xdc` | 1 (FT2232H) |
| `scripts/200t/build_200t.tcl` | `radar_system_top` | `xc7a200t_fbg484.xdc` | 0 (FT601) |
You do NOT need to set `USB_MODE` manually. The top module selection handles it:
- `radar_system_top_50t` forces `USB_MODE=1` internally
- `radar_system_top` defaults to `USB_MODE=0`
## How to Select Constraints in Vivado
In the Vivado project, only one target XDC should be active at a time: In the Vivado project, only one target XDC should be active at a time:
@@ -85,12 +187,12 @@ read_xdc constraints/te0713_te0701_minimal.xdc
## Top Modules by Target ## Top Modules by Target
| Target | Top module | Notes | | Target | Top module | USB_MODE | USB Interface | Notes |
|--------|------------|-------| |--------|------------|----------|---------------|-------|
| Upstream FTG256 | `radar_system_top` | Legacy board support | | 50T Production (FTG256) | `radar_system_top_50t` | 1 | FT2232H (8-bit) | Wrapper sets USB_MODE=1, ties off FT601 |
| Production FBG484 | `radar_system_top` | Main AERIS-10 board | | 200T Dev (FBG484) | `radar_system_top` | 0 (default) | FT601 (32-bit) | No wrapper needed |
| Trenz TE0712/TE0701 | `radar_system_top_te0712_dev` | Minimal bring-up wrapper while pinout/peripherals are migrated | | Trenz TE0712/TE0701 | `radar_system_top_te0712_dev` | 0 (default) | FT601 (32-bit) | Minimal bring-up wrapper |
| Trenz TE0713/TE0701 | `radar_system_top_te0713_dev` | Alternate SoM wrapper (TE0713 clock mapping) | | Trenz TE0713/TE0701 | `radar_system_top_te0713_dev` | 0 (default) | FT601 (32-bit) | Alternate SoM wrapper |
## Trenz Split Status ## Trenz Split Status
@@ -142,11 +244,19 @@ TE0713 outputs:
## Notes ## Notes
- The production XDC pin assignments are **recommended** for the new PCB. - **USB_MODE is compile-time only.** You cannot switch USB interfaces at runtime.
Each board target has exactly one USB chip physically connected.
- The 50T production build must use `radar_system_top_50t` as top module. Using
`radar_system_top` directly will default to FT601 (USB_MODE=0), which has no
physical connection on the 50T board.
- The 200T XDC pin assignments are **recommended** for the new PCB.
The PCB designer should follow this allocation. The PCB designer should follow this allocation.
- Bank 16 (FT601) is fully utilized at 50/50 pins. No room for expansion - Bank 16 on the 200T (FT601) is fully utilized at 50/50 pins. No room for expansion
on that bank. on that bank.
- Bank 35 (status/debug) is also at capacity (50/50). Additional debug - Bank 35 on the 200T (status/debug) is also at capacity (50/50). Additional debug
signals should use Bank 13 spare pins (18 remaining). signals should use Bank 13 spare pins (18 remaining).
- Bank 35 on the 50T is used for FT2232H (15 signals). Remaining pins are available
for future expansion.
- Clock inputs are placed on MRCC (Multi-Region Clock Capable) pins to - Clock inputs are placed on MRCC (Multi-Region Clock Capable) pins to
ensure proper clock tree access. ensure proper clock tree access. The FT2232H CLKOUT (60 MHz) is on
pin C4 (`IO_L12N_T1_MRCC_35`).
@@ -62,12 +62,24 @@ set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_mmcm_out0]
set_false_path -through [get_pins rx_inst/adc/mmcm_inst/mmcm_adc_400m/LOCKED] set_false_path -through [get_pins rx_inst/adc/mmcm_inst/mmcm_adc_400m/LOCKED]
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# Hold waiver for BUFIO→MMCM domain transfer (if Vivado flags hold violations) # Hold waiver for source-synchronous ADC capture (BUFIO-clocked IDDR)
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# The existing hold waiver for BUFIO source-synchronous capture stays: # The AD9484 ADC provides a source-synchronous interface: data (adc_d_p/n)
# set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p] # and clock (adc_dco_p/n) are output from the same chip with matched timing.
# On the PCB, data and DCO traces are length-matched.
# #
# The MMCM BUFG re-registration of IDDR outputs: since BUFIO and MMCM output # Inside the FPGA, the DCO clock path goes through IBUFDS → BUFIO, adding
# are derived from the same IBUFDS source, hold is inherently met (MMCM adds # ~2.2ns of insertion delay (IBUFDS 0.9ns + routing 0.6ns + BUFIO 1.3ns).
# insertion delay). If Vivado flags hold violations on this transfer, uncomment: # The data path goes through IBUFDS only (~0.85ns), arriving at the IDDR
# set_false_path -hold -from [get_clocks adc_dco_p] -to [get_clocks clk_mmcm_out0] # ~1.4ns before the clock. Vivado's hold analysis sees the data "changing"
# before the clock edge and reports WHS = -1.955ns.
#
# This is correct internal behavior: the BUFIO clock intentionally arrives
# after the data. The IDDR captures on the BUFIO edge, by which time the
# data is stable. Hold timing is guaranteed by the external PCB layout
# (ADC data valid window centered on DCO edge), not by FPGA clock tree
# delays. Vivado's STA model cannot account for this external relationship.
#
# Waiving hold on these 8 paths (adc_d_p[0..7] → IDDR) is standard practice
# for source-synchronous LVDS ADC interfaces using BUFIO capture.
set_false_path -hold -from [get_ports {adc_d_p[*]}] -to [get_clocks adc_dco_p]
@@ -12,12 +12,50 @@
# #
# I/O Bank Voltage Summary: # I/O Bank Voltage Summary:
# Bank 0: VCCO = 3.3V (JTAG, flash CS) # Bank 0: VCCO = 3.3V (JTAG, flash CS)
# Bank 14: VCCO = 3.3V (ADC LVDS data, SPI flash) # Bank 14: VCCO = 2.5V (ADC LVDS_25 data — placer-enforced; adc_pwdn as LVCMOS25)
# Bank 15: VCCO = 3.3V (DAC, clocks, STM32 SPI 3.3V side, DIG bus, mixer) # Bank 15: VCCO = 3.3V (DAC, clocks, STM32 SPI 3.3V side, DIG bus, mixer)
# Bank 34: VCCO = 1.8V (ADAR1000 beamformer control, SPI 1.8V side) # Bank 34: VCCO = 1.8V (ADAR1000 beamformer control, SPI 1.8V side)
# Bank 35: VCCO = 3.3V (unusedno signal connections) # Bank 35: VCCO = 3.3V (FT2232H USB 2.0 FIFO15 signals)
#
# DRC Fix History:
# - PLIO-9: Moved clk_120m_dac from C13 (N-type) to D13 (P-type MRCC).
# Clock inputs must use the P-type pin of a Multi-Region Clock-Capable pair.
# - BIVC-1 / Place 30-372: Bank 14 must have a single VCCO. LVDS_25 forces
# VCCO=2.5V, so adc_pwdn was changed from LVCMOS33 to LVCMOS25 to match.
# IBUFDS input buffers are VCCO-independent. BIVC-1 also waived via
# set_property SEVERITY in the build script as an additional safety net.
# in the build script. adc_pwdn (LVCMOS25) coexists in the same bank.
# - UCIO/NSTD: Unconstrained ports (FT601 ports inactive with USB_MODE=1,
# status/debug outputs have no physical pins). Handled with SEVERITY
# demotion + default IOSTANDARD.
# - PLIO-9: FT2232H CLKOUT routed to C4 (IO_L12N_T1_MRCC_35, N-type).
# Clock inputs normally use P-type MRCC pins, but IBUFG works correctly
# on N-type. Demote PLIO-9 to warning in build script.
# ============================================================================ # ============================================================================
# ============================================================================
# CONFIGURATION
# ============================================================================
set_property CFGBVS VCCO [current_design]
set_property CONFIG_VOLTAGE 3.3 [current_design]
# ============================================================================
# DRC WAIVERS — Hardware-Level Known Issues (applied in build script)
# ============================================================================
# BIVC-1: Bank 14 VCCO=3.3V with LVDS_25 inputs + LVCMOS33 adc_pwdn.
# IBUFDS input buffers are VCCO-independent on 7-series — they use an
# internal differential amplifier that works correctly at any VCCO.
# The BIVC-1 DRC check is intended for OBUFDS *outputs* where VCCO
# directly affects the output swing. Since Bank 14 has only LVDS inputs
# and one LVCMOS33 output, this is safe to demote to a warning.
# → Applied in build_50t_test.tcl: set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
#
# NSTD-1 / UCIO-1: Unconstrained ports — FT601 USB ports (inactive with
# USB_MODE=1 generate block), dac_clk (DAC clock comes from AD9523, not FPGA),
# and all status/debug outputs (no physical pins available). These ports are
# present in the shared RTL but have no connections on the 50T board.
# → Applied in build_50t_test.tcl: set_property SEVERITY {Warning} [get_drc_checks {NSTD-1 UCIO-1}]
# ============================================================================ # ============================================================================
# CLOCK CONSTRAINTS # CLOCK CONSTRAINTS
# ============================================================================ # ============================================================================
@@ -28,33 +66,48 @@ set_property IOSTANDARD LVCMOS33 [get_ports {clk_100m}]
create_clock -name clk_100m -period 10.0 [get_ports {clk_100m}] create_clock -name clk_100m -period 10.0 [get_ports {clk_100m}]
set_input_jitter [get_clocks clk_100m] 0.1 set_input_jitter [get_clocks clk_100m] 0.1
# 120MHz DAC Clock (AD9523 OUT11 → FPGA_DAC_CLOCK → Bank 15 MRCC pin C13) # 120MHz DAC Clock (AD9523 OUT11 → FPGA_DAC_CLOCK → Bank 15 MRCC pin D13)
# NOTE: The physical DAC (U3, AD9708) receives its clock directly from the # NOTE: The physical DAC (U3, AD9708) receives its clock directly from the
# AD9523 via a separate net (DAC_CLOCK), NOT from the FPGA. The FPGA # AD9523 via a separate net (DAC_CLOCK), NOT from the FPGA. The FPGA
# uses this clock input for internal DAC data timing only. The RTL port # uses this clock input for internal DAC data timing only. The RTL port
# `dac_clk` is an output that assigns clk_120m directly — it has no # `dac_clk` is an output that assigns clk_120m directly — it has no
# separate physical pin on this board and should be removed from the # separate physical pin on this board and should be removed from the
# RTL or left unconnected. # RTL or left unconnected.
set_property PACKAGE_PIN C13 [get_ports {clk_120m_dac}] # FIX: Moved from C13 (IO_L12N = N-type) to D13 (IO_L12P = P-type MRCC).
# Clock inputs must use the P-type pin of an MRCC pair (PLIO-9 DRC).
set_property PACKAGE_PIN D13 [get_ports {clk_120m_dac}]
set_property IOSTANDARD LVCMOS33 [get_ports {clk_120m_dac}] set_property IOSTANDARD LVCMOS33 [get_ports {clk_120m_dac}]
create_clock -name clk_120m_dac -period 8.333 [get_ports {clk_120m_dac}] create_clock -name clk_120m_dac -period 8.333 [get_ports {clk_120m_dac}]
set_input_jitter [get_clocks clk_120m_dac] 0.1 set_input_jitter [get_clocks clk_120m_dac] 0.1
# ADC DCO Clock (400MHz LVDS — AD9523 OUT5 → AD9484 → FPGA, Bank 14 MRCC) # ADC DCO Clock (400MHz LVDS — AD9523 OUT5 → AD9484 → FPGA, Bank 14 MRCC)
# NOTE: LVDS_25 is the only valid differential input standard on 7-series HR
# banks. IBUFDS input buffers are VCCO-independent — they work correctly even
# with VCCO=3.3V. The BIVC-1 DRC (voltage conflict with LVCMOS33 adc_pwdn)
# is waived in the build script since this bank has only LVDS *inputs*.
set_property PACKAGE_PIN N14 [get_ports {adc_dco_p}] set_property PACKAGE_PIN N14 [get_ports {adc_dco_p}]
set_property PACKAGE_PIN P14 [get_ports {adc_dco_n}] set_property PACKAGE_PIN P14 [get_ports {adc_dco_n}]
set_property IOSTANDARD LVDS_33 [get_ports {adc_dco_p}] set_property IOSTANDARD LVDS_25 [get_ports {adc_dco_p}]
set_property IOSTANDARD LVDS_33 [get_ports {adc_dco_n}] set_property IOSTANDARD LVDS_25 [get_ports {adc_dco_n}]
set_property DIFF_TERM TRUE [get_ports {adc_dco_p}] set_property DIFF_TERM TRUE [get_ports {adc_dco_p}]
create_clock -name adc_dco_p -period 2.5 [get_ports {adc_dco_p}] create_clock -name adc_dco_p -period 2.5 [get_ports {adc_dco_p}]
set_input_jitter [get_clocks adc_dco_p] 0.05 set_input_jitter [get_clocks adc_dco_p] 0.05
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# FT601 Clock — COMMENTED OUT: FT601 (U6) is placed in schematic but has # FT2232H 60 MHz CLKOUT (Bank 35, MRCC pin C4)
# zero net connections. No physical clock pin exists on this board.
# -------------------------------------------------------------------------- # --------------------------------------------------------------------------
# create_clock -name ft601_clk_in -period 10.0 [get_ports {ft601_clk_in}] # The FT2232H provides a 60 MHz clock in 245 Synchronous FIFO mode.
# set_input_jitter [get_clocks ft601_clk_in] 0.1 # Pin C4 is IO_L12N_T1_MRCC_35 (N-type of MRCC pair). Vivado requires
# CLOCK_DEDICATED_ROUTE FALSE for clock inputs on N-type MRCC pins
# (Place 30-876). The schematic routes CLKOUT to C4; this cannot be
# changed without a board respin. The clock still uses an IBUFG and
# reaches the clock network — the constraint only disables the DRC check.
set_property PACKAGE_PIN C4 [get_ports {ft_clkout}]
set_property IOSTANDARD LVCMOS33 [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
# N-type MRCC pin requires dedicated route override (Place 30-876)
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
# ============================================================================ # ============================================================================
# RESET (Active-Low) # RESET (Active-Low)
@@ -197,41 +250,80 @@ set_property PACKAGE_PIN R7 [get_ports {adc_d_n[7]}]
# ADC DCO Clock (LVDS) — already constrained above in CLOCK section # ADC DCO Clock (LVDS) — already constrained above in CLOCK section
# ADC Power Down — ADC_PWRD net (single-ended, Bank 14) # ADC Power Down — ADC_PWRD net (single-ended, Bank 14)
# Uses LVCMOS25 to be voltage-compatible with LVDS_25 in the same bank.
# The placer enforces a single VCCO per bank; LVDS_25 demands VCCO=2.5V.
# LVCMOS25 output drives the AD9484 PWDN pin (CMOS threshold ~0.8V) safely.
# On the physical board (VCCO=3.3V), output swings follow actual VCCO.
set_property PACKAGE_PIN T5 [get_ports {adc_pwdn}] set_property PACKAGE_PIN T5 [get_ports {adc_pwdn}]
set_property IOSTANDARD LVCMOS33 [get_ports {adc_pwdn}] set_property IOSTANDARD LVCMOS25 [get_ports {adc_pwdn}]
# LVDS I/O Standard — Bank 14 VCCO = 3.3V → use LVDS_33 (not LVDS_25) # LVDS I/O Standard — LVDS_25 is the only valid differential input standard
set_property IOSTANDARD LVDS_33 [get_ports {adc_d_p[*]}] # on 7-series HR banks. IBUFDS inputs work correctly regardless of VCCO.
set_property IOSTANDARD LVDS_33 [get_ports {adc_d_n[*]}] set_property IOSTANDARD LVDS_25 [get_ports {adc_d_p[*]}]
set_property IOSTANDARD LVDS_25 [get_ports {adc_d_n[*]}]
# Differential termination # Differential termination — DIFF_TERM uses a ~100-ohm on-die termination
# inside the IBUFDS. This is VCCO-independent for 7-series input buffers.
# RTL IBUFDS uses DIFF_TERM("FALSE") so this XDC property takes precedence.
set_property DIFF_TERM TRUE [get_ports {adc_d_p[*]}] set_property DIFF_TERM TRUE [get_ports {adc_d_p[*]}]
# Input delay for ADC data relative to DCO (adjust based on PCB trace length) # Input delay for ADC data relative to DCO (adjust based on PCB trace length)
# DDR interface: constrain both rising and falling clock edges
set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 [get_ports {adc_d_p[*]}] set_input_delay -clock [get_clocks adc_dco_p] -max 1.0 [get_ports {adc_d_p[*]}]
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] -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] -min 0.2 -clock_fall [get_ports {adc_d_p[*]}] -add_delay
# ============================================================================ # ============================================================================
# FT601 USB 3.0 INTERFACE — ACTIVE: NO PHYSICAL CONNECTIONS # FT2232H USB 2.0 INTERFACE (Bank 35, VCCO=3.3V)
# ============================================================================ # ============================================================================
# The FT601 chip (U6, FT601Q-B-T) is placed in the Eagle schematic but has # FT2232H (U6) Channel A in 245 Synchronous FIFO mode.
# ZERO net connections — no signals are routed between it and the FPGA. # All signals are direct connections to FPGA Bank 35 (LVCMOS33).
# Bank 35 (which would logically host FT601 signals) has no signal pins # Pin mapping extracted from Eagle schematic (RADAR_Main_Board.sch).
# connected, only VCCO_35 power.
# #
# ALL FT601 constraints are commented out. The RTL module usb_data_interface.v # The FT2232H replaces the previously-unwired FT601 on the 50T production
# instantiates the FT601 interface, but it cannot function without physical # board. The 200T dev board retains FT601 USB 3.0 (32-bit).
# pin assignments. To use USB, the schematic must be updated to wire the
# FT601 to FPGA Bank 35 pins, and then these constraints can be populated.
#
# Ports affected (from radar_system_top.v):
# ft601_data[31:0], ft601_be[1:0], ft601_txe_n, ft601_rxf_n, ft601_txe,
# ft601_rxf, ft601_wr_n, ft601_rd_n, ft601_oe_n, ft601_siwu_n,
# ft601_srb[1:0], ft601_swb[1:0], ft601_clk_out, ft601_clk_in
#
# TODO: Wire FT601 in schematic, then assign pins here.
# ============================================================================ # ============================================================================
# 8-bit bidirectional data bus (ADBUS0ADBUS7)
set_property PACKAGE_PIN K1 [get_ports {ft_data[0]}] ;# ADBUS0 → IO_L22P_T3_35
set_property PACKAGE_PIN J3 [get_ports {ft_data[1]}] ;# ADBUS1 → IO_L21P_T3_DQS_35
set_property PACKAGE_PIN H3 [get_ports {ft_data[2]}] ;# ADBUS2 → IO_L21N_T3_DQS_35
set_property PACKAGE_PIN G4 [get_ports {ft_data[3]}] ;# ADBUS3 → IO_L16N_T2_35
set_property PACKAGE_PIN F2 [get_ports {ft_data[4]}] ;# ADBUS4 → IO_L15P_T2_DQS_35
set_property PACKAGE_PIN D1 [get_ports {ft_data[5]}] ;# ADBUS5 → IO_L10N_T1_AD15N_35
set_property PACKAGE_PIN C3 [get_ports {ft_data[6]}] ;# ADBUS6 → IO_L7P_T1_AD6P_35
set_property PACKAGE_PIN C1 [get_ports {ft_data[7]}] ;# ADBUS7 → IO_L9P_T1_DQS_AD7P_35
set_property IOSTANDARD LVCMOS33 [get_ports {ft_data[*]}]
# Control signals (active low where noted)
set_property PACKAGE_PIN A2 [get_ports {ft_rxf_n}] ;# ACBUS0 → IO_L8N_T1_AD14N_35
set_property PACKAGE_PIN B2 [get_ports {ft_txe_n}] ;# ACBUS1 → IO_L8P_T1_AD14P_35
set_property PACKAGE_PIN A3 [get_ports {ft_rd_n}] ;# ACBUS2 → IO_L4N_T0_35
set_property PACKAGE_PIN A4 [get_ports {ft_wr_n}] ;# ACBUS3 → IO_L3N_T0_DQS_AD5N_35
set_property PACKAGE_PIN A5 [get_ports {ft_siwu}] ;# ACBUS4 → IO_L3P_T0_DQS_AD5P_35
set_property PACKAGE_PIN B7 [get_ports {ft_oe_n}] ;# ACBUS6 → IO_L1P_T0_AD4P_35
set_property IOSTANDARD LVCMOS33 [get_ports {ft_rxf_n}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft_txe_n}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft_rd_n}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft_wr_n}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft_siwu}]
set_property IOSTANDARD LVCMOS33 [get_ports {ft_oe_n}]
# Output timing: SLEW FAST + DRIVE 8 for FT2232H signals
set_property SLEW FAST [get_ports {ft_rd_n}]
set_property SLEW FAST [get_ports {ft_wr_n}]
set_property SLEW FAST [get_ports {ft_oe_n}]
set_property SLEW FAST [get_ports {ft_siwu}]
set_property SLEW FAST [get_ports {ft_data[*]}]
set_property DRIVE 8 [get_ports {ft_rd_n}]
set_property DRIVE 8 [get_ports {ft_wr_n}]
set_property DRIVE 8 [get_ports {ft_oe_n}]
set_property DRIVE 8 [get_ports {ft_siwu}]
set_property DRIVE 8 [get_ports {ft_data[*]}]
# ft_clkout constrained above in CLOCK CONSTRAINTS section (C4, 60 MHz)
# ============================================================================ # ============================================================================
# STATUS / DEBUG OUTPUTS — NO PHYSICAL CONNECTIONS # STATUS / DEBUG OUTPUTS — NO PHYSICAL CONNECTIONS
# ============================================================================ # ============================================================================
@@ -283,7 +375,13 @@ set_false_path -from [get_clocks adc_dco_p] -to [get_clocks clk_100m]
set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_120m_dac] set_false_path -from [get_clocks clk_100m] -to [get_clocks clk_120m_dac]
set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_100m] set_false_path -from [get_clocks clk_120m_dac] -to [get_clocks clk_100m]
# FT601 CDC paths removed — no ft601_clk_in clock defined (chip unwired) # FT2232H CDC: clk_100m ↔ ft_clkout (60 MHz), toggle CDC in RTL
set_false_path -from [get_clocks clk_100m] -to [get_clocks ft_clkout]
set_false_path -from [get_clocks ft_clkout] -to [get_clocks clk_100m]
# FT2232H CDC: clk_120m_dac ↔ ft_clkout (no direct crossing, but belt-and-suspenders)
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]
# ============================================================================ # ============================================================================
# PHYSICAL CONSTRAINTS # PHYSICAL CONSTRAINTS
+43 -15
View File
@@ -103,13 +103,17 @@ reg [7:0] signal_power_i, signal_power_q;
// Internal mixing signals // Internal mixing signals
// DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining // DSP48E1 with AREG=1, BREG=1, MREG=1, PREG=1 handles all internal pipelining
// Latency: 3 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG) // Latency: 4 cycles (1 for AREG/BREG, 1 for MREG, 1 for PREG, 1 for post-DSP retiming)
wire signed [MIXER_WIDTH-1:0] adc_signed_w; wire signed [MIXER_WIDTH-1:0] adc_signed_w;
reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q; reg signed [MIXER_WIDTH + NCO_WIDTH -1:0] mixed_i, mixed_q;
reg mixed_valid; reg mixed_valid;
reg mixer_overflow_i, mixer_overflow_q; reg mixer_overflow_i, mixer_overflow_q;
// Pipeline valid tracking: 3-stage shift register to match DSP48E1 AREG+MREG+PREG latency // Pipeline valid tracking: 4-stage shift register (3 for DSP48E1 + 1 for post-DSP retiming)
reg [2:0] dsp_valid_pipe; reg [3:0] dsp_valid_pipe;
// Post-DSP retiming registers breaks DSP48E1 CLKP to fabric timing path
// This extra pipeline stage absorbs the 1.866ns DSP output prop delay + routing,
// ensuring WNS > 0 at 400 MHz regardless of placement seed
(* DONT_TOUCH = "TRUE" *) reg signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_retimed, mult_q_retimed;
// Output stage registers // Output stage registers
reg signed [17:0] baseband_i_reg, baseband_q_reg; reg signed [17:0] baseband_i_reg, baseband_q_reg;
@@ -219,12 +223,12 @@ nco_400m_enhanced nco_core (
assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} - assign adc_signed_w = {1'b0, adc_data, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} -
{1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2; {1'b0, {ADC_WIDTH{1'b1}}, {(MIXER_WIDTH-ADC_WIDTH-1){1'b0}}} / 2;
// Valid pipeline: 3-stage shift register matching DSP48E1 AREG+MREG+PREG latency // Valid pipeline: 4-stage shift register (3 for DSP48E1 AREG+MREG+PREG + 1 for retiming)
always @(posedge clk_400m or negedge reset_n_400m) begin always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin if (!reset_n_400m) begin
dsp_valid_pipe <= 3'b000; dsp_valid_pipe <= 4'b0000;
end else begin end else begin
dsp_valid_pipe <= {dsp_valid_pipe[1:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)}; dsp_valid_pipe <= {dsp_valid_pipe[2:0], (nco_ready && adc_data_valid_i && adc_data_valid_q)};
end end
end end
@@ -271,6 +275,17 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
end end
end end
// Stage 4: Post-DSP retiming register (matches synthesis path)
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mult_i_retimed <= 0;
mult_q_retimed <= 0;
end else begin
mult_i_retimed <= mult_i_reg;
mult_q_retimed <= mult_q_reg;
end
end
`else `else
// ---- Direct DSP48E1 instantiation for Vivado synthesis ---- // ---- Direct DSP48E1 instantiation for Vivado synthesis ----
// This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz // This guarantees AREG/BREG/MREG are used, achieving timing closure at 400 MHz
@@ -448,6 +463,19 @@ DSP48E1 #(
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg = dsp_p_i[MIXER_WIDTH+NCO_WIDTH-1:0]; wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_i_reg = dsp_p_i[MIXER_WIDTH+NCO_WIDTH-1:0];
wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_q_reg = dsp_p_q[MIXER_WIDTH+NCO_WIDTH-1:0]; wire signed [MIXER_WIDTH+NCO_WIDTH-1:0] mult_q_reg = dsp_p_q[MIXER_WIDTH+NCO_WIDTH-1:0];
// Stage 4: Post-DSP retiming register breaks DSP48E1 CLKP to fabric path
// Without this, the DSP output prop delay (1.866ns) + routing (0.515ns) exceeds
// the 2.500ns clock period at slow process corner
always @(posedge clk_400m or negedge reset_n_400m) begin
if (!reset_n_400m) begin
mult_i_retimed <= 0;
mult_q_retimed <= 0;
end else begin
mult_i_retimed <= mult_i_reg;
mult_q_retimed <= mult_q_reg;
end
end
`endif `endif
// ============================================================================ // ============================================================================
@@ -464,7 +492,7 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
mixer_overflow_q <= 0; mixer_overflow_q <= 0;
saturation_count <= 0; saturation_count <= 0;
overflow_detected <= 0; overflow_detected <= 0;
end else if (dsp_valid_pipe[2]) begin end else if (dsp_valid_pipe[3]) begin
// Force saturation for testing (applied after DSP output, not on input path) // Force saturation for testing (applied after DSP output, not on input path)
if (force_saturation_sync) begin if (force_saturation_sync) begin
mixed_i <= 34'h1FFFFFFFF; mixed_i <= 34'h1FFFFFFFF;
@@ -472,15 +500,15 @@ always @(posedge clk_400m or negedge reset_n_400m) begin
mixer_overflow_i <= 1'b1; mixer_overflow_i <= 1'b1;
mixer_overflow_q <= 1'b1; mixer_overflow_q <= 1'b1;
end else begin end else begin
// Normal path: take DSP48E1 multiply result // Normal path: take retimed DSP48E1 multiply result
mixed_i <= mult_i_reg; mixed_i <= mult_i_retimed;
mixed_q <= mult_q_reg; mixed_q <= mult_q_retimed;
// Overflow detection on current cycle's multiply result // Overflow detection on retimed multiply result
mixer_overflow_i <= (mult_i_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) || mixer_overflow_i <= (mult_i_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_i_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2))); (mult_i_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
mixer_overflow_q <= (mult_q_reg > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) || mixer_overflow_q <= (mult_q_retimed > (2**(MIXER_WIDTH+NCO_WIDTH-2)-1)) ||
(mult_q_reg < -(2**(MIXER_WIDTH+NCO_WIDTH-2))); (mult_q_retimed < -(2**(MIXER_WIDTH+NCO_WIDTH-2)));
end end
mixed_valid <= 1; mixed_valid <= 1;
-11
View File
@@ -1,11 +0,0 @@
// Quarter-wave cosine ROM for 32-point FFT
// 8 entries, 16-bit signed Q15 ($readmemh format)
// cos(2*pi*k/32) for k = 0..7
7FFF
7D89
7641
6A6D
5A82
471C
30FB
18F9
+7 -4
View File
@@ -57,13 +57,16 @@ wire signed [DATA_WIDTH+COEFF_WIDTH-1:0] mult_result [0:TAPS-1];
// Level 0: 16 pairwise sums of 32 products // Level 0: 16 pairwise sums of 32 products
reg signed [ACCUM_WIDTH-1:0] add_l0 [0:15]; reg signed [ACCUM_WIDTH-1:0] add_l0 [0:15];
// Level 1: 8 pairwise sums // Level 1: 8 pairwise sums
reg signed [ACCUM_WIDTH-1:0] add_l1 [0:7]; // USE_DSP="no" forces pure additions to fabric CARRY4 chains, freeing DSP48E1
// slices for the FFT butterfly multipliers that otherwise spill to 18-level
// fabric carry chains causing timing violations on the XC7A50T (120 DSP budget).
(* USE_DSP = "no" *) reg signed [ACCUM_WIDTH-1:0] add_l1 [0:7];
// Level 2: 4 pairwise sums // Level 2: 4 pairwise sums
reg signed [ACCUM_WIDTH-1:0] add_l2 [0:3]; (* USE_DSP = "no" *) reg signed [ACCUM_WIDTH-1:0] add_l2 [0:3];
// Level 3: 2 pairwise sums // Level 3: 2 pairwise sums
reg signed [ACCUM_WIDTH-1:0] add_l3 [0:1]; (* USE_DSP = "no" *) reg signed [ACCUM_WIDTH-1:0] add_l3 [0:1];
// Level 4: final sum // Level 4: final sum
reg signed [ACCUM_WIDTH-1:0] accumulator_reg; (* USE_DSP = "no" *) reg signed [ACCUM_WIDTH-1:0] accumulator_reg;
// Valid pipeline: 9-stage shift register (was 7 before BREG+MREG addition) // Valid pipeline: 9-stage shift register (was 7 before BREG+MREG addition)
// [0]=BREG done, [1]=MREG done, [2]=L0 done, [3]=L1 done, [4]=L2 done, // [0]=BREG done, [1]=MREG done, [2]=L0 done, [3]=L1 done, [4]=L2 done,
@@ -4,24 +4,24 @@ cover
[options] [options]
bmc: mode bmc bmc: mode bmc
bmc: depth 150 bmc: depth 512
cover: mode cover cover: mode cover
cover: depth 150 cover: depth 1024
[engines] [engines]
smtbmc z3 smtbmc z3
[script] [script]
read_verilog -formal doppler_processor.v read_verilog -formal doppler_processor.v
read_verilog -formal xfft_32.v read_verilog -formal xfft_16.v
read_verilog -formal fft_engine.v read_verilog -formal fft_engine.v
read_verilog -formal fv_doppler_processor.v read_verilog -formal fv_doppler_processor.v
prep -top fv_doppler_processor prep -top fv_doppler_processor
[files] [files]
../doppler_processor.v ../doppler_processor.v
../xfft_32.v ../xfft_16.v
../fft_engine.v ../fft_engine.v
../fft_twiddle_32.mem ../fft_twiddle_16.mem
../fft_twiddle_1024.mem ../fft_twiddle_1024.mem
fv_doppler_processor.v fv_doppler_processor.v
@@ -8,7 +8,8 @@
// Single-clock design: clk is an input wire, async2sync handles async reset. // Single-clock design: clk is an input wire, async2sync handles async reset.
// Each formal step = one clock edge. // Each formal step = one clock edge.
// //
// Parameters reduced: RANGE_BINS=4, CHIRPS_PER_FRAME=4, CHIRPS_PER_SUBFRAME=2, DOPPLER_FFT_SIZE=2. // Parameters: RANGE_BINS reduced for tractability, but the FFT/sub-frame size
// remains 16 so the wrapper matches the real xfft_16 interface.
// Includes full xfft_16 and fft_engine sub-modules. // Includes full xfft_16 and fft_engine sub-modules.
// //
// Focus: memory address bounds (highest-value finding) and state encoding. // Focus: memory address bounds (highest-value finding) and state encoding.
@@ -17,12 +18,12 @@ module fv_doppler_processor (
input wire clk input wire clk
); );
// Reduced parameters for tractable BMC // Only RANGE_BINS is reduced; the FFT wrapper still expects 16 samples.
localparam RANGE_BINS = 4; localparam RANGE_BINS = 2;
localparam CHIRPS_PER_FRAME = 4; localparam CHIRPS_PER_FRAME = 32;
localparam CHIRPS_PER_SUBFRAME = 2; // Dual sub-frame: 2 chirps per sub-frame localparam CHIRPS_PER_SUBFRAME = 16;
localparam DOPPLER_FFT_SIZE = 2; // FFT size matches sub-frame size localparam DOPPLER_FFT_SIZE = 16;
localparam MEM_DEPTH = RANGE_BINS * CHIRPS_PER_FRAME; // 16 localparam MEM_DEPTH = RANGE_BINS * CHIRPS_PER_FRAME;
// State encoding (mirrors DUT localparams) // State encoding (mirrors DUT localparams)
localparam S_IDLE = 3'b000; localparam S_IDLE = 3'b000;
@@ -130,9 +131,11 @@ module fv_doppler_processor (
assume(!data_valid); assume(!data_valid);
end end
// new_chirp_frame must be a clean pulse (not during active processing) // new_chirp_frame may assert during accumulation at the 16-chirp boundary.
// Only suppress it during FFT-processing states.
always @(posedge clk) begin always @(posedge clk) begin
if (reset_n && state != S_IDLE) if (reset_n && (state == S_PRE_READ || state == S_LOAD_FFT ||
state == S_FFT_WAIT || state == S_OUTPUT))
assume(!new_chirp_frame); assume(!new_chirp_frame);
end end
@@ -201,11 +204,17 @@ module fv_doppler_processor (
end end
// ================================================================ // ================================================================
// COVER 1: Complete processing of all range bins // COVER 1: Complete processing of all range bins after a full frame was
// actually accumulated.
// ================================================================ // ================================================================
reg f_seen_full;
initial f_seen_full = 1'b0;
always @(posedge clk)
if (frame_buffer_full) f_seen_full <= 1'b1;
always @(posedge clk) begin always @(posedge clk) begin
if (reset_n) if (reset_n)
cover(frame_complete && f_past_valid); cover(frame_complete && f_seen_full);
end end
// ================================================================ // ================================================================
@@ -6,7 +6,7 @@ cover
bmc: mode bmc bmc: mode bmc
bmc: depth 200 bmc: depth 200
cover: mode cover cover: mode cover
cover: depth 200 cover: depth 600
[engines] [engines]
smtbmc z3 smtbmc z3
@@ -66,13 +66,15 @@ module fv_radar_mode_controller (
(* anyseq *) wire stm32_new_azimuth; (* anyseq *) wire stm32_new_azimuth;
(* anyseq *) wire trigger; (* anyseq *) wire trigger;
// Gap 2: Formal cfg_* inputs — solver-driven for exhaustive coverage // Runtime config inputs are pinned to the reduced localparams so this
(* anyseq *) wire [15:0] cfg_long_chirp_cycles; // wrapper proves one tractable configuration. It does not sweep the full
(* anyseq *) wire [15:0] cfg_long_listen_cycles; // runtime-configurable cfg_* space.
(* anyseq *) wire [15:0] cfg_guard_cycles; wire [15:0] cfg_long_chirp_cycles = LONG_CHIRP_CYCLES;
(* anyseq *) wire [15:0] cfg_short_chirp_cycles; wire [15:0] cfg_long_listen_cycles = LONG_LISTEN_CYCLES;
(* anyseq *) wire [15:0] cfg_short_listen_cycles; wire [15:0] cfg_guard_cycles = GUARD_CYCLES;
(* anyseq *) wire [5:0] cfg_chirps_per_elev; wire [15:0] cfg_short_chirp_cycles = SHORT_CHIRP_CYCLES;
wire [15:0] cfg_short_listen_cycles = SHORT_LISTEN_CYCLES;
wire [5:0] cfg_chirps_per_elev = CHIRPS_PER_ELEVATION;
// ================================================================ // ================================================================
// DUT outputs // DUT outputs
@@ -109,7 +111,8 @@ module fv_radar_mode_controller (
.stm32_new_elevation(stm32_new_elevation), .stm32_new_elevation(stm32_new_elevation),
.stm32_new_azimuth (stm32_new_azimuth), .stm32_new_azimuth (stm32_new_azimuth),
.trigger (trigger), .trigger (trigger),
// Gap 2: Runtime-configurable timing inputs // Runtime-configurable timing ports, bound here to the reduced wrapper
// constants for tractable proof depth.
.cfg_long_chirp_cycles (cfg_long_chirp_cycles), .cfg_long_chirp_cycles (cfg_long_chirp_cycles),
.cfg_long_listen_cycles (cfg_long_listen_cycles), .cfg_long_listen_cycles (cfg_long_listen_cycles),
.cfg_guard_cycles (cfg_guard_cycles), .cfg_guard_cycles (cfg_guard_cycles),
@@ -181,7 +184,7 @@ module fv_radar_mode_controller (
// ================================================================ // ================================================================
always @(posedge clk) begin always @(posedge clk) begin
if (reset_n && f_past_valid) begin if (reset_n && f_past_valid) begin
if ($past(mode) == 2'b10 && if ($past(mode) == 2'b10 && mode == 2'b10 &&
$past(scan_state) == S_LONG_LISTEN && $past(scan_state) == S_LONG_LISTEN &&
$past(timer) == LONG_LISTEN_CYCLES - 1) $past(timer) == LONG_LISTEN_CYCLES - 1)
assert(scan_state == S_IDLE); assert(scan_state == S_IDLE);
@@ -202,11 +205,11 @@ module fv_radar_mode_controller (
end end
// ================================================================ // ================================================================
// COVER 1: Full scan completes (scan_complete pulses) // COVER 1: Full auto-scan completes
// ================================================================ // ================================================================
always @(posedge clk) begin always @(posedge clk) begin
if (reset_n) if (reset_n)
cover(scan_complete); cover(scan_complete && mode == 2'b01);
end end
// ================================================================ // ================================================================
+3 -2
View File
@@ -404,9 +404,10 @@ assign range_data_valid = mti_range_valid;
// ========== DOPPLER PROCESSOR ========== // ========== DOPPLER PROCESSOR ==========
doppler_processor_optimized #( doppler_processor_optimized #(
.DOPPLER_FFT_SIZE(32), .DOPPLER_FFT_SIZE(16),
.RANGE_BINS(64), .RANGE_BINS(64),
.CHIRPS_PER_FRAME(32) // MUST MATCH YOUR ACTUAL FRAME SIZE! .CHIRPS_PER_FRAME(32),
.CHIRPS_PER_SUBFRAME(16)
) doppler_proc ( ) doppler_proc (
.clk(clk), .clk(clk),
.reset_n(reset_n), .reset_n(reset_n),
+117 -20
View File
@@ -7,12 +7,16 @@
* Integrates: * Integrates:
* - Radar Transmitter (PLFM chirp generation) * - Radar Transmitter (PLFM chirp generation)
* - Radar Receiver (ADC interface, DDC, matched filtering, Doppler processing) * - Radar Receiver (ADC interface, DDC, matched filtering, Doppler processing)
* - USB Data Interface (FT601 for high-speed data transfer) * - USB Data Interface (FT601 USB 3.0 or FT2232H USB 2.0, selected by USB_MODE)
* *
* Clock domains: * Clock domains:
* - clk_100m: System clock (100MHz) * - clk_100m: System clock (100MHz)
* - clk_120m_dac: DAC clock (120MHz) * - clk_120m_dac: DAC clock (120MHz)
* - ft601_clk: FT601 interface clock (100MHz from FT601) * - ft601_clk: USB interface clock (100MHz FT601 or 60MHz FT2232H)
*
* USB_MODE parameter:
* 0 = FT601 (32-bit, USB 3.0) 200T premium board
* 1 = FT2232H (8-bit, USB 2.0) 50T production board
*/ */
module radar_system_top ( module radar_system_top (
@@ -93,9 +97,19 @@ module radar_system_top (
input wire [1:0] ft601_srb, // Selected read buffer input wire [1:0] ft601_srb, // Selected read buffer
input wire [1:0] ft601_swb, // Selected write buffer input wire [1:0] ft601_swb, // Selected write buffer
// Clock output (optional) // Clock output (optional, FT601 only not used for FT2232H)
output wire ft601_clk_out, output wire ft601_clk_out,
// ========== FT2232H USB 2.0 INTERFACE (USB_MODE=1) ==========
// 8-bit bidirectional data bus (245 Synchronous FIFO mode, Channel A)
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
input wire ft_rxf_n, // RX FIFO not empty (active low)
input wire ft_txe_n, // TX FIFO not full (active low)
output wire ft_rd_n, // Read strobe (active low)
output wire ft_wr_n, // Write strobe (active low)
output wire ft_oe_n, // Output enable / bus direction
output wire ft_siwu, // Send Immediate / WakeUp
// ========== STATUS OUTPUTS ========== // ========== STATUS OUTPUTS ==========
// Beam position tracking // Beam position tracking
@@ -122,6 +136,7 @@ module radar_system_top (
parameter USE_LONG_CHIRP = 1'b1; // Default to long chirp parameter USE_LONG_CHIRP = 1'b1; // Default to long chirp
parameter DOPPLER_ENABLE = 1'b1; // Enable Doppler processing parameter DOPPLER_ENABLE = 1'b1; // Enable Doppler processing
parameter USB_ENABLE = 1'b1; // Enable USB data transfer parameter USB_ENABLE = 1'b1; // Enable USB data transfer
parameter USB_MODE = 0; // 0=FT601 (32-bit, 200T), 1=FT2232H (8-bit, 50T)
// ============================================================================ // ============================================================================
// INTERNAL SIGNALS // INTERNAL SIGNALS
@@ -217,10 +232,11 @@ reg [5:0] host_chirps_per_elev; // Opcode 0x15 (default 32)
reg host_status_request; // Opcode 0xFF (self-clearing pulse) reg host_status_request; // Opcode 0xFF (self-clearing pulse)
// Fix 4: Doppler/chirps mismatch protection // Fix 4: Doppler/chirps mismatch protection
// DOPPLER_FFT_SIZE is compile-time (32). If host sets chirps_per_elev to a // DOPPLER_FRAME_CHIRPS is the fixed chirp count expected by the staggered-PRI
// Doppler path (16 long + 16 short). If host sets chirps_per_elev to a
// different value, Doppler accumulation is corrupted. Clamp at command decode // different value, Doppler accumulation is corrupted. Clamp at command decode
// and flag the mismatch so the host knows. // and flag the mismatch so the host knows.
localparam DOPPLER_FFT_SIZE = 32; // Must match doppler_processor parameter localparam DOPPLER_FRAME_CHIRPS = 32; // Total chirps per Doppler frame
reg chirps_mismatch_error; // Set if host tried to set chirps != FFT size reg chirps_mismatch_error; // Set if host tried to set chirps != FFT size
// Fix 7: Range-mode register (opcode 0x20) // Fix 7: Range-mode register (opcode 0x20)
@@ -532,16 +548,21 @@ assign rx_doppler_data_valid = rx_doppler_valid;
// ============================================================================ // ============================================================================
// DC NOTCH FILTER (post-Doppler-FFT, pre-CFAR) // DC NOTCH FILTER (post-Doppler-FFT, pre-CFAR)
// ============================================================================ // ============================================================================
// Zeros out Doppler bins within ±host_dc_notch_width of DC (bin 0). // Zeros out Doppler bins within ±host_dc_notch_width of DC for BOTH
// In a 32-point FFT, DC is bin 0; negative Doppler wraps to bins 31,30,... // sub-frames in the dual 16-pt FFT architecture.
// notch_width=1 → zero bins {0}. notch_width=2 → zero bins {0,1,31}. etc. // doppler_bin[4:0] = {sub_frame, bin[3:0]}:
// Sub-frame 0: bins 0-15, DC = bin 0, wrap = bin 15
// Sub-frame 1: bins 16-31, DC = bin 16, wrap = bin 31
// notch_width=1 → zero bins {0,16}. notch_width=2 → zero bins
// {0,1,15,16,17,31}. etc.
// When host_dc_notch_width=0: pass-through (no zeroing). // When host_dc_notch_width=0: pass-through (no zeroing).
wire dc_notch_active; wire dc_notch_active;
wire [4:0] dop_bin_unsigned = rx_doppler_bin; wire [4:0] dop_bin_unsigned = rx_doppler_bin;
wire [3:0] bin_within_sf = dop_bin_unsigned[3:0];
assign dc_notch_active = (host_dc_notch_width != 3'd0) && assign dc_notch_active = (host_dc_notch_width != 3'd0) &&
(dop_bin_unsigned < {2'b0, host_dc_notch_width} || (bin_within_sf < {1'b0, host_dc_notch_width} ||
dop_bin_unsigned > (5'd31 - {2'b0, host_dc_notch_width} + 5'd1)); bin_within_sf > (4'd15 - {1'b0, host_dc_notch_width} + 4'd1));
// Notched Doppler data: zero I/Q when in notch zone, pass through otherwise // Notched Doppler data: zero I/Q when in notch zone, pass through otherwise
wire [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output; wire [31:0] notched_doppler_data = dc_notch_active ? 32'd0 : rx_doppler_output;
@@ -661,13 +682,16 @@ assign usb_detect_flag = rx_detect_flag;
assign usb_detect_valid = rx_detect_valid; assign usb_detect_valid = rx_detect_valid;
// ============================================================================ // ============================================================================
// USB DATA INTERFACE INSTANTIATION // USB DATA INTERFACE INSTANTIATION (parametric: FT601 or FT2232H)
// ============================================================================ // ============================================================================
generate
if (USB_MODE == 0) begin : gen_ft601
// ---- FT601 USB 3.0 (32-bit, 200T premium board) ----
usb_data_interface usb_inst ( usb_data_interface usb_inst (
.clk(clk_100m_buf), .clk(clk_100m_buf),
.reset_n(sys_reset_n), .reset_n(sys_reset_n),
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset .ft601_reset_n(sys_reset_ft601_n),
// Radar data inputs // Radar data inputs
.range_profile(usb_range_profile), .range_profile(usb_range_profile),
@@ -694,17 +718,17 @@ usb_data_interface usb_inst (
.ft601_clk_out(ft601_clk_out), .ft601_clk_out(ft601_clk_out),
.ft601_clk_in(ft601_clk_buf), .ft601_clk_in(ft601_clk_buf),
// Host command outputs (Gap 4: USB Read Path) // Host command outputs
.cmd_data(usb_cmd_data), .cmd_data(usb_cmd_data),
.cmd_valid(usb_cmd_valid), .cmd_valid(usb_cmd_valid),
.cmd_opcode(usb_cmd_opcode), .cmd_opcode(usb_cmd_opcode),
.cmd_addr(usb_cmd_addr), .cmd_addr(usb_cmd_addr),
.cmd_value(usb_cmd_value), .cmd_value(usb_cmd_value),
// Gap 2: Stream control (clk_100m domain, CDC'd inside usb_data_interface) // Stream control
.stream_control(host_stream_control), .stream_control(host_stream_control),
// Gap 2: Status readback inputs // Status readback inputs
.status_request(host_status_request), .status_request(host_status_request),
.status_cfar_threshold(host_detect_threshold), .status_cfar_threshold(host_detect_threshold),
.status_stream_ctrl(host_stream_control), .status_stream_ctrl(host_stream_control),
@@ -723,6 +747,79 @@ usb_data_interface usb_inst (
.status_self_test_busy(self_test_busy) .status_self_test_busy(self_test_busy)
); );
// FT2232H ports unused in FT601 mode — tie off
assign ft_rd_n = 1'b1;
assign ft_wr_n = 1'b1;
assign ft_oe_n = 1'b1;
assign ft_siwu = 1'b0;
end else begin : gen_ft2232h
// ---- FT2232H USB 2.0 (8-bit, 50T production board) ----
usb_data_interface_ft2232h usb_inst (
.clk(clk_100m_buf),
.reset_n(sys_reset_n),
.ft_reset_n(sys_reset_ft601_n), // Reuse same synchronized reset
// Radar data inputs
.range_profile(usb_range_profile),
.range_valid(usb_range_valid),
.doppler_real(usb_doppler_real),
.doppler_imag(usb_doppler_imag),
.doppler_valid(usb_doppler_valid),
.cfar_detection(usb_detect_flag),
.cfar_valid(usb_detect_valid),
// FT2232H Interface
.ft_data(ft_data),
.ft_rxf_n(ft_rxf_n),
.ft_txe_n(ft_txe_n),
.ft_rd_n(ft_rd_n),
.ft_wr_n(ft_wr_n),
.ft_oe_n(ft_oe_n),
.ft_siwu(ft_siwu),
.ft_clk(ft601_clk_buf), // Reuse BUFG'd USB clock
// Host command outputs
.cmd_data(usb_cmd_data),
.cmd_valid(usb_cmd_valid),
.cmd_opcode(usb_cmd_opcode),
.cmd_addr(usb_cmd_addr),
.cmd_value(usb_cmd_value),
// Stream control
.stream_control(host_stream_control),
// Status readback inputs
.status_request(host_status_request),
.status_cfar_threshold(host_detect_threshold),
.status_stream_ctrl(host_stream_control),
.status_radar_mode(host_radar_mode),
.status_long_chirp(host_long_chirp_cycles),
.status_long_listen(host_long_listen_cycles),
.status_guard(host_guard_cycles),
.status_short_chirp(host_short_chirp_cycles),
.status_short_listen(host_short_listen_cycles),
.status_chirps_per_elev(host_chirps_per_elev),
.status_range_mode(host_range_mode),
// Self-test status readback
.status_self_test_flags(self_test_flags_latched),
.status_self_test_detail(self_test_detail_latched),
.status_self_test_busy(self_test_busy)
);
// FT601 ports unused in FT2232H mode — tie off
assign ft601_be = 4'b0000;
assign ft601_txe_n = 1'b1;
assign ft601_rxf_n = 1'b1;
assign ft601_wr_n = 1'b1;
assign ft601_rd_n = 1'b1;
assign ft601_oe_n = 1'b1;
assign ft601_siwu_n = 1'b1;
assign ft601_clk_out = 1'b0;
end
endgenerate
// ============================================================================ // ============================================================================
// USB COMMAND CDC: ft601_clk → clk_100m (Gap 4: USB Read Path) // USB COMMAND CDC: ft601_clk → clk_100m (Gap 4: USB Read Path)
// ============================================================================ // ============================================================================
@@ -814,18 +911,18 @@ always @(posedge clk_100m_buf or negedge sys_reset_n) begin
8'h13: host_short_chirp_cycles <= usb_cmd_value; 8'h13: host_short_chirp_cycles <= usb_cmd_value;
8'h14: host_short_listen_cycles <= usb_cmd_value; 8'h14: host_short_listen_cycles <= usb_cmd_value;
8'h15: begin 8'h15: begin
// Fix 4: Clamp chirps_per_elev to DOPPLER_FFT_SIZE. // Fix 4: Clamp chirps_per_elev to the fixed Doppler frame size.
// If host requests a different value, clamp and set error flag. // If host requests a different value, clamp and set error flag.
if (usb_cmd_value[5:0] > DOPPLER_FFT_SIZE[5:0]) begin if (usb_cmd_value[5:0] > DOPPLER_FRAME_CHIRPS[5:0]) begin
host_chirps_per_elev <= DOPPLER_FFT_SIZE[5:0]; host_chirps_per_elev <= DOPPLER_FRAME_CHIRPS[5:0];
chirps_mismatch_error <= 1'b1; chirps_mismatch_error <= 1'b1;
end else if (usb_cmd_value[5:0] == 6'd0) begin end else if (usb_cmd_value[5:0] == 6'd0) begin
host_chirps_per_elev <= DOPPLER_FFT_SIZE[5:0]; host_chirps_per_elev <= DOPPLER_FRAME_CHIRPS[5:0];
chirps_mismatch_error <= 1'b1; chirps_mismatch_error <= 1'b1;
end else begin end else begin
host_chirps_per_elev <= usb_cmd_value[5:0]; host_chirps_per_elev <= usb_cmd_value[5:0];
// Clear error only if value matches FFT size exactly // Clear error only if value matches FFT size exactly
chirps_mismatch_error <= (usb_cmd_value[5:0] != DOPPLER_FFT_SIZE[5:0]); chirps_mismatch_error <= (usb_cmd_value[5:0] != DOPPLER_FRAME_CHIRPS[5:0]);
end end
end end
8'h16: host_gain_shift <= usb_cmd_value[3:0]; // Fix 3: digital gain 8'h16: host_gain_shift <= usb_cmd_value[3:0]; // Fix 3: digital gain
+213
View File
@@ -0,0 +1,213 @@
`timescale 1ns / 1ps
/**
* radar_system_top_50t.v
*
* 50T Production Wrapper for radar_system_top
*
* The XC7A50T-FTG256 has only 69 usable IO pins, but radar_system_top
* declares many port bits (including FT601 USB 3.0, debug outputs, and
* status signals that have no physical connections on the 50T board).
*
* This wrapper exposes the physically-connected ports and ties off unused
* inputs. Unused outputs remain internally connected so the full radar
* pipeline is preserved in the netlist.
*
* USB: FT2232H (USB 2.0, 8-bit, 245 Synchronous FIFO mode)
* - USB_MODE=1 selects the FT2232H interface in radar_system_top
* - FT2232H CLKOUT (60 MHz) connected to ft601_clk_in (shared clock port)
* - 15 signals on Bank 35 (VCCO=3.3V, LVCMOS33)
*/
module radar_system_top_50t (
// ===== System Clocks (Bank 15: 3.3V) =====
input wire clk_100m,
input wire clk_120m_dac,
input wire reset_n,
// ===== DAC Interface (Bank 15: 3.3V) =====
output wire [7:0] dac_data,
output wire dac_sleep,
// ===== RF Control (Bank 15: 3.3V) =====
output wire fpga_rf_switch,
output wire rx_mixer_en,
output wire tx_mixer_en,
// ===== ADAR1000 Beamformer (Bank 34: 1.8V) =====
output wire adar_tx_load_1, adar_rx_load_1,
output wire adar_tx_load_2, adar_rx_load_2,
output wire adar_tx_load_3, adar_rx_load_3,
output wire adar_tx_load_4, adar_rx_load_4,
output wire adar_tr_1, adar_tr_2, adar_tr_3, adar_tr_4,
// ===== STM32 SPI 3.3V side (Bank 15) =====
input wire stm32_sclk_3v3,
input wire stm32_mosi_3v3,
output wire stm32_miso_3v3,
input wire stm32_cs_adar1_3v3, stm32_cs_adar2_3v3,
input wire stm32_cs_adar3_3v3, stm32_cs_adar4_3v3,
// ===== STM32 SPI 1.8V side (Bank 34) =====
output wire stm32_sclk_1v8,
output wire stm32_mosi_1v8,
input wire stm32_miso_1v8,
output wire stm32_cs_adar1_1v8, stm32_cs_adar2_1v8,
output wire stm32_cs_adar3_1v8, stm32_cs_adar4_1v8,
// ===== ADC Interface (Bank 14: LVDS_25) =====
input wire [7:0] adc_d_p,
input wire [7:0] adc_d_n,
input wire adc_dco_p,
input wire adc_dco_n,
output wire adc_pwdn,
// ===== STM32 Control (Bank 15: 3.3V) =====
input wire stm32_new_chirp,
input wire stm32_new_elevation,
input wire stm32_new_azimuth,
input wire stm32_mixers_enable,
// ===== FT2232H USB 2.0 Interface (Bank 35: 3.3V) =====
input wire ft_clkout, // 60 MHz from FT2232H CLKOUT (MRCC pin C4)
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
input wire ft_rxf_n, // RX FIFO not empty (active low)
input wire ft_txe_n, // TX FIFO not full (active low)
output wire ft_rd_n, // Read strobe (active low)
output wire ft_wr_n, // Write strobe (active low)
output wire ft_oe_n, // Output enable / bus direction
output wire ft_siwu // Send Immediate / WakeUp
);
// ===== Tie-off wires for unconstrained FT601 inputs (inactive with USB_MODE=1) =====
wire ft601_txe_tied = 1'b0;
wire ft601_rxf_tied = 1'b0;
wire [1:0] ft601_srb_tied = 2'b00;
wire [1:0] ft601_swb_tied = 2'b00;
// ===== FT601 inout bus tie to high-Z =====
wire [31:0] ft601_data_internal;
assign ft601_data_internal = 32'hZZZZZZZZ;
// ===== Unconnected output wires (synthesis preserves driving logic) =====
wire dac_clk_nc;
wire [3:0] ft601_be_nc;
wire ft601_txe_n_nc;
wire ft601_rxf_n_nc;
wire ft601_wr_n_nc;
wire ft601_rd_n_nc;
wire ft601_oe_n_nc;
wire ft601_siwu_n_nc;
wire ft601_clk_out_nc;
wire [5:0] current_elevation_nc;
wire [5:0] current_azimuth_nc;
wire [5:0] current_chirp_nc;
wire new_chirp_frame_nc;
wire [31:0] dbg_doppler_data_nc;
wire dbg_doppler_valid_nc;
wire [4:0] dbg_doppler_bin_nc;
wire [5:0] dbg_range_bin_nc;
wire [3:0] system_status_nc;
(* DONT_TOUCH = "TRUE" *)
radar_system_top #(
.USB_MODE(1) // FT2232H (8-bit USB 2.0) for 50T production
) u_core (
// ----- Clocks & Reset -----
.clk_100m (clk_100m),
.clk_120m_dac (clk_120m_dac),
.ft601_clk_in (ft_clkout), // FT2232H 60 MHz CLKOUT shared USB clock port
.reset_n (reset_n),
// ----- DAC -----
.dac_data (dac_data),
.dac_clk (dac_clk_nc),
.dac_sleep (dac_sleep),
// ----- RF -----
.fpga_rf_switch (fpga_rf_switch),
.rx_mixer_en (rx_mixer_en),
.tx_mixer_en (tx_mixer_en),
// ----- Beamformer -----
.adar_tx_load_1 (adar_tx_load_1),
.adar_rx_load_1 (adar_rx_load_1),
.adar_tx_load_2 (adar_tx_load_2),
.adar_rx_load_2 (adar_rx_load_2),
.adar_tx_load_3 (adar_tx_load_3),
.adar_rx_load_3 (adar_rx_load_3),
.adar_tx_load_4 (adar_tx_load_4),
.adar_rx_load_4 (adar_rx_load_4),
.adar_tr_1 (adar_tr_1),
.adar_tr_2 (adar_tr_2),
.adar_tr_3 (adar_tr_3),
.adar_tr_4 (adar_tr_4),
// ----- SPI 3.3V -----
.stm32_sclk_3v3 (stm32_sclk_3v3),
.stm32_mosi_3v3 (stm32_mosi_3v3),
.stm32_miso_3v3 (stm32_miso_3v3),
.stm32_cs_adar1_3v3 (stm32_cs_adar1_3v3),
.stm32_cs_adar2_3v3 (stm32_cs_adar2_3v3),
.stm32_cs_adar3_3v3 (stm32_cs_adar3_3v3),
.stm32_cs_adar4_3v3 (stm32_cs_adar4_3v3),
// ----- SPI 1.8V -----
.stm32_sclk_1v8 (stm32_sclk_1v8),
.stm32_mosi_1v8 (stm32_mosi_1v8),
.stm32_miso_1v8 (stm32_miso_1v8),
.stm32_cs_adar1_1v8 (stm32_cs_adar1_1v8),
.stm32_cs_adar2_1v8 (stm32_cs_adar2_1v8),
.stm32_cs_adar3_1v8 (stm32_cs_adar3_1v8),
.stm32_cs_adar4_1v8 (stm32_cs_adar4_1v8),
// ----- ADC -----
.adc_d_p (adc_d_p),
.adc_d_n (adc_d_n),
.adc_dco_p (adc_dco_p),
.adc_dco_n (adc_dco_n),
.adc_pwdn (adc_pwdn),
// ----- STM32 Control -----
.stm32_new_chirp (stm32_new_chirp),
.stm32_new_elevation (stm32_new_elevation),
.stm32_new_azimuth (stm32_new_azimuth),
.stm32_mixers_enable (stm32_mixers_enable),
// ----- FT2232H USB 2.0 (active on 50T, USB_MODE=1) -----
.ft_data (ft_data),
.ft_rxf_n (ft_rxf_n),
.ft_txe_n (ft_txe_n),
.ft_rd_n (ft_rd_n),
.ft_wr_n (ft_wr_n),
.ft_oe_n (ft_oe_n),
.ft_siwu (ft_siwu),
// ----- FT601 (inactive with USB_MODE=1 generate block ties off) -----
.ft601_data (ft601_data_internal),
.ft601_be (ft601_be_nc),
.ft601_txe_n (ft601_txe_n_nc),
.ft601_rxf_n (ft601_rxf_n_nc),
.ft601_txe (ft601_txe_tied),
.ft601_rxf (ft601_rxf_tied),
.ft601_wr_n (ft601_wr_n_nc),
.ft601_rd_n (ft601_rd_n_nc),
.ft601_oe_n (ft601_oe_n_nc),
.ft601_siwu_n (ft601_siwu_n_nc),
.ft601_srb (ft601_srb_tied),
.ft601_swb (ft601_swb_tied),
.ft601_clk_out (ft601_clk_out_nc),
// ----- Status/Debug (no pins on 50T) -----
.current_elevation (current_elevation_nc),
.current_azimuth (current_azimuth_nc),
.current_chirp (current_chirp_nc),
.new_chirp_frame (new_chirp_frame_nc),
.dbg_doppler_data (dbg_doppler_data_nc),
.dbg_doppler_valid (dbg_doppler_valid_nc),
.dbg_doppler_bin (dbg_doppler_bin_nc),
.dbg_range_bin (dbg_range_bin_nc),
.system_status (system_status_nc)
);
endmodule
+7 -11
View File
@@ -67,7 +67,7 @@ PROD_RTL=(
matched_filter_processing_chain.v matched_filter_processing_chain.v
range_bin_decimator.v range_bin_decimator.v
doppler_processor.v doppler_processor.v
xfft_32.v xfft_16.v
fft_engine.v fft_engine.v
usb_data_interface.v usb_data_interface.v
edge_detector.v edge_detector.v
@@ -369,7 +369,7 @@ run_test "Chirp Contract" \
run_test "Doppler Processor (DSP48)" \ run_test "Doppler Processor (DSP48)" \
tb/tb_doppler_reg.vvp \ tb/tb_doppler_reg.vvp \
tb/tb_doppler_cosim.v doppler_processor.v xfft_32.v fft_engine.v tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v
run_test "Threshold Detector (detection bugs)" \ run_test "Threshold Detector (detection bugs)" \
tb/tb_threshold_detector.vvp \ tb/tb_threshold_detector.vvp \
@@ -414,7 +414,7 @@ if [[ "$QUICK" -eq 0 ]]; then
cdc_modules.v fir_lowpass.v ddc_input_interface.v \ cdc_modules.v fir_lowpass.v ddc_input_interface.v \
chirp_memory_loader_param.v latency_buffer.v \ chirp_memory_loader_param.v latency_buffer.v \
matched_filter_multi_segment.v matched_filter_processing_chain.v \ matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \ range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
rx_gain_control.v mti_canceller.v rx_gain_control.v mti_canceller.v
# Golden compare # Golden compare
@@ -426,7 +426,7 @@ if [[ "$QUICK" -eq 0 ]]; then
cdc_modules.v fir_lowpass.v ddc_input_interface.v \ cdc_modules.v fir_lowpass.v ddc_input_interface.v \
chirp_memory_loader_param.v latency_buffer.v \ chirp_memory_loader_param.v latency_buffer.v \
matched_filter_multi_segment.v matched_filter_processing_chain.v \ matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \ range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
rx_gain_control.v mti_canceller.v rx_gain_control.v mti_canceller.v
# Full system top (monitoring-only, legacy) # Full system top (monitoring-only, legacy)
@@ -439,7 +439,7 @@ if [[ "$QUICK" -eq 0 ]]; then
cdc_modules.v fir_lowpass.v ddc_input_interface.v \ cdc_modules.v fir_lowpass.v ddc_input_interface.v \
chirp_memory_loader_param.v latency_buffer.v \ chirp_memory_loader_param.v latency_buffer.v \
matched_filter_multi_segment.v matched_filter_processing_chain.v \ matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \ range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
usb_data_interface.v edge_detector.v radar_mode_controller.v \ usb_data_interface.v edge_detector.v radar_mode_controller.v \
rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v
@@ -453,7 +453,7 @@ if [[ "$QUICK" -eq 0 ]]; then
cdc_modules.v fir_lowpass.v ddc_input_interface.v \ cdc_modules.v fir_lowpass.v ddc_input_interface.v \
chirp_memory_loader_param.v latency_buffer.v \ chirp_memory_loader_param.v latency_buffer.v \
matched_filter_multi_segment.v matched_filter_processing_chain.v \ matched_filter_multi_segment.v matched_filter_processing_chain.v \
range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \ range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
usb_data_interface.v edge_detector.v radar_mode_controller.v \ usb_data_interface.v edge_detector.v radar_mode_controller.v \
rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v rx_gain_control.v cfar_ca.v mti_canceller.v fpga_self_test.v
else else
@@ -472,10 +472,6 @@ run_test "FFT Engine" \
tb/tb_fft_reg.vvp \ tb/tb_fft_reg.vvp \
tb/tb_fft_engine.v fft_engine.v tb/tb_fft_engine.v fft_engine.v
run_test "XFFT-32 Wrapper" \
tb/tb_xfft_reg.vvp \
tb/tb_xfft_32.v xfft_32.v fft_engine.v
run_test "NCO 400MHz" \ run_test "NCO 400MHz" \
tb/tb_nco_reg.vvp \ tb/tb_nco_reg.vvp \
tb/tb_nco_400m.v nco_400m_enhanced.v tb/tb_nco_400m.v nco_400m_enhanced.v
@@ -487,7 +483,7 @@ run_test "FIR Lowpass" \
run_test "Matched Filter Chain" \ run_test "Matched Filter Chain" \
tb/tb_mf_reg.vvp \ tb/tb_mf_reg.vvp \
tb/tb_matched_filter_processing_chain.v matched_filter_processing_chain.v \ tb/tb_matched_filter_processing_chain.v matched_filter_processing_chain.v \
xfft_32.v fft_engine.v chirp_memory_loader_param.v fft_engine.v chirp_memory_loader_param.v
echo "" echo ""
@@ -1,42 +1,14 @@
################################################################################ ################################################################################
# build21_fft_e2e.tcl # build_200t.tcl XC7A200T Dev Board Build
# AERIS-10 Build for the 200T development board (FBG484)
# #
# AERIS-10 Build 21: FFT Optimizations + E2E RTL Fixes
# Target: XC7A200T-2FBG484I # Target: XC7A200T-2FBG484I
# Design: radar_system_top # Design: radar_system_top
# Base: Build 20 (v0.1.3-build20, WNS +0.426 ns)
#
# Changes vs Build 20:
# - fft_engine.v: merged SHIFT state into WRITE (54 cycle butterfly,
# 20% throughput gain). Twiddle index uses barrel-shift instead of
# multiplier (frees 1 DSP48).
# - plfm_chirp_controller.v: TX/RX mixer enables now mutually exclusive
# by FSM state (Fix #4).
# - usb_data_interface.v: stream control reset default 3'b001 (range-only),
# doppler/cfar data_pending sticky flags, write FSM triggers on
# range_valid only (Fix #5).
# - radar_receiver_final.v: STM32 toggle signal inputs for mode-00,
# dynamic frame detection via host_chirps_per_elev.
# - radar_system_top.v: STM32 toggle wiring to receiver instance.
# - chirp_memory_loader_param.v: explicit readmemh range for short chirp.
#
# Expected impact:
# - DSP48E1: 140 139 (1 from barrel-shift twiddle, +0 net from
# FFT multiplier removal; CIC CREG DSPs unchanged)
# - LUT/FF: slight increase from USB data_pending logic + receiver
# toggle inputs. Slight decrease from FFT state removal.
# - Timing: should remain positive (Build 20 had +0.426 ns WNS).
# Gap 2 build (pre-FFT-opt) had +0.078 ns WNS with similar RTL.
#
# Generates ALL reports required for the 15-point Vivado TCL Build Report.
# #
# Usage: # Usage:
# vivado -mode batch -source build21_fft_e2e.tcl \ # cd 9_Firmware/9_2_FPGA
# -log ~/PLFM_RADAR_work/vivado_project/build21.log \ # vivado -mode batch -source scripts/200t/build_200t.tcl \
# -journal ~/PLFM_RADAR_work/vivado_project/build21.jou # -log build/build.log -journal build/build.jou
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-20
################################################################################ ################################################################################
# ============================================================================== # ==============================================================================
@@ -44,8 +16,10 @@
# ============================================================================== # ==============================================================================
set project_name "aeris10_radar" set project_name "aeris10_radar"
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project" set script_dir [file dirname [file normalize [info script]]]
set rtl_dir "/home/jason-stone/PLFM_RADAR_work/PLFM_RADAR/9_Firmware/9_2_FPGA" set project_root [file normalize [file join $script_dir "../.."]]
set project_dir [file join $project_root "build"]
set rtl_dir $project_root
set top_module "radar_system_top" set top_module "radar_system_top"
set fpga_part "xc7a200tfbg484-2" set fpga_part "xc7a200tfbg484-2"
set report_dir "${project_dir}/reports_build21" set report_dir "${project_dir}/reports_build21"
@@ -81,7 +55,6 @@ set rtl_files [list \
"${rtl_dir}/adc_clk_mmcm.v" \ "${rtl_dir}/adc_clk_mmcm.v" \
"${rtl_dir}/ad9484_interface_400m.v" \ "${rtl_dir}/ad9484_interface_400m.v" \
"${rtl_dir}/cdc_modules.v" \ "${rtl_dir}/cdc_modules.v" \
"${rtl_dir}/chirp_lut_init.v" \
"${rtl_dir}/chirp_memory_loader_param.v" \ "${rtl_dir}/chirp_memory_loader_param.v" \
"${rtl_dir}/cic_decimator_4x_enhanced.v" \ "${rtl_dir}/cic_decimator_4x_enhanced.v" \
"${rtl_dir}/dac_interface_single.v" \ "${rtl_dir}/dac_interface_single.v" \
@@ -89,13 +62,9 @@ set rtl_files [list \
"${rtl_dir}/ddc_input_interface.v" \ "${rtl_dir}/ddc_input_interface.v" \
"${rtl_dir}/doppler_processor.v" \ "${rtl_dir}/doppler_processor.v" \
"${rtl_dir}/edge_detector.v" \ "${rtl_dir}/edge_detector.v" \
"${rtl_dir}/fft_1024_forward.v" \
"${rtl_dir}/fft_1024_inverse.v" \
"${rtl_dir}/fir_lowpass.v" \ "${rtl_dir}/fir_lowpass.v" \
"${rtl_dir}/frequency_matched_filter.v" \ "${rtl_dir}/frequency_matched_filter.v" \
"${rtl_dir}/latency_buffer.v" \ "${rtl_dir}/latency_buffer.v" \
"${rtl_dir}/level_shifter_interface.v" \
"${rtl_dir}/lvds_to_cmos_400m.v" \
"${rtl_dir}/matched_filter_multi_segment.v" \ "${rtl_dir}/matched_filter_multi_segment.v" \
"${rtl_dir}/matched_filter_processing_chain.v" \ "${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \ "${rtl_dir}/nco_400m_enhanced.v" \
@@ -105,9 +74,13 @@ set rtl_files [list \
"${rtl_dir}/radar_system_top.v" \ "${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \ "${rtl_dir}/radar_transmitter.v" \
"${rtl_dir}/range_bin_decimator.v" \ "${rtl_dir}/range_bin_decimator.v" \
"${rtl_dir}/rx_gain_control.v" \
"${rtl_dir}/mti_canceller.v" \
"${rtl_dir}/cfar_ca.v" \
"${rtl_dir}/fpga_self_test.v" \
"${rtl_dir}/usb_data_interface.v" \ "${rtl_dir}/usb_data_interface.v" \
"${rtl_dir}/usb_packet_analyzer.v" \ "${rtl_dir}/usb_data_interface_ft2232h.v" \
"${rtl_dir}/xfft_32.v" \ "${rtl_dir}/xfft_16.v" \
"${rtl_dir}/fft_engine.v" \ "${rtl_dir}/fft_engine.v" \
] ]
@@ -130,8 +103,8 @@ foreach f $mem_files {
} }
# Add constraints main production XDC + MMCM supplementary XDC (FIXED) # Add constraints main production XDC + MMCM supplementary XDC (FIXED)
add_files -fileset constrs_1 -norecurse "${project_dir}/synth_only.xdc" add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "xc7a200t_fbg484.xdc"]
add_files -fileset constrs_1 -norecurse "${rtl_dir}/constraints/adc_clk_mmcm.xdc" add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "adc_clk_mmcm.xdc"]
set_property top $top_module [current_fileset] set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset] set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
@@ -196,7 +169,7 @@ set impl_status [get_property STATUS [get_runs impl_1]]
puts " Implementation status: $impl_status" puts " Implementation status: $impl_status"
puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)" puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)"
if {![string match "*Complete*" $impl_status]} { if {![string match "*Complete*" $impl_status] && ![string match "*write_bitstream*" $impl_status]} {
puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status" puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status"
close_project close_project
exit 1 exit 1
@@ -212,7 +185,10 @@ puts " Phase 3/5: Bitstream Generation"
puts "================================================================" puts "================================================================"
set bit_start [clock seconds] set bit_start [clock seconds]
launch_runs impl_1 -to_step write_bitstream -jobs 8 # Handle case where Vivado auto-proceeds to write_bitstream after impl
if {[catch {launch_runs impl_1 -to_step write_bitstream -jobs 8} launch_err]} {
puts " Note: write_bitstream may already be in progress: $launch_err"
}
wait_on_run impl_1 wait_on_run impl_1
set bit_elapsed [expr {[clock seconds] - $bit_start}] set bit_elapsed [expr {[clock seconds] - $bit_start}]
puts " Bitstream time: ${bit_elapsed}s" puts " Bitstream time: ${bit_elapsed}s"
@@ -302,7 +278,7 @@ report_cdc -details -file "${report_dir}/12_cdc.rpt"
puts " [13/15] Log scan see build21.log" puts " [13/15] Log scan see build21.log"
# --- Additional reports --- # --- Additional reports ---
puts " [extra] Generating additional diagnostic reports..." puts " \[extra\] Generating additional diagnostic reports..."
# report_exceptions can fail in Vivado 2025.2 wrap in catch # report_exceptions can fail in Vivado 2025.2 wrap in catch
if {[catch {report_exceptions -file "${report_dir}/13_exceptions.rpt"} err]} { if {[catch {report_exceptions -file "${report_dir}/13_exceptions.rpt"} err]} {
@@ -332,13 +308,14 @@ puts $summary_fh "Impl Time: ${impl_elapsed}s"
puts $summary_fh "Bitstream Time: ${bit_elapsed}s" puts $summary_fh "Bitstream Time: ${bit_elapsed}s"
puts $summary_fh "" puts $summary_fh ""
# Extract key timing numbers # Extract key timing numbers use catch to handle empty STATS properties
# (Vivado 2025.2 may return empty strings after write_bitstream auto-launch)
puts $summary_fh "--- Timing ---" puts $summary_fh "--- Timing ---"
set wns [get_property STATS.WNS [current_design]] if {[catch {set wns [get_property STATS.WNS [current_design]]}] || $wns eq ""} { set wns "N/A" }
set tns [get_property STATS.TNS [current_design]] if {[catch {set tns [get_property STATS.TNS [current_design]]}] || $tns eq ""} { set tns "N/A" }
set whs [get_property STATS.WHS [current_design]] if {[catch {set whs [get_property STATS.WHS [current_design]]}] || $whs eq ""} { set whs "N/A" }
set ths [get_property STATS.THS [current_design]] if {[catch {set ths [get_property STATS.THS [current_design]]}] || $ths eq ""} { set ths "N/A" }
set fail_ep [get_property STATS.TPWS [current_design]] if {[catch {set fail_ep [get_property STATS.TPWS [current_design]]}] || $fail_ep eq ""} { set fail_ep "N/A" }
puts $summary_fh " WNS: $wns ns" puts $summary_fh " WNS: $wns ns"
puts $summary_fh " TNS: $tns ns" puts $summary_fh " TNS: $tns ns"
puts $summary_fh " WHS: $whs ns" puts $summary_fh " WHS: $whs ns"
@@ -346,8 +323,16 @@ puts $summary_fh " THS: $ths ns"
puts $summary_fh "" puts $summary_fh ""
puts $summary_fh " Build 20 Baseline: WNS = +0.426 ns, WHS = +0.058 ns" puts $summary_fh " Build 20 Baseline: WNS = +0.426 ns, WHS = +0.058 ns"
puts $summary_fh " Gap 2 Build (ref): WNS = +0.078 ns, WHS = +0.054 ns" puts $summary_fh " Gap 2 Build (ref): WNS = +0.078 ns, WHS = +0.054 ns"
if {[string is double -strict $wns]} {
puts $summary_fh " Delta WNS vs B20: [expr {$wns - 0.426}] ns" puts $summary_fh " Delta WNS vs B20: [expr {$wns - 0.426}] ns"
} else {
puts $summary_fh " Delta WNS vs B20: N/A (timing stats unavailable)"
}
if {[string is double -strict $whs]} {
puts $summary_fh " Delta WHS vs B20: [expr {$whs - 0.058}] ns" puts $summary_fh " Delta WHS vs B20: [expr {$whs - 0.058}] ns"
} else {
puts $summary_fh " Delta WHS vs B20: N/A (timing stats unavailable)"
}
puts $summary_fh "" puts $summary_fh ""
# Extract utilization # Extract utilization
@@ -393,19 +378,25 @@ puts $summary_fh ""
# Signoff # Signoff
puts $summary_fh "--- Final Signoff ---" puts $summary_fh "--- Final Signoff ---"
set signoff_pass 1 set signoff_pass 1
if {$wns < 0} { if {![string is double -strict $wns]} {
puts $summary_fh " WARN: WNS = N/A (timing stats unavailable check reports)"
} elseif {$wns < 0} {
puts $summary_fh " FAIL: WNS = $wns (negative slack)" puts $summary_fh " FAIL: WNS = $wns (negative slack)"
set signoff_pass 0 set signoff_pass 0
} else { } else {
puts $summary_fh " PASS: WNS = $wns ns (no setup violations)" puts $summary_fh " PASS: WNS = $wns ns (no setup violations)"
} }
if {$whs < 0} { if {![string is double -strict $whs]} {
puts $summary_fh " WARN: WHS = N/A (timing stats unavailable check reports)"
} elseif {$whs < 0} {
puts $summary_fh " FAIL: WHS = $whs (hold violation)" puts $summary_fh " FAIL: WHS = $whs (hold violation)"
set signoff_pass 0 set signoff_pass 0
} else { } else {
puts $summary_fh " PASS: WHS = $whs ns (no hold violations)" puts $summary_fh " PASS: WHS = $whs ns (no hold violations)"
} }
if {$tns != 0} { if {![string is double -strict $tns]} {
puts $summary_fh " WARN: TNS = N/A (timing stats unavailable check reports)"
} elseif {$tns != 0} {
puts $summary_fh " FAIL: TNS = $tns (total negative slack)" puts $summary_fh " FAIL: TNS = $tns (total negative slack)"
set signoff_pass 0 set signoff_pass 0
} else { } else {
@@ -426,11 +417,11 @@ if {[file exists $bit_src]} {
puts $summary_fh "" puts $summary_fh ""
# Timing regression check vs Build 20 # Timing regression check vs Build 20
if {$wns < 0.078} { if {[string is double -strict $wns] && $wns < 0.078} {
puts $summary_fh " *** WARNING: WNS REGRESSED below Gap 2 build (was +0.078 ns, now $wns ns) ***" puts $summary_fh " *** WARNING: WNS REGRESSED below Gap 2 build (was +0.078 ns, now $wns ns) ***"
puts $summary_fh " *** Review critical paths FFT opts or RTL fixes may have introduced new timing pressure ***" puts $summary_fh " *** Review critical paths FFT opts or RTL fixes may have introduced new timing pressure ***"
} }
if {$whs < 0.054} { if {[string is double -strict $whs] && $whs < 0.054} {
puts $summary_fh " *** WARNING: WHS REGRESSED below Gap 2 build (was +0.054 ns, now $whs ns) ***" puts $summary_fh " *** WARNING: WHS REGRESSED below Gap 2 build (was +0.054 ns, now $whs ns) ***"
} }
@@ -0,0 +1,162 @@
################################################################################
# build_50t.tcl — XC7A50T Production Build
# Builds the AERIS-10 design targeting the production 50T (FTG256) board
#
# Usage:
# cd 9_Firmware/9_2_FPGA
# vivado -mode batch -source scripts/50t/build_50t.tcl 2>&1 | tee build_50t/vivado.log
################################################################################
set project_name "aeris10_radar_50t"
set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir "../.."]]
set project_dir [file join $project_root "build_50t"]
set rtl_dir $project_root
set fpga_part "xc7a50tftg256-2"
set top_module "radar_system_top_50t"
puts "================================================================"
puts " AERIS-10 XC7A50T Production Build"
puts " Target: $fpga_part"
puts " Project: $project_dir"
puts "================================================================"
file mkdir $project_dir
set report_dir [file join $project_dir "reports_50t"]
file mkdir $report_dir
set bit_dir [file join $project_dir "bitstream"]
file mkdir $bit_dir
create_project $project_name $project_dir -part $fpga_part -force
# Add ALL RTL files in the project root (avoid stale/dev tops)
set skip_patterns {*_te0712_* *_te0713_*}
foreach f [glob -directory $rtl_dir *.v] {
set skip 0
foreach pat $skip_patterns {
if {[string match $pat [file tail $f]]} { set skip 1; break }
}
if {!$skip} {
add_files -norecurse $f
puts " Added: [file tail $f]"
}
}
set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
# Constraints — 50T XDC + MMCM supplement
add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "xc7a50t_ftg256.xdc"]
add_files -fileset constrs_1 -norecurse [file join $project_root "constraints" "adc_clk_mmcm.xdc"]
# ============================================================================
# DRC SEVERITY WAIVERS — 50T Hardware-Specific
# ============================================================================
# NOTE: DRC severity waivers are set both before synthesis and after open_run
# synth_1. Implementation uses direct commands (opt_design, place_design, etc.)
# rather than launch_runs/wait_on_run, so all commands share the same Vivado
# context where the waivers are active.
#
# The top module is radar_system_top_50t — a thin wrapper that exposes only
# the 64 physically-connected ports on the FTG256 board. Unconstrained ports
# (FT601, debug, status) are tied off internally, keeping the full radar
# pipeline intact while fitting within the 69 available IO pins.
#
# BIVC-1: Bank 14 VCCO=2.5V (enforced by LVDS_25) with LVCMOS25 adc_pwdn.
# This should no longer fire now that adc_pwdn is LVCMOS25, but we keep
# the waiver as a safety net in case future XDC changes re-introduce the
# conflict.
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
# NSTD-1 / UCIO-1: Unconstrained port bits — FT601 USB ports (inactive with
# USB_MODE=1 generate block), dac_clk (DAC clock from AD9523, not FPGA),
# and all status/debug outputs (no physical pins on FTG256 package).
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
# PLIO-9: FT2232H CLKOUT is routed to C4 (IO_L12N_T1_MRCC_35), the N-type
# pin of a Multi-Region Clock-Capable pair. Clock inputs should ideally use
# the P-type pin, but IBUFG works correctly on either. The schematic routes
# to C4 and cannot be changed. Safe to demote.
set_property SEVERITY {Warning} [get_drc_checks PLIO-9]
# ===== SYNTHESIS =====
set synth_start [clock seconds]
launch_runs synth_1 -jobs 8
wait_on_run synth_1
set synth_elapsed [expr {[clock seconds] - $synth_start}]
set synth_status [get_property STATUS [get_runs synth_1]]
puts " Synthesis status: $synth_status"
puts " Synthesis time: ${synth_elapsed}s"
if {![string match "*Complete*" $synth_status]} {
puts "CRITICAL: SYNTHESIS FAILED: $synth_status"
close_project
exit 1
}
open_run synth_1
report_timing_summary -file "${report_dir}/01_timing_post_synth.rpt"
report_utilization -file "${report_dir}/01_utilization_post_synth.rpt"
# ===== IMPLEMENTATION (non-project-mode style) =====
# We run implementation steps directly in the parent process instead of
# using launch_runs/wait_on_run. This ensures DRC waivers are active in
# the same Vivado context as place_design.
set impl_start [clock seconds]
# Re-apply DRC waivers in this context (parent process)
set_property SEVERITY {Warning} [get_drc_checks BIVC-1]
set_property SEVERITY {Warning} [get_drc_checks NSTD-1]
set_property SEVERITY {Warning} [get_drc_checks UCIO-1]
set_property SEVERITY {Warning} [get_drc_checks PLIO-9]
# FT2232H CLKOUT on C4 (N-type MRCC) — override dedicated clock route check.
# The schematic routes the FT2232H 60 MHz clock to the N-pin of a differential
# MRCC pair. Vivado Place 30-876 requires this property to allow placement.
# The clock still reaches the clock network via IBUFG — this only suppresses
# the DRC that demands the P-type pin.
set_property CLOCK_DEDICATED_ROUTE FALSE [get_nets {ft_clkout_IBUF}]
# ---- Run implementation steps ----
opt_design -directive Explore
place_design -directive Explore
phys_opt_design -directive AggressiveExplore
route_design -directive Explore
phys_opt_design -directive AggressiveExplore
set impl_elapsed [expr {[clock seconds] - $impl_start}]
puts " Implementation time: ${impl_elapsed}s"
# ===== BITSTREAM =====
set bit_start [clock seconds]
set dst_bit [file join $bit_dir "radar_system_top_50t.bit"]
write_bitstream -force $dst_bit
set bit_elapsed [expr {[clock seconds] - $bit_start}]
if {[file exists $dst_bit]} {
puts " Bitstream: $dst_bit ([expr {[file size $dst_bit] / 1024}] KB)"
} else {
puts " WARNING: Bitstream not generated!"
}
# ===== REPORTS =====
report_timing_summary -file "${report_dir}/02_timing_summary.rpt"
report_utilization -file "${report_dir}/04_utilization.rpt"
report_drc -file "${report_dir}/06_drc.rpt"
report_io -file "${report_dir}/07_io.rpt"
puts "================================================================"
puts " XC7A50T Build Complete"
puts " Synth: ${synth_elapsed}s"
puts " Impl: ${impl_elapsed}s"
puts " Bit: ${bit_elapsed}s"
set wns_val "N/A"
set whs_val "N/A"
catch {set wns_val [get_property STATS.WNS [current_design]]}
catch {set whs_val [get_property STATS.WHS [current_design]]}
puts " WNS: $wns_val ns"
puts " WHS: $whs_val ns"
puts "================================================================"
close_project
exit 0
@@ -1,449 +0,0 @@
################################################################################
# build17_production.tcl
#
# AERIS-10 Build 17: Full Production Build + Comprehensive Report Suite
# Target: XC7A200T-2FBG484I
# Design: radar_system_top
# Tag: v0.1.0-bringup (commit 8ca6d99)
#
# Generates ALL reports required for the 15-point Vivado TCL Build Report
# Analysis Checklist:
# 1. Run Status (synth/opt/place/route completion)
# 2. Timing Summary (WNS/TNS/WHS)
# 3. Clock Analysis (report_clocks)
# 4. Utilization Report (LUT/FF/BRAM/DSP)
# 5. Power Report (dynamic/static/thermal)
# 6. DRC (Design Rule Check)
# 7. IO and Constraints (report_io, unconstrained ports)
# 8. Congestion Analysis (report_design_analysis -congestion)
# 9. Route Status (unrouted nets)
# 10. Critical Paths (report_timing -max_paths 20)
# 11. QoR Summary (report_qor_summary)
# 12. Incremental Compile comparison (timing vs Build 16)
# 13. Log File Scan (captured in build log)
# 14. Bitstream Generation (write_bitstream)
# 15. Final Signoff Criteria (all above combined)
#
# Usage:
# vivado -mode batch -source build17_production.tcl \
# -log ~/PLFM_RADAR_work/vivado_project/build17.log \
# -journal ~/PLFM_RADAR_work/vivado_project/build17.jou
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-19
################################################################################
# ==============================================================================
# 0. Configuration
# ==============================================================================
set project_name "aeris10_radar"
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set rtl_dir "/home/jason-stone/PLFM_RADAR_work/PLFM_RADAR/9_Firmware/9_2_FPGA"
set top_module "radar_system_top"
set fpga_part "xc7a200tfbg484-2"
set report_dir "${project_dir}/reports_build17"
set sim_dir "${project_dir}/sim"
set bitstream_dir "${project_dir}/bitstream"
set build_tag "build17"
file mkdir $report_dir
file mkdir $sim_dir
file mkdir $bitstream_dir
# Record start time
set build_start [clock seconds]
set build_timestamp [clock format $build_start -format {%Y-%m-%d %H:%M:%S}]
puts "================================================================"
puts " AERIS-10 Build 17: Full Production Build"
puts " Target: $fpga_part"
puts " Top: $top_module"
puts " Reports: $report_dir"
puts " Started: $build_timestamp"
puts "================================================================"
# ==============================================================================
# 1. Project Creation + Source Files
# ==============================================================================
create_project $project_name $project_dir -part $fpga_part -force
set_property target_language Verilog [current_project]
# --- Add RTL sources ---
set rtl_files [list \
"${rtl_dir}/ad9484_interface_400m.v" \
"${rtl_dir}/cdc_modules.v" \
"${rtl_dir}/chirp_lut_init.v" \
"${rtl_dir}/chirp_memory_loader_param.v" \
"${rtl_dir}/cic_decimator_4x_enhanced.v" \
"${rtl_dir}/dac_interface_single.v" \
"${rtl_dir}/ddc_400m.v" \
"${rtl_dir}/ddc_input_interface.v" \
"${rtl_dir}/doppler_processor.v" \
"${rtl_dir}/edge_detector.v" \
"${rtl_dir}/fft_1024_forward.v" \
"${rtl_dir}/fft_1024_inverse.v" \
"${rtl_dir}/fir_lowpass.v" \
"${rtl_dir}/frequency_matched_filter.v" \
"${rtl_dir}/latency_buffer.v" \
"${rtl_dir}/level_shifter_interface.v" \
"${rtl_dir}/lvds_to_cmos_400m.v" \
"${rtl_dir}/matched_filter_multi_segment.v" \
"${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.v" \
"${rtl_dir}/radar_mode_controller.v" \
"${rtl_dir}/radar_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \
"${rtl_dir}/range_bin_decimator.v" \
"${rtl_dir}/usb_data_interface.v" \
"${rtl_dir}/usb_packet_analyzer.v" \
"${rtl_dir}/xfft_32.v" \
"${rtl_dir}/fft_engine.v" \
]
set file_count 0
foreach f $rtl_files {
if {[file exists $f]} {
add_files -norecurse $f
incr file_count
} else {
puts " WARNING: RTL file not found: $f"
}
}
puts " Added $file_count RTL files"
# Add .mem files for BRAM initialization
set mem_files [glob -nocomplain "${rtl_dir}/*.mem"]
foreach f $mem_files {
add_files -norecurse $f
puts " Added MEM: [file tail $f]"
}
# Add constraints
add_files -fileset constrs_1 -norecurse "${project_dir}/synth_only.xdc"
set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
# ==============================================================================
# 2. Synthesis
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 1/5: Synthesis"
puts "================================================================"
set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY rebuilt [get_runs synth_1]
set_property STEPS.SYNTH_DESIGN.ARGS.KEEP_EQUIVALENT_REGISTERS true [get_runs synth_1]
set synth_start [clock seconds]
launch_runs synth_1 -jobs 8
wait_on_run synth_1
set synth_elapsed [expr {[clock seconds] - $synth_start}]
set synth_status [get_property STATUS [get_runs synth_1]]
puts " Synthesis status: $synth_status"
puts " Synthesis time: ${synth_elapsed}s ([expr {$synth_elapsed/60}]m [expr {$synth_elapsed%60}]s)"
if {[string match "*ERROR*" $synth_status] || [string match "*FAILED*" $synth_status]} {
puts "CRITICAL: SYNTHESIS FAILED aborting build"
close_project
exit 1
}
# Post-synth timing (for comparison)
open_run synth_1 -name synth_1
report_timing_summary -delay_type min_max -max_paths 10 -file "${report_dir}/01_timing_post_synth.rpt"
report_utilization -file "${report_dir}/01_utilization_post_synth.rpt"
close_design
# ==============================================================================
# 3. Implementation (opt → place → phys_opt → route → post_route_phys_opt)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 2/5: Implementation"
puts "================================================================"
# Aggressive directives for best timing
set_property STEPS.OPT_DESIGN.ARGS.DIRECTIVE Explore [get_runs impl_1]
set_property STEPS.PLACE_DESIGN.ARGS.DIRECTIVE ExtraTimingOpt [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.ROUTE_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set impl_start [clock seconds]
launch_runs impl_1 -jobs 8
wait_on_run impl_1
set impl_elapsed [expr {[clock seconds] - $impl_start}]
set impl_status [get_property STATUS [get_runs impl_1]]
puts " Implementation status: $impl_status"
puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)"
if {![string match "*Complete*" $impl_status]} {
puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status"
close_project
exit 1
}
# ==============================================================================
# 4. Bitstream Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 3/5: Bitstream Generation"
puts "================================================================"
set bit_start [clock seconds]
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
set bit_elapsed [expr {[clock seconds] - $bit_start}]
puts " Bitstream time: ${bit_elapsed}s"
# Copy bitstream to known location
set bit_src "${project_dir}/aeris10_radar.runs/impl_1/${top_module}.bit"
if {[file exists $bit_src]} {
file copy -force $bit_src "${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Size: [file size $bit_src] bytes"
} else {
puts " WARNING: Bitstream file not found at $bit_src"
}
# ==============================================================================
# 5. Comprehensive Report Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 4/5: Report Generation (15-point checklist)"
puts "================================================================"
# Open the routed design for reporting
open_run impl_1 -name impl_1
# --- Checklist Item 2: Timing Summary ---
puts " [2/15] Timing Summary..."
report_timing_summary -delay_type min_max -max_paths 100 \
-report_unconstrained \
-file "${report_dir}/02_timing_summary.rpt"
# --- Checklist Item 3: Clock Analysis ---
puts " [3/15] Clock Analysis..."
report_clocks -file "${report_dir}/03_clocks.rpt"
report_clock_interaction -delay_type min_max \
-file "${report_dir}/03_clock_interaction.rpt"
report_clock_networks -file "${report_dir}/03_clock_networks.rpt"
# --- Checklist Item 4: Utilization ---
puts " [4/15] Utilization..."
report_utilization -file "${report_dir}/04_utilization.rpt"
report_utilization -hierarchical -file "${report_dir}/04_utilization_hierarchical.rpt"
# --- Checklist Item 5: Power ---
puts " [5/15] Power Report..."
report_power -file "${report_dir}/05_power.rpt"
# --- Checklist Item 6: DRC ---
puts " [6/15] DRC..."
report_drc -file "${report_dir}/06_drc.rpt"
# --- Checklist Item 7: IO and Constraints ---
puts " [7/15] IO Report..."
report_io -file "${report_dir}/07_io.rpt"
report_timing -from [all_inputs] -to [all_outputs] -max_paths 20 \
-file "${report_dir}/07_io_timing.rpt"
# --- Checklist Item 8: Congestion Analysis ---
puts " [8/15] Congestion Analysis..."
report_design_analysis -congestion -file "${report_dir}/08_congestion.rpt"
# --- Checklist Item 9: Route Status ---
puts " [9/15] Route Status..."
report_route_status -file "${report_dir}/09_route_status.rpt"
# --- Checklist Item 10: Critical Paths ---
puts " [10/15] Critical Paths..."
report_timing -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_setup.rpt"
report_timing -delay_type min -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_hold.rpt"
report_high_fanout_nets -timing -load_type -max_nets 20 \
-file "${report_dir}/10_high_fanout_nets.rpt"
# --- Checklist Item 11: QoR Summary ---
puts " [11/15] QoR Summary..."
report_design_analysis -timing -file "${report_dir}/11_design_analysis_timing.rpt"
report_design_analysis -logic_level_distribution -file "${report_dir}/11_logic_level_dist.rpt"
report_methodology -file "${report_dir}/11_methodology.rpt"
# --- Checklist Item 12: CDC Analysis ---
puts " [12/15] CDC Analysis..."
report_cdc -details -file "${report_dir}/12_cdc.rpt"
# --- Checklist Item 13: Log Scan (captured separately in build log) ---
puts " [13/15] Log scan see build17.log"
# --- Additional reports ---
puts " [extra] Generating additional diagnostic reports..."
# Check_timing for completeness
report_exceptions -file "${report_dir}/13_exceptions.rpt"
check_timing -verbose -file "${report_dir}/13_check_timing.rpt"
# Compile configuration summary into a single text file
set summary_fh [open "${report_dir}/00_build17_summary.txt" w]
puts $summary_fh "================================================================"
puts $summary_fh " AERIS-10 Build 17 Production Build Summary"
puts $summary_fh "================================================================"
puts $summary_fh ""
puts $summary_fh "Build Tag: $build_tag"
puts $summary_fh "Build Timestamp: $build_timestamp"
puts $summary_fh "FPGA Part: $fpga_part"
puts $summary_fh "Top Module: $top_module"
puts $summary_fh "RTL Files: $file_count"
puts $summary_fh "Synth Status: $synth_status"
puts $summary_fh "Synth Time: ${synth_elapsed}s"
puts $summary_fh "Impl Status: $impl_status"
puts $summary_fh "Impl Time: ${impl_elapsed}s"
puts $summary_fh "Bitstream Time: ${bit_elapsed}s"
puts $summary_fh ""
# Extract key timing numbers
puts $summary_fh "--- Timing ---"
set wns [get_property STATS.WNS [current_design]]
set tns [get_property STATS.TNS [current_design]]
set whs [get_property STATS.WHS [current_design]]
set ths [get_property STATS.THS [current_design]]
set fail_ep [get_property STATS.TPWS [current_design]]
puts $summary_fh " WNS: $wns ns"
puts $summary_fh " TNS: $tns ns"
puts $summary_fh " WHS: $whs ns"
puts $summary_fh " THS: $ths ns"
puts $summary_fh ""
# Extract utilization
puts $summary_fh "--- Utilization ---"
set lut_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.LUT.*}]]
set ff_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.FF.*}]]
set bram_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ BMEM.*}]]
set dsp_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ MULT.DSP.*}]]
puts $summary_fh " LUTs: $lut_used / 134600"
puts $summary_fh " FFs: $ff_used / 269200"
puts $summary_fh " BRAM: $bram_used cells"
puts $summary_fh " DSP: $dsp_used cells"
puts $summary_fh ""
# Route status
set unrouted [llength [get_nets -hierarchical -filter {ROUTE_STATUS == UNROUTED}]]
puts $summary_fh "--- Route ---"
puts $summary_fh " Unrouted nets: $unrouted"
puts $summary_fh ""
# Bitstream
if {[file exists $bit_src]} {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " File: ${top_module}_${build_tag}.bit"
puts $summary_fh " Size: [file size $bit_src] bytes"
} else {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " WARNING: NOT GENERATED"
}
puts $summary_fh ""
# Signoff
puts $summary_fh "--- Final Signoff ---"
set signoff_pass 1
if {$wns < 0} {
puts $summary_fh " FAIL: WNS = $wns (negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WNS = $wns ns (no setup violations)"
}
if {$whs < 0} {
puts $summary_fh " FAIL: WHS = $whs (hold violation)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WHS = $whs ns (no hold violations)"
}
if {$tns != 0} {
puts $summary_fh " FAIL: TNS = $tns (total negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: TNS = 0 ns"
}
if {$unrouted > 0} {
puts $summary_fh " FAIL: $unrouted unrouted nets"
set signoff_pass 0
} else {
puts $summary_fh " PASS: All nets routed"
}
if {[file exists $bit_src]} {
puts $summary_fh " PASS: Bitstream generated"
} else {
puts $summary_fh " FAIL: No bitstream"
set signoff_pass 0
}
puts $summary_fh ""
if {$signoff_pass} {
puts $summary_fh " *** SIGNOFF: PASS ***"
} else {
puts $summary_fh " *** SIGNOFF: FAIL ***"
}
close $summary_fh
puts " Summary written to: ${report_dir}/00_build17_summary.txt"
# ==============================================================================
# 6. SDF + Timing Netlist (for post-route simulation)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 5/5: SDF + Timing Netlist"
puts "================================================================"
write_verilog -force -mode timesim "${sim_dir}/post_impl_timesim.v"
write_sdf -force "${sim_dir}/post_impl_timesim.sdf"
close_design
open_run synth_1 -name synth_1
write_verilog -force -mode funcsim "${sim_dir}/post_synth_funcsim.v"
# ==============================================================================
# Done
# ==============================================================================
set build_total [expr {[clock seconds] - $build_start}]
set build_end [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
puts ""
puts "================================================================"
puts " BUILD 17 COMPLETE"
puts "================================================================"
puts " Started: $build_timestamp"
puts " Finished: $build_end"
puts " Total time: ${build_total}s ([expr {$build_total/60}]m [expr {$build_total%60}]s)"
puts " Synth: ${synth_elapsed}s"
puts " Impl: ${impl_elapsed}s"
puts " Bitstream: ${bit_elapsed}s"
puts " Reports: $report_dir"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " WNS: $wns ns | WHS: $whs ns | TNS: $tns ns"
if {$signoff_pass} {
puts " SIGNOFF: PASS"
} else {
puts " SIGNOFF: FAIL"
}
puts "================================================================"
close_project
puts "Done."
@@ -1,459 +0,0 @@
################################################################################
# build18_production.tcl
#
# AERIS-10 Build 18: Post-Optimization Production Build
# Target: XC7A200T-2FBG484I
# Design: radar_system_top
# Tag: v0.1.1-build17 + FIR DSP48 pipelining + matched filter BRAM migration
#
# Changes vs Build 17:
# - FIR DSP48 BREG+MREG pipelining (fixes 68 DPIP-1 + 35 DPOP-2 warnings)
# - Matched filter input buffer migrated from register arrays to BRAM
# (~33K FF savings expected, +2 BRAM18 used)
# - Fixed: report_exceptions Vivado 2025.2 syntax (catch block)
#
# Generates ALL reports required for the 15-point Vivado TCL Build Report
# Analysis Checklist:
# 1. Run Status (synth/opt/place/route completion)
# 2. Timing Summary (WNS/TNS/WHS)
# 3. Clock Analysis (report_clocks)
# 4. Utilization Report (LUT/FF/BRAM/DSP)
# 5. Power Report (dynamic/static/thermal)
# 6. DRC (Design Rule Check)
# 7. IO and Constraints (report_io, unconstrained ports)
# 8. Congestion Analysis (report_design_analysis -congestion)
# 9. Route Status (unrouted nets)
# 10. Critical Paths (report_timing -max_paths 20)
# 11. QoR Summary (report_qor_summary)
# 12. CDC Analysis
# 13. Log File Scan (captured in build log)
# 14. Bitstream Generation (write_bitstream)
# 15. Final Signoff Criteria (all above combined)
#
# Usage:
# vivado -mode batch -source build18_production.tcl \
# -log ~/PLFM_RADAR_work/vivado_project/build18.log \
# -journal ~/PLFM_RADAR_work/vivado_project/build18.jou
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-19
################################################################################
# ==============================================================================
# 0. Configuration
# ==============================================================================
set project_name "aeris10_radar"
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set rtl_dir "/home/jason-stone/PLFM_RADAR_work/PLFM_RADAR/9_Firmware/9_2_FPGA"
set top_module "radar_system_top"
set fpga_part "xc7a200tfbg484-2"
set report_dir "${project_dir}/reports_build18"
set sim_dir "${project_dir}/sim"
set bitstream_dir "${project_dir}/bitstream"
set build_tag "build18"
file mkdir $report_dir
file mkdir $sim_dir
file mkdir $bitstream_dir
# Record start time
set build_start [clock seconds]
set build_timestamp [clock format $build_start -format {%Y-%m-%d %H:%M:%S}]
puts "================================================================"
puts " AERIS-10 Build 18: Post-Optimization Production Build"
puts " Target: $fpga_part"
puts " Top: $top_module"
puts " Reports: $report_dir"
puts " Started: $build_timestamp"
puts "================================================================"
# ==============================================================================
# 1. Project Creation + Source Files
# ==============================================================================
create_project $project_name $project_dir -part $fpga_part -force
set_property target_language Verilog [current_project]
# --- Add RTL sources ---
set rtl_files [list \
"${rtl_dir}/ad9484_interface_400m.v" \
"${rtl_dir}/cdc_modules.v" \
"${rtl_dir}/chirp_lut_init.v" \
"${rtl_dir}/chirp_memory_loader_param.v" \
"${rtl_dir}/cic_decimator_4x_enhanced.v" \
"${rtl_dir}/dac_interface_single.v" \
"${rtl_dir}/ddc_400m.v" \
"${rtl_dir}/ddc_input_interface.v" \
"${rtl_dir}/doppler_processor.v" \
"${rtl_dir}/edge_detector.v" \
"${rtl_dir}/fft_1024_forward.v" \
"${rtl_dir}/fft_1024_inverse.v" \
"${rtl_dir}/fir_lowpass.v" \
"${rtl_dir}/frequency_matched_filter.v" \
"${rtl_dir}/latency_buffer.v" \
"${rtl_dir}/level_shifter_interface.v" \
"${rtl_dir}/lvds_to_cmos_400m.v" \
"${rtl_dir}/matched_filter_multi_segment.v" \
"${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.v" \
"${rtl_dir}/radar_mode_controller.v" \
"${rtl_dir}/radar_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \
"${rtl_dir}/range_bin_decimator.v" \
"${rtl_dir}/usb_data_interface.v" \
"${rtl_dir}/usb_packet_analyzer.v" \
"${rtl_dir}/xfft_32.v" \
"${rtl_dir}/fft_engine.v" \
]
set file_count 0
foreach f $rtl_files {
if {[file exists $f]} {
add_files -norecurse $f
incr file_count
} else {
puts " WARNING: RTL file not found: $f"
}
}
puts " Added $file_count RTL files"
# Add .mem files for BRAM initialization
set mem_files [glob -nocomplain "${rtl_dir}/*.mem"]
foreach f $mem_files {
add_files -norecurse $f
puts " Added MEM: [file tail $f]"
}
# Add constraints
add_files -fileset constrs_1 -norecurse "${project_dir}/synth_only.xdc"
set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
# ==============================================================================
# 2. Synthesis
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 1/5: Synthesis"
puts "================================================================"
set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY rebuilt [get_runs synth_1]
set_property STEPS.SYNTH_DESIGN.ARGS.KEEP_EQUIVALENT_REGISTERS true [get_runs synth_1]
set synth_start [clock seconds]
launch_runs synth_1 -jobs 8
wait_on_run synth_1
set synth_elapsed [expr {[clock seconds] - $synth_start}]
set synth_status [get_property STATUS [get_runs synth_1]]
puts " Synthesis status: $synth_status"
puts " Synthesis time: ${synth_elapsed}s ([expr {$synth_elapsed/60}]m [expr {$synth_elapsed%60}]s)"
if {[string match "*ERROR*" $synth_status] || [string match "*FAILED*" $synth_status]} {
puts "CRITICAL: SYNTHESIS FAILED aborting build"
close_project
exit 1
}
# Post-synth timing (for comparison)
open_run synth_1 -name synth_1
report_timing_summary -delay_type min_max -max_paths 10 -file "${report_dir}/01_timing_post_synth.rpt"
report_utilization -file "${report_dir}/01_utilization_post_synth.rpt"
close_design
# ==============================================================================
# 3. Implementation (opt → place → phys_opt → route → post_route_phys_opt)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 2/5: Implementation"
puts "================================================================"
# Aggressive directives for best timing
set_property STEPS.OPT_DESIGN.ARGS.DIRECTIVE Explore [get_runs impl_1]
set_property STEPS.PLACE_DESIGN.ARGS.DIRECTIVE ExtraTimingOpt [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.ROUTE_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set impl_start [clock seconds]
launch_runs impl_1 -jobs 8
wait_on_run impl_1
set impl_elapsed [expr {[clock seconds] - $impl_start}]
set impl_status [get_property STATUS [get_runs impl_1]]
puts " Implementation status: $impl_status"
puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)"
if {![string match "*Complete*" $impl_status]} {
puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status"
close_project
exit 1
}
# ==============================================================================
# 4. Bitstream Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 3/5: Bitstream Generation"
puts "================================================================"
set bit_start [clock seconds]
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
set bit_elapsed [expr {[clock seconds] - $bit_start}]
puts " Bitstream time: ${bit_elapsed}s"
# Copy bitstream to known location
set bit_src "${project_dir}/aeris10_radar.runs/impl_1/${top_module}.bit"
if {[file exists $bit_src]} {
file copy -force $bit_src "${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Size: [file size $bit_src] bytes"
} else {
puts " WARNING: Bitstream file not found at $bit_src"
}
# ==============================================================================
# 5. Comprehensive Report Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 4/5: Report Generation (15-point checklist)"
puts "================================================================"
# Open the routed design for reporting
open_run impl_1 -name impl_1
# --- Checklist Item 2: Timing Summary ---
puts " [2/15] Timing Summary..."
report_timing_summary -delay_type min_max -max_paths 100 \
-report_unconstrained \
-file "${report_dir}/02_timing_summary.rpt"
# --- Checklist Item 3: Clock Analysis ---
puts " [3/15] Clock Analysis..."
report_clocks -file "${report_dir}/03_clocks.rpt"
report_clock_interaction -delay_type min_max \
-file "${report_dir}/03_clock_interaction.rpt"
report_clock_networks -file "${report_dir}/03_clock_networks.rpt"
# --- Checklist Item 4: Utilization ---
puts " [4/15] Utilization..."
report_utilization -file "${report_dir}/04_utilization.rpt"
report_utilization -hierarchical -file "${report_dir}/04_utilization_hierarchical.rpt"
# --- Checklist Item 5: Power ---
puts " [5/15] Power Report..."
report_power -file "${report_dir}/05_power.rpt"
# --- Checklist Item 6: DRC ---
puts " [6/15] DRC..."
report_drc -file "${report_dir}/06_drc.rpt"
# --- Checklist Item 7: IO and Constraints ---
puts " [7/15] IO Report..."
report_io -file "${report_dir}/07_io.rpt"
report_timing -from [all_inputs] -to [all_outputs] -max_paths 20 \
-file "${report_dir}/07_io_timing.rpt"
# --- Checklist Item 8: Congestion Analysis ---
puts " [8/15] Congestion Analysis..."
report_design_analysis -congestion -file "${report_dir}/08_congestion.rpt"
# --- Checklist Item 9: Route Status ---
puts " [9/15] Route Status..."
report_route_status -file "${report_dir}/09_route_status.rpt"
# --- Checklist Item 10: Critical Paths ---
puts " [10/15] Critical Paths..."
report_timing -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_setup.rpt"
report_timing -delay_type min -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_hold.rpt"
report_high_fanout_nets -timing -load_type -max_nets 20 \
-file "${report_dir}/10_high_fanout_nets.rpt"
# --- Checklist Item 11: QoR Summary ---
puts " [11/15] QoR Summary..."
report_design_analysis -timing -file "${report_dir}/11_design_analysis_timing.rpt"
report_design_analysis -logic_level_distribution -file "${report_dir}/11_logic_level_dist.rpt"
report_methodology -file "${report_dir}/11_methodology.rpt"
# --- Checklist Item 12: CDC Analysis ---
puts " [12/15] CDC Analysis..."
report_cdc -details -file "${report_dir}/12_cdc.rpt"
# --- Checklist Item 13: Log Scan (captured separately in build log) ---
puts " [13/15] Log scan see build18.log"
# --- Additional reports ---
puts " [extra] Generating additional diagnostic reports..."
# Check_timing for completeness
# report_exceptions can fail in Vivado 2025.2 — wrap in catch
if {[catch {report_exceptions -file "${report_dir}/13_exceptions.rpt"} err]} {
puts " WARNING: report_exceptions failed: $err"
puts " (Known Vivado 2025.2 issue non-critical)"
}
check_timing -verbose -file "${report_dir}/13_check_timing.rpt"
# Compile configuration summary into a single text file
set summary_fh [open "${report_dir}/00_build18_summary.txt" w]
puts $summary_fh "================================================================"
puts $summary_fh " AERIS-10 Build 18 Post-Optimization Production Build Summary"
puts $summary_fh "================================================================"
puts $summary_fh ""
puts $summary_fh "Build Tag: $build_tag"
puts $summary_fh "Build Timestamp: $build_timestamp"
puts $summary_fh "FPGA Part: $fpga_part"
puts $summary_fh "Top Module: $top_module"
puts $summary_fh "RTL Files: $file_count"
puts $summary_fh "Synth Status: $synth_status"
puts $summary_fh "Synth Time: ${synth_elapsed}s"
puts $summary_fh "Impl Status: $impl_status"
puts $summary_fh "Impl Time: ${impl_elapsed}s"
puts $summary_fh "Bitstream Time: ${bit_elapsed}s"
puts $summary_fh ""
# Extract key timing numbers
puts $summary_fh "--- Timing ---"
set wns [get_property STATS.WNS [current_design]]
set tns [get_property STATS.TNS [current_design]]
set whs [get_property STATS.WHS [current_design]]
set ths [get_property STATS.THS [current_design]]
set fail_ep [get_property STATS.TPWS [current_design]]
puts $summary_fh " WNS: $wns ns"
puts $summary_fh " TNS: $tns ns"
puts $summary_fh " WHS: $whs ns"
puts $summary_fh " THS: $ths ns"
puts $summary_fh ""
# Extract utilization
puts $summary_fh "--- Utilization ---"
set lut_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.LUT.*}]]
set ff_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.FF.*}]]
set bram_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ BMEM.*}]]
set dsp_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ MULT.DSP.*}]]
puts $summary_fh " LUTs: $lut_used / 134600"
puts $summary_fh " FFs: $ff_used / 269200"
puts $summary_fh " BRAM: $bram_used cells"
puts $summary_fh " DSP: $dsp_used cells"
puts $summary_fh ""
# Route status
set unrouted [llength [get_nets -hierarchical -filter {ROUTE_STATUS == UNROUTED}]]
puts $summary_fh "--- Route ---"
puts $summary_fh " Unrouted nets: $unrouted"
puts $summary_fh ""
# Bitstream
if {[file exists $bit_src]} {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " File: ${top_module}_${build_tag}.bit"
puts $summary_fh " Size: [file size $bit_src] bytes"
} else {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " WARNING: NOT GENERATED"
}
puts $summary_fh ""
# Signoff
puts $summary_fh "--- Final Signoff ---"
set signoff_pass 1
if {$wns < 0} {
puts $summary_fh " FAIL: WNS = $wns (negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WNS = $wns ns (no setup violations)"
}
if {$whs < 0} {
puts $summary_fh " FAIL: WHS = $whs (hold violation)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WHS = $whs ns (no hold violations)"
}
if {$tns != 0} {
puts $summary_fh " FAIL: TNS = $tns (total negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: TNS = 0 ns"
}
if {$unrouted > 0} {
puts $summary_fh " FAIL: $unrouted unrouted nets"
set signoff_pass 0
} else {
puts $summary_fh " PASS: All nets routed"
}
if {[file exists $bit_src]} {
puts $summary_fh " PASS: Bitstream generated"
} else {
puts $summary_fh " FAIL: No bitstream"
set signoff_pass 0
}
puts $summary_fh ""
if {$signoff_pass} {
puts $summary_fh " *** SIGNOFF: PASS ***"
} else {
puts $summary_fh " *** SIGNOFF: FAIL ***"
}
close $summary_fh
puts " Summary written to: ${report_dir}/00_build18_summary.txt"
# ==============================================================================
# 6. SDF + Timing Netlist (for post-route simulation)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 5/5: SDF + Timing Netlist"
puts "================================================================"
write_verilog -force -mode timesim "${sim_dir}/post_impl_timesim.v"
write_sdf -force "${sim_dir}/post_impl_timesim.sdf"
close_design
open_run synth_1 -name synth_1
write_verilog -force -mode funcsim "${sim_dir}/post_synth_funcsim.v"
# ==============================================================================
# Done
# ==============================================================================
set build_total [expr {[clock seconds] - $build_start}]
set build_end [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
puts ""
puts "================================================================"
puts " BUILD 18 COMPLETE"
puts "================================================================"
puts " Started: $build_timestamp"
puts " Finished: $build_end"
puts " Total time: ${build_total}s ([expr {$build_total/60}]m [expr {$build_total%60}]s)"
puts " Synth: ${synth_elapsed}s"
puts " Impl: ${impl_elapsed}s"
puts " Bitstream: ${bit_elapsed}s"
puts " Reports: $report_dir"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " WNS: $wns ns | WHS: $whs ns | TNS: $tns ns"
if {$signoff_pass} {
puts " SIGNOFF: PASS"
} else {
puts " SIGNOFF: FAIL"
}
puts "================================================================"
close_project
puts "Done."
@@ -1,475 +0,0 @@
################################################################################
# build19_mmcm.tcl
#
# AERIS-10 Build 19: MMCM Jitter-Cleaning on ADC 400 MHz Clock (Gap 7)
# Target: XC7A200T-2FBG484I
# Design: radar_system_top
# Tag: v0.1.2-build18 + adc_clk_mmcm jitter cleaning wrapper
#
# Changes vs Build 18:
# - NEW MODULE: adc_clk_mmcm.v — MMCME2_ADV jitter-cleaning wrapper
# - MODIFIED: ad9484_interface_400m.v — BUFG replaced with MMCM path,
# reset gated on mmcm_locked
# - NEW XDC: adc_clk_mmcm.xdc — generated clock rename, CDC false paths
#
# Expected impact:
# - WNS improvement: +20-40 ps (reduced clock uncertainty from jitter cleaning)
# - MMCME2 usage: 0 → 1 (of 10 available)
# - BUFG usage: 4 → 5 (of 32 available; feedback BUFG inside MMCM wrapper)
#
# Generates ALL reports required for the 15-point Vivado TCL Build Report.
#
# Usage:
# vivado -mode batch -source build19_mmcm.tcl \
# -log ~/PLFM_RADAR_work/vivado_project/build19.log \
# -journal ~/PLFM_RADAR_work/vivado_project/build19.jou
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-19
################################################################################
# ==============================================================================
# 0. Configuration
# ==============================================================================
set project_name "aeris10_radar"
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set rtl_dir "/home/jason-stone/PLFM_RADAR_work/PLFM_RADAR/9_Firmware/9_2_FPGA"
set top_module "radar_system_top"
set fpga_part "xc7a200tfbg484-2"
set report_dir "${project_dir}/reports_build19"
set sim_dir "${project_dir}/sim"
set bitstream_dir "${project_dir}/bitstream"
set build_tag "build19"
file mkdir $report_dir
file mkdir $sim_dir
file mkdir $bitstream_dir
# Record start time
set build_start [clock seconds]
set build_timestamp [clock format $build_start -format {%Y-%m-%d %H:%M:%S}]
puts "================================================================"
puts " AERIS-10 Build 19: MMCM Jitter-Cleaning (Gap 7)"
puts " Target: $fpga_part"
puts " Top: $top_module"
puts " Reports: $report_dir"
puts " Started: $build_timestamp"
puts "================================================================"
# ==============================================================================
# 1. Project Creation + Source Files
# ==============================================================================
create_project $project_name $project_dir -part $fpga_part -force
set_property target_language Verilog [current_project]
# --- Add RTL sources ---
# NOTE: adc_clk_mmcm.v is NEW for Build 19 (Gap 7 MMCM wrapper)
set rtl_files [list \
"${rtl_dir}/adc_clk_mmcm.v" \
"${rtl_dir}/ad9484_interface_400m.v" \
"${rtl_dir}/cdc_modules.v" \
"${rtl_dir}/chirp_lut_init.v" \
"${rtl_dir}/chirp_memory_loader_param.v" \
"${rtl_dir}/cic_decimator_4x_enhanced.v" \
"${rtl_dir}/dac_interface_single.v" \
"${rtl_dir}/ddc_400m.v" \
"${rtl_dir}/ddc_input_interface.v" \
"${rtl_dir}/doppler_processor.v" \
"${rtl_dir}/edge_detector.v" \
"${rtl_dir}/fft_1024_forward.v" \
"${rtl_dir}/fft_1024_inverse.v" \
"${rtl_dir}/fir_lowpass.v" \
"${rtl_dir}/frequency_matched_filter.v" \
"${rtl_dir}/latency_buffer.v" \
"${rtl_dir}/level_shifter_interface.v" \
"${rtl_dir}/lvds_to_cmos_400m.v" \
"${rtl_dir}/matched_filter_multi_segment.v" \
"${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.v" \
"${rtl_dir}/radar_mode_controller.v" \
"${rtl_dir}/radar_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \
"${rtl_dir}/range_bin_decimator.v" \
"${rtl_dir}/usb_data_interface.v" \
"${rtl_dir}/usb_packet_analyzer.v" \
"${rtl_dir}/xfft_32.v" \
"${rtl_dir}/fft_engine.v" \
]
set file_count 0
foreach f $rtl_files {
if {[file exists $f]} {
add_files -norecurse $f
incr file_count
} else {
puts " WARNING: RTL file not found: $f"
}
}
puts " Added $file_count RTL files"
# Add .mem files for BRAM initialization
set mem_files [glob -nocomplain "${rtl_dir}/*.mem"]
foreach f $mem_files {
add_files -norecurse $f
puts " Added MEM: [file tail $f]"
}
# Add constraints — main production XDC + MMCM supplementary XDC
add_files -fileset constrs_1 -norecurse "${project_dir}/synth_only.xdc"
add_files -fileset constrs_1 -norecurse "${rtl_dir}/constraints/adc_clk_mmcm.xdc"
set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
# ==============================================================================
# 2. Synthesis
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 1/5: Synthesis"
puts "================================================================"
set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY rebuilt [get_runs synth_1]
set_property STEPS.SYNTH_DESIGN.ARGS.KEEP_EQUIVALENT_REGISTERS true [get_runs synth_1]
set synth_start [clock seconds]
launch_runs synth_1 -jobs 8
wait_on_run synth_1
set synth_elapsed [expr {[clock seconds] - $synth_start}]
set synth_status [get_property STATUS [get_runs synth_1]]
puts " Synthesis status: $synth_status"
puts " Synthesis time: ${synth_elapsed}s ([expr {$synth_elapsed/60}]m [expr {$synth_elapsed%60}]s)"
if {[string match "*ERROR*" $synth_status] || [string match "*FAILED*" $synth_status]} {
puts "CRITICAL: SYNTHESIS FAILED aborting build"
close_project
exit 1
}
# Post-synth timing (for comparison)
open_run synth_1 -name synth_1
report_timing_summary -delay_type min_max -max_paths 10 -file "${report_dir}/01_timing_post_synth.rpt"
report_utilization -file "${report_dir}/01_utilization_post_synth.rpt"
close_design
# ==============================================================================
# 3. Implementation (opt → place → phys_opt → route → post_route_phys_opt)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 2/5: Implementation"
puts "================================================================"
# Aggressive directives for best timing
set_property STEPS.OPT_DESIGN.ARGS.DIRECTIVE Explore [get_runs impl_1]
set_property STEPS.PLACE_DESIGN.ARGS.DIRECTIVE ExtraTimingOpt [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.ROUTE_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set impl_start [clock seconds]
launch_runs impl_1 -jobs 8
wait_on_run impl_1
set impl_elapsed [expr {[clock seconds] - $impl_start}]
set impl_status [get_property STATUS [get_runs impl_1]]
puts " Implementation status: $impl_status"
puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)"
if {![string match "*Complete*" $impl_status]} {
puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status"
close_project
exit 1
}
# ==============================================================================
# 4. Bitstream Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 3/5: Bitstream Generation"
puts "================================================================"
set bit_start [clock seconds]
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
set bit_elapsed [expr {[clock seconds] - $bit_start}]
puts " Bitstream time: ${bit_elapsed}s"
# Copy bitstream to known location
set bit_src "${project_dir}/aeris10_radar.runs/impl_1/${top_module}.bit"
if {[file exists $bit_src]} {
file copy -force $bit_src "${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Size: [file size $bit_src] bytes"
} else {
puts " WARNING: Bitstream file not found at $bit_src"
}
# ==============================================================================
# 5. Comprehensive Report Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 4/5: Report Generation (15-point checklist)"
puts "================================================================"
# Open the routed design for reporting
open_run impl_1 -name impl_1
# --- Checklist Item 2: Timing Summary ---
puts " [2/15] Timing Summary..."
report_timing_summary -delay_type min_max -max_paths 100 \
-report_unconstrained \
-file "${report_dir}/02_timing_summary.rpt"
# --- Checklist Item 3: Clock Analysis ---
puts " [3/15] Clock Analysis..."
report_clocks -file "${report_dir}/03_clocks.rpt"
report_clock_interaction -delay_type min_max \
-file "${report_dir}/03_clock_interaction.rpt"
report_clock_networks -file "${report_dir}/03_clock_networks.rpt"
# --- Checklist Item 4: Utilization ---
puts " [4/15] Utilization..."
report_utilization -file "${report_dir}/04_utilization.rpt"
report_utilization -hierarchical -file "${report_dir}/04_utilization_hierarchical.rpt"
# --- Checklist Item 5: Power ---
puts " [5/15] Power Report..."
report_power -file "${report_dir}/05_power.rpt"
# --- Checklist Item 6: DRC ---
puts " [6/15] DRC..."
report_drc -file "${report_dir}/06_drc.rpt"
# --- Checklist Item 7: IO and Constraints ---
puts " [7/15] IO Report..."
report_io -file "${report_dir}/07_io.rpt"
report_timing -from [all_inputs] -to [all_outputs] -max_paths 20 \
-file "${report_dir}/07_io_timing.rpt"
# --- Checklist Item 8: Congestion Analysis ---
puts " [8/15] Congestion Analysis..."
report_design_analysis -congestion -file "${report_dir}/08_congestion.rpt"
# --- Checklist Item 9: Route Status ---
puts " [9/15] Route Status..."
report_route_status -file "${report_dir}/09_route_status.rpt"
# --- Checklist Item 10: Critical Paths ---
puts " [10/15] Critical Paths..."
report_timing -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_setup.rpt"
report_timing -delay_type min -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_hold.rpt"
report_high_fanout_nets -timing -load_type -max_nets 20 \
-file "${report_dir}/10_high_fanout_nets.rpt"
# --- Checklist Item 11: QoR Summary ---
puts " [11/15] QoR Summary..."
report_design_analysis -timing -file "${report_dir}/11_design_analysis_timing.rpt"
report_design_analysis -logic_level_distribution -file "${report_dir}/11_logic_level_dist.rpt"
report_methodology -file "${report_dir}/11_methodology.rpt"
# --- Checklist Item 12: CDC Analysis ---
puts " [12/15] CDC Analysis..."
report_cdc -details -file "${report_dir}/12_cdc.rpt"
# --- Checklist Item 13: Log Scan (captured separately in build log) ---
puts " [13/15] Log scan see build19.log"
# --- Additional reports ---
puts " [extra] Generating additional diagnostic reports..."
# report_exceptions can fail in Vivado 2025.2 — wrap in catch
if {[catch {report_exceptions -file "${report_dir}/13_exceptions.rpt"} err]} {
puts " WARNING: report_exceptions failed: $err"
puts " (Known Vivado 2025.2 issue non-critical)"
}
check_timing -verbose -file "${report_dir}/13_check_timing.rpt"
# Compile configuration summary into a single text file
set summary_fh [open "${report_dir}/00_build19_summary.txt" w]
puts $summary_fh "================================================================"
puts $summary_fh " AERIS-10 Build 19 MMCM Jitter-Cleaning (Gap 7) Summary"
puts $summary_fh "================================================================"
puts $summary_fh ""
puts $summary_fh "Build Tag: $build_tag"
puts $summary_fh "Build Timestamp: $build_timestamp"
puts $summary_fh "FPGA Part: $fpga_part"
puts $summary_fh "Top Module: $top_module"
puts $summary_fh "RTL Files: $file_count"
puts $summary_fh "Synth Status: $synth_status"
puts $summary_fh "Synth Time: ${synth_elapsed}s"
puts $summary_fh "Impl Status: $impl_status"
puts $summary_fh "Impl Time: ${impl_elapsed}s"
puts $summary_fh "Bitstream Time: ${bit_elapsed}s"
puts $summary_fh ""
# Extract key timing numbers
puts $summary_fh "--- Timing ---"
set wns [get_property STATS.WNS [current_design]]
set tns [get_property STATS.TNS [current_design]]
set whs [get_property STATS.WHS [current_design]]
set ths [get_property STATS.THS [current_design]]
set fail_ep [get_property STATS.TPWS [current_design]]
puts $summary_fh " WNS: $wns ns"
puts $summary_fh " TNS: $tns ns"
puts $summary_fh " WHS: $whs ns"
puts $summary_fh " THS: $ths ns"
puts $summary_fh ""
puts $summary_fh " Build 18 Baseline: WNS = +0.062 ns, WHS = +0.059 ns"
puts $summary_fh " Delta WNS: [expr {$wns - 0.062}] ns"
puts $summary_fh " Delta WHS: [expr {$whs - 0.059}] ns"
puts $summary_fh ""
# Extract utilization
puts $summary_fh "--- Utilization ---"
set lut_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.LUT.*}]]
set ff_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.FF.*}]]
set bram_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ BMEM.*}]]
set dsp_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ MULT.DSP.*}]]
puts $summary_fh " LUTs: $lut_used / 134600"
puts $summary_fh " FFs: $ff_used / 269200"
puts $summary_fh " BRAM: $bram_used cells"
puts $summary_fh " DSP: $dsp_used cells"
puts $summary_fh ""
puts $summary_fh " Build 18 Baseline: LUTs=6088, FFs=8946, BRAM=16, DSP=140"
puts $summary_fh ""
# Route status
set unrouted [llength [get_nets -hierarchical -filter {ROUTE_STATUS == UNROUTED}]]
puts $summary_fh "--- Route ---"
puts $summary_fh " Unrouted nets: $unrouted"
puts $summary_fh ""
# MMCM usage (new for Build 19)
puts $summary_fh "--- MMCM Usage (Gap 7) ---"
set mmcm_count [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLOCK.MMCM.*}]]
puts $summary_fh " MMCME2 used: $mmcm_count / 10"
puts $summary_fh " Expected: 1 (adc_clk_mmcm jitter cleaner)"
puts $summary_fh ""
# Bitstream
if {[file exists $bit_src]} {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " File: ${top_module}_${build_tag}.bit"
puts $summary_fh " Size: [file size $bit_src] bytes"
} else {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " WARNING: NOT GENERATED"
}
puts $summary_fh ""
# Signoff
puts $summary_fh "--- Final Signoff ---"
set signoff_pass 1
if {$wns < 0} {
puts $summary_fh " FAIL: WNS = $wns (negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WNS = $wns ns (no setup violations)"
}
if {$whs < 0} {
puts $summary_fh " FAIL: WHS = $whs (hold violation)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WHS = $whs ns (no hold violations)"
}
if {$tns != 0} {
puts $summary_fh " FAIL: TNS = $tns (total negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: TNS = 0 ns"
}
if {$unrouted > 0} {
puts $summary_fh " FAIL: $unrouted unrouted nets"
set signoff_pass 0
} else {
puts $summary_fh " PASS: All nets routed"
}
if {[file exists $bit_src]} {
puts $summary_fh " PASS: Bitstream generated"
} else {
puts $summary_fh " FAIL: No bitstream"
set signoff_pass 0
}
puts $summary_fh ""
# Timing regression check vs Build 18
if {$wns < 0.062} {
puts $summary_fh " *** WARNING: WNS REGRESSED vs Build 18 (was +0.062 ns, now $wns ns) ***"
puts $summary_fh " *** Consider reverting MMCM changes per revert-safety policy ***"
}
if {$whs < 0.059} {
puts $summary_fh " *** WARNING: WHS REGRESSED vs Build 18 (was +0.059 ns, now $whs ns) ***"
}
if {$signoff_pass} {
puts $summary_fh " *** SIGNOFF: PASS ***"
} else {
puts $summary_fh " *** SIGNOFF: FAIL ***"
}
close $summary_fh
puts " Summary written to: ${report_dir}/00_build19_summary.txt"
# ==============================================================================
# 6. SDF + Timing Netlist (for post-route simulation)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 5/5: SDF + Timing Netlist"
puts "================================================================"
write_verilog -force -mode timesim "${sim_dir}/post_impl_timesim.v"
write_sdf -force "${sim_dir}/post_impl_timesim.sdf"
close_design
open_run synth_1 -name synth_1
write_verilog -force -mode funcsim "${sim_dir}/post_synth_funcsim.v"
# ==============================================================================
# Done
# ==============================================================================
set build_total [expr {[clock seconds] - $build_start}]
set build_end [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
puts ""
puts "================================================================"
puts " BUILD 19 COMPLETE"
puts "================================================================"
puts " Started: $build_timestamp"
puts " Finished: $build_end"
puts " Total time: ${build_total}s ([expr {$build_total/60}]m [expr {$build_total%60}]s)"
puts " Synth: ${synth_elapsed}s"
puts " Impl: ${impl_elapsed}s"
puts " Bitstream: ${bit_elapsed}s"
puts " Reports: $report_dir"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " WNS: $wns ns | WHS: $whs ns | TNS: $tns ns"
puts " Build 18 baseline: WNS +0.062 | WHS +0.059"
if {$signoff_pass} {
puts " SIGNOFF: PASS"
} else {
puts " SIGNOFF: FAIL"
}
puts "================================================================"
close_project
puts "Done."
@@ -1,483 +0,0 @@
################################################################################
# build20_mmcm_creg.tcl
#
# AERIS-10 Build 20: MMCM XDC Clock-Name Fix + CIC Comb CREG Pipeline
# Target: XC7A200T-2FBG484I
# Design: radar_system_top
# Tag: v0.1.2-build18 + MMCM (Gap 7) + XDC fix + CIC CREG
#
# Changes vs Build 19:
# - FIX: adc_clk_mmcm.xdc — removed conflicting create_generated_clock
# (clk_400m_mmcm), replaced all references with Vivado auto-generated
# clk_mmcm_out0. This fixes the CDC false path that wasn't applying
# to the actual clk_mmcm_out0→clk_100m crossing (Build 19 WNS -0.011).
# - NEW: cic_decimator_4x_enhanced.v — explicit DSP48E1 for comb[0] with
# CREG=1/AREG=1/BREG=1/PREG=1. Absorbs the integrator_sampled_comb
# fabric register into DSP48 C-port pipeline, eliminating 0.643 ns
# fabric→DSP route delay (Build 18 tightest path, WNS +0.062).
#
# Expected impact:
# - WNS: should be >> +0.062 ns (CREG eliminates Build 18 critical path,
# XDC fix properly applies CDC false path)
# - DSP48E1: 140 → 142 (+2: one per CIC I/Q channel for comb_0_dsp)
# - LUT/FF: ~same (CREG replaces fabric FDREs with DSP internal registers)
#
# Generates ALL reports required for the 15-point Vivado TCL Build Report.
#
# Usage:
# vivado -mode batch -source build20_mmcm_creg.tcl \
# -log ~/PLFM_RADAR_work/vivado_project/build20.log \
# -journal ~/PLFM_RADAR_work/vivado_project/build20.jou
#
# Author: auto-generated for Jason Stone
# Date: 2026-03-19
################################################################################
# ==============================================================================
# 0. Configuration
# ==============================================================================
set project_name "aeris10_radar"
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project"
set rtl_dir "/home/jason-stone/PLFM_RADAR_work/PLFM_RADAR/9_Firmware/9_2_FPGA"
set top_module "radar_system_top"
set fpga_part "xc7a200tfbg484-2"
set report_dir "${project_dir}/reports_build20"
set sim_dir "${project_dir}/sim"
set bitstream_dir "${project_dir}/bitstream"
set build_tag "build20"
file mkdir $report_dir
file mkdir $sim_dir
file mkdir $bitstream_dir
# Record start time
set build_start [clock seconds]
set build_timestamp [clock format $build_start -format {%Y-%m-%d %H:%M:%S}]
puts "================================================================"
puts " AERIS-10 Build 20: MMCM XDC Fix + CIC CREG Pipeline"
puts " Target: $fpga_part"
puts " Top: $top_module"
puts " Reports: $report_dir"
puts " Started: $build_timestamp"
puts "================================================================"
# ==============================================================================
# 1. Project Creation + Source Files
# ==============================================================================
create_project $project_name $project_dir -part $fpga_part -force
set_property target_language Verilog [current_project]
# --- Add RTL sources ---
set rtl_files [list \
"${rtl_dir}/adc_clk_mmcm.v" \
"${rtl_dir}/ad9484_interface_400m.v" \
"${rtl_dir}/cdc_modules.v" \
"${rtl_dir}/chirp_lut_init.v" \
"${rtl_dir}/chirp_memory_loader_param.v" \
"${rtl_dir}/cic_decimator_4x_enhanced.v" \
"${rtl_dir}/dac_interface_single.v" \
"${rtl_dir}/ddc_400m.v" \
"${rtl_dir}/ddc_input_interface.v" \
"${rtl_dir}/doppler_processor.v" \
"${rtl_dir}/edge_detector.v" \
"${rtl_dir}/fft_1024_forward.v" \
"${rtl_dir}/fft_1024_inverse.v" \
"${rtl_dir}/fir_lowpass.v" \
"${rtl_dir}/frequency_matched_filter.v" \
"${rtl_dir}/latency_buffer.v" \
"${rtl_dir}/level_shifter_interface.v" \
"${rtl_dir}/lvds_to_cmos_400m.v" \
"${rtl_dir}/matched_filter_multi_segment.v" \
"${rtl_dir}/matched_filter_processing_chain.v" \
"${rtl_dir}/nco_400m_enhanced.v" \
"${rtl_dir}/plfm_chirp_controller.v" \
"${rtl_dir}/radar_mode_controller.v" \
"${rtl_dir}/radar_receiver_final.v" \
"${rtl_dir}/radar_system_top.v" \
"${rtl_dir}/radar_transmitter.v" \
"${rtl_dir}/range_bin_decimator.v" \
"${rtl_dir}/usb_data_interface.v" \
"${rtl_dir}/usb_packet_analyzer.v" \
"${rtl_dir}/xfft_32.v" \
"${rtl_dir}/fft_engine.v" \
]
set file_count 0
foreach f $rtl_files {
if {[file exists $f]} {
add_files -norecurse $f
incr file_count
} else {
puts " WARNING: RTL file not found: $f"
}
}
puts " Added $file_count RTL files"
# Add .mem files for BRAM initialization
set mem_files [glob -nocomplain "${rtl_dir}/*.mem"]
foreach f $mem_files {
add_files -norecurse $f
puts " Added MEM: [file tail $f]"
}
# Add constraints — main production XDC + MMCM supplementary XDC (FIXED)
add_files -fileset constrs_1 -norecurse "${project_dir}/synth_only.xdc"
add_files -fileset constrs_1 -norecurse "${rtl_dir}/constraints/adc_clk_mmcm.xdc"
set_property top $top_module [current_fileset]
set_property verilog_define {FFT_XPM_BRAM} [current_fileset]
# ==============================================================================
# 2. Synthesis
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 1/5: Synthesis"
puts "================================================================"
set_property STEPS.SYNTH_DESIGN.ARGS.FLATTEN_HIERARCHY rebuilt [get_runs synth_1]
set_property STEPS.SYNTH_DESIGN.ARGS.KEEP_EQUIVALENT_REGISTERS true [get_runs synth_1]
set synth_start [clock seconds]
launch_runs synth_1 -jobs 8
wait_on_run synth_1
set synth_elapsed [expr {[clock seconds] - $synth_start}]
set synth_status [get_property STATUS [get_runs synth_1]]
puts " Synthesis status: $synth_status"
puts " Synthesis time: ${synth_elapsed}s ([expr {$synth_elapsed/60}]m [expr {$synth_elapsed%60}]s)"
if {[string match "*ERROR*" $synth_status] || [string match "*FAILED*" $synth_status]} {
puts "CRITICAL: SYNTHESIS FAILED aborting build"
close_project
exit 1
}
# Post-synth timing (for comparison)
open_run synth_1 -name synth_1
report_timing_summary -delay_type min_max -max_paths 10 -file "${report_dir}/01_timing_post_synth.rpt"
report_utilization -file "${report_dir}/01_utilization_post_synth.rpt"
close_design
# ==============================================================================
# 3. Implementation (opt → place → phys_opt → route → post_route_phys_opt)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 2/5: Implementation"
puts "================================================================"
# Aggressive directives for best timing
set_property STEPS.OPT_DESIGN.ARGS.DIRECTIVE Explore [get_runs impl_1]
set_property STEPS.PLACE_DESIGN.ARGS.DIRECTIVE ExtraTimingOpt [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.ROUTE_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.IS_ENABLED true [get_runs impl_1]
set_property STEPS.POST_ROUTE_PHYS_OPT_DESIGN.ARGS.DIRECTIVE AggressiveExplore [get_runs impl_1]
set impl_start [clock seconds]
launch_runs impl_1 -jobs 8
wait_on_run impl_1
set impl_elapsed [expr {[clock seconds] - $impl_start}]
set impl_status [get_property STATUS [get_runs impl_1]]
puts " Implementation status: $impl_status"
puts " Implementation time: ${impl_elapsed}s ([expr {$impl_elapsed/60}]m [expr {$impl_elapsed%60}]s)"
if {![string match "*Complete*" $impl_status]} {
puts "CRITICAL: IMPLEMENTATION FAILED: $impl_status"
close_project
exit 1
}
# ==============================================================================
# 4. Bitstream Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 3/5: Bitstream Generation"
puts "================================================================"
set bit_start [clock seconds]
launch_runs impl_1 -to_step write_bitstream -jobs 8
wait_on_run impl_1
set bit_elapsed [expr {[clock seconds] - $bit_start}]
puts " Bitstream time: ${bit_elapsed}s"
# Copy bitstream to known location
set bit_src "${project_dir}/aeris10_radar.runs/impl_1/${top_module}.bit"
if {[file exists $bit_src]} {
file copy -force $bit_src "${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " Size: [file size $bit_src] bytes"
} else {
puts " WARNING: Bitstream file not found at $bit_src"
}
# ==============================================================================
# 5. Comprehensive Report Generation
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 4/5: Report Generation (15-point checklist)"
puts "================================================================"
# Open the routed design for reporting
open_run impl_1 -name impl_1
# --- Checklist Item 2: Timing Summary ---
puts " [2/15] Timing Summary..."
report_timing_summary -delay_type min_max -max_paths 100 \
-report_unconstrained \
-file "${report_dir}/02_timing_summary.rpt"
# --- Checklist Item 3: Clock Analysis ---
puts " [3/15] Clock Analysis..."
report_clocks -file "${report_dir}/03_clocks.rpt"
report_clock_interaction -delay_type min_max \
-file "${report_dir}/03_clock_interaction.rpt"
report_clock_networks -file "${report_dir}/03_clock_networks.rpt"
# --- Checklist Item 4: Utilization ---
puts " [4/15] Utilization..."
report_utilization -file "${report_dir}/04_utilization.rpt"
report_utilization -hierarchical -file "${report_dir}/04_utilization_hierarchical.rpt"
# --- Checklist Item 5: Power ---
puts " [5/15] Power Report..."
report_power -file "${report_dir}/05_power.rpt"
# --- Checklist Item 6: DRC ---
puts " [6/15] DRC..."
report_drc -file "${report_dir}/06_drc.rpt"
# --- Checklist Item 7: IO and Constraints ---
puts " [7/15] IO Report..."
report_io -file "${report_dir}/07_io.rpt"
report_timing -from [all_inputs] -to [all_outputs] -max_paths 20 \
-file "${report_dir}/07_io_timing.rpt"
# --- Checklist Item 8: Congestion Analysis ---
puts " [8/15] Congestion Analysis..."
report_design_analysis -congestion -file "${report_dir}/08_congestion.rpt"
# --- Checklist Item 9: Route Status ---
puts " [9/15] Route Status..."
report_route_status -file "${report_dir}/09_route_status.rpt"
# --- Checklist Item 10: Critical Paths ---
puts " [10/15] Critical Paths..."
report_timing -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_setup.rpt"
report_timing -delay_type min -max_paths 20 -sort_by slack -nworst 5 \
-file "${report_dir}/10_critical_paths_hold.rpt"
report_high_fanout_nets -timing -load_type -max_nets 20 \
-file "${report_dir}/10_high_fanout_nets.rpt"
# --- Checklist Item 11: QoR Summary ---
puts " [11/15] QoR Summary..."
report_design_analysis -timing -file "${report_dir}/11_design_analysis_timing.rpt"
report_design_analysis -logic_level_distribution -file "${report_dir}/11_logic_level_dist.rpt"
report_methodology -file "${report_dir}/11_methodology.rpt"
# --- Checklist Item 12: CDC Analysis ---
puts " [12/15] CDC Analysis..."
report_cdc -details -file "${report_dir}/12_cdc.rpt"
# --- Checklist Item 13: Log Scan (captured separately in build log) ---
puts " [13/15] Log scan see build20.log"
# --- Additional reports ---
puts " [extra] Generating additional diagnostic reports..."
# report_exceptions can fail in Vivado 2025.2 — wrap in catch
if {[catch {report_exceptions -file "${report_dir}/13_exceptions.rpt"} err]} {
puts " WARNING: report_exceptions failed: $err"
puts " (Known Vivado 2025.2 issue non-critical)"
}
check_timing -verbose -file "${report_dir}/13_check_timing.rpt"
# Compile configuration summary into a single text file
set summary_fh [open "${report_dir}/00_build20_summary.txt" w]
puts $summary_fh "================================================================"
puts $summary_fh " AERIS-10 Build 20 MMCM XDC Fix + CIC CREG Pipeline"
puts $summary_fh "================================================================"
puts $summary_fh ""
puts $summary_fh "Build Tag: $build_tag"
puts $summary_fh "Build Timestamp: $build_timestamp"
puts $summary_fh "FPGA Part: $fpga_part"
puts $summary_fh "Top Module: $top_module"
puts $summary_fh "RTL Files: $file_count"
puts $summary_fh "Synth Status: $synth_status"
puts $summary_fh "Synth Time: ${synth_elapsed}s"
puts $summary_fh "Impl Status: $impl_status"
puts $summary_fh "Impl Time: ${impl_elapsed}s"
puts $summary_fh "Bitstream Time: ${bit_elapsed}s"
puts $summary_fh ""
# Extract key timing numbers
puts $summary_fh "--- Timing ---"
set wns [get_property STATS.WNS [current_design]]
set tns [get_property STATS.TNS [current_design]]
set whs [get_property STATS.WHS [current_design]]
set ths [get_property STATS.THS [current_design]]
set fail_ep [get_property STATS.TPWS [current_design]]
puts $summary_fh " WNS: $wns ns"
puts $summary_fh " TNS: $tns ns"
puts $summary_fh " WHS: $whs ns"
puts $summary_fh " THS: $ths ns"
puts $summary_fh ""
puts $summary_fh " Build 18 Baseline: WNS = +0.062 ns, WHS = +0.059 ns"
puts $summary_fh " Build 19 (FAILED): WNS = -0.011 ns, WHS = +0.055 ns"
puts $summary_fh " Delta WNS vs B18: [expr {$wns - 0.062}] ns"
puts $summary_fh " Delta WHS vs B18: [expr {$whs - 0.059}] ns"
puts $summary_fh ""
# Extract utilization
puts $summary_fh "--- Utilization ---"
set lut_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.LUT.*}]]
set ff_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLB.FF.*}]]
set bram_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ BMEM.*}]]
set dsp_used [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ MULT.DSP.*}]]
puts $summary_fh " LUTs: $lut_used / 134600"
puts $summary_fh " FFs: $ff_used / 269200"
puts $summary_fh " BRAM: $bram_used cells"
puts $summary_fh " DSP: $dsp_used cells"
puts $summary_fh ""
puts $summary_fh " Build 18 Baseline: LUTs=6088, FFs=8946, BRAM=16, DSP=140"
puts $summary_fh " Build 19: LUTs=6093, FFs=8949, BRAM=16, DSP=140"
puts $summary_fh " Expected Build 20: DSP=142 (+2 for comb_0_dsp I/Q)"
puts $summary_fh ""
# Route status
set unrouted [llength [get_nets -hierarchical -filter {ROUTE_STATUS == UNROUTED}]]
puts $summary_fh "--- Route ---"
puts $summary_fh " Unrouted nets: $unrouted"
puts $summary_fh ""
# MMCM usage
puts $summary_fh "--- MMCM Usage (Gap 7) ---"
set mmcm_count [llength [get_cells -hierarchical -filter {PRIMITIVE_TYPE =~ CLOCK.MMCM.*}]]
puts $summary_fh " MMCME2 used: $mmcm_count / 10"
puts $summary_fh " Expected: 1 (adc_clk_mmcm jitter cleaner)"
puts $summary_fh ""
# Bitstream
if {[file exists $bit_src]} {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " File: ${top_module}_${build_tag}.bit"
puts $summary_fh " Size: [file size $bit_src] bytes"
} else {
puts $summary_fh "--- Bitstream ---"
puts $summary_fh " WARNING: NOT GENERATED"
}
puts $summary_fh ""
# Signoff
puts $summary_fh "--- Final Signoff ---"
set signoff_pass 1
if {$wns < 0} {
puts $summary_fh " FAIL: WNS = $wns (negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WNS = $wns ns (no setup violations)"
}
if {$whs < 0} {
puts $summary_fh " FAIL: WHS = $whs (hold violation)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: WHS = $whs ns (no hold violations)"
}
if {$tns != 0} {
puts $summary_fh " FAIL: TNS = $tns (total negative slack)"
set signoff_pass 0
} else {
puts $summary_fh " PASS: TNS = 0 ns"
}
if {$unrouted > 0} {
puts $summary_fh " FAIL: $unrouted unrouted nets"
set signoff_pass 0
} else {
puts $summary_fh " PASS: All nets routed"
}
if {[file exists $bit_src]} {
puts $summary_fh " PASS: Bitstream generated"
} else {
puts $summary_fh " FAIL: No bitstream"
set signoff_pass 0
}
puts $summary_fh ""
# Timing regression check vs Build 18
if {$wns < 0.062} {
puts $summary_fh " *** WARNING: WNS REGRESSED vs Build 18 (was +0.062 ns, now $wns ns) ***"
puts $summary_fh " *** Review critical paths CREG fix may not have helped ***"
}
if {$whs < 0.059} {
puts $summary_fh " *** WARNING: WHS REGRESSED vs Build 18 (was +0.059 ns, now $whs ns) ***"
}
if {$signoff_pass} {
puts $summary_fh " *** SIGNOFF: PASS ***"
} else {
puts $summary_fh " *** SIGNOFF: FAIL ***"
}
close $summary_fh
puts " Summary written to: ${report_dir}/00_build20_summary.txt"
# ==============================================================================
# 6. SDF + Timing Netlist (for post-route simulation)
# ==============================================================================
puts ""
puts "================================================================"
puts " Phase 5/5: SDF + Timing Netlist"
puts "================================================================"
write_verilog -force -mode timesim "${sim_dir}/post_impl_timesim.v"
write_sdf -force "${sim_dir}/post_impl_timesim.sdf"
close_design
open_run synth_1 -name synth_1
write_verilog -force -mode funcsim "${sim_dir}/post_synth_funcsim.v"
# ==============================================================================
# Done
# ==============================================================================
set build_total [expr {[clock seconds] - $build_start}]
set build_end [clock format [clock seconds] -format {%Y-%m-%d %H:%M:%S}]
puts ""
puts "================================================================"
puts " BUILD 20 COMPLETE"
puts "================================================================"
puts " Started: $build_timestamp"
puts " Finished: $build_end"
puts " Total time: ${build_total}s ([expr {$build_total/60}]m [expr {$build_total%60}]s)"
puts " Synth: ${synth_elapsed}s"
puts " Impl: ${impl_elapsed}s"
puts " Bitstream: ${bit_elapsed}s"
puts " Reports: $report_dir"
puts " Bitstream: ${bitstream_dir}/${top_module}_${build_tag}.bit"
puts " WNS: $wns ns | WHS: $whs ns | TNS: $tns ns"
puts " Build 18 baseline: WNS +0.062 | WHS +0.059"
puts " Build 19 (failed): WNS -0.011 | WHS +0.055"
if {$signoff_pass} {
puts " SIGNOFF: PASS"
} else {
puts " SIGNOFF: FAIL"
}
puts "================================================================"
close_project
puts "Done."
@@ -11,7 +11,7 @@
# pins and placeholder LED/status pins. # pins and placeholder LED/status pins.
set script_dir [file dirname [file normalize [info script]]] set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir ".."]] set project_root [file normalize [file join $script_dir "../.."]]
set project_name "aeris10_te0712_dev" set project_name "aeris10_te0712_dev"
set build_dir [file join $project_root "vivado_te0712_dev"] set build_dir [file join $project_root "vivado_te0712_dev"]
@@ -6,7 +6,7 @@
# vivado -mode batch -source scripts/build_te0713_dev.tcl # vivado -mode batch -source scripts/build_te0713_dev.tcl
set script_dir [file dirname [file normalize [info script]]] set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir ".."]] set project_root [file normalize [file join $script_dir "../.."]]
set project_name "aeris10_te0713_dev" set project_name "aeris10_te0713_dev"
set build_dir [file join $project_root "vivado_te0713_dev"] set build_dir [file join $project_root "vivado_te0713_dev"]
@@ -3,7 +3,7 @@
# Vivado batch build for Trenz TE0713/TE0701 with UMFT601X-B over FMC LPC. # Vivado batch build for Trenz TE0713/TE0701 with UMFT601X-B over FMC LPC.
set script_dir [file dirname [file normalize [info script]]] set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir ".."]] set project_root [file normalize [file join $script_dir "../.."]]
set project_name "aeris10_te0713_umft601x_dev" set project_name "aeris10_te0713_umft601x_dev"
set build_dir [file join $project_root "vivado_te0713_umft601x_dev"] set build_dir [file join $project_root "vivado_te0713_umft601x_dev"]
@@ -34,7 +34,10 @@
set default_server "localhost" set default_server "localhost"
set default_port 3121 set default_port 3121
set default_ltx "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.ltx" set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir "../.."]]
set default_ltx [file join $project_root "build" "aeris10_radar.runs" "impl_ila" "radar_system_top.ltx"]
set default_output_base [file join $project_root "build" "captures"]
set default_depth 4096 set default_depth 4096
set default_timeout 30 set default_timeout 30
@@ -108,7 +111,7 @@ proc log_kv {key value} {
proc parse_args {} { proc parse_args {} {
global argc argv global argc argv
global default_server default_port default_ltx default_depth default_timeout global default_server default_port default_ltx default_output_base default_depth default_timeout
global hw_server_host hw_server_port probes_path capture_depth trigger_timeout global hw_server_host hw_server_port probes_path capture_depth trigger_timeout
global capture_scenario use_immediate output_dir global capture_scenario use_immediate output_dir
@@ -185,7 +188,7 @@ proc parse_args {} {
# Auto-generate timestamped output directory if not specified # Auto-generate timestamped output directory if not specified
if {$output_dir eq ""} { if {$output_dir eq ""} {
set timestamp [clock format [clock seconds] -format {%Y%m%d_%H%M%S}] set timestamp [clock format [clock seconds] -format {%Y%m%d_%H%M%S}]
set output_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project/captures/ila_${capture_scenario}_${timestamp}" set output_dir [file join $default_output_base "ila_${capture_scenario}_${timestamp}"]
} }
} }
@@ -32,9 +32,11 @@
# 0. Configuration all paths and parameters in one place # 0. Configuration all paths and parameters in one place
# ============================================================================== # ==============================================================================
set project_base "/home/jason-stone/PLFM_RADAR_work/vivado_project" set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir "../.."]]
set project_base [file join $project_root "build"]
set synth_dcp "${project_base}/aeris10_radar.runs/synth_1/radar_system_top.dcp" set synth_dcp "${project_base}/aeris10_radar.runs/synth_1/radar_system_top.dcp"
set synth_xdc "${project_base}/synth_only.xdc" set synth_xdc [file join $project_root "constraints" "xc7a200t_fbg484.xdc"]
set output_dir "${project_base}/aeris10_radar.runs/impl_ila" set output_dir "${project_base}/aeris10_radar.runs/impl_ila"
set top_module "radar_system_top" set top_module "radar_system_top"
set part "xc7a200tfbg484-2" set part "xc7a200tfbg484-2"
@@ -25,8 +25,10 @@
set default_server "localhost" set default_server "localhost"
set default_port 3121 set default_port 3121
set default_bit "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.bit" set script_dir [file dirname [file normalize [info script]]]
set default_ltx "/home/jason-stone/PLFM_RADAR_work/vivado_project/bitstream/radar_system_top.ltx" set project_root [file normalize [file join $script_dir "../.."]]
set default_bit [file join $project_root "build" "bitstream" "radar_system_top_build21.bit"]
set default_ltx [file join $project_root "build" "aeris10_radar.runs" "impl_ila" "radar_system_top.ltx"]
set expected_part "xc7a200t" set expected_part "xc7a200t"
set expected_pkg "fbg484" set expected_pkg "fbg484"
@@ -5,8 +5,11 @@
# #
# Usage: vivado -mode batch -source run_cdc_and_netlist.tcl # Usage: vivado -mode batch -source run_cdc_and_netlist.tcl
set project_dir "/home/jason-stone/PLFM_RADAR_work/vivado_project" set script_dir [file dirname [file normalize [info script]]]
set project_root [file normalize [file join $script_dir "../.."]]
set project_dir [file join $project_root "build"]
set report_dir "${project_dir}/reports_impl" set report_dir "${project_dir}/reports_impl"
file mkdir $report_dir
# Open the routed checkpoint # Open the routed checkpoint
open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp open_checkpoint ${project_dir}/aeris10_radar.runs/impl_1/radar_system_top_routed.dcp
@@ -76,23 +76,20 @@ FFT_DATA_W = 16
FFT_INTERNAL_W = 32 FFT_INTERNAL_W = 32
FFT_TWIDDLE_W = 16 FFT_TWIDDLE_W = 16
# Doppler # Doppler — dual 16-pt FFT architecture
DOPPLER_FFT_SIZE = 32 DOPPLER_FFT_SIZE = 16 # per sub-frame
DOPPLER_TOTAL_BINS = 32 # total output (2 sub-frames x 16)
DOPPLER_RANGE_BINS = 64 DOPPLER_RANGE_BINS = 64
DOPPLER_CHIRPS = 32 DOPPLER_CHIRPS = 32
CHIRPS_PER_SUBFRAME = 16
DOPPLER_WINDOW_TYPE = 0 # Hamming DOPPLER_WINDOW_TYPE = 0 # Hamming
# Hamming window coefficients from doppler_processor.v (Q15) # 16-point Hamming window coefficients from doppler_processor.v (Q15)
HAMMING_Q15 = [ HAMMING_Q15 = [
0x0800, 0x0862, 0x09CB, 0x0C3B, 0x0A3D, 0x0E5C, 0x1B6D, 0x3088,
0x0FB2, 0x142F, 0x19B2, 0x2039, 0x4B33, 0x6573, 0x7642, 0x7F62,
0x27C4, 0x3050, 0x39DB, 0x4462, 0x7F62, 0x7642, 0x6573, 0x4B33,
0x4FE3, 0x5C5A, 0x69C4, 0x781D, 0x3088, 0x1B6D, 0x0E5C, 0x0A3D,
0x7FFF, # Peak
0x781D, 0x69C4, 0x5C5A, 0x4FE3,
0x4462, 0x39DB, 0x3050, 0x27C4,
0x2039, 0x19B2, 0x142F, 0x0FB2,
0x0C3B, 0x09CB, 0x0862,
] ]
# ADI dataset parameters # ADI dataset parameters
@@ -652,83 +649,85 @@ def run_range_bin_decimator(range_fft_i, range_fft_q,
# =========================================================================== # ===========================================================================
# Stage 3: Doppler FFT (32-point with Hamming window, bit-accurate) # Stage 3: Doppler FFT (dual 16-point with Hamming window, bit-accurate)
# =========================================================================== # ===========================================================================
def run_doppler_fft(range_data_i, range_data_q, twiddle_file_32=None): def run_doppler_fft(range_data_i, range_data_q, twiddle_file_16=None):
""" """
Bit-accurate Doppler processor matching doppler_processor.v. Bit-accurate Doppler processor matching doppler_processor.v (dual 16-pt FFT).
Input: range_data_i/q shape (DOPPLER_CHIRPS, FFT_SIZE) — 16-bit signed Input: range_data_i/q shape (DOPPLER_CHIRPS, FFT_SIZE) — 16-bit signed
Only first DOPPLER_RANGE_BINS columns are processed. Only first DOPPLER_RANGE_BINS columns are processed.
Output: doppler_map_i/q shape (DOPPLER_RANGE_BINS, DOPPLER_FFT_SIZE) — 16-bit signed Output: doppler_map_i/q shape (DOPPLER_RANGE_BINS, DOPPLER_TOTAL_BINS) — 16-bit signed
Pipeline per range bin: Architecture per range bin:
1. Read 32 chirps for this range bin Sub-frame 0 (long PRI): chirps 0..15 → 16-pt Hamming → 16-pt FFT → bins 0-15
2. Apply Hamming window (Q15 multiply + round >>> 15) Sub-frame 1 (short PRI): chirps 16..31 → 16-pt Hamming → 16-pt FFT → bins 16-31
3. 32-point FFT
""" """
n_chirps = DOPPLER_CHIRPS n_chirps = DOPPLER_CHIRPS
n_range = DOPPLER_RANGE_BINS n_range = DOPPLER_RANGE_BINS
n_fft = DOPPLER_FFT_SIZE n_fft = DOPPLER_FFT_SIZE
n_total = DOPPLER_TOTAL_BINS
n_sf = CHIRPS_PER_SUBFRAME
print(f"[DOPPLER] Processing {n_range} range bins x {n_chirps} chirps → {n_fft}-point FFT") print(f"[DOPPLER] Processing {n_range} range bins x {n_chirps} chirps → dual {n_fft}-point FFT")
# Build Hamming window as signed 16-bit # Build 16-point Hamming window as signed 16-bit
hamming = np.array([int(v) for v in HAMMING_Q15], dtype=np.int64) hamming = np.array([int(v) for v in HAMMING_Q15], dtype=np.int64)
assert len(hamming) == n_fft, f"Hamming length {len(hamming)} != {n_fft}" assert len(hamming) == n_fft, f"Hamming length {len(hamming)} != {n_fft}"
# Build 32-point twiddle factors # Build 16-point twiddle factors
if twiddle_file_32 and os.path.exists(twiddle_file_32): if twiddle_file_16 and os.path.exists(twiddle_file_16):
cos_rom_32 = load_twiddle_rom(twiddle_file_32) cos_rom_16 = load_twiddle_rom(twiddle_file_16)
else: else:
cos_rom_32 = np.round(32767 * np.cos(2 * np.pi * np.arange(n_fft // 4) / n_fft)).astype(np.int64) cos_rom_16 = np.round(32767 * np.cos(2 * np.pi * np.arange(n_fft // 4) / n_fft)).astype(np.int64)
doppler_map_i = np.zeros((n_range, n_fft), dtype=np.int64) LOG2N_16 = 4
doppler_map_q = np.zeros((n_range, n_fft), dtype=np.int64) doppler_map_i = np.zeros((n_range, n_total), dtype=np.int64)
doppler_map_q = np.zeros((n_range, n_total), dtype=np.int64)
for rbin in range(n_range): for rbin in range(n_range):
# Extract chirp stack for this range bin
chirp_i = np.zeros(n_chirps, dtype=np.int64) chirp_i = np.zeros(n_chirps, dtype=np.int64)
chirp_q = np.zeros(n_chirps, dtype=np.int64) chirp_q = np.zeros(n_chirps, dtype=np.int64)
for c in range(n_chirps): for c in range(n_chirps):
chirp_i[c] = int(range_data_i[c, rbin]) chirp_i[c] = int(range_data_i[c, rbin])
chirp_q[c] = int(range_data_q[c, rbin]) chirp_q[c] = int(range_data_q[c, rbin])
# Apply Hamming window (Q15 multiply with rounding) # Process each sub-frame independently
for sf in range(2):
chirp_start = sf * n_sf
bin_offset = sf * n_fft
windowed_i = np.zeros(n_fft, dtype=np.int64) windowed_i = np.zeros(n_fft, dtype=np.int64)
windowed_q = np.zeros(n_fft, dtype=np.int64) windowed_q = np.zeros(n_fft, dtype=np.int64)
for k in range(n_fft): for k in range(n_fft):
# 16-bit x 16-bit = 32-bit, then round and shift >>> 15 ci = chirp_i[chirp_start + k]
mult_i = chirp_i[k] * hamming[k] cq = chirp_q[chirp_start + k]
mult_q = chirp_q[k] * hamming[k] mult_i = ci * hamming[k]
mult_q = cq * hamming[k]
windowed_i[k] = saturate((mult_i + (1 << 14)) >> 15, 16) windowed_i[k] = saturate((mult_i + (1 << 14)) >> 15, 16)
windowed_q[k] = saturate((mult_q + (1 << 14)) >> 15, 16) windowed_q[k] = saturate((mult_q + (1 << 14)) >> 15, 16)
# 32-point FFT (same algorithm as range FFT, different N)
LOG2N_32 = 5
mem_re = np.zeros(n_fft, dtype=np.int64) mem_re = np.zeros(n_fft, dtype=np.int64)
mem_im = np.zeros(n_fft, dtype=np.int64) mem_im = np.zeros(n_fft, dtype=np.int64)
# Bit-reversed loading, sign-extend to 32-bit
for n in range(n_fft): for n in range(n_fft):
br = 0 br = 0
for b in range(LOG2N_32): for b in range(LOG2N_16):
if n & (1 << b): if n & (1 << b):
br |= (1 << (LOG2N_32 - 1 - b)) br |= (1 << (LOG2N_16 - 1 - b))
mem_re[br] = windowed_i[n] mem_re[br] = windowed_i[n]
mem_im[br] = windowed_q[n] mem_im[br] = windowed_q[n]
# Butterfly stages
half = 1 half = 1
for stg in range(LOG2N_32): for stg in range(LOG2N_16):
for bfly in range(n_fft // 2): for bfly in range(n_fft // 2):
idx = bfly & (half - 1) idx = bfly & (half - 1)
grp = bfly - idx grp = bfly - idx
addr_even = (grp << 1) | idx addr_even = (grp << 1) | idx
addr_odd = addr_even + half addr_odd = addr_even + half
tw_idx = (idx << (LOG2N_32 - 1 - stg)) % (n_fft // 2) tw_idx = (idx << (LOG2N_16 - 1 - stg)) % (n_fft // 2)
tw_cos, tw_sin = fft_twiddle_lookup(tw_idx, n_fft, cos_rom_32) tw_cos, tw_sin = fft_twiddle_lookup(tw_idx, n_fft, cos_rom_16)
a_re = mem_re[addr_even] a_re = mem_re[addr_even]
a_im = mem_im[addr_even] a_im = mem_im[addr_even]
@@ -748,12 +747,11 @@ def run_doppler_fft(range_data_i, range_data_q, twiddle_file_32=None):
half <<= 1 half <<= 1
# Saturate 32-bit → 16-bit
for n in range(n_fft): for n in range(n_fft):
doppler_map_i[rbin, n] = saturate(mem_re[n], 16) doppler_map_i[rbin, bin_offset + n] = saturate(mem_re[n], 16)
doppler_map_q[rbin, n] = saturate(mem_im[n], 16) doppler_map_q[rbin, bin_offset + n] = saturate(mem_im[n], 16)
print(f" Doppler map: shape ({n_range}, {n_fft}), " print(f" Doppler map: shape ({n_range}, {n_total}), "
f"I range [{doppler_map_i.min()}, {doppler_map_i.max()}]") f"I range [{doppler_map_i.min()}, {doppler_map_i.max()}]")
return doppler_map_i, doppler_map_q return doppler_map_i, doppler_map_q
@@ -821,23 +819,24 @@ def run_dc_notch(doppler_i, doppler_q, width=2):
Input: doppler_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed Input: doppler_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed
Output: notched_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed Output: notched_i/q — shape (NUM_RANGE_BINS, NUM_DOPPLER_BINS), 16-bit signed
Zeros Doppler bins within ±width of DC (bin 0). Zeros Doppler bins within ±width of DC for BOTH sub-frames.
In a 32-point FFT, DC is bin 0; negative Doppler wraps to bins 31,30,... doppler_bin[4:0] = {sub_frame, bin[3:0]}:
Sub-frame 0: bins 0-15, DC = bin 0, wrap = bin 15
Sub-frame 1: bins 16-31, DC = bin 16, wrap = bin 31
width=0: pass-through width=0: pass-through
width=1: zero bins {0} width=1: zero bins {0, 16}
width=2: zero bins {0, 1, 31} width=2: zero bins {0, 1, 15, 16, 17, 31} etc.
width=3: zero bins {0, 1, 2, 30, 31} etc.
RTL logic (from radar_system_top.v lines 517-524): RTL logic (from radar_system_top.v):
bin_within_sf = dop_bin[3:0]
dc_notch_active = (width != 0) && dc_notch_active = (width != 0) &&
(dop_bin < width || dop_bin > (31 - width + 1)) (bin_within_sf < width || bin_within_sf > (15 - width + 1))
notched_data = dc_notch_active ? 0 : doppler_data
""" """
n_range, n_doppler = doppler_i.shape n_range, n_doppler = doppler_i.shape
notched_i = doppler_i.copy() notched_i = doppler_i.copy()
notched_q = doppler_q.copy() notched_q = doppler_q.copy()
print(f"[DC NOTCH] width={width}, {n_range} range bins x {n_doppler} Doppler bins") print(f"[DC NOTCH] width={width}, {n_range} range bins x {n_doppler} Doppler bins (dual sub-frame)")
if width == 0: if width == 0:
print(f" Pass-through (width=0)") print(f" Pass-through (width=0)")
@@ -845,9 +844,8 @@ def run_dc_notch(doppler_i, doppler_q, width=2):
zeroed_count = 0 zeroed_count = 0
for dbin in range(n_doppler): for dbin in range(n_doppler):
# Replicate RTL comparison (unsigned 5-bit): bin_within_sf = dbin & 0xF
# dop_bin < width OR dop_bin > (31 - width + 1) active = (bin_within_sf < width) or (bin_within_sf > (15 - width + 1))
active = (dbin < width) or (dbin > (31 - width + 1))
if active: if active:
notched_i[:, dbin] = 0 notched_i[:, dbin] = 0
notched_q[:, dbin] = 0 notched_q[:, dbin] = 0
@@ -1049,11 +1047,15 @@ def run_float_reference(iq_i, iq_q):
n_range = min(DOPPLER_RANGE_BINS, n_samples) n_range = min(DOPPLER_RANGE_BINS, n_samples)
hamming_float = np.array(HAMMING_Q15, dtype=np.float64) / 32768.0 hamming_float = np.array(HAMMING_Q15, dtype=np.float64) / 32768.0
doppler_map = np.zeros((n_range, DOPPLER_FFT_SIZE), dtype=np.complex128) doppler_map = np.zeros((n_range, DOPPLER_TOTAL_BINS), dtype=np.complex128)
for rbin in range(n_range): for rbin in range(n_range):
chirp_stack = range_fft[:DOPPLER_CHIRPS, rbin] chirp_stack = range_fft[:DOPPLER_CHIRPS, rbin]
windowed = chirp_stack * hamming_float for sf in range(2):
doppler_map[rbin, :] = np.fft.fft(windowed) sf_start = sf * CHIRPS_PER_SUBFRAME
sf_end = sf_start + CHIRPS_PER_SUBFRAME
bin_offset = sf * DOPPLER_FFT_SIZE
windowed = chirp_stack[sf_start:sf_end] * hamming_float
doppler_map[rbin, bin_offset:bin_offset + DOPPLER_FFT_SIZE] = np.fft.fft(windowed)
return range_fft, doppler_map return range_fft, doppler_map
@@ -1235,10 +1237,10 @@ def main():
# Run Doppler FFT (bit-accurate) — "direct" path (first 64 bins) # Run Doppler FFT (bit-accurate) — "direct" path (first 64 bins)
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
print(f"\n{'=' * 72}") print(f"\n{'=' * 72}")
print("Stage 3: Doppler FFT (32-point with Hamming window)") print("Stage 3: Doppler FFT (dual 16-point with Hamming window)")
print(" [direct path: first 64 range bins, no decimation]") print(" [direct path: first 64 range bins, no decimation]")
twiddle_32 = os.path.join(fpga_dir, "fft_twiddle_32.mem") twiddle_16 = os.path.join(fpga_dir, "fft_twiddle_16.mem")
doppler_i, doppler_q = run_doppler_fft(all_range_i, all_range_q, twiddle_file_32=twiddle_32) doppler_i, doppler_q = run_doppler_fft(all_range_i, all_range_q, twiddle_file_16=twiddle_16)
write_hex_files(output_dir, doppler_i, doppler_q, "doppler_map") write_hex_files(output_dir, doppler_i, doppler_q, "doppler_map")
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
@@ -1276,7 +1278,7 @@ def main():
print(f"\n{'=' * 72}") print(f"\n{'=' * 72}")
print("Stage 3b: Doppler FFT on decimated data (full-chain path)") print("Stage 3b: Doppler FFT on decimated data (full-chain path)")
fc_doppler_i, fc_doppler_q = run_doppler_fft( fc_doppler_i, fc_doppler_q = run_doppler_fft(
decim_i, decim_q, twiddle_file_32=twiddle_32 decim_i, decim_q, twiddle_file_16=twiddle_16
) )
write_hex_files(output_dir, fc_doppler_i, fc_doppler_q, "fullchain_doppler_ref") write_hex_files(output_dir, fc_doppler_i, fc_doppler_q, "fullchain_doppler_ref")
@@ -1284,12 +1286,12 @@ def main():
fc_doppler_packed_file = os.path.join(output_dir, "fullchain_doppler_ref_packed.hex") fc_doppler_packed_file = os.path.join(output_dir, "fullchain_doppler_ref_packed.hex")
with open(fc_doppler_packed_file, 'w') as f: with open(fc_doppler_packed_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
i_val = int(fc_doppler_i[rbin, dbin]) & 0xFFFF i_val = int(fc_doppler_i[rbin, dbin]) & 0xFFFF
q_val = int(fc_doppler_q[rbin, dbin]) & 0xFFFF q_val = int(fc_doppler_q[rbin, dbin]) & 0xFFFF
packed = (q_val << 16) | i_val packed = (q_val << 16) | i_val
f.write(f"{packed:08X}\n") f.write(f"{packed:08X}\n")
print(f" Wrote {fc_doppler_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} packed IQ words)") print(f" Wrote {fc_doppler_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)")
# Save numpy arrays for the full-chain path # Save numpy arrays for the full-chain path
np.save(os.path.join(output_dir, "decimated_range_i.npy"), decim_i) np.save(os.path.join(output_dir, "decimated_range_i.npy"), decim_i)
@@ -1313,7 +1315,7 @@ def main():
print(f"\n{'=' * 72}") print(f"\n{'=' * 72}")
print("Stage 3b+c: Doppler FFT on MTI-filtered decimated data") print("Stage 3b+c: Doppler FFT on MTI-filtered decimated data")
mti_doppler_i, mti_doppler_q = run_doppler_fft( mti_doppler_i, mti_doppler_q = run_doppler_fft(
mti_i, mti_q, twiddle_file_32=twiddle_32 mti_i, mti_q, twiddle_file_16=twiddle_16
) )
write_hex_files(output_dir, mti_doppler_i, mti_doppler_q, "fullchain_mti_doppler_ref") write_hex_files(output_dir, mti_doppler_i, mti_doppler_q, "fullchain_mti_doppler_ref")
np.save(os.path.join(output_dir, "fullchain_mti_doppler_i.npy"), mti_doppler_i) np.save(os.path.join(output_dir, "fullchain_mti_doppler_i.npy"), mti_doppler_i)
@@ -1330,12 +1332,12 @@ def main():
fc_notched_packed_file = os.path.join(output_dir, "fullchain_notched_ref_packed.hex") fc_notched_packed_file = os.path.join(output_dir, "fullchain_notched_ref_packed.hex")
with open(fc_notched_packed_file, 'w') as f: with open(fc_notched_packed_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
i_val = int(notched_i[rbin, dbin]) & 0xFFFF i_val = int(notched_i[rbin, dbin]) & 0xFFFF
q_val = int(notched_q[rbin, dbin]) & 0xFFFF q_val = int(notched_q[rbin, dbin]) & 0xFFFF
packed = (q_val << 16) | i_val packed = (q_val << 16) | i_val
f.write(f"{packed:08X}\n") f.write(f"{packed:08X}\n")
print(f" Wrote {fc_notched_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} packed IQ words)") print(f" Wrote {fc_notched_packed_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} packed IQ words)")
# CFAR on DC-notched data # CFAR on DC-notched data
CFAR_GUARD = 2 CFAR_GUARD = 2
@@ -1355,28 +1357,28 @@ def main():
cfar_mag_file = os.path.join(output_dir, "fullchain_cfar_mag.hex") cfar_mag_file = os.path.join(output_dir, "fullchain_cfar_mag.hex")
with open(cfar_mag_file, 'w') as f: with open(cfar_mag_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
m = int(cfar_mag[rbin, dbin]) & 0x1FFFF m = int(cfar_mag[rbin, dbin]) & 0x1FFFF
f.write(f"{m:05X}\n") f.write(f"{m:05X}\n")
print(f" Wrote {cfar_mag_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} mag values)") print(f" Wrote {cfar_mag_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} mag values)")
# 2. Threshold map (17-bit unsigned) # 2. Threshold map (17-bit unsigned)
cfar_thr_file = os.path.join(output_dir, "fullchain_cfar_thr.hex") cfar_thr_file = os.path.join(output_dir, "fullchain_cfar_thr.hex")
with open(cfar_thr_file, 'w') as f: with open(cfar_thr_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
t = int(cfar_thr[rbin, dbin]) & 0x1FFFF t = int(cfar_thr[rbin, dbin]) & 0x1FFFF
f.write(f"{t:05X}\n") f.write(f"{t:05X}\n")
print(f" Wrote {cfar_thr_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} threshold values)") print(f" Wrote {cfar_thr_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} threshold values)")
# 3. Detection flags (1-bit per cell) # 3. Detection flags (1-bit per cell)
cfar_det_file = os.path.join(output_dir, "fullchain_cfar_det.hex") cfar_det_file = os.path.join(output_dir, "fullchain_cfar_det.hex")
with open(cfar_det_file, 'w') as f: with open(cfar_det_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
d = 1 if cfar_flags[rbin, dbin] else 0 d = 1 if cfar_flags[rbin, dbin] else 0
f.write(f"{d:01X}\n") f.write(f"{d:01X}\n")
print(f" Wrote {cfar_det_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} detection flags)") print(f" Wrote {cfar_det_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} detection flags)")
# 4. Detection list (text) # 4. Detection list (text)
cfar_detections = np.argwhere(cfar_flags) cfar_detections = np.argwhere(cfar_flags)
@@ -1416,10 +1418,10 @@ def main():
fc_det_mag_file = os.path.join(output_dir, "fullchain_detection_mag.hex") fc_det_mag_file = os.path.join(output_dir, "fullchain_detection_mag.hex")
with open(fc_det_mag_file, 'w') as f: with open(fc_det_mag_file, 'w') as f:
for rbin in range(DOPPLER_RANGE_BINS): for rbin in range(DOPPLER_RANGE_BINS):
for dbin in range(DOPPLER_FFT_SIZE): for dbin in range(DOPPLER_TOTAL_BINS):
m = int(fc_mag[rbin, dbin]) & 0x1FFFF # 17-bit unsigned m = int(fc_mag[rbin, dbin]) & 0x1FFFF # 17-bit unsigned
f.write(f"{m:05X}\n") f.write(f"{m:05X}\n")
print(f" Wrote {fc_det_mag_file} ({DOPPLER_RANGE_BINS * DOPPLER_FFT_SIZE} magnitude values)") print(f" Wrote {fc_det_mag_file} ({DOPPLER_RANGE_BINS * DOPPLER_TOTAL_BINS} magnitude values)")
# ----------------------------------------------------------------------- # -----------------------------------------------------------------------
# Run detection on direct-path Doppler map (for backward compatibility) # Run detection on direct-path Doppler map (for backward compatibility)
@@ -30,7 +30,7 @@ T_LONG_CHIRP = 30e-6 # 30 us long chirp
T_SHORT_CHIRP = 0.5e-6 # 0.5 us short chirp T_SHORT_CHIRP = 0.5e-6 # 0.5 us short chirp
CIC_DECIMATION = 4 CIC_DECIMATION = 4
FFT_SIZE = 1024 FFT_SIZE = 1024
DOPPLER_FFT_SIZE = 32 DOPPLER_FFT_SIZE = 16
LONG_CHIRP_SAMPLES = int(T_LONG_CHIRP * FS_SYS) # 3000 at 100 MHz LONG_CHIRP_SAMPLES = int(T_LONG_CHIRP * FS_SYS) # 3000 at 100 MHz
# Overlap-save parameters # Overlap-save parameters
@@ -84,7 +84,7 @@ def test_structural():
expected = { expected = {
# FFT twiddle files (quarter-wave cosine ROMs) # FFT twiddle files (quarter-wave cosine ROMs)
'fft_twiddle_1024.mem': {'lines': 256, 'desc': '1024-pt FFT quarter-wave cos ROM'}, 'fft_twiddle_1024.mem': {'lines': 256, 'desc': '1024-pt FFT quarter-wave cos ROM'},
'fft_twiddle_32.mem': {'lines': 8, 'desc': '32-pt FFT quarter-wave cos ROM'}, 'fft_twiddle_16.mem': {'lines': 4, 'desc': '16-pt FFT quarter-wave cos ROM'},
# Long chirp segments (4 segments x 1024 samples each) # Long chirp segments (4 segments x 1024 samples each)
'long_chirp_seg0_i.mem': {'lines': 1024, 'desc': 'Long chirp seg 0 I'}, 'long_chirp_seg0_i.mem': {'lines': 1024, 'desc': 'Long chirp seg 0 I'},
'long_chirp_seg0_q.mem': {'lines': 1024, 'desc': 'Long chirp seg 0 Q'}, 'long_chirp_seg0_q.mem': {'lines': 1024, 'desc': 'Long chirp seg 0 Q'},
@@ -145,13 +145,13 @@ def test_twiddle_1024():
print(f" Max twiddle error: {max_err} LSB across {len(vals)} entries") print(f" Max twiddle error: {max_err} LSB across {len(vals)} entries")
def test_twiddle_32(): def test_twiddle_16():
print("\n=== TEST 2b: FFT Twiddle 32 Validation ===") print("\n=== TEST 2b: FFT Twiddle 16 Validation ===")
vals = read_mem_hex('fft_twiddle_32.mem') vals = read_mem_hex('fft_twiddle_16.mem')
max_err = 0 max_err = 0
for k in range(min(8, len(vals))): for k in range(min(4, len(vals))):
angle = 2.0 * math.pi * k / 32.0 angle = 2.0 * math.pi * k / 16.0
expected = int(round(math.cos(angle) * 32767.0)) expected = int(round(math.cos(angle) * 32767.0))
expected = max(-32768, min(32767, expected)) expected = max(-32768, min(32767, expected))
actual = vals[k] actual = vals[k]
@@ -160,13 +160,13 @@ def test_twiddle_32():
max_err = err max_err = err
check(max_err <= 1, check(max_err <= 1,
f"fft_twiddle_32.mem: max twiddle error = {max_err} LSB (tolerance: 1)") f"fft_twiddle_16.mem: max twiddle error = {max_err} LSB (tolerance: 1)")
print(f" Max twiddle error: {max_err} LSB across {len(vals)} entries") print(f" Max twiddle error: {max_err} LSB across {len(vals)} entries")
# Print all 8 entries for reference # Print all 4 entries for reference
print(" Twiddle 32 entries:") print(" Twiddle 16 entries:")
for k in range(min(8, len(vals))): for k in range(min(4, len(vals))):
angle = 2.0 * math.pi * k / 32.0 angle = 2.0 * math.pi * k / 16.0
expected = int(round(math.cos(angle) * 32767.0)) expected = int(round(math.cos(angle) * 32767.0))
print(f" k={k}: file=0x{vals[k] & 0xFFFF:04x} ({vals[k]:6d}), " print(f" k={k}: file=0x{vals[k] & 0xFFFF:04x} ({vals[k]:6d}), "
f"expected=0x{expected & 0xFFFF:04x} ({expected:6d}), " f"expected=0x{expected & 0xFFFF:04x} ({expected:6d}), "
@@ -605,7 +605,7 @@ def main():
test_structural() test_structural()
test_twiddle_1024() test_twiddle_1024()
test_twiddle_32() test_twiddle_16()
test_long_chirp() test_long_chirp()
test_short_chirp() test_short_chirp()
test_chirp_vs_model() test_chirp_vs_model()
File diff suppressed because it is too large Load Diff
+1 -1
View File
@@ -619,7 +619,7 @@ initial begin
// Optional: dump specific signals for debugging // Optional: dump specific signals for debugging
$dumpvars(1, dut.tx_inst); $dumpvars(1, dut.tx_inst);
$dumpvars(1, dut.rx_inst); $dumpvars(1, dut.rx_inst);
$dumpvars(1, dut.usb_inst); $dumpvars(1, dut.gen_ft601.usb_inst);
end end
endmodule endmodule
+6 -6
View File
@@ -6,8 +6,8 @@
* *
* Tests the complete Doppler processing pipeline: * Tests the complete Doppler processing pipeline:
* - Accumulates 32 chirps x 64 range bins into BRAM * - Accumulates 32 chirps x 64 range bins into BRAM
* - Processes each range bin: Hamming window -> 32-pt FFT * - Processes each range bin: Hamming window -> dual 16-pt FFT (staggered PRF)
* - Outputs 2048 samples (64 range bins x 32 Doppler bins) * - Outputs 2048 samples (64 range bins x 32 packed Doppler bins)
* *
* Validates: * Validates:
* 1. FSM state transitions (IDLE -> ACCUMULATE -> LOAD_FFT -> ... -> OUTPUT) * 1. FSM state transitions (IDLE -> ACCUMULATE -> LOAD_FFT -> ... -> OUTPUT)
@@ -20,10 +20,10 @@
* RTL output written to: tb/cosim/rtl_doppler_<scenario>.csv * RTL output written to: tb/cosim/rtl_doppler_<scenario>.csv
* RTL FFT inputs written: tb/cosim/rtl_doppler_fft_in_<scenario>.csv * RTL FFT inputs written: tb/cosim/rtl_doppler_fft_in_<scenario>.csv
* *
* Compile (SIMULATION branch uses behavioral xfft_32/fft_engine): * Compile (SIMULATION branch uses behavioral xfft_16/fft_engine):
* iverilog -g2001 -DSIMULATION \ * iverilog -g2001 -DSIMULATION \
* -o tb/tb_doppler_cosim.vvp \ * -o tb/tb_doppler_cosim.vvp \
* tb/tb_doppler_cosim.v doppler_processor.v xfft_32.v fft_engine.v * tb/tb_doppler_cosim.v doppler_processor.v xfft_16.v fft_engine.v
* *
* Scenarios (use -D flags): * Scenarios (use -D flags):
* default: stationary target * default: stationary target
@@ -37,7 +37,7 @@ module tb_doppler_cosim;
// Parameters // Parameters
// ============================================================================ // ============================================================================
localparam CLK_PERIOD = 10.0; // 100 MHz localparam CLK_PERIOD = 10.0; // 100 MHz
localparam DOPPLER_FFT = 32; localparam DOPPLER_FFT = 32; // Total packed Doppler bins (2 sub-frames x 16-pt FFT)
localparam RANGE_BINS = 64; localparam RANGE_BINS = 64;
localparam CHIRPS = 32; localparam CHIRPS = 32;
localparam TOTAL_INPUTS = CHIRPS * RANGE_BINS; // 2048 localparam TOTAL_INPUTS = CHIRPS * RANGE_BINS; // 2048
@@ -193,7 +193,7 @@ initial begin
$display("Doppler Processor Co-Sim Testbench"); $display("Doppler Processor Co-Sim Testbench");
$display("Scenario: %0s", SCENARIO); $display("Scenario: %0s", SCENARIO);
$display("Input samples: %0d (32 chirps x 64 range bins)", TOTAL_INPUTS); $display("Input samples: %0d (32 chirps x 64 range bins)", TOTAL_INPUTS);
$display("Expected outputs: %0d (64 range bins x 32 doppler bins)", $display("Expected outputs: %0d (64 range bins x 32 packed Doppler bins, dual 16-pt FFT)",
TOTAL_OUTPUTS); TOTAL_OUTPUTS);
$display("============================================================"); $display("============================================================");
+2 -2
View File
@@ -17,7 +17,7 @@
* Compile: * Compile:
* iverilog -Wall -DSIMULATION -g2012 \ * iverilog -Wall -DSIMULATION -g2012 \
* -o tb/tb_doppler_realdata.vvp \ * -o tb/tb_doppler_realdata.vvp \
* tb/tb_doppler_realdata.v doppler_processor.v xfft_32.v fft_engine.v * tb/tb_doppler_realdata.v doppler_processor.v xfft_16.v fft_engine.v
* *
* Run from: 9_Firmware/9_2_FPGA/ * Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_doppler_realdata.vvp * vvp tb/tb_doppler_realdata.vvp
@@ -29,7 +29,7 @@ module tb_doppler_realdata;
// PARAMETERS // PARAMETERS
// ============================================================================ // ============================================================================
localparam CLK_PERIOD = 10.0; // 100 MHz localparam CLK_PERIOD = 10.0; // 100 MHz
localparam DOPPLER_FFT = 32; localparam DOPPLER_FFT = 32; // Total packed Doppler bins (2 sub-frames x 16-pt FFT)
localparam RANGE_BINS = 64; localparam RANGE_BINS = 64;
localparam CHIRPS = 32; localparam CHIRPS = 32;
localparam TOTAL_INPUTS = CHIRPS * RANGE_BINS; // 2048 localparam TOTAL_INPUTS = CHIRPS * RANGE_BINS; // 2048
+5 -5
View File
@@ -4,7 +4,7 @@
* tb_fft_engine.v * tb_fft_engine.v
* *
* Testbench for the synthesizable FFT engine. * Testbench for the synthesizable FFT engine.
* Tests with N=32 first (fast), then validates key properties. * Tests with N=16 (matching the dual-16 Doppler architecture).
* *
* Test Groups: * Test Groups:
* 1. Impulse response: FFT of delta[0] should be all 1s * 1. Impulse response: FFT of delta[0] should be all 1s
@@ -19,10 +19,10 @@
module tb_fft_engine; module tb_fft_engine;
// ============================================================================ // ============================================================================
// PARAMETERS test with 32-pt for speed // PARAMETERS test with 16-pt to match dual-FFT Doppler architecture
// ============================================================================ // ============================================================================
localparam N = 32; localparam N = 16;
localparam LOG2N = 5; localparam LOG2N = 4;
localparam DATA_W = 16; localparam DATA_W = 16;
localparam INT_W = 32; localparam INT_W = 32;
localparam TW_W = 16; localparam TW_W = 16;
@@ -47,7 +47,7 @@ fft_engine #(
.DATA_W(DATA_W), .DATA_W(DATA_W),
.INTERNAL_W(INT_W), .INTERNAL_W(INT_W),
.TWIDDLE_W(TW_W), .TWIDDLE_W(TW_W),
.TWIDDLE_FILE("fft_twiddle_32.mem") .TWIDDLE_FILE("fft_twiddle_16.mem")
) dut ( ) dut (
.clk(clk), .clk(clk),
.reset_n(reset_n), .reset_n(reset_n),
@@ -9,7 +9,7 @@
* *
* range_bin_decimator (peak detection, 1024->64) * range_bin_decimator (peak detection, 1024->64)
* -> mti_canceller (2-pulse, mti_enable=1) * -> mti_canceller (2-pulse, mti_enable=1)
* -> doppler_processor_optimized (Hamming + 32-pt FFT) * -> doppler_processor_optimized (Hamming + dual 16-pt FFT)
* -> DC notch filter (width=2, inline logic) * -> DC notch filter (width=2, inline logic)
* -> cfar_ca (CA mode, guard=2, train=8, alpha=0x30) * -> cfar_ca (CA mode, guard=2, train=8, alpha=0x30)
* *
@@ -41,7 +41,7 @@
* -o tb/tb_fullchain_mti_cfar_realdata.vvp \ * -o tb/tb_fullchain_mti_cfar_realdata.vvp \
* tb/tb_fullchain_mti_cfar_realdata.v \ * tb/tb_fullchain_mti_cfar_realdata.v \
* range_bin_decimator.v mti_canceller.v doppler_processor.v \ * range_bin_decimator.v mti_canceller.v doppler_processor.v \
* xfft_32.v fft_engine.v cfar_ca.v * xfft_16.v fft_engine.v cfar_ca.v
* *
* Run from: 9_Firmware/9_2_FPGA/ * Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_fullchain_mti_cfar_realdata.vvp * vvp tb/tb_fullchain_mti_cfar_realdata.vvp
@@ -375,7 +375,7 @@ initial begin
$display(" Full-Chain Real-Data Co-Simulation (MTI + CFAR)"); $display(" Full-Chain Real-Data Co-Simulation (MTI + CFAR)");
$display(" range_bin_decimator (peak, 1024->64)"); $display(" range_bin_decimator (peak, 1024->64)");
$display(" -> mti_canceller (2-pulse, enable=1)"); $display(" -> mti_canceller (2-pulse, enable=1)");
$display(" -> doppler_processor_optimized (Hamming + 32-pt FFT)"); $display(" -> doppler_processor_optimized (Hamming + dual 16-pt FFT)");
$display(" -> DC notch filter (width=%0d)", DC_NOTCH_WIDTH); $display(" -> DC notch filter (width=%0d)", DC_NOTCH_WIDTH);
$display(" -> cfar_ca (CA, guard=2, train=8, alpha=0x30)"); $display(" -> cfar_ca (CA, guard=2, train=8, alpha=0x30)");
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW"); $display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
@@ -7,7 +7,7 @@
* (post-range-FFT, 32 chirps x 1024 bins) through: * (post-range-FFT, 32 chirps x 1024 bins) through:
* *
* range_bin_decimator (peak detection, 102464) * range_bin_decimator (peak detection, 102464)
* doppler_processor_optimized (Hamming + 32-pt FFT) * doppler_processor_optimized (Hamming + dual 16-pt FFT)
* *
* and compares the Doppler output bit-for-bit against the Python golden * and compares the Doppler output bit-for-bit against the Python golden
* reference that models the same chain (golden_reference.py). * reference that models the same chain (golden_reference.py).
@@ -27,7 +27,7 @@
* iverilog -Wall -DSIMULATION -g2012 \ * iverilog -Wall -DSIMULATION -g2012 \
* -o tb/tb_fullchain_realdata.vvp \ * -o tb/tb_fullchain_realdata.vvp \
* tb/tb_fullchain_realdata.v \ * tb/tb_fullchain_realdata.v \
* range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v * range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v
* *
* Run from: 9_Firmware/9_2_FPGA/ * Run from: 9_Firmware/9_2_FPGA/
* vvp tb/tb_fullchain_realdata.vvp * vvp tb/tb_fullchain_realdata.vvp
@@ -243,7 +243,7 @@ initial begin
$display("============================================================"); $display("============================================================");
$display(" Full-Chain Real-Data Co-Simulation"); $display(" Full-Chain Real-Data Co-Simulation");
$display(" range_bin_decimator (peak, 1024->64)"); $display(" range_bin_decimator (peak, 1024->64)");
$display(" -> doppler_processor_optimized (Hamming + 32-pt FFT)"); $display(" -> doppler_processor_optimized (Hamming + dual 16-pt FFT)");
$display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW"); $display(" ADI CN0566 Phaser 10.525 GHz X-band FMCW");
$display(" Input: %0d chirps x %0d range FFT bins = %0d samples", $display(" Input: %0d chirps x %0d range FFT bins = %0d samples",
CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES); CHIRPS, INPUT_BINS, TOTAL_INPUT_SAMPLES);
+1 -1
View File
@@ -34,7 +34,7 @@
* cdc_modules.v fir_lowpass.v ddc_input_interface.v \ * cdc_modules.v fir_lowpass.v ddc_input_interface.v \
* chirp_memory_loader_param.v latency_buffer.v \ * chirp_memory_loader_param.v latency_buffer.v \
* matched_filter_multi_segment.v matched_filter_processing_chain.v \ * matched_filter_multi_segment.v matched_filter_processing_chain.v \
* range_bin_decimator.v doppler_processor.v xfft_32.v fft_engine.v \ * range_bin_decimator.v doppler_processor.v xfft_16.v fft_engine.v \
* usb_data_interface.v edge_detector.v radar_mode_controller.v * usb_data_interface.v edge_detector.v radar_mode_controller.v
* *
* Run: * Run:
-355
View File
@@ -1,355 +0,0 @@
`timescale 1ns / 1ps
/**
* tb_xfft_32.v
*
* Testbench for xfft_32 AXI-Stream FFT wrapper.
* Verifies the wrapper correctly interfaces with fft_engine via AXI-Stream.
*
* Test Groups:
* 1. Impulse response (all output bins = input amplitude)
* 2. DC input (bin 0 = A*N, rest ~= 0)
* 3. Single tone detection
* 4. AXI-Stream handshake correctness (tvalid, tlast, tready)
* 5. Back-to-back transforms (no state leakage)
*/
module tb_xfft_32;
// ============================================================================
// PARAMETERS
// ============================================================================
localparam N = 32;
localparam CLK_PERIOD = 10;
// ============================================================================
// SIGNALS
// ============================================================================
reg aclk, aresetn;
reg [7:0] cfg_tdata;
reg cfg_tvalid;
wire cfg_tready;
reg [31:0] din_tdata;
reg din_tvalid;
reg din_tlast;
wire [31:0] dout_tdata;
wire dout_tvalid;
wire dout_tlast;
reg dout_tready;
// ============================================================================
// DUT
// ============================================================================
xfft_32 dut (
.aclk(aclk),
.aresetn(aresetn),
.s_axis_config_tdata(cfg_tdata),
.s_axis_config_tvalid(cfg_tvalid),
.s_axis_config_tready(cfg_tready),
.s_axis_data_tdata(din_tdata),
.s_axis_data_tvalid(din_tvalid),
.s_axis_data_tlast(din_tlast),
.m_axis_data_tdata(dout_tdata),
.m_axis_data_tvalid(dout_tvalid),
.m_axis_data_tlast(dout_tlast),
.m_axis_data_tready(dout_tready)
);
// ============================================================================
// CLOCK
// ============================================================================
initial aclk = 0;
always #(CLK_PERIOD/2) aclk = ~aclk;
// ============================================================================
// PASS/FAIL TRACKING
// ============================================================================
integer pass_count, fail_count;
task check;
input cond;
input [512*8-1:0] label;
begin
if (cond) begin
$display(" [PASS] %0s", label);
pass_count = pass_count + 1;
end else begin
$display(" [FAIL] %0s", label);
fail_count = fail_count + 1;
end
end
endtask
// ============================================================================
// OUTPUT CAPTURE
// ============================================================================
reg signed [15:0] out_re [0:N-1];
reg signed [15:0] out_im [0:N-1];
integer out_idx;
reg got_tlast;
integer tlast_count;
// ============================================================================
// HELPER TASKS
// ============================================================================
task do_reset;
begin
aresetn = 0;
cfg_tdata = 0;
cfg_tvalid = 0;
din_tdata = 0;
din_tvalid = 0;
din_tlast = 0;
dout_tready = 1;
repeat(5) @(posedge aclk);
aresetn = 1;
repeat(2) @(posedge aclk);
end
endtask
// Send config (forward FFT: tdata[0]=1)
// Waits for cfg_tready (wrapper in S_IDLE) before sending
task send_config;
input [7:0] cfg;
integer wait_cnt;
begin
// Wait for wrapper to be ready (S_IDLE)
wait_cnt = 0;
while (!cfg_tready && wait_cnt < 5000) begin
@(posedge aclk);
wait_cnt = wait_cnt + 1;
end
cfg_tdata = cfg;
cfg_tvalid = 1;
@(posedge aclk);
cfg_tvalid = 0;
cfg_tdata = 0;
end
endtask
// Feed N samples: each sample is {im[15:0], re[15:0]}
// in_re_arr and in_im_arr must be pre-loaded
reg signed [15:0] feed_re [0:N-1];
reg signed [15:0] feed_im [0:N-1];
task feed_data;
integer i;
begin
for (i = 0; i < N; i = i + 1) begin
din_tdata = {feed_im[i], feed_re[i]};
din_tvalid = 1;
din_tlast = (i == N - 1) ? 1 : 0;
@(posedge aclk);
end
din_tvalid = 0;
din_tlast = 0;
din_tdata = 0;
end
endtask
// Capture N output samples
task capture_output;
integer timeout;
begin
out_idx = 0;
got_tlast = 0;
tlast_count = 0;
timeout = 0;
while (out_idx < N && timeout < 5000) begin
@(posedge aclk);
if (dout_tvalid && dout_tready) begin
out_re[out_idx] = dout_tdata[15:0];
out_im[out_idx] = dout_tdata[31:16];
if (dout_tlast) begin
got_tlast = 1;
tlast_count = tlast_count + 1;
end
out_idx = out_idx + 1;
end
timeout = timeout + 1;
end
end
endtask
// ============================================================================
// VCD
// ============================================================================
initial begin
$dumpfile("tb_xfft_32.vcd");
$dumpvars(0, tb_xfft_32);
end
// ============================================================================
// MAIN TEST
// ============================================================================
integer i;
reg signed [31:0] err;
integer max_err;
integer max_mag_bin;
reg signed [31:0] max_mag, mag;
real angle;
initial begin
pass_count = 0;
fail_count = 0;
$display("============================================================");
$display(" xfft_32 AXI-Stream Wrapper Testbench");
$display("============================================================");
do_reset;
// ================================================================
// TEST 1: Impulse Response
// ================================================================
$display("");
$display("--- Test 1: Impulse Response ---");
for (i = 0; i < N; i = i + 1) begin
feed_re[i] = (i == 0) ? 16'sd1000 : 16'sd0;
feed_im[i] = 16'sd0;
end
send_config(8'h01); // Forward FFT
feed_data;
capture_output;
check(out_idx == N, "Received N output samples");
check(got_tlast == 1, "Got tlast on output");
max_err = 0;
for (i = 0; i < N; i = i + 1) begin
err = out_re[i] - 1000;
if (err < 0) err = -err;
if (err > max_err) max_err = err;
err = out_im[i];
if (err < 0) err = -err;
if (err > max_err) max_err = err;
end
$display(" Impulse max error: %0d", max_err);
check(max_err < 10, "Impulse: all bins ~= 1000");
// ================================================================
// TEST 2: DC Input
// ================================================================
$display("");
$display("--- Test 2: DC Input ---");
for (i = 0; i < N; i = i + 1) begin
feed_re[i] = 16'sd100;
feed_im[i] = 16'sd0;
end
send_config(8'h01);
feed_data;
capture_output;
$display(" DC bin[0] = %0d + j%0d (expect ~3200)", out_re[0], out_im[0]);
check(out_re[0] >= 3100 && out_re[0] <= 3300, "DC: bin 0 ~= 3200 (5% tol)");
max_err = 0;
for (i = 1; i < N; i = i + 1) begin
err = out_re[i]; if (err < 0) err = -err;
if (err > max_err) max_err = err;
err = out_im[i]; if (err < 0) err = -err;
if (err > max_err) max_err = err;
end
$display(" DC max non-DC: %0d", max_err);
check(max_err < 25, "DC: non-DC bins ~= 0");
// ================================================================
// TEST 3: Single Tone (bin 4)
// ================================================================
$display("");
$display("--- Test 3: Single Tone (bin 4) ---");
for (i = 0; i < N; i = i + 1) begin
angle = 6.28318530718 * 4.0 * i / 32.0;
feed_re[i] = $rtoi($cos(angle) * 1000.0);
feed_im[i] = 16'sd0;
end
send_config(8'h01);
feed_data;
capture_output;
max_mag = 0;
max_mag_bin = 0;
for (i = 0; i < N; i = i + 1) begin
mag = out_re[i] * out_re[i] + out_im[i] * out_im[i];
if (mag > max_mag) begin
max_mag = mag;
max_mag_bin = i;
end
end
$display(" Tone peak bin: %0d (expect 4 or 28)", max_mag_bin);
check(max_mag_bin == 4 || max_mag_bin == 28, "Tone: peak at bin 4 or 28");
// ================================================================
// TEST 4: Back-to-back transforms
// ================================================================
$display("");
$display("--- Test 4: Back-to-Back Transforms ---");
// First: impulse
for (i = 0; i < N; i = i + 1) begin
feed_re[i] = (i == 0) ? 16'sd500 : 16'sd0;
feed_im[i] = 16'sd0;
end
send_config(8'h01);
feed_data;
capture_output;
check(out_idx == N, "Back-to-back 1st: got N outputs");
// Second: DC immediately after
for (i = 0; i < N; i = i + 1) begin
feed_re[i] = 16'sd50;
feed_im[i] = 16'sd0;
end
send_config(8'h01);
feed_data;
capture_output;
check(out_idx == N, "Back-to-back 2nd: got N outputs");
$display(" 2nd transform bin[0] = %0d (expect ~1600)", out_re[0]);
check(out_re[0] >= 1500 && out_re[0] <= 1700, "Back-to-back 2nd: bin 0 ~= 1600");
// ================================================================
// TEST 5: Zero input
// ================================================================
$display("");
$display("--- Test 5: Zero Input ---");
for (i = 0; i < N; i = i + 1) begin
feed_re[i] = 16'sd0;
feed_im[i] = 16'sd0;
end
send_config(8'h01);
feed_data;
capture_output;
max_err = 0;
for (i = 0; i < N; i = i + 1) begin
err = out_re[i]; if (err < 0) err = -err;
if (err > max_err) max_err = err;
err = out_im[i]; if (err < 0) err = -err;
if (err > max_err) max_err = err;
end
check(max_err == 0, "Zero input: all outputs = 0");
// ================================================================
// SUMMARY
// ================================================================
$display("");
$display("============================================================");
$display(" RESULTS: %0d/%0d passed", pass_count, pass_count + fail_count);
if (fail_count == 0)
$display(" ALL TESTS PASSED");
else
$display(" SOME TESTS FAILED");
$display("============================================================");
$finish;
end
endmodule
@@ -0,0 +1,539 @@
`timescale 1ns / 1ps
/**
* usb_data_interface_ft2232h.v
*
* FT2232H USB 2.0 Hi-Speed FIFO Interface (245 Synchronous FIFO Mode)
* Channel A only 8-bit data bus, 60 MHz CLKOUT from FT2232H.
*
* This module is the 50T production board equivalent of usb_data_interface.v
* (FT601, 32-bit, USB 3.0). Both share the same internal interface signals
* so they can be swapped via a generate block in radar_system_top.v.
*
* Data packet (FPGAHost): 11 bytes
* Byte 0: 0xAA (header)
* Bytes 1-4: range_profile[31:0] = {range_q[15:0], range_i[15:0]} MSB first
* Bytes 5-6: doppler_real[15:0] MSB first
* Bytes 7-8: doppler_imag[15:0] MSB first
* Byte 9: {7'b0, cfar_detection}
* Byte 10: 0x55 (footer)
*
* Status packet (FPGAHost): 26 bytes
* Byte 0: 0xBB (status header)
* Bytes 1-24: 6 × 32-bit status words, MSB first
* Byte 25: 0x55 (footer)
*
* Command (HostFPGA): 4 bytes received sequentially
* Byte 0: opcode[7:0]
* Byte 1: addr[7:0]
* Byte 2: value[15:8]
* Byte 3: value[7:0]
*
* CDC: Toggle CDC (not level sync) for all valid pulse crossings from
* 100 MHz 60 MHz. Toggle CDC is guaranteed to work regardless of
* clock frequency ratio.
*
* Clock domains:
* clk = 100 MHz system clock (radar data domain)
* ft_clk = 60 MHz from FT2232H CLKOUT (USB FIFO domain)
*/
module usb_data_interface_ft2232h (
input wire clk, // Main clock (100 MHz)
input wire reset_n, // System reset (clk domain)
input wire ft_reset_n, // FT2232H-domain synchronized reset
// Radar data inputs (clk domain)
input wire [31:0] range_profile,
input wire range_valid,
input wire [15:0] doppler_real,
input wire [15:0] doppler_imag,
input wire doppler_valid,
input wire cfar_detection,
input wire cfar_valid,
// FT2232H Physical Interface (245 Synchronous FIFO mode)
inout wire [7:0] ft_data, // 8-bit bidirectional data bus
input wire ft_rxf_n, // Receive FIFO not empty (active low)
input wire ft_txe_n, // Transmit FIFO not full (active low)
output reg ft_rd_n, // Read strobe (active low)
output reg ft_wr_n, // Write strobe (active low)
output reg ft_oe_n, // Output enable (active low) bus direction
output reg ft_siwu, // Send Immediate / WakeUp
// Clock from FT2232H (directly used no ODDR forwarding needed)
input wire ft_clk, // 60 MHz from FT2232H CLKOUT
// Host command outputs (ft_clk domain CDC'd by consumer)
output reg [31:0] cmd_data,
output reg cmd_valid,
output reg [7:0] cmd_opcode,
output reg [7:0] cmd_addr,
output reg [15:0] cmd_value,
// Stream control input (clk domain, CDC'd internally)
input wire [2:0] stream_control,
// Status readback inputs (clk domain, CDC'd internally)
input wire status_request,
input wire [15:0] status_cfar_threshold,
input wire [2:0] status_stream_ctrl,
input wire [1:0] status_radar_mode,
input wire [15:0] status_long_chirp,
input wire [15:0] status_long_listen,
input wire [15:0] status_guard,
input wire [15:0] status_short_chirp,
input wire [15:0] status_short_listen,
input wire [5:0] status_chirps_per_elev,
input wire [1:0] status_range_mode,
// Self-test status readback
input wire [4:0] status_self_test_flags,
input wire [7:0] status_self_test_detail,
input wire status_self_test_busy
);
// ============================================================================
// PACKET FORMAT CONSTANTS
// ============================================================================
localparam HEADER = 8'hAA;
localparam FOOTER = 8'h55;
localparam STATUS_HEADER = 8'hBB;
// Data packet: 11 bytes total
localparam DATA_PKT_LEN = 5'd11;
// Status packet: 26 bytes total (1 header + 24 data + 1 footer)
localparam STATUS_PKT_LEN = 5'd26;
// ============================================================================
// WRITE FSM STATES (FPGA Host)
// ============================================================================
localparam [2:0] WR_IDLE = 3'd0,
WR_DATA_SEND = 3'd1,
WR_STATUS_SEND = 3'd2,
WR_DONE = 3'd3;
reg [2:0] wr_state;
reg [4:0] wr_byte_idx; // Byte counter within packet (0..10 data, 0..25 status)
// ============================================================================
// READ FSM STATES (Host FPGA)
// ============================================================================
localparam [2:0] RD_IDLE = 3'd0,
RD_OE_ASSERT = 3'd1,
RD_READING = 3'd2,
RD_DEASSERT = 3'd3,
RD_PROCESS = 3'd4;
reg [2:0] rd_state;
reg [1:0] rd_byte_cnt; // 0..3 for 4-byte command word
reg [31:0] rd_shift_reg; // Shift register to assemble 4-byte command
// ============================================================================
// DATA BUS DIRECTION CONTROL
// ============================================================================
reg [7:0] ft_data_out;
reg ft_data_oe; // 1 = FPGA drives bus, 0 = FT2232H drives bus
assign ft_data = ft_data_oe ? ft_data_out : 8'hZZ;
// ============================================================================
// TOGGLE CDC: clk (100 MHz) ft_clk (60 MHz)
// ============================================================================
// Toggle CDC is used instead of level synchronizers because a 10 ns pulse
// on clk_100m could be missed by the 16.67 ns ft_clk period. Toggle CDC
// converts pulses to level transitions, which are always captured.
// --- Toggle registers (clk domain) ---
reg range_valid_toggle;
reg doppler_valid_toggle;
reg cfar_valid_toggle;
reg status_req_toggle;
// --- Holding registers (clk domain) ---
// Data captured on valid pulse, held stable for ft_clk domain to read
reg [31:0] range_profile_hold;
reg [15:0] doppler_real_hold;
reg [15:0] doppler_imag_hold;
reg cfar_detection_hold;
always @(posedge clk or negedge reset_n) begin
if (!reset_n) begin
range_valid_toggle <= 1'b0;
doppler_valid_toggle <= 1'b0;
cfar_valid_toggle <= 1'b0;
status_req_toggle <= 1'b0;
range_profile_hold <= 32'd0;
doppler_real_hold <= 16'd0;
doppler_imag_hold <= 16'd0;
cfar_detection_hold <= 1'b0;
end else begin
if (range_valid) begin
range_valid_toggle <= ~range_valid_toggle;
range_profile_hold <= range_profile;
end
if (doppler_valid) begin
doppler_valid_toggle <= ~doppler_valid_toggle;
doppler_real_hold <= doppler_real;
doppler_imag_hold <= doppler_imag;
end
if (cfar_valid) begin
cfar_valid_toggle <= ~cfar_valid_toggle;
cfar_detection_hold <= cfar_detection;
end
if (status_request)
status_req_toggle <= ~status_req_toggle;
end
end
// --- 3-stage synchronizers (ft_clk domain) ---
// 3 stages for better MTBF at 60 MHz
(* ASYNC_REG = "TRUE" *) reg [2:0] range_toggle_sync;
(* ASYNC_REG = "TRUE" *) reg [2:0] doppler_toggle_sync;
(* ASYNC_REG = "TRUE" *) reg [2:0] cfar_toggle_sync;
(* ASYNC_REG = "TRUE" *) reg [2:0] status_toggle_sync;
reg range_toggle_prev;
reg doppler_toggle_prev;
reg cfar_toggle_prev;
reg status_toggle_prev;
// Edge-detected pulses in ft_clk domain
wire range_valid_ft = range_toggle_sync[2] ^ range_toggle_prev;
wire doppler_valid_ft = doppler_toggle_sync[2] ^ doppler_toggle_prev;
wire cfar_valid_ft = cfar_toggle_sync[2] ^ cfar_toggle_prev;
wire status_req_ft = status_toggle_sync[2] ^ status_toggle_prev;
// --- Stream control CDC (per-bit 2-stage, changes infrequently) ---
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_0;
(* ASYNC_REG = "TRUE" *) reg [2:0] stream_ctrl_sync_1;
wire stream_range_en = stream_ctrl_sync_1[0];
wire stream_doppler_en = stream_ctrl_sync_1[1];
wire stream_cfar_en = stream_ctrl_sync_1[2];
// --- Captured data in ft_clk domain ---
reg [31:0] range_profile_cap;
reg [15:0] doppler_real_cap;
reg [15:0] doppler_imag_cap;
reg cfar_detection_cap;
// Data-pending flags (ft_clk domain)
reg doppler_data_pending;
reg cfar_data_pending;
// Status snapshot (ft_clk domain)
reg [31:0] status_words [0:5];
integer si; // status_words loop index
always @(posedge ft_clk or negedge ft_reset_n) begin
if (!ft_reset_n) begin
range_toggle_sync <= 3'b000;
doppler_toggle_sync <= 3'b000;
cfar_toggle_sync <= 3'b000;
status_toggle_sync <= 3'b000;
range_toggle_prev <= 1'b0;
doppler_toggle_prev <= 1'b0;
cfar_toggle_prev <= 1'b0;
status_toggle_prev <= 1'b0;
range_profile_cap <= 32'd0;
doppler_real_cap <= 16'd0;
doppler_imag_cap <= 16'd0;
cfar_detection_cap <= 1'b0;
// Default to range-only on reset (prevents write FSM deadlock)
stream_ctrl_sync_0 <= 3'b001;
stream_ctrl_sync_1 <= 3'b001;
// Explicit reset for status_words to avoid Synth 8-7137
for (si = 0; si < 6; si = si + 1)
status_words[si] <= 32'd0;
end else begin
// 3-stage toggle synchronizers
range_toggle_sync <= {range_toggle_sync[1:0], range_valid_toggle};
doppler_toggle_sync <= {doppler_toggle_sync[1:0], doppler_valid_toggle};
cfar_toggle_sync <= {cfar_toggle_sync[1:0], cfar_valid_toggle};
status_toggle_sync <= {status_toggle_sync[1:0], status_req_toggle};
// Previous toggle value for edge detection
range_toggle_prev <= range_toggle_sync[2];
doppler_toggle_prev <= doppler_toggle_sync[2];
cfar_toggle_prev <= cfar_toggle_sync[2];
status_toggle_prev <= status_toggle_sync[2];
// Stream control CDC (2-stage)
stream_ctrl_sync_0 <= stream_control;
stream_ctrl_sync_1 <= stream_ctrl_sync_0;
// Capture data on toggle edge
if (range_valid_ft)
range_profile_cap <= range_profile_hold;
if (doppler_valid_ft) begin
doppler_real_cap <= doppler_real_hold;
doppler_imag_cap <= doppler_imag_hold;
end
if (cfar_valid_ft)
cfar_detection_cap <= cfar_detection_hold;
// Status snapshot on request
if (status_req_ft) begin
status_words[0] <= {8'hFF, 3'b000, status_radar_mode,
5'b00000, status_stream_ctrl,
status_cfar_threshold};
status_words[1] <= {status_long_chirp, status_long_listen};
status_words[2] <= {status_guard, status_short_chirp};
status_words[3] <= {status_short_listen, 10'd0, status_chirps_per_elev};
status_words[4] <= {30'd0, status_range_mode};
status_words[5] <= {7'd0, status_self_test_busy,
8'd0, status_self_test_detail,
3'd0, status_self_test_flags};
end
end
end
// ============================================================================
// WRITE DATA MUX byte selection for data packet (11 bytes)
// ============================================================================
// Mux-based byte selection is simpler than a shift register and gives
// explicit byte ordering for synthesis.
reg [7:0] data_pkt_byte;
always @(*) begin
case (wr_byte_idx)
5'd0: data_pkt_byte = HEADER;
5'd1: data_pkt_byte = range_profile_cap[31:24]; // range MSB
5'd2: data_pkt_byte = range_profile_cap[23:16];
5'd3: data_pkt_byte = range_profile_cap[15:8];
5'd4: data_pkt_byte = range_profile_cap[7:0]; // range LSB
5'd5: data_pkt_byte = doppler_real_cap[15:8]; // doppler_real MSB
5'd6: data_pkt_byte = doppler_real_cap[7:0]; // doppler_real LSB
5'd7: data_pkt_byte = doppler_imag_cap[15:8]; // doppler_imag MSB
5'd8: data_pkt_byte = doppler_imag_cap[7:0]; // doppler_imag LSB
5'd9: data_pkt_byte = {7'b0, cfar_detection_cap}; // detection
5'd10: data_pkt_byte = FOOTER;
default: data_pkt_byte = 8'h00;
endcase
end
// ============================================================================
// WRITE DATA MUX byte selection for status packet (26 bytes)
// ============================================================================
reg [7:0] status_pkt_byte;
always @(*) begin
case (wr_byte_idx)
5'd0: status_pkt_byte = STATUS_HEADER;
// Word 0 (bytes 1-4)
5'd1: status_pkt_byte = status_words[0][31:24];
5'd2: status_pkt_byte = status_words[0][23:16];
5'd3: status_pkt_byte = status_words[0][15:8];
5'd4: status_pkt_byte = status_words[0][7:0];
// Word 1 (bytes 5-8)
5'd5: status_pkt_byte = status_words[1][31:24];
5'd6: status_pkt_byte = status_words[1][23:16];
5'd7: status_pkt_byte = status_words[1][15:8];
5'd8: status_pkt_byte = status_words[1][7:0];
// Word 2 (bytes 9-12)
5'd9: status_pkt_byte = status_words[2][31:24];
5'd10: status_pkt_byte = status_words[2][23:16];
5'd11: status_pkt_byte = status_words[2][15:8];
5'd12: status_pkt_byte = status_words[2][7:0];
// Word 3 (bytes 13-16)
5'd13: status_pkt_byte = status_words[3][31:24];
5'd14: status_pkt_byte = status_words[3][23:16];
5'd15: status_pkt_byte = status_words[3][15:8];
5'd16: status_pkt_byte = status_words[3][7:0];
// Word 4 (bytes 17-20)
5'd17: status_pkt_byte = status_words[4][31:24];
5'd18: status_pkt_byte = status_words[4][23:16];
5'd19: status_pkt_byte = status_words[4][15:8];
5'd20: status_pkt_byte = status_words[4][7:0];
// Word 5 (bytes 21-24)
5'd21: status_pkt_byte = status_words[5][31:24];
5'd22: status_pkt_byte = status_words[5][23:16];
5'd23: status_pkt_byte = status_words[5][15:8];
5'd24: status_pkt_byte = status_words[5][7:0];
// Footer (byte 25)
5'd25: status_pkt_byte = FOOTER;
default: status_pkt_byte = 8'h00;
endcase
end
// ============================================================================
// MAIN FSM (ft_clk domain)
// ============================================================================
// Write FSM and Read FSM share the bus. Write FSM operates when Read FSM
// is idle. Read FSM takes priority when host has data available.
always @(posedge ft_clk or negedge ft_reset_n) begin
if (!ft_reset_n) begin
wr_state <= WR_IDLE;
wr_byte_idx <= 5'd0;
rd_state <= RD_IDLE;
rd_byte_cnt <= 2'd0;
rd_shift_reg <= 32'd0;
ft_data_out <= 8'd0;
ft_data_oe <= 1'b0;
ft_rd_n <= 1'b1;
ft_wr_n <= 1'b1;
ft_oe_n <= 1'b1;
ft_siwu <= 1'b0;
cmd_data <= 32'd0;
cmd_valid <= 1'b0;
cmd_opcode <= 8'd0;
cmd_addr <= 8'd0;
cmd_value <= 16'd0;
doppler_data_pending <= 1'b0;
cfar_data_pending <= 1'b0;
end else begin
// Default: clear one-shot signals
cmd_valid <= 1'b0;
// Data-pending flag management
if (doppler_valid_ft)
doppler_data_pending <= 1'b1;
if (cfar_valid_ft)
cfar_data_pending <= 1'b1;
// ================================================================
// READ FSM Host FPGA command path (4-byte sequential read)
// ================================================================
case (rd_state)
RD_IDLE: begin
// Only start reading if write FSM is idle and host has data
if (wr_state == WR_IDLE && !ft_rxf_n) begin
ft_oe_n <= 1'b0; // Assert OE: FT2232H drives bus
ft_data_oe <= 1'b0; // FPGA releases bus
rd_state <= RD_OE_ASSERT;
end
end
RD_OE_ASSERT: begin
// 1-cycle turnaround: OE asserted, bus settling
if (!ft_rxf_n) begin
ft_rd_n <= 1'b0; // Assert RD: start reading
rd_state <= RD_READING;
end else begin
// Host withdrew data abort
ft_oe_n <= 1'b1;
rd_state <= RD_IDLE;
end
end
RD_READING: begin
// Sample byte and shift into command register
// Byte order: opcode, addr, value_hi, value_lo
rd_shift_reg <= {rd_shift_reg[23:0], ft_data};
if (rd_byte_cnt == 2'd3) begin
// All 4 bytes received
ft_rd_n <= 1'b1;
rd_byte_cnt <= 2'd0;
rd_state <= RD_DEASSERT;
end else begin
rd_byte_cnt <= rd_byte_cnt + 2'd1;
// Keep reading if more data available
if (ft_rxf_n) begin
// Host ran out of data mid-command abort
ft_rd_n <= 1'b1;
rd_byte_cnt <= 2'd0;
rd_state <= RD_DEASSERT;
end
end
end
RD_DEASSERT: begin
// Deassert OE (1 cycle after RD deasserted)
ft_oe_n <= 1'b1;
// Only process if we received a full 4-byte command
if (rd_byte_cnt == 2'd0) begin
rd_state <= RD_PROCESS;
end else begin
// Incomplete command discard
rd_state <= RD_IDLE;
end
end
RD_PROCESS: begin
// Decode the assembled command word
cmd_data <= rd_shift_reg;
cmd_opcode <= rd_shift_reg[31:24];
cmd_addr <= rd_shift_reg[23:16];
cmd_value <= rd_shift_reg[15:0];
cmd_valid <= 1'b1;
rd_state <= RD_IDLE;
end
default: rd_state <= RD_IDLE;
endcase
// ================================================================
// WRITE FSM FPGA Host data streaming (byte-sequential)
// ================================================================
if (rd_state == RD_IDLE) begin
case (wr_state)
WR_IDLE: begin
ft_wr_n <= 1'b1;
ft_data_oe <= 1'b0; // Release data bus
// Status readback takes priority
if (status_req_ft && ft_rxf_n) begin
wr_state <= WR_STATUS_SEND;
wr_byte_idx <= 5'd0;
end
// Trigger on range_valid edge (primary data trigger)
else if (range_valid_ft && stream_range_en) begin
if (ft_rxf_n) begin // No host read pending
wr_state <= WR_DATA_SEND;
wr_byte_idx <= 5'd0;
end
end
end
WR_DATA_SEND: begin
if (!ft_txe_n) begin
// TXE# low = TX FIFO has room
ft_data_oe <= 1'b1;
ft_data_out <= data_pkt_byte;
ft_wr_n <= 1'b0; // Assert write strobe
if (wr_byte_idx == DATA_PKT_LEN - 5'd1) begin
// Last byte of data packet
wr_state <= WR_DONE;
wr_byte_idx <= 5'd0;
end else begin
wr_byte_idx <= wr_byte_idx + 5'd1;
end
end
end
WR_STATUS_SEND: begin
if (!ft_txe_n) begin
ft_data_oe <= 1'b1;
ft_data_out <= status_pkt_byte;
ft_wr_n <= 1'b0;
if (wr_byte_idx == STATUS_PKT_LEN - 5'd1) begin
wr_state <= WR_DONE;
wr_byte_idx <= 5'd0;
end else begin
wr_byte_idx <= wr_byte_idx + 5'd1;
end
end
end
WR_DONE: begin
ft_wr_n <= 1'b1;
ft_data_oe <= 1'b0; // Release data bus
// Clear pending flags data consumed
doppler_data_pending <= 1'b0;
cfar_data_pending <= 1'b0;
wr_state <= WR_IDLE;
end
default: wr_state <= WR_IDLE;
endcase
end
end
end
endmodule
+1 -1
View File
@@ -5,7 +5,7 @@
// Wraps the synthesizable fft_engine (radix-2 DIT) with the AXI-Stream port // Wraps the synthesizable fft_engine (radix-2 DIT) with the AXI-Stream port
// interface expected by the doppler_processor dual-FFT architecture. // interface expected by the doppler_processor dual-FFT architecture.
// //
// Identical interface to xfft_32.v but with N=16. // Used by the doppler_processor dual-FFT architecture (2 x 16-pt sub-frames).
// //
// Data format: {Q[15:0], I[15:0]} packed 32-bit. // Data format: {Q[15:0], I[15:0]} packed 32-bit.
// Config tdata[0]: 1 = forward FFT, 0 = inverse FFT. // Config tdata[0]: 1 = forward FFT, 0 = inverse FFT.
-278
View File
@@ -1,278 +0,0 @@
`timescale 1ns / 1ps
// ============================================================================
// xfft_32.v 32-point FFT with AXI-Stream interface
// ============================================================================
// Wraps the synthesizable fft_engine (radix-2 DIT) with the AXI-Stream port
// interface expected by doppler_processor.v.
//
// Port interface matches the Xilinx LogiCORE IP Fast Fourier Transform
// (AXI-Stream variant) as instantiated in doppler_processor.v.
//
// Data format: {Q[15:0], I[15:0]} packed 32-bit.
// Config tdata[0]: 1 = forward FFT, 0 = inverse FFT.
// ============================================================================
module xfft_32 (
input wire aclk,
input wire aresetn,
// Configuration channel (AXI-Stream slave)
input wire [7:0] s_axis_config_tdata,
input wire s_axis_config_tvalid,
output wire s_axis_config_tready,
// Data input channel (AXI-Stream slave)
input wire [31:0] s_axis_data_tdata,
input wire s_axis_data_tvalid,
input wire s_axis_data_tlast,
// Data output channel (AXI-Stream master)
output wire [31:0] m_axis_data_tdata,
output wire m_axis_data_tvalid,
output wire m_axis_data_tlast,
input wire m_axis_data_tready
);
// ============================================================================
// PARAMETERS
// ============================================================================
localparam N = 32;
localparam LOG2N = 5;
// ============================================================================
// INTERNAL SIGNALS
// ============================================================================
// FSM states
localparam [2:0] S_IDLE = 3'd0,
S_CONFIG = 3'd1, // Latch config (fwd/inv)
S_FEED = 3'd2, // Feed input to FFT engine
S_WAIT = 3'd3, // Wait for FFT to complete
S_OUTPUT = 3'd4; // Stream output
reg [2:0] state;
// Configuration
reg inverse_reg;
// Input buffering
reg signed [15:0] in_buf_re [0:N-1];
reg signed [15:0] in_buf_im [0:N-1];
reg [5:0] in_count; // 0..31 for loading, extra bit for overflow check
// Output buffering
reg signed [15:0] out_buf_re [0:N-1];
reg signed [15:0] out_buf_im [0:N-1];
reg [5:0] out_count;
reg [5:0] out_total; // counts how many outputs captured from engine
// FFT engine interface
reg fft_start;
reg fft_inverse;
reg signed [15:0] fft_din_re, fft_din_im;
reg fft_din_valid;
wire signed [15:0] fft_dout_re, fft_dout_im;
wire fft_dout_valid;
wire fft_busy;
wire fft_done;
// Feed counter for streaming into engine
reg [5:0] feed_count;
// ============================================================================
// FFT ENGINE INSTANCE
// ============================================================================
fft_engine #(
.N(N),
.LOG2N(LOG2N),
.DATA_W(16),
.INTERNAL_W(32),
.TWIDDLE_W(16),
.TWIDDLE_FILE("fft_twiddle_32.mem")
) fft_core (
.clk(aclk),
.reset_n(aresetn),
.start(fft_start),
.inverse(fft_inverse),
.din_re(fft_din_re),
.din_im(fft_din_im),
.din_valid(fft_din_valid),
.dout_re(fft_dout_re),
.dout_im(fft_dout_im),
.dout_valid(fft_dout_valid),
.busy(fft_busy),
.done(fft_done)
);
// ============================================================================
// AXI-STREAM OUTPUTS
// ============================================================================
// Config is accepted when idle
assign s_axis_config_tready = (state == S_IDLE);
// Output data: {Q, I} packed
assign m_axis_data_tdata = {out_buf_im[out_count[4:0]], out_buf_re[out_count[4:0]]};
assign m_axis_data_tvalid = (state == S_OUTPUT) && (out_count < N);
assign m_axis_data_tlast = (state == S_OUTPUT) && (out_count == N - 1);
// ============================================================================
// BUFFER WRITE LOGIC separate always block, NO async reset
// Allows Vivado to infer distributed RAM instead of dissolving into registers.
// ============================================================================
// Input buffer write enable
reg in_buf_we;
reg [4:0] in_buf_waddr;
reg signed [15:0] in_buf_wdata_re, in_buf_wdata_im;
// Output buffer write enable
reg out_buf_we;
reg [4:0] out_buf_waddr;
reg signed [15:0] out_buf_wdata_re, out_buf_wdata_im;
always @(posedge aclk) begin
if (in_buf_we) begin
in_buf_re[in_buf_waddr] <= in_buf_wdata_re;
in_buf_im[in_buf_waddr] <= in_buf_wdata_im;
end
if (out_buf_we) begin
out_buf_re[out_buf_waddr] <= out_buf_wdata_re;
out_buf_im[out_buf_waddr] <= out_buf_wdata_im;
end
end
// ============================================================================
// MAIN FSM
// ============================================================================
always @(posedge aclk or negedge aresetn) begin
if (!aresetn) begin
state <= S_IDLE;
inverse_reg <= 1'b0;
in_count <= 0;
out_count <= 0;
out_total <= 0;
feed_count <= 0;
fft_start <= 1'b0;
fft_inverse <= 1'b0;
fft_din_re <= 0;
fft_din_im <= 0;
fft_din_valid <= 1'b0;
in_buf_we <= 1'b0;
in_buf_waddr <= 0;
in_buf_wdata_re <= 0;
in_buf_wdata_im <= 0;
out_buf_we <= 1'b0;
out_buf_waddr <= 0;
out_buf_wdata_re <= 0;
out_buf_wdata_im <= 0;
end else begin
// Defaults
fft_start <= 1'b0;
fft_din_valid <= 1'b0;
in_buf_we <= 1'b0;
out_buf_we <= 1'b0;
case (state)
// ================================================================
S_IDLE: begin
in_count <= 0;
if (s_axis_config_tvalid) begin
// Config tdata[0]: 1=forward, 0=inverse
// fft_engine: inverse=0 means forward, inverse=1 means inverse
inverse_reg <= ~s_axis_config_tdata[0];
state <= S_FEED;
in_count <= 0;
feed_count <= 0;
end
end
// ================================================================
// S_FEED: Buffer all N inputs first, then start engine.
// ================================================================
S_FEED: begin
if (in_count < N) begin
// Still accepting input data
if (s_axis_data_tvalid) begin
in_buf_we <= 1'b1;
in_buf_waddr <= in_count[4:0];
in_buf_wdata_re <= s_axis_data_tdata[15:0];
in_buf_wdata_im <= s_axis_data_tdata[31:16];
in_count <= in_count + 1;
end
end else if (feed_count == 0) begin
// All N inputs buffered, start the FFT engine
fft_start <= 1'b1;
fft_inverse <= inverse_reg;
feed_count <= 0;
state <= S_WAIT;
out_total <= 0;
end
end
// ================================================================
// S_WAIT: Feed buffered data to engine, then wait for output
// ================================================================
S_WAIT: begin
if (feed_count < N) begin
fft_din_re <= in_buf_re[feed_count[4:0]];
fft_din_im <= in_buf_im[feed_count[4:0]];
fft_din_valid <= 1'b1;
feed_count <= feed_count + 1;
end
// Capture engine outputs
if (fft_dout_valid && out_total < N) begin
out_buf_we <= 1'b1;
out_buf_waddr <= out_total[4:0];
out_buf_wdata_re <= fft_dout_re;
out_buf_wdata_im <= fft_dout_im;
out_total <= out_total + 1;
end
// Engine done
if (fft_done) begin
state <= S_OUTPUT;
out_count <= 0;
end
end
// ================================================================
// S_OUTPUT: Stream buffered results via AXI-Stream master
// ================================================================
S_OUTPUT: begin
if (m_axis_data_tready || !m_axis_data_tvalid) begin
if (out_count < N) begin
// m_axis_data_tdata driven combinationally from out_buf
if (m_axis_data_tready) begin
out_count <= out_count + 1;
end
end
if (out_count >= N - 1 && m_axis_data_tready) begin
state <= S_IDLE;
end
end
end
default: state <= S_IDLE;
endcase
end
end
// ============================================================================
// MEMORY INIT (simulation only)
// ============================================================================
`ifdef SIMULATION
integer init_k;
initial begin
for (init_k = 0; init_k < N; init_k = init_k + 1) begin
in_buf_re[init_k] = 0;
in_buf_im[init_k] = 0;
out_buf_re[init_k] = 0;
out_buf_im[init_k] = 0;
end
end
`endif
endmodule
+2 -2
View File
@@ -8,6 +8,6 @@ GUI_V5 ==> Added Mercury Color
GUI_V6 ==> Added USB3 FT601 support GUI_V6 ==> Added USB3 FT601 support
radar_dashboard ==> Board bring-up dashboard (FT601 reader, real-time R-D heatmap, CFAR overlay, waterfall, host commands, HDF5 recording) radar_dashboard ==> Board bring-up dashboard (FT2232H reader, real-time R-D heatmap, CFAR overlay, waterfall, host commands, HDF5 recording)
radar_protocol ==> Protocol layer (packet parsing, command building, FT601 connection, data recorder, acquisition thread) radar_protocol ==> Protocol layer (packet parsing, command building, FT2232H connection, data recorder, acquisition thread)
smoke_test ==> Board bring-up smoke test host script (triggers FPGA self-test via opcode 0x30) smoke_test ==> Board bring-up smoke test host script (triggers FPGA self-test via opcode 0x30)
+20 -37
View File
@@ -3,10 +3,10 @@
AERIS-10 Radar Dashboard — Board Bring-Up Edition AERIS-10 Radar Dashboard — Board Bring-Up Edition
=================================================== ===================================================
Real-time visualization and control for the AERIS-10 phased-array radar Real-time visualization and control for the AERIS-10 phased-array radar
via FT601 USB 3.0 interface. via FT2232H USB 2.0 interface.
Features: Features:
- FT601 USB reader with packet parsing (matches usb_data_interface.v) - FT2232H USB reader with packet parsing (matches usb_data_interface_ft2232h.v)
- Real-time range-Doppler magnitude heatmap (64x32) - Real-time range-Doppler magnitude heatmap (64x32)
- CFAR detection overlay (flagged cells highlighted) - CFAR detection overlay (flagged cells highlighted)
- Range profile waterfall plot (range vs. time) - Range profile waterfall plot (range vs. time)
@@ -17,7 +17,7 @@ Features:
Usage: Usage:
python radar_dashboard.py # Launch with mock data python radar_dashboard.py # Launch with mock data
python radar_dashboard.py --live # Launch with FT601 hardware python radar_dashboard.py --live # Launch with FT2232H hardware
python radar_dashboard.py --record # Launch with HDF5 recording python radar_dashboard.py --record # Launch with HDF5 recording
""" """
@@ -43,7 +43,7 @@ from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
# Import protocol layer (no GUI deps) # Import protocol layer (no GUI deps)
from radar_protocol import ( from radar_protocol import (
RadarProtocol, FT601Connection, ReplayConnection, RadarProtocol, FT2232HConnection, ReplayConnection,
DataRecorder, RadarAcquisition, DataRecorder, RadarAcquisition,
RadarFrame, StatusResponse, Opcode, RadarFrame, StatusResponse, Opcode,
NUM_RANGE_BINS, NUM_DOPPLER_BINS, WATERFALL_DEPTH, NUM_RANGE_BINS, NUM_DOPPLER_BINS, WATERFALL_DEPTH,
@@ -78,17 +78,11 @@ class RadarDashboard:
UPDATE_INTERVAL_MS = 100 # 10 Hz display refresh UPDATE_INTERVAL_MS = 100 # 10 Hz display refresh
# Radar parameters for physical axis labels (ADI CN0566 defaults) # Radar parameters used for range-axis scaling.
# Config: [sample_rate=4e6, IF=1e5, RF=9.9e9, chirps=256, BW=500e6,
# ramp_time=300e-6, ...]
SAMPLE_RATE = 4e6 # Hz — ADC sample rate (baseband)
BANDWIDTH = 500e6 # Hz — chirp bandwidth BANDWIDTH = 500e6 # Hz — chirp bandwidth
RAMP_TIME = 300e-6 # s — chirp ramp time
CENTER_FREQ = 10.5e9 # Hz — X-band center frequency
NUM_CHIRPS_FRAME = 32 # chirps per Doppler frame
C = 3e8 # m/s — speed of light C = 3e8 # m/s — speed of light
def __init__(self, root: tk.Tk, connection: FT601Connection, def __init__(self, root: tk.Tk, connection: FT2232HConnection,
recorder: DataRecorder): recorder: DataRecorder):
self.root = root self.root = root
self.conn = connection self.conn = connection
@@ -188,15 +182,8 @@ class RadarDashboard:
range_per_bin = range_res * 16 range_per_bin = range_res * 16
max_range = range_per_bin * NUM_RANGE_BINS max_range = range_per_bin * NUM_RANGE_BINS
# Velocity resolution: dv = lambda / (2 * N_chirps * T_chirp) doppler_bin_lo = 0
wavelength = self.C / self.CENTER_FREQ doppler_bin_hi = NUM_DOPPLER_BINS
# Max unambiguous velocity = lambda / (4 * T_chirp)
max_vel = wavelength / (4.0 * self.RAMP_TIME)
vel_per_bin = 2.0 * max_vel / NUM_DOPPLER_BINS
# Doppler axis: bin 0 = 0 Hz (DC), wraps at Nyquist
# For display: center DC, so shift axis to [-max_vel, +max_vel)
vel_lo = -max_vel
vel_hi = max_vel
# Matplotlib figure with 3 subplots # Matplotlib figure with 3 subplots
self.fig = Figure(figsize=(14, 7), facecolor=BG) self.fig = Figure(figsize=(14, 7), facecolor=BG)
@@ -209,20 +196,17 @@ class RadarDashboard:
self._rd_img = self.ax_rd.imshow( self._rd_img = self.ax_rd.imshow(
np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS)), np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS)),
aspect="auto", cmap="inferno", origin="lower", aspect="auto", cmap="inferno", origin="lower",
extent=[vel_lo, vel_hi, 0, max_range], extent=[doppler_bin_lo, doppler_bin_hi, 0, max_range],
vmin=0, vmax=1000, vmin=0, vmax=1000,
) )
self.ax_rd.set_title("Range-Doppler Map", color=FG, fontsize=12) self.ax_rd.set_title("Range-Doppler Map", color=FG, fontsize=12)
self.ax_rd.set_xlabel("Velocity (m/s)", color=FG) self.ax_rd.set_xlabel("Doppler Bin (0-15: long PRI, 16-31: short PRI)", color=FG)
self.ax_rd.set_ylabel("Range (m)", color=FG) self.ax_rd.set_ylabel("Range (m)", color=FG)
self.ax_rd.tick_params(colors=FG) self.ax_rd.tick_params(colors=FG)
# Save axis limits for coordinate conversions # Save axis limits for coordinate conversions
self._vel_lo = vel_lo
self._vel_hi = vel_hi
self._max_range = max_range self._max_range = max_range
self._range_per_bin = range_per_bin self._range_per_bin = range_per_bin
self._vel_per_bin = vel_per_bin
# CFAR detection overlay (scatter) # CFAR detection overlay (scatter)
self._det_scatter = self.ax_rd.scatter([], [], s=30, c=GREEN, self._det_scatter = self.ax_rd.scatter([], [], s=30, c=GREEN,
@@ -504,10 +488,9 @@ class RadarDashboard:
self.lbl_detections.config(text=f"Det: {frame.detection_count}") self.lbl_detections.config(text=f"Det: {frame.detection_count}")
self.lbl_frame.config(text=f"Frame: {frame.frame_number}") self.lbl_frame.config(text=f"Frame: {frame.frame_number}")
# Update range-Doppler heatmap # Update range-Doppler heatmap in raw dual-subframe bin order
# FFT-shift Doppler axis so DC (bin 0) is in the center mag = frame.magnitude
mag = np.fft.fftshift(frame.magnitude, axes=1) det_shifted = frame.detections
det_shifted = np.fft.fftshift(frame.detections, axes=1)
# Stable colorscale via EMA smoothing of vmax # Stable colorscale via EMA smoothing of vmax
frame_vmax = float(np.max(mag)) if np.max(mag) > 0 else 1.0 frame_vmax = float(np.max(mag)) if np.max(mag) > 0 else 1.0
@@ -518,13 +501,13 @@ class RadarDashboard:
self._rd_img.set_data(mag) self._rd_img.set_data(mag)
self._rd_img.set_clim(vmin=0, vmax=stable_vmax) self._rd_img.set_clim(vmin=0, vmax=stable_vmax)
# Update CFAR overlay — convert bin indices to physical coordinates # Update CFAR overlay in raw Doppler-bin coordinates
det_coords = np.argwhere(det_shifted > 0) det_coords = np.argwhere(det_shifted > 0)
if len(det_coords) > 0: if len(det_coords) > 0:
# det_coords[:, 0] = range bin, det_coords[:, 1] = Doppler bin # det_coords[:, 0] = range bin, det_coords[:, 1] = Doppler bin
range_m = (det_coords[:, 0] + 0.5) * self._range_per_bin range_m = (det_coords[:, 0] + 0.5) * self._range_per_bin
vel_ms = self._vel_lo + (det_coords[:, 1] + 0.5) * self._vel_per_bin doppler_bins = det_coords[:, 1] + 0.5
offsets = np.column_stack([vel_ms, range_m]) offsets = np.column_stack([doppler_bins, range_m])
self._det_scatter.set_offsets(offsets) self._det_scatter.set_offsets(offsets)
else: else:
self._det_scatter.set_offsets(np.empty((0, 2))) self._det_scatter.set_offsets(np.empty((0, 2)))
@@ -569,7 +552,7 @@ class _TextHandler(logging.Handler):
def main(): def main():
parser = argparse.ArgumentParser(description="AERIS-10 Radar Dashboard") parser = argparse.ArgumentParser(description="AERIS-10 Radar Dashboard")
parser.add_argument("--live", action="store_true", parser.add_argument("--live", action="store_true",
help="Use real FT601 hardware (default: mock mode)") help="Use real FT2232H hardware (default: mock mode)")
parser.add_argument("--replay", type=str, metavar="NPY_DIR", parser.add_argument("--replay", type=str, metavar="NPY_DIR",
help="Replay real data from .npy directory " help="Replay real data from .npy directory "
"(e.g. tb/cosim/real_data/hex/)") "(e.g. tb/cosim/real_data/hex/)")
@@ -578,7 +561,7 @@ def main():
parser.add_argument("--record", action="store_true", parser.add_argument("--record", action="store_true",
help="Start HDF5 recording immediately") help="Start HDF5 recording immediately")
parser.add_argument("--device", type=int, default=0, parser.add_argument("--device", type=int, default=0,
help="FT601 device index (default: 0)") help="FT2232H device index (default: 0)")
args = parser.parse_args() args = parser.parse_args()
if args.replay: if args.replay:
@@ -586,10 +569,10 @@ def main():
conn = ReplayConnection(npy_dir, use_mti=not args.no_mti) conn = ReplayConnection(npy_dir, use_mti=not args.no_mti)
mode_str = f"REPLAY ({npy_dir}, MTI={'OFF' if args.no_mti else 'ON'})" mode_str = f"REPLAY ({npy_dir}, MTI={'OFF' if args.no_mti else 'ON'})"
elif args.live: elif args.live:
conn = FT601Connection(mock=False) conn = FT2232HConnection(mock=False)
mode_str = "LIVE" mode_str = "LIVE"
else: else:
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
mode_str = "MOCK" mode_str = "MOCK"
recorder = DataRecorder() recorder = DataRecorder()
+108 -133
View File
@@ -2,17 +2,17 @@
""" """
AERIS-10 Radar Protocol Layer AERIS-10 Radar Protocol Layer
=============================== ===============================
Pure-logic module for FT601 packet parsing and command building. Pure-logic module for USB packet parsing and command building.
No GUI dependencies — safe to import from tests and headless scripts. No GUI dependencies — safe to import from tests and headless scripts.
Matches usb_data_interface.v packet format exactly. USB Interface: FT2232H USB 2.0 (8-bit, 50T production board) via pyftdi
USB Packet Protocol: USB Packet Protocol (11-byte):
TX (FPGA→Host): TX (FPGA→Host):
Data packet: [0xAA] [range 4×32b] [doppler 4×32b] [det 1B] [0x55] Data packet: [0xAA] [range_q 2B] [range_i 2B] [dop_re 2B] [dop_im 2B] [det 1B] [0x55]
Status packet: [0xBB] [status 6×32b] [0x55] Status packet: [0xBB] [status 6×32b] [0x55]
RX (Host→FPGA): RX (Host→FPGA):
Command word: {opcode[31:24], addr[23:16], value[15:0]} Command: 4 bytes received sequentially {opcode, addr, value_hi, value_lo}
""" """
import os import os
@@ -38,6 +38,10 @@ HEADER_BYTE = 0xAA
FOOTER_BYTE = 0x55 FOOTER_BYTE = 0x55
STATUS_HEADER_BYTE = 0xBB STATUS_HEADER_BYTE = 0xBB
# Packet sizes
DATA_PACKET_SIZE = 11 # 1 + 4 + 2 + 2 + 1 + 1
STATUS_PACKET_SIZE = 26 # 1 + 24 + 1
NUM_RANGE_BINS = 64 NUM_RANGE_BINS = 64
NUM_DOPPLER_BINS = 32 NUM_DOPPLER_BINS = 32
NUM_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS # 2048 NUM_CELLS = NUM_RANGE_BINS * NUM_DOPPLER_BINS # 2048
@@ -134,7 +138,7 @@ class RadarProtocol:
def build_command(opcode: int, value: int, addr: int = 0) -> bytes: def build_command(opcode: int, value: int, addr: int = 0) -> bytes:
""" """
Build a 32-bit command word: {opcode[31:24], addr[23:16], value[15:0]}. Build a 32-bit command word: {opcode[31:24], addr[23:16], value[15:0]}.
Returns 4 bytes, big-endian (MSB first as FT601 expects). Returns 4 bytes, big-endian (MSB first).
""" """
word = ((opcode & 0xFF) << 24) | ((addr & 0xFF) << 16) | (value & 0xFFFF) word = ((opcode & 0xFF) << 24) | ((addr & 0xFF) << 16) | (value & 0xFFFF)
return struct.pack(">I", word) return struct.pack(">I", word)
@@ -142,61 +146,39 @@ class RadarProtocol:
@staticmethod @staticmethod
def parse_data_packet(raw: bytes) -> Optional[Dict[str, Any]]: def parse_data_packet(raw: bytes) -> Optional[Dict[str, Any]]:
""" """
Parse a single data packet from the FPGA byte stream. Parse an 11-byte data packet from the FT2232H byte stream.
Returns dict with keys: 'range_i', 'range_q', 'doppler_i', 'doppler_q', Returns dict with keys: 'range_i', 'range_q', 'doppler_i', 'doppler_q',
'detection', or None if invalid. 'detection', or None if invalid.
Packet format (all streams enabled): Packet format (11 bytes):
[0xAA] [range 4×4B] [doppler 4×4B] [det 1B] [0x55] Byte 0: 0xAA (header)
= 1 + 16 + 16 + 1 + 1 = 35 bytes Bytes 1-2: range_q[15:0] MSB first
Bytes 3-4: range_i[15:0] MSB first
With byte-enables, the FT601 delivers only valid bytes. Bytes 5-6: doppler_real[15:0] MSB first
Header/footer/detection use BE=0001 → 1 byte each. Bytes 7-8: doppler_imag[15:0] MSB first
Range/doppler use BE=1111 → 4 bytes each × 4 transfers. Byte 9: {7'b0, cfar_detection}
Byte 10: 0x55 (footer)
In practice, the range data word 0 contains the full 32-bit value
{range_q[15:0], range_i[15:0]}. Words 13 are shifted copies.
Similarly, doppler word 0 = {doppler_real, doppler_imag}.
""" """
if len(raw) < 3: if len(raw) < DATA_PACKET_SIZE:
return None return None
if raw[0] != HEADER_BYTE: if raw[0] != HEADER_BYTE:
return None return None
if raw[10] != FOOTER_BYTE:
result = {}
pos = 1
# Range data: 4 × 4 bytes, only word 0 matters
if pos + 16 <= len(raw):
range_word0 = struct.unpack_from(">I", raw, pos)[0]
result["range_i"] = _to_signed16(range_word0 & 0xFFFF)
result["range_q"] = _to_signed16((range_word0 >> 16) & 0xFFFF)
pos += 16
else:
return None return None
# Doppler data: 4 × 4 bytes, only word 0 matters range_q = _to_signed16(struct.unpack_from(">H", raw, 1)[0])
# Word 0 layout: {doppler_real[31:16], doppler_imag[15:0]} range_i = _to_signed16(struct.unpack_from(">H", raw, 3)[0])
if pos + 16 <= len(raw): doppler_i = _to_signed16(struct.unpack_from(">H", raw, 5)[0])
dop_word0 = struct.unpack_from(">I", raw, pos)[0] doppler_q = _to_signed16(struct.unpack_from(">H", raw, 7)[0])
result["doppler_q"] = _to_signed16(dop_word0 & 0xFFFF) detection = raw[9] & 0x01
result["doppler_i"] = _to_signed16((dop_word0 >> 16) & 0xFFFF)
pos += 16
else:
return None
# Detection: 1 byte return {
if pos + 1 <= len(raw): "range_i": range_i,
result["detection"] = raw[pos] & 0x01 "range_q": range_q,
pos += 1 "doppler_i": doppler_i,
else: "doppler_q": doppler_q,
return None "detection": detection,
}
# Footer
if pos < len(raw) and raw[pos] == FOOTER_BYTE:
pos += 1
return result
@staticmethod @staticmethod
def parse_status_packet(raw: bytes) -> Optional[StatusResponse]: def parse_status_packet(raw: bytes) -> Optional[StatusResponse]:
@@ -250,16 +232,15 @@ class RadarProtocol:
i = 0 i = 0
while i < len(buf): while i < len(buf):
if buf[i] == HEADER_BYTE: if buf[i] == HEADER_BYTE:
# Data packet: 35 bytes (all streams) end = i + DATA_PACKET_SIZE
end = i + 35
if end <= len(buf): if end <= len(buf):
packets.append((i, end, "data")) packets.append((i, end, "data"))
i = end i = end
else: else:
break break
elif buf[i] == STATUS_HEADER_BYTE: elif buf[i] == STATUS_HEADER_BYTE:
# Status packet: 26 bytes (6 words + header + footer) # Status packet: 26 bytes (same for both interfaces)
end = i + 26 end = i + STATUS_PACKET_SIZE
if end <= len(buf): if end <= len(buf):
packets.append((i, end, "status")) packets.append((i, end, "status"))
i = end i = end
@@ -271,26 +252,30 @@ class RadarProtocol:
# ============================================================================ # ============================================================================
# FT601 USB Connection # FT2232H USB 2.0 Connection (pyftdi, 245 Synchronous FIFO)
# ============================================================================ # ============================================================================
# Optional ftd3xx import # Optional pyftdi import
try: try:
import ftd3xx from pyftdi.ftdi import Ftdi as PyFtdi
FTD3XX_AVAILABLE = True PYFTDI_AVAILABLE = True
except ImportError: except ImportError:
FTD3XX_AVAILABLE = False PYFTDI_AVAILABLE = False
class FT601Connection: class FT2232HConnection:
""" """
FT601 USB 3.0 FIFO bridge communication. FT2232H USB 2.0 Hi-Speed FIFO bridge communication.
Supports ftd3xx (native D3XX) or mock mode. Uses pyftdi in 245 Synchronous FIFO mode (Channel A).
VID:PID = 0x0403:0x6010 (FTDI default for FT2232H).
""" """
VID = 0x0403
PID = 0x6010
def __init__(self, mock: bool = True): def __init__(self, mock: bool = True):
self._mock = mock self._mock = mock
self._device = None self._ftdi = None
self._lock = threading.Lock() self._lock = threading.Lock()
self.is_open = False self.is_open = False
# Mock state # Mock state
@@ -300,36 +285,42 @@ class FT601Connection:
def open(self, device_index: int = 0) -> bool: def open(self, device_index: int = 0) -> bool:
if self._mock: if self._mock:
self.is_open = True self.is_open = True
log.info("FT601 mock device opened (no hardware)") log.info("FT2232H mock device opened (no hardware)")
return True return True
if not FTD3XX_AVAILABLE: if not PYFTDI_AVAILABLE:
log.error("ftd3xx not installed — cannot open real FT601 device") log.error("pyftdi not installed — cannot open real FT2232H device")
return False return False
try: try:
self._device = ftd3xx.create(device_index, ftd3xx.CONFIGURATION_CHANNEL_0) self._ftdi = PyFtdi()
if self._device is None: url = f"ftdi://0x{self.VID:04x}:0x{self.PID:04x}/{device_index + 1}"
log.error("ftd3xx.create returned None") self._ftdi.open_from_url(url)
return False # Configure for 245 Synchronous FIFO mode
self._ftdi.set_bitmode(0xFF, PyFtdi.BitMode.SYNCFF)
# Set USB transfer size for throughput
self._ftdi.read_data_set_chunksize(65536)
self._ftdi.write_data_set_chunksize(65536)
# Purge buffers
self._ftdi.purge_buffers()
self.is_open = True self.is_open = True
log.info(f"FT601 device {device_index} opened") log.info(f"FT2232H device opened: {url}")
return True return True
except Exception as e: except Exception as e:
log.error(f"FT601 open failed: {e}") log.error(f"FT2232H open failed: {e}")
return False return False
def close(self): def close(self):
if self._device is not None: if self._ftdi is not None:
try: try:
self._device.close() self._ftdi.close()
except Exception: except Exception:
pass pass
self._device = None self._ftdi = None
self.is_open = False self.is_open = False
def read(self, size: int = 4096) -> Optional[bytes]: def read(self, size: int = 4096) -> Optional[bytes]:
"""Read raw bytes from FT601. Returns None on error/timeout.""" """Read raw bytes from FT2232H. Returns None on error/timeout."""
if not self.is_open: if not self.is_open:
return None return None
@@ -338,51 +329,50 @@ class FT601Connection:
with self._lock: with self._lock:
try: try:
buf = self._device.readPipe(0x82, size, raw=True) data = self._ftdi.read_data(size)
return bytes(buf) if buf else None return bytes(data) if data else None
except Exception as e: except Exception as e:
log.error(f"FT601 read error: {e}") log.error(f"FT2232H read error: {e}")
return None return None
def write(self, data: bytes) -> bool: def write(self, data: bytes) -> bool:
"""Write raw bytes to FT601.""" """Write raw bytes to FT2232H (4-byte commands)."""
if not self.is_open: if not self.is_open:
return False return False
if self._mock: if self._mock:
log.info(f"FT601 mock write: {data.hex()}") log.info(f"FT2232H mock write: {data.hex()}")
return True return True
with self._lock: with self._lock:
try: try:
self._device.writePipe(0x02, data, len(data)) written = self._ftdi.write_data(data)
return True return written == len(data)
except Exception as e: except Exception as e:
log.error(f"FT601 write error: {e}") log.error(f"FT2232H write error: {e}")
return False return False
def _mock_read(self, size: int) -> bytes: def _mock_read(self, size: int) -> bytes:
""" """
Generate synthetic radar data packets for testing. Generate synthetic compact radar data packets (11-byte) for testing.
Generate synthetic 11-byte radar data packets for testing.
Simulates a batch of packets with a target near range bin 20, Doppler bin 8. Simulates a batch of packets with a target near range bin 20, Doppler bin 8.
""" """
time.sleep(0.05) # Simulate USB latency time.sleep(0.05)
self._mock_frame_num += 1 self._mock_frame_num += 1
buf = bytearray() buf = bytearray()
num_packets = min(32, size // 35) num_packets = min(32, size // DATA_PACKET_SIZE)
for _ in range(num_packets): for _ in range(num_packets):
rbin = self._mock_rng.randint(0, NUM_RANGE_BINS) rbin = self._mock_rng.randint(0, NUM_RANGE_BINS)
dbin = self._mock_rng.randint(0, NUM_DOPPLER_BINS) dbin = self._mock_rng.randint(0, NUM_DOPPLER_BINS)
# Simulate range profile with a target at bin ~20 and noise
range_i = int(self._mock_rng.normal(0, 100)) range_i = int(self._mock_rng.normal(0, 100))
range_q = int(self._mock_rng.normal(0, 100)) range_q = int(self._mock_rng.normal(0, 100))
if abs(rbin - 20) < 3: if abs(rbin - 20) < 3:
range_i += 5000 range_i += 5000
range_q += 3000 range_q += 3000
# Simulate Doppler with target at Doppler bin ~8
dop_i = int(self._mock_rng.normal(0, 50)) dop_i = int(self._mock_rng.normal(0, 50))
dop_q = int(self._mock_rng.normal(0, 50)) dop_q = int(self._mock_rng.normal(0, 50))
if abs(rbin - 20) < 3 and abs(dbin - 8) < 2: if abs(rbin - 20) < 3 and abs(dbin - 8) < 2:
@@ -391,22 +381,13 @@ class FT601Connection:
detection = 1 if (abs(rbin - 20) < 2 and abs(dbin - 8) < 2) else 0 detection = 1 if (abs(rbin - 20) < 2 and abs(dbin - 8) < 2) else 0
# Build packet # Build compact 11-byte packet
pkt = bytearray() pkt = bytearray()
pkt.append(HEADER_BYTE) pkt.append(HEADER_BYTE)
pkt += struct.pack(">h", np.clip(range_q, -32768, 32767))
rword = (((range_q & 0xFFFF) << 16) | (range_i & 0xFFFF)) & 0xFFFFFFFF pkt += struct.pack(">h", np.clip(range_i, -32768, 32767))
pkt += struct.pack(">I", rword) pkt += struct.pack(">h", np.clip(dop_i, -32768, 32767))
pkt += struct.pack(">I", ((rword << 8) & 0xFFFFFFFF)) pkt += struct.pack(">h", np.clip(dop_q, -32768, 32767))
pkt += struct.pack(">I", ((rword << 16) & 0xFFFFFFFF))
pkt += struct.pack(">I", ((rword << 24) & 0xFFFFFFFF))
dword = (((dop_i & 0xFFFF) << 16) | (dop_q & 0xFFFF)) & 0xFFFFFFFF
pkt += struct.pack(">I", dword)
pkt += struct.pack(">I", ((dword << 8) & 0xFFFFFFFF))
pkt += struct.pack(">I", ((dword << 16) & 0xFFFFFFFF))
pkt += struct.pack(">I", ((dword << 24) & 0xFFFFFFFF))
pkt.append(detection & 0x01) pkt.append(detection & 0x01)
pkt.append(FOOTER_BYTE) pkt.append(FOOTER_BYTE)
@@ -756,8 +737,8 @@ class ReplayConnection:
det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=bool) det = np.zeros((NUM_RANGE_BINS, NUM_DOPPLER_BINS), dtype=bool)
det_count = int(det.sum()) det_count = int(det.sum())
log.info(f"Replay: rebuilt {NUM_CELLS} packets " log.info(f"Replay: rebuilt {NUM_CELLS} packets ("
f"(MTI={'ON' if self._mti_enable else 'OFF'}, " f"MTI={'ON' if self._mti_enable else 'OFF'}, "
f"DC_notch={self._dc_notch_width}, " f"DC_notch={self._dc_notch_width}, "
f"CFAR={'ON' if self._cfar_enable else 'OFF'} " f"CFAR={'ON' if self._cfar_enable else 'OFF'} "
f"G={self._cfar_guard} T={self._cfar_train} " f"G={self._cfar_guard} T={self._cfar_train} "
@@ -767,34 +748,27 @@ class ReplayConnection:
range_i = self._range_i_vec range_i = self._range_i_vec
range_q = self._range_q_vec range_q = self._range_q_vec
# Pre-allocate buffer (35 bytes per packet * 2048 cells) return self._build_packets_data(range_i, range_q, dop_i, dop_q, det)
buf = bytearray(NUM_CELLS * 35)
def _build_packets_data(self, range_i, range_q, dop_i, dop_q, det) -> bytes:
"""Build 11-byte data packets for FT2232H interface."""
buf = bytearray(NUM_CELLS * DATA_PACKET_SIZE)
pos = 0 pos = 0
for rbin in range(NUM_RANGE_BINS): for rbin in range(NUM_RANGE_BINS):
ri = int(np.clip(range_i[rbin], -32768, 32767)) & 0xFFFF ri = int(np.clip(range_i[rbin], -32768, 32767))
rq = int(np.clip(range_q[rbin], -32768, 32767)) & 0xFFFF rq = int(np.clip(range_q[rbin], -32768, 32767))
rword = ((rq << 16) | ri) & 0xFFFFFFFF rq_bytes = struct.pack(">h", rq)
rw0 = struct.pack(">I", rword) ri_bytes = struct.pack(">h", ri)
rw1 = struct.pack(">I", (rword << 8) & 0xFFFFFFFF)
rw2 = struct.pack(">I", (rword << 16) & 0xFFFFFFFF)
rw3 = struct.pack(">I", (rword << 24) & 0xFFFFFFFF)
for dbin in range(NUM_DOPPLER_BINS): for dbin in range(NUM_DOPPLER_BINS):
di = int(np.clip(dop_i[rbin, dbin], -32768, 32767)) & 0xFFFF di = int(np.clip(dop_i[rbin, dbin], -32768, 32767))
dq = int(np.clip(dop_q[rbin, dbin], -32768, 32767)) & 0xFFFF dq = int(np.clip(dop_q[rbin, dbin], -32768, 32767))
d = 1 if det[rbin, dbin] else 0 d = 1 if det[rbin, dbin] else 0
dword = ((di << 16) | dq) & 0xFFFFFFFF buf[pos] = HEADER_BYTE; pos += 1
buf[pos:pos+2] = rq_bytes; pos += 2
buf[pos] = HEADER_BYTE buf[pos:pos+2] = ri_bytes; pos += 2
pos += 1 buf[pos:pos+2] = struct.pack(">h", di); pos += 2
buf[pos:pos+4] = rw0; pos += 4 buf[pos:pos+2] = struct.pack(">h", dq); pos += 2
buf[pos:pos+4] = rw1; pos += 4
buf[pos:pos+4] = rw2; pos += 4
buf[pos:pos+4] = rw3; pos += 4
buf[pos:pos+4] = struct.pack(">I", dword); pos += 4
buf[pos:pos+4] = struct.pack(">I", (dword << 8) & 0xFFFFFFFF); pos += 4
buf[pos:pos+4] = struct.pack(">I", (dword << 16) & 0xFFFFFFFF); pos += 4
buf[pos:pos+4] = struct.pack(">I", (dword << 24) & 0xFFFFFFFF); pos += 4
buf[pos] = d; pos += 1 buf[pos] = d; pos += 1
buf[pos] = FOOTER_BYTE; pos += 1 buf[pos] = FOOTER_BYTE; pos += 1
@@ -879,11 +853,11 @@ class DataRecorder:
class RadarAcquisition(threading.Thread): class RadarAcquisition(threading.Thread):
""" """
Background thread: reads from FT601, parses packets, assembles frames, Background thread: reads from USB (FT2232H), parses 11-byte packets,
and pushes complete frames to the display queue. assembles frames, and pushes complete frames to the display queue.
""" """
def __init__(self, connection: FT601Connection, frame_queue: queue.Queue, def __init__(self, connection, frame_queue: queue.Queue,
recorder: Optional[DataRecorder] = None, recorder: Optional[DataRecorder] = None,
status_callback=None): status_callback=None):
super().__init__(daemon=True) super().__init__(daemon=True)
@@ -910,7 +884,8 @@ class RadarAcquisition(threading.Thread):
packets = RadarProtocol.find_packet_boundaries(raw) packets = RadarProtocol.find_packet_boundaries(raw)
for start, end, ptype in packets: for start, end, ptype in packets:
if ptype == "data": if ptype == "data":
parsed = RadarProtocol.parse_data_packet(raw[start:end]) parsed = RadarProtocol.parse_data_packet(
raw[start:end])
if parsed is not None: if parsed is not None:
self._ingest_sample(parsed) self._ingest_sample(parsed)
elif ptype == "status": elif ptype == "status":
@@ -5,5 +5,5 @@ numpy>=1.24
matplotlib>=3.7 matplotlib>=3.7
h5py>=3.8 h5py>=3.8
# FT601 USB 3.0 driver (install from FTDI website if not on PyPI) # FT2232H USB 2.0 driver (pyftdi — pure Python, pip-installable)
# ftd3xx # Optional: only needed for --live mode with real hardware # pyftdi>=0.54 # Optional: only needed for --live mode with real hardware
+7 -7
View File
@@ -8,7 +8,7 @@ optionally captures raw ADC samples for offline analysis.
Usage: Usage:
python smoke_test.py # Mock mode (no hardware) python smoke_test.py # Mock mode (no hardware)
python smoke_test.py --live # Real FT601 hardware python smoke_test.py --live # Real FT2232H hardware
python smoke_test.py --live --adc-dump adc_raw.npy # Capture ADC data python smoke_test.py --live --adc-dump adc_raw.npy # Capture ADC data
Self-Test Subsystems: Self-Test Subsystems:
@@ -35,7 +35,7 @@ import numpy as np
# Add parent directory for radar_protocol import # Add parent directory for radar_protocol import
sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) sys.path.insert(0, os.path.dirname(os.path.abspath(__file__)))
from radar_protocol import RadarProtocol, FT601Connection from radar_protocol import RadarProtocol, FT2232HConnection
logging.basicConfig( logging.basicConfig(
level=logging.INFO, level=logging.INFO,
@@ -67,7 +67,7 @@ TEST_NAMES = {
class SmokeTest: class SmokeTest:
"""Host-side smoke test controller.""" """Host-side smoke test controller."""
def __init__(self, connection: FT601Connection, adc_dump_path: str = None): def __init__(self, connection: FT2232HConnection, adc_dump_path: str = None):
self.conn = connection self.conn = connection
self.adc_dump_path = adc_dump_path self.adc_dump_path = adc_dump_path
self._adc_samples = [] self._adc_samples = []
@@ -85,7 +85,7 @@ class SmokeTest:
# Step 1: Connect # Step 1: Connect
if not self.conn.is_open: if not self.conn.is_open:
if not self.conn.open(): if not self.conn.open():
log.error("Failed to open FT601 connection") log.error("Failed to open FT2232H connection")
return False return False
# Step 2: Send self-test trigger (opcode 0x30) # Step 2: Send self-test trigger (opcode 0x30)
@@ -205,15 +205,15 @@ class SmokeTest:
def main(): def main():
parser = argparse.ArgumentParser(description="AERIS-10 Board Smoke Test") parser = argparse.ArgumentParser(description="AERIS-10 Board Smoke Test")
parser.add_argument("--live", action="store_true", parser.add_argument("--live", action="store_true",
help="Use real FT601 hardware (default: mock)") help="Use real FT2232H hardware (default: mock)")
parser.add_argument("--device", type=int, default=0, parser.add_argument("--device", type=int, default=0,
help="FT601 device index") help="FT2232H device index")
parser.add_argument("--adc-dump", type=str, default=None, parser.add_argument("--adc-dump", type=str, default=None,
help="Save raw ADC samples to .npy file") help="Save raw ADC samples to .npy file")
args = parser.parse_args() args = parser.parse_args()
mock_mode = not args.live mock_mode = not args.live
conn = FT601Connection(mock=mock_mode) conn = FT2232HConnection(mock=mock_mode)
tester = SmokeTest(conn, adc_dump_path=args.adc_dump) tester = SmokeTest(conn, adc_dump_path=args.adc_dump)
success = tester.run() success = tester.run()
+30 -44
View File
@@ -16,10 +16,11 @@ import unittest
import numpy as np import numpy as np
from radar_protocol import ( from radar_protocol import (
RadarProtocol, FT601Connection, DataRecorder, RadarAcquisition, RadarProtocol, FT2232HConnection, DataRecorder, RadarAcquisition,
RadarFrame, StatusResponse, Opcode, RadarFrame, StatusResponse, Opcode,
HEADER_BYTE, FOOTER_BYTE, STATUS_HEADER_BYTE, HEADER_BYTE, FOOTER_BYTE, STATUS_HEADER_BYTE,
NUM_RANGE_BINS, NUM_DOPPLER_BINS, NUM_CELLS, NUM_RANGE_BINS, NUM_DOPPLER_BINS, NUM_CELLS,
DATA_PACKET_SIZE,
_HARDWARE_ONLY_OPCODES, _REPLAY_ADJUSTABLE_OPCODES, _HARDWARE_ONLY_OPCODES, _REPLAY_ADJUSTABLE_OPCODES,
) )
@@ -72,23 +73,13 @@ class TestRadarProtocol(unittest.TestCase):
# ---------------------------------------------------------------- # ----------------------------------------------------------------
def _make_data_packet(self, range_i=100, range_q=200, def _make_data_packet(self, range_i=100, range_q=200,
dop_i=300, dop_q=400, detection=0): dop_i=300, dop_q=400, detection=0):
"""Build a synthetic 35-byte data packet matching FPGA format.""" """Build a synthetic 11-byte data packet matching FT2232H format."""
pkt = bytearray() pkt = bytearray()
pkt.append(HEADER_BYTE) pkt.append(HEADER_BYTE)
pkt += struct.pack(">h", range_q & 0xFFFF if range_q >= 0 else range_q)
# Range: word 0 = {range_q[15:0], range_i[15:0]} pkt += struct.pack(">h", range_i & 0xFFFF if range_i >= 0 else range_i)
rword = (((range_q & 0xFFFF) << 16) | (range_i & 0xFFFF)) & 0xFFFFFFFF pkt += struct.pack(">h", dop_i & 0xFFFF if dop_i >= 0 else dop_i)
pkt += struct.pack(">I", rword) pkt += struct.pack(">h", dop_q & 0xFFFF if dop_q >= 0 else dop_q)
# Words 1-3: shifted copies (don't matter for parsing)
for shift in [8, 16, 24]:
pkt += struct.pack(">I", ((rword << shift) & 0xFFFFFFFF))
# Doppler: word 0 = {dop_i[15:0], dop_q[15:0]}
dword = (((dop_i & 0xFFFF) << 16) | (dop_q & 0xFFFF)) & 0xFFFFFFFF
pkt += struct.pack(">I", dword)
for shift in [8, 16, 24]:
pkt += struct.pack(">I", ((dword << shift) & 0xFFFFFFFF))
pkt.append(detection & 0x01) pkt.append(detection & 0x01)
pkt.append(FOOTER_BYTE) pkt.append(FOOTER_BYTE)
return bytes(pkt) return bytes(pkt)
@@ -265,23 +256,23 @@ class TestRadarProtocol(unittest.TestCase):
def test_find_boundaries_truncated(self): def test_find_boundaries_truncated(self):
"""Truncated packet should not be returned.""" """Truncated packet should not be returned."""
data_pkt = self._make_data_packet() data_pkt = self._make_data_packet()
buf = data_pkt[:20] # truncated buf = data_pkt[:6] # truncated (less than 11-byte packet size)
boundaries = RadarProtocol.find_packet_boundaries(buf) boundaries = RadarProtocol.find_packet_boundaries(buf)
self.assertEqual(len(boundaries), 0) self.assertEqual(len(boundaries), 0)
class TestFT601Connection(unittest.TestCase): class TestFT2232HConnection(unittest.TestCase):
"""Test mock FT601 connection.""" """Test mock FT2232H connection."""
def test_mock_open_close(self): def test_mock_open_close(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
self.assertTrue(conn.open()) self.assertTrue(conn.open())
self.assertTrue(conn.is_open) self.assertTrue(conn.is_open)
conn.close() conn.close()
self.assertFalse(conn.is_open) self.assertFalse(conn.is_open)
def test_mock_read_returns_data(self): def test_mock_read_returns_data(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
conn.open() conn.open()
data = conn.read(4096) data = conn.read(4096)
self.assertIsNotNone(data) self.assertIsNotNone(data)
@@ -290,7 +281,7 @@ class TestFT601Connection(unittest.TestCase):
def test_mock_read_contains_valid_packets(self): def test_mock_read_contains_valid_packets(self):
"""Mock data should contain parseable data packets.""" """Mock data should contain parseable data packets."""
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
conn.open() conn.open()
raw = conn.read(4096) raw = conn.read(4096)
packets = RadarProtocol.find_packet_boundaries(raw) packets = RadarProtocol.find_packet_boundaries(raw)
@@ -302,18 +293,18 @@ class TestFT601Connection(unittest.TestCase):
conn.close() conn.close()
def test_mock_write(self): def test_mock_write(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
conn.open() conn.open()
cmd = RadarProtocol.build_command(0x01, 1) cmd = RadarProtocol.build_command(0x01, 1)
self.assertTrue(conn.write(cmd)) self.assertTrue(conn.write(cmd))
conn.close() conn.close()
def test_read_when_closed(self): def test_read_when_closed(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
self.assertIsNone(conn.read()) self.assertIsNone(conn.read())
def test_write_when_closed(self): def test_write_when_closed(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
self.assertFalse(conn.write(b"\x00\x00\x00\x00")) self.assertFalse(conn.write(b"\x00\x00\x00\x00"))
@@ -365,7 +356,7 @@ class TestRadarAcquisition(unittest.TestCase):
"""Test acquisition thread with mock connection.""" """Test acquisition thread with mock connection."""
def test_acquisition_produces_frames(self): def test_acquisition_produces_frames(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
conn.open() conn.open()
fq = queue.Queue(maxsize=16) fq = queue.Queue(maxsize=16)
acq = RadarAcquisition(conn, fq) acq = RadarAcquisition(conn, fq)
@@ -392,7 +383,7 @@ class TestRadarAcquisition(unittest.TestCase):
# If no frame arrived in timeout, that's still OK for a fast CI run # If no frame arrived in timeout, that's still OK for a fast CI run
def test_acquisition_stop(self): def test_acquisition_stop(self):
conn = FT601Connection(mock=True) conn = FT2232HConnection(mock=True)
conn.open() conn.open()
fq = queue.Queue(maxsize=4) fq = queue.Queue(maxsize=4)
acq = RadarAcquisition(conn, fq) acq = RadarAcquisition(conn, fq)
@@ -438,25 +429,20 @@ class TestEndToEnd(unittest.TestCase):
self.assertEqual(word & 0xFFFF, 42) self.assertEqual(word & 0xFFFF, 42)
def test_data_packet_roundtrip(self): def test_data_packet_roundtrip(self):
"""Build a data packet, parse it, verify values match.""" """Build an 11-byte data packet, parse it, verify values match."""
# Build packet manually ri, rq, di, dq = 1234, -5678, 9012, -3456
pkt = bytearray() pkt = bytearray()
pkt.append(HEADER_BYTE) pkt.append(HEADER_BYTE)
pkt += struct.pack(">h", rq)
ri, rq, di, dq = 1234, -5678, 9012, -3456 pkt += struct.pack(">h", ri)
rword = (((rq & 0xFFFF) << 16) | (ri & 0xFFFF)) & 0xFFFFFFFF pkt += struct.pack(">h", di)
pkt += struct.pack(">I", rword) pkt += struct.pack(">h", dq)
for s in [8, 16, 24]:
pkt += struct.pack(">I", (rword << s) & 0xFFFFFFFF)
dword = (((di & 0xFFFF) << 16) | (dq & 0xFFFF)) & 0xFFFFFFFF
pkt += struct.pack(">I", dword)
for s in [8, 16, 24]:
pkt += struct.pack(">I", (dword << s) & 0xFFFFFFFF)
pkt.append(1) pkt.append(1)
pkt.append(FOOTER_BYTE) pkt.append(FOOTER_BYTE)
self.assertEqual(len(pkt), DATA_PACKET_SIZE)
result = RadarProtocol.parse_data_packet(bytes(pkt)) result = RadarProtocol.parse_data_packet(bytes(pkt))
self.assertIsNotNone(result) self.assertIsNotNone(result)
self.assertEqual(result["range_i"], ri) self.assertEqual(result["range_i"], ri)
@@ -497,8 +483,8 @@ class TestReplayConnection(unittest.TestCase):
from radar_protocol import ReplayConnection from radar_protocol import ReplayConnection
conn = ReplayConnection(self.NPY_DIR, use_mti=True) conn = ReplayConnection(self.NPY_DIR, use_mti=True)
conn.open() conn.open()
# Each packet is 35 bytes, total = 2048 * 35 # Each packet is 11 bytes, total = 2048 * 11
expected_bytes = NUM_CELLS * 35 expected_bytes = NUM_CELLS * DATA_PACKET_SIZE
self.assertEqual(conn._frame_len, expected_bytes) self.assertEqual(conn._frame_len, expected_bytes)
conn.close() conn.close()
@@ -548,7 +534,7 @@ class TestReplayConnection(unittest.TestCase):
from radar_protocol import ReplayConnection from radar_protocol import ReplayConnection
conn = ReplayConnection(self.NPY_DIR, use_mti=False) conn = ReplayConnection(self.NPY_DIR, use_mti=False)
conn.open() conn.open()
self.assertEqual(conn._frame_len, NUM_CELLS * 35) self.assertEqual(conn._frame_len, NUM_CELLS * DATA_PACKET_SIZE)
# No-MTI with DC notch=2 and default CFAR → 0 detections # No-MTI with DC notch=2 and default CFAR → 0 detections
raw = conn._packets raw = conn._packets
boundaries = RadarProtocol.find_packet_boundaries(raw) boundaries = RadarProtocol.find_packet_boundaries(raw)
+144
View File
@@ -0,0 +1,144 @@
# Contributing to PLFM_RADAR (AERIS-10)
Thanks for your interest in the project! This guide covers the basics
for getting a change reviewed and merged.
## Getting started
1. Fork the repository and create a topic branch from `develop`.
2. Keep generated outputs (Vivado projects, bitstreams, build logs)
out of version control — the `.gitignore` already covers most of
these.
## Repository layout
| Path | Contents |
|------|----------|
| `4_Schematics and Boards Layout/` | KiCad schematics, Gerbers, BOM/CPL |
| `9_Firmware/9_2_FPGA/` | Verilog RTL, constraints, testbenches, build scripts |
| `9_Firmware/9_2_FPGA/formal/` | SymbiYosys formal-verification wrappers |
| `9_Firmware/9_2_FPGA/scripts/` | Vivado TCL build & debug scripts |
| `9_Firmware/9_3_GUI/` | Python radar dashboard (Tkinter + matplotlib) |
| `docs/` | GitHub Pages documentation site |
## Before submitting a pull request
- **Python** — verify syntax: `python3 -m py_compile <file>`
- **Verilog** — if you have Vivado, run the relevant `build*.tcl`;
if not, note which scripts your change affects
- **Whitespace** — `git diff --check` should be clean
- Keep PRs focused: one logical change per PR is easier to review
- **Run the regression tests** (see below)
## Running regression tests
After any change, run the relevant test suites to verify nothing is
broken. All commands assume you are at the repository root.
### Prerequisites
| Tool | Used by | Install |
|------|---------|---------|
| [Icarus Verilog](http://iverilog.icarus.com/) (`iverilog`) | FPGA regression | `brew install icarus-verilog` / `apt install iverilog` |
| Python 3.8+ | GUI tests, co-sim | Usually pre-installed |
| GNU Make | MCU tests | Usually pre-installed |
| [SymbiYosys](https://symbiyosys.readthedocs.io/) (`sby`) | Formal verification | Optional — see SymbiYosys docs |
### FPGA regression (RTL lint + unit/integration/signal-processing tests)
```bash
cd 9_Firmware/9_2_FPGA
bash run_regression.sh
```
This runs four phases:
| Phase | What it checks |
|-------|----------------|
| 0 — Lint | `iverilog -Wall` on all production RTL + static regex checks |
| 1 — Changed Modules | Unit tests for individual blocks (CIC, Doppler, CFAR, etc.) |
| 2 — Integration | DDC chain, receiver golden-compare, system-top, end-to-end |
| 3 — Signal Processing | FFT engine, NCO, FIR, matched filter chain |
| 4 — Infrastructure | CDC modules, edge detector, USB interface, range-bin decimator, mode controller |
All tests must pass (exit code 0). Advisory lint warnings (e.g., `case
without default`) are non-blocking.
### MCU unit tests
```bash
cd 9_Firmware/9_1_Microcontroller/tests
make clean && make all
```
Runs 20 C-based unit tests covering safety, bug-fix, and gap-3 tests.
Every test binary must exit 0.
### GUI / dashboard tests
```bash
cd 9_Firmware/9_3_GUI
python3 -m pytest test_radar_dashboard.py -v
# or without pytest:
python3 -m unittest test_radar_dashboard -v
```
57+ protocol and rendering tests. The `test_record_and_stop` test
requires `h5py` and will be skipped if it is not installed.
### Co-simulation (Python vs RTL golden comparison)
Run from the co-sim directory after a successful FPGA regression (the
regression generates the RTL CSV outputs that the co-sim scripts compare
against):
```bash
cd 9_Firmware/9_2_FPGA/tb/cosim
# Validate all .mem files (twiddles, chirp ROMs, addressing)
python3 validate_mem_files.py
# DDC chain: RTL vs Python model (5 scenarios)
python3 compare.py dc
python3 compare.py single_target
python3 compare.py multi_target
python3 compare.py noise_only
python3 compare.py sine_1mhz
# Doppler processor: RTL vs golden reference
python3 compare_doppler.py stationary
# Matched filter: RTL vs Python model (4 scenarios)
python3 compare_mf.py all
```
Each script prints PASS/FAIL per scenario and exits non-zero on failure.
### Formal verification (optional)
Requires SymbiYosys (`sby`), Yosys, and a solver (z3 or boolector):
```bash
cd 9_Firmware/9_2_FPGA/formal
sby -f fv_doppler_processor.sby
sby -f fv_radar_mode_controller.sby
```
### Quick checklist
Before pushing, confirm:
1. `bash run_regression.sh` — all phases pass
2. `make all` (MCU tests) — 20/20 pass
3. `python3 -m unittest test_radar_dashboard -v` — all pass
4. `python3 validate_mem_files.py` — all checks pass
5. `python3 compare.py dc && python3 compare_doppler.py stationary && python3 compare_mf.py all`
6. `git diff --check` — no whitespace issues
## Areas where help is especially welcome
See the list in [README.md](README.md#-contributing).
## Questions?
Open a GitHub issue — that way the discussion is visible to everyone.
+7 -7
View File
@@ -49,7 +49,7 @@ The AERIS-10 main sub-systems are:
- **2x Microwave Mixers (LT5552)** - For up-conversion and IF-down-conversion - **2x Microwave Mixers (LT5552)** - For up-conversion and IF-down-conversion
- **4x 4-Channel Phase Shifters (ADAR1000)** - For RX and TX chain beamforming - **4x 4-Channel Phase Shifters (ADAR1000)** - For RX and TX chain beamforming
- **16x Front End Chips (ADTR1107)** - Used for both Low Noise Amplifying (RX) and Power Amplifying (TX) stages - **16x Front End Chips (ADTR1107)** - Used for both Low Noise Amplifying (RX) and Power Amplifying (TX) stages
- **XC7A100T FPGA** - Handles RADAR Signal Processing: - **XC7A50T FPGA** - Handles RADAR Signal Processing on the upstream FTG256 board:
- PLFM Chirps generation via the DAC - PLFM Chirps generation via the DAC
- Raw ADC data read - Raw ADC data read
- Automatic Gain Control (AGC) - Automatic Gain Control (AGC)
@@ -150,11 +150,11 @@ To keep the repository root clean and make artifacts easy to find, place generat
### Hardware Assembly ### Hardware Assembly
1. **Order PCBs**: All Gerber files are available in `/4_Schematics and Boards Layout` 1. **Order PCBs**: Production outputs are under `/4_Schematics and Boards Layout/4_7_Production Files`
2. **Source Components**: Bill of materials (BOM) in `/4_Schematics and Boards Layout/4_7_Production Files` 2. **Source Components**: BOM/CPL files are co-located under `/4_Schematics and Boards Layout/4_7_Production Files`
3. **Assembly**: Follow the assembly guide in `/10_docs/assembly_guide.md` 3. **Assembly**: Use the schematics in `/4_Schematics and Boards Layout/4_6_Schematics` together with the production outputs above; a standalone assembly guide is not currently tracked
4. **Antenna**: Choose appropriate array for your version 4. **Antenna**: Choose appropriate array files for your target variant
5. **Enclosure**: 3D printable files in `/10_docs/Hardware/Enclosure` 5. **Enclosure**: Mechanical drawings currently live in `/8_Utils/Mechanical_Drawings`
## 📜 License ## 📜 License
@@ -207,7 +207,7 @@ Comprehensive documentation is available in the `/docs` folder and served via Gi
## 🤝 Contributing ## 🤝 Contributing
We welcome contributions! Please see our [Contributing Guidelines](/CONTRIBUTING.md) for details. We welcome contributions! Please see our [Contributing Guidelines](/CONTRIBUTING.md) for details on repo layout, branch workflow, and basic PR checks.
Areas where help is especially appreciated: Areas where help is especially appreciated:
- **RF Engineers**: Review designs, optimize antenna performance - **RF Engineers**: Review designs, optimize antenna performance