Merge pull request #46 from NawfalMotii79/develop
Merge the develop branch into main with the fixed changes.
This commit is contained in:
@@ -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),
|
||||||
|
|||||||
@@ -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 (unused — no signal connections)
|
# Bank 35: VCCO = 3.3V (FT2232H USB 2.0 FIFO — 15 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 (ADBUS0–ADBUS7)
|
||||||
|
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
|
||||||
|
|||||||
@@ -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 CLK→P 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 CLK→P 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;
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
||||||
|
|
||||||
// ================================================================
|
// ================================================================
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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,67 +682,143 @@ 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)
|
||||||
// ============================================================================
|
// ============================================================================
|
||||||
|
|
||||||
usb_data_interface usb_inst (
|
generate
|
||||||
.clk(clk_100m_buf),
|
if (USB_MODE == 0) begin : gen_ft601
|
||||||
.reset_n(sys_reset_n),
|
// ---- FT601 USB 3.0 (32-bit, 200T premium board) ----
|
||||||
.ft601_reset_n(sys_reset_ft601_n), // FT601-domain synchronized reset
|
usb_data_interface usb_inst (
|
||||||
|
.clk(clk_100m_buf),
|
||||||
|
.reset_n(sys_reset_n),
|
||||||
|
.ft601_reset_n(sys_reset_ft601_n),
|
||||||
|
|
||||||
// Radar data inputs
|
// Radar data inputs
|
||||||
.range_profile(usb_range_profile),
|
.range_profile(usb_range_profile),
|
||||||
.range_valid(usb_range_valid),
|
.range_valid(usb_range_valid),
|
||||||
.doppler_real(usb_doppler_real),
|
.doppler_real(usb_doppler_real),
|
||||||
.doppler_imag(usb_doppler_imag),
|
.doppler_imag(usb_doppler_imag),
|
||||||
.doppler_valid(usb_doppler_valid),
|
.doppler_valid(usb_doppler_valid),
|
||||||
.cfar_detection(usb_detect_flag),
|
.cfar_detection(usb_detect_flag),
|
||||||
.cfar_valid(usb_detect_valid),
|
.cfar_valid(usb_detect_valid),
|
||||||
|
|
||||||
// FT601 Interface
|
// FT601 Interface
|
||||||
.ft601_data(ft601_data),
|
.ft601_data(ft601_data),
|
||||||
.ft601_be(ft601_be),
|
.ft601_be(ft601_be),
|
||||||
.ft601_txe_n(ft601_txe_n),
|
.ft601_txe_n(ft601_txe_n),
|
||||||
.ft601_rxf_n(ft601_rxf_n),
|
.ft601_rxf_n(ft601_rxf_n),
|
||||||
.ft601_txe(ft601_txe),
|
.ft601_txe(ft601_txe),
|
||||||
.ft601_rxf(ft601_rxf),
|
.ft601_rxf(ft601_rxf),
|
||||||
.ft601_wr_n(ft601_wr_n),
|
.ft601_wr_n(ft601_wr_n),
|
||||||
.ft601_rd_n(ft601_rd_n),
|
.ft601_rd_n(ft601_rd_n),
|
||||||
.ft601_oe_n(ft601_oe_n),
|
.ft601_oe_n(ft601_oe_n),
|
||||||
.ft601_siwu_n(ft601_siwu_n),
|
.ft601_siwu_n(ft601_siwu_n),
|
||||||
.ft601_srb(ft601_srb),
|
.ft601_srb(ft601_srb),
|
||||||
.ft601_swb(ft601_swb),
|
.ft601_swb(ft601_swb),
|
||||||
.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),
|
||||||
.status_radar_mode(host_radar_mode),
|
.status_radar_mode(host_radar_mode),
|
||||||
.status_long_chirp(host_long_chirp_cycles),
|
.status_long_chirp(host_long_chirp_cycles),
|
||||||
.status_long_listen(host_long_listen_cycles),
|
.status_long_listen(host_long_listen_cycles),
|
||||||
.status_guard(host_guard_cycles),
|
.status_guard(host_guard_cycles),
|
||||||
.status_short_chirp(host_short_chirp_cycles),
|
.status_short_chirp(host_short_chirp_cycles),
|
||||||
.status_short_listen(host_short_listen_cycles),
|
.status_short_listen(host_short_listen_cycles),
|
||||||
.status_chirps_per_elev(host_chirps_per_elev),
|
.status_chirps_per_elev(host_chirps_per_elev),
|
||||||
.status_range_mode(host_range_mode),
|
.status_range_mode(host_range_mode),
|
||||||
|
|
||||||
// Self-test status readback
|
// Self-test status readback
|
||||||
.status_self_test_flags(self_test_flags_latched),
|
.status_self_test_flags(self_test_flags_latched),
|
||||||
.status_self_test_detail(self_test_detail_latched),
|
.status_self_test_detail(self_test_detail_latched),
|
||||||
.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
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -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 ""
|
||||||
|
|
||||||
|
|||||||
+51
-60
@@ -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 (5→4 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"
|
||||||
puts $summary_fh " Delta WNS vs B20: [expr {$wns - 0.426}] ns"
|
if {[string is double -strict $wns]} {
|
||||||
puts $summary_fh " Delta WHS vs B20: [expr {$whs - 0.058}] 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"
|
||||||
|
} 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."
|
|
||||||
+1
-1
@@ -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"]
|
||||||
+1
-1
@@ -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"]
|
||||||
+1
-1
@@ -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"]
|
||||||
+6
-3
@@ -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}"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
+4
-2
@@ -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"
|
||||||
+4
-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
-2
@@ -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 report_dir "${project_dir}/reports_impl"
|
set project_root [file normalize [file join $script_dir "../.."]]
|
||||||
|
set project_dir [file join $project_root "build"]
|
||||||
|
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,108 +649,109 @@ 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
|
||||||
windowed_i = np.zeros(n_fft, dtype=np.int64)
|
for sf in range(2):
|
||||||
windowed_q = np.zeros(n_fft, dtype=np.int64)
|
chirp_start = sf * n_sf
|
||||||
for k in range(n_fft):
|
bin_offset = sf * n_fft
|
||||||
# 16-bit x 16-bit = 32-bit, then round and shift >>> 15
|
|
||||||
mult_i = chirp_i[k] * hamming[k]
|
|
||||||
mult_q = chirp_q[k] * hamming[k]
|
|
||||||
windowed_i[k] = saturate((mult_i + (1 << 14)) >> 15, 16)
|
|
||||||
windowed_q[k] = saturate((mult_q + (1 << 14)) >> 15, 16)
|
|
||||||
|
|
||||||
# 32-point FFT (same algorithm as range FFT, different N)
|
windowed_i = np.zeros(n_fft, dtype=np.int64)
|
||||||
LOG2N_32 = 5
|
windowed_q = np.zeros(n_fft, dtype=np.int64)
|
||||||
mem_re = np.zeros(n_fft, dtype=np.int64)
|
for k in range(n_fft):
|
||||||
mem_im = np.zeros(n_fft, dtype=np.int64)
|
ci = chirp_i[chirp_start + k]
|
||||||
|
cq = chirp_q[chirp_start + k]
|
||||||
|
mult_i = ci * hamming[k]
|
||||||
|
mult_q = cq * hamming[k]
|
||||||
|
windowed_i[k] = saturate((mult_i + (1 << 14)) >> 15, 16)
|
||||||
|
windowed_q[k] = saturate((mult_q + (1 << 14)) >> 15, 16)
|
||||||
|
|
||||||
# Bit-reversed loading, sign-extend to 32-bit
|
mem_re = np.zeros(n_fft, dtype=np.int64)
|
||||||
for n in range(n_fft):
|
mem_im = np.zeros(n_fft, dtype=np.int64)
|
||||||
br = 0
|
|
||||||
for b in range(LOG2N_32):
|
|
||||||
if n & (1 << b):
|
|
||||||
br |= (1 << (LOG2N_32 - 1 - b))
|
|
||||||
mem_re[br] = windowed_i[n]
|
|
||||||
mem_im[br] = windowed_q[n]
|
|
||||||
|
|
||||||
# Butterfly stages
|
for n in range(n_fft):
|
||||||
half = 1
|
br = 0
|
||||||
for stg in range(LOG2N_32):
|
for b in range(LOG2N_16):
|
||||||
for bfly in range(n_fft // 2):
|
if n & (1 << b):
|
||||||
idx = bfly & (half - 1)
|
br |= (1 << (LOG2N_16 - 1 - b))
|
||||||
grp = bfly - idx
|
mem_re[br] = windowed_i[n]
|
||||||
addr_even = (grp << 1) | idx
|
mem_im[br] = windowed_q[n]
|
||||||
addr_odd = addr_even + half
|
|
||||||
|
|
||||||
tw_idx = (idx << (LOG2N_32 - 1 - stg)) % (n_fft // 2)
|
half = 1
|
||||||
tw_cos, tw_sin = fft_twiddle_lookup(tw_idx, n_fft, cos_rom_32)
|
for stg in range(LOG2N_16):
|
||||||
|
for bfly in range(n_fft // 2):
|
||||||
|
idx = bfly & (half - 1)
|
||||||
|
grp = bfly - idx
|
||||||
|
addr_even = (grp << 1) | idx
|
||||||
|
addr_odd = addr_even + half
|
||||||
|
|
||||||
a_re = mem_re[addr_even]
|
tw_idx = (idx << (LOG2N_16 - 1 - stg)) % (n_fft // 2)
|
||||||
a_im = mem_im[addr_even]
|
tw_cos, tw_sin = fft_twiddle_lookup(tw_idx, n_fft, cos_rom_16)
|
||||||
b_re = mem_re[addr_odd]
|
|
||||||
b_im = mem_im[addr_odd]
|
|
||||||
|
|
||||||
prod_re = b_re * tw_cos + b_im * tw_sin
|
a_re = mem_re[addr_even]
|
||||||
prod_im = b_im * tw_cos - b_re * tw_sin
|
a_im = mem_im[addr_even]
|
||||||
|
b_re = mem_re[addr_odd]
|
||||||
|
b_im = mem_im[addr_odd]
|
||||||
|
|
||||||
prod_re_shifted = prod_re >> 15
|
prod_re = b_re * tw_cos + b_im * tw_sin
|
||||||
prod_im_shifted = prod_im >> 15
|
prod_im = b_im * tw_cos - b_re * tw_sin
|
||||||
|
|
||||||
mem_re[addr_even] = a_re + prod_re_shifted
|
prod_re_shifted = prod_re >> 15
|
||||||
mem_im[addr_even] = a_im + prod_im_shifted
|
prod_im_shifted = prod_im >> 15
|
||||||
mem_re[addr_odd] = a_re - prod_re_shifted
|
|
||||||
mem_im[addr_odd] = a_im - prod_im_shifted
|
|
||||||
|
|
||||||
half <<= 1
|
mem_re[addr_even] = a_re + prod_re_shifted
|
||||||
|
mem_im[addr_even] = a_im + prod_im_shifted
|
||||||
|
mem_re[addr_odd] = a_re - prod_re_shifted
|
||||||
|
mem_im[addr_odd] = a_im - prod_im_shifted
|
||||||
|
|
||||||
# Saturate 32-bit → 16-bit
|
half <<= 1
|
||||||
for n in range(n_fft):
|
|
||||||
doppler_map_i[rbin, n] = saturate(mem_re[n], 16)
|
|
||||||
doppler_map_q[rbin, n] = saturate(mem_im[n], 16)
|
|
||||||
|
|
||||||
print(f" Doppler map: shape ({n_range}, {n_fft}), "
|
for n in range(n_fft):
|
||||||
|
doppler_map_i[rbin, bin_offset + n] = saturate(mem_re[n], 16)
|
||||||
|
doppler_map_q[rbin, bin_offset + n] = saturate(mem_im[n], 16)
|
||||||
|
|
||||||
|
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
@@ -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,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("============================================================");
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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, 1024→64)
|
* range_bin_decimator (peak detection, 1024→64)
|
||||||
* → 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);
|
||||||
|
|||||||
@@ -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:
|
||||||
|
|||||||
@@ -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 (FPGA→Host): 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 (FPGA→Host): 26 bytes
|
||||||
|
* Byte 0: 0xBB (status header)
|
||||||
|
* Bytes 1-24: 6 × 32-bit status words, MSB first
|
||||||
|
* Byte 25: 0x55 (footer)
|
||||||
|
*
|
||||||
|
* Command (Host→FPGA): 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
|
||||||
@@ -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.
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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)
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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 1–3 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
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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
@@ -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.
|
||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user