Implement generic MAX2771 raw Ethernet recorder

This commit is contained in:
onur
2026-08-01 01:15:47 +03:00
commit 79d07fdda7
57 changed files with 4810 additions and 0 deletions
+19
View File
@@ -0,0 +1,19 @@
.PHONY: sim build program selftest-build selftest-program clean-help
sim:
./scripts/run_sim.sh
build:
./scripts/build_efinity.sh
program:
./scripts/program_efinity.sh
selftest-build:
./scripts/build_ethernet_selftest.sh
selftest-program:
./scripts/program_ethernet_selftest.sh
clean-help:
@echo "Generated files are under build/. Remove that directory manually if a clean build is required."
+232
View File
@@ -0,0 +1,232 @@
# MAX2771 raw recorder — Efinix Titanium Ti375 C529 kit
This is a separate board target for the Efinix Titanium Ti375 C529 development
kit. It does not modify the Genesys 2 top-level, constraints, or Vivado flow.
The protocol-compatible capture FIFO, packetizer, CRC and raw Ethernet MAC are
the read-only common RTL in `../../rtl/common`; every Efinix-specific source is
contained in this directory and the source list is explicit in
`scripts/generate_project.py`.
No RISC-V/SoC block, CPU, AXI fabric, generated Ethernet MAC, IP stack, or
Efinix Ethernet IP is used. Only the Efinity periphery resources needed for
GPIO DDIO and PLL clocking are generated.
## Data path
```text
MAX2771 J13/PMOD0
clock -> A12/GPIOR_95_CLK12 global clock -> GPIO DDR input registers
data ---------------------> rising/falling sample selection
-> sample index + loss marker
-> handwritten 16,384 x 69 async FIFO
-> 2936-sample packetizer
-> handwritten Ethernet MAC/FCS
100 MHz oscillator -> Ethernet PLL -> 125 MHz / 125 MHz +90 degrees
-> GPIO DDIO RGMII -> RTL8211FDI -> RJ45
```
`CAPTURE_RISING=1` selects the GPIO `_HI` samples; `0` selects `_LO` samples.
`EARLIER_SAMPLE_HIGH_NIBBLE=1` produces the required byte order: earlier sample
in bits 7:4, later sample in bits 3:0.
## MAX2771 wiring
Use J13, identified as PMOD0 in the official kit guide. The PMOD header is
3.3 V. Do not reuse the Genesys 2 JA ball names on this board.
| RTL signal | J13 pin | Ti375 logical pin | C529 ball | Level |
|---|---:|---|---|---|
| RF clock input | 10 | GPIOR_95_CLK12 | A12 | 3.3 V |
| data bit 0 | 2 | GPIOR_89 | G16 | 3.3 V |
| data bit 1 | 3 | GPIOR_90 | F16 | 3.3 V |
| data bit 2 | 4 | GPIOR_91 | F17 | 3.3 V |
| data bit 3 | 7 | GPIOR_92 | G17 | 3.3 V |
| ground | 5 or 11 | — | — | 0 V |
| PMOD supply | 6 or 12 | — | — | 3.3 V |
The MAX2771 clock-to-data minimum/maximum values and cable/adapter skew are not
known. Interface Designer generates constraints for its internal GPIO register
paths; those numbers are not a substitute for MAX2771 external timing. Measure
or obtain the external values before claiming complete board-level RF input
timing closure. See `docs/hardware_assumptions.md`.
## Ethernet hardware
The board PHY is RTL8211FDI and the RGMII bus is 1.8 V. The board straps PHY
address 2, receive delay enabled and transmit delay disabled. Receive is not
implemented. The FPGA creates the required transmit-clock phase shift with a
125 MHz, +90-degree PLL output and sends TXD/TXCTL with the unshifted 125 MHz
clock. PHY reset remains low for 10 ms, followed by a 30 ms settle period.
The MDIO controller selects Realtek page `0xa43`, reads PHYSR register `0x1a`,
restores page 0, and only asserts `link_ready` for real-time link=1,
speed=1000 Mb/s and full duplex. A 100 Mb/s link deliberately transmits no
frames: the 130.944 Mb/s raw sample stream already exceeds a 100 Mb/s line
before Ethernet overhead.
## Clocks
| Function | Source | PLL | Outputs |
|---|---|---|---|
| Ethernet | board 100 MHz oscillator, U4 | PLL_BL0, EXT_CLK1 | CLK0 100 MHz dummy, CLK1 125 MHz, CLK2 125 MHz +90° |
| RF capture | MAX2771 clock, J13.10/A12 | direct GPIOR_95_CLK12 GCLK | nominal 32.736 MHz |
The 100 MHz dummy output satisfies the Titanium CLKOUT0 restriction. The
Ethernet PLL outputs and their physical reference resources are checked by the Efinity
Interface Designer API during every build. The direct RF GCLK period defaults
to 30.547 ns and can be overridden with `RF_CLOCK_PERIOD_NS` when the actual
MAX2771 clock differs.
## Raw Ethernet protocol
Frames use destination `ff:ff:ff:ff:ff:ff`, source `02:27:71:00:00:01`, and
EtherType `0x88b5`. A normal Ethernet payload is exactly 1500 bytes: the
32-byte `MX27` header followed by 1468 packed bytes/2936 samples. All metadata
is network byte order. The byte protocol is unchanged from
[`docs/raw_ethernet_protocol.md`](../../docs/raw_ethernet_protocol.md).
The FPGA emits preamble, SFD, Ethernet header, payload, FCS and 12-byte IFG.
Normal host NICs remove preamble/SFD/FCS before delivery to `AF_PACKET`.
## Efinity headless CLI build
“Non-project mode” is Vivado terminology and is not used here. This target has
the Efinity equivalent headless flow: scripts generate the periphery and a
temporary project XML, then invoke the supported command-line compile flow.
The installed path on this machine is:
```bash
export EFINITY_HOME=/tools/efinix/efinity/2026.1
cd targets/efinix_ti375_c529
make build
```
Outputs are under `build/outflow/`, including the pinout, interface timing,
synthesis, P&R, timing reports and `ti375_max2771_recorder.bit`. The script
fails on API/DRC/compile errors, absent required clocks, missing timing report,
negative timing slack, or missing bitstream.
The final A12/GPIOR_95 Efinity 2026.1 C4 build completed successfully on
1 August 2026. Post-route slack was +0.636 ns setup/+0.094 ns hold at 125 MHz
and +25.614 ns setup/+0.031 ns hold at 32.736 MHz. The generated CDC report says
`No Synchronizer warnings to report`. Placement used 36,253/362,880 XLRs
(9.99%), 138/2,688 memory blocks (5.13%), and no DSP blocks.
The kit guide revision describes a C4 timing grade and the local Efinity kit
example also uses C4. Confirm the marking on the actual FPGA. Override only the
timing model, not the base package device, when necessary:
```bash
EFINIX_TIMING_MODEL=I3 make build
```
## Programming
Connect the board USB/JTAG interface, power the fan as required by the kit, and
run:
```bash
export EFINITY_HOME=/tools/efinix/efinity/2026.1
make program
```
This calls `efx_run.py ... --flow program --pgm_opts mode=jtag` with the
generated `.bit` file. It does not alter the boot flash.
For an RF-independent physical Ethernet test, a separate diagnostic top-level
generates the repeating nibble sequence 0 through 15 and uses the production
packetizer, MAC, FCS, MDIO and RGMII blocks:
```bash
make selftest-build
make selftest-program
```
Restore the recorder afterwards with `make program`; JTAG configuration is
volatile. On the tested board/direct cable, a 3.034-second self-test capture
received 35,446 consecutive frames/104,069,456 samples with no protocol gap,
FPGA drop, socket drop, or pattern mismatch.
## Host capture
The raw protocol and host application are board-independent:
```bash
make -C ../../host
sudo ip link set dev enp3s0 up
sudo ip addr flush dev enp3s0
sudo ethtool enp3s0
sudo ethtool -K enp3s0 gro off lro off # unsupported options may be ignored
sudo sysctl -w net.core.rmem_max=268435456
sudo ../../host/max2771_raw_rx \
--interface enp3s0 \
--output max2771_capture.bin \
--gap-mode zero-fill
```
No IP address, ARP or firewall change is required. Prefer a direct cable or an
isolated switch because every frame is broadcast. For non-root operation:
```bash
sudo setcap cap_net_raw,cap_net_admin=eip ../../host/max2771_raw_rx
```
Use a dedicated Gigabit NIC and storage capable of sustained writes above
16.368 MB/s.
## Throughput
At 32.736 Msps, the source is 130.944 Mb/s (16.368 MB/s). Full frames occur at
approximately 11,149.864 frames/s. Including preamble/SFD, MAC header, 1500-byte
payload, FCS and IFG, each frame occupies 1538 byte-times, or 12,304 bits. Line
use is approximately 137.188 Mb/s, 13.719% of 1 Gb/s; raw line-rate margin is
about 7.29x.
The current packetizer first loads a frame buffer and then transmits it. At 125
MHz, its conservative three-clock read handshake loads 2936 samples in about
70.464 us; the frame plus IFG takes 12.304 us. This gives about 8.3% scheduler
margin over the required frame rate. During loading the FIFO drains faster than
the RF source; during frame transmission about 403 new samples arrive. In
steady state it reaches the 2936-sample launch threshold, finishes near 2709
samples, then refills. Link-down occupancy can instead reach the full 16,384
entries; all further input samples are counted as drops, never overwritten.
## LEDs
| LED | Meaning |
|---|---|
| LED9 | Ethernet PLL/reset system ready; may toggle only while Ethernet PLL is acquiring lock |
| LED10 | qualified 1000/full link ready |
| LED11 | RF sample activity |
| LED12 | FIFO has data; frame-count divider gives visible TX activity |
| LED13 | FIFO overflow/sample loss latched |
There are five verified user LEDs, so FIFO and transmit activity share LED12.
## Simulation
```bash
cd targets/efinix_ti375_c529
make sim
```
The target tests verify RGMII half-cycle/control mapping and RTL8211FDI PHYSR
link qualification/link-down counting. The script then runs the full shared
recorder regression: standard CRC vector, complete Ethernet bytes/FCS/IFG,
random async FIFO CDC, two-frame sample reconstruction, header/index/sequence,
reset behavior and overflow handling.
## Troubleshooting
- No Ethernet frames: verify `ethtool` reports 1000 Mb/s/full duplex and LED10
is on. A 100 Mb/s negotiation is intentionally rejected.
- LED13 on before link: capture cannot be backpressured; the FIFO fills while
link is unavailable, and the loss is correctly counted.
- Interface DRC failure: check that the base device is `Ti375C529` and the
board wiring/pins have not been changed.
- RF timing failure or corrupted nibbles: confirm 3.3 V levels, ground, capture
edge and the actual MAX2771 clock-to-data window with measurement.
- Efinity warns about `libstdc++.so`: this is emitted by the installed 2026.1
setup script on this host. Do not delete tool files automatically; follow the
vendor’s installation guidance if the tools fail to run.
@@ -0,0 +1,26 @@
# Core timing constraints for the Ti375 C529 target. The Efinity-generated
# outflow/ti375_max2771_recorder.pt.sdc is prepended by build_efinity.sh and
# contains the periphery clock constraints.
# The external 100 MHz Ethernet reference, its PLL outputs, and the direct
# 32.736 MHz GPIOR_95_CLK12 input are constrained in the generated periphery
# SDC. Their core-side net names include eth_clk and rf_clk_pmod; no duplicate
# create_clock is added here.
# MAX2771 sampling is source-synchronous but exact clock-to-data plus board
# skew is not supplied by the MAX2771 connection specification. Do not invent
# set_input_delay numbers. Add measured/datasheet-derived -min and -max values
# here before claiming external I/O timing closure, for example:
# set_input_delay -clock rf_clk_pmod -max <MEASURED_MAX_NS> [get_ports {rf_data_pmod[*]}]
# set_input_delay -clock rf_clk_pmod -min <MEASURED_MIN_NS> [get_ports {rf_data_pmod[*]}]
# Only the RF and Ethernet clocks are asynchronous. FIFO Gray-pointer
# synchronizers, the Gray counter synchronizer and pulse synchronizer are the
# deliberate CDC paths. No functional RF-to-Ethernet combinational path exists.
set_clock_groups -asynchronous \
-group [get_clocks {rf_clk_pmod}] \
-group [get_clocks {eth_clk eth_clk90 pll_dummy_clk}]
# Efinity's async_reg attribute keeps the two synchronizer stages adjacent.
# The RTL also limits CDC to Gray-coded pointers/counters or single-bit toggles;
# no blanket false path is applied to unrelated logic.
@@ -0,0 +1,26 @@
# Headless Efinity build flow
This target does not call itself “non-project mode”; that is a Vivado-specific
term. Efinity requires a project XML for `efx_run.py`. The XML is generated on
every command-line build and is not maintained through the GUI.
1. `generate_project.py` creates the device/source/constraint XML.
2. `create_interface.py` uses the supported Efinity Python API to create PLL,
GPIO, DDIO, pin and I/O-standard resources and runs Interface Designer DRC.
3. The generated `.pt.sdc` is merged with the handwritten CDC constraints.
4. `efx_run.py --flow compile` performs synthesis, place-and-route and
bitstream generation.
5. `check_timing.py` rejects missing clocks and negative setup/hold slack.
6. `program_efinity.sh` uses the supported JTAG program flow.
No `.xci`, block design, processor, AXI system or generated Ethernet core is
created. The generated periphery is limited to low-level Titanium PLL and GPIO
register resources, equivalent in role to primitive instantiation on the
Genesys target.
The exact command on this workstation is:
```bash
export EFINITY_HOME=/tools/efinix/efinity/2026.1
make -C targets/efinix_ti375_c529 build
```
@@ -0,0 +1,67 @@
# Ti375 C529 hardware facts and assumptions
## Verified from official Efinix material
- Board: Titanium Ti375 C529 development kit, C529 package.
- On-board Ethernet is an RTL8211FDI 10/100/1000 transceiver with RGMII.
- PHY reset is active-low and must be asserted for at least 10 ms.
- PHY address straps are `010`, address 2.
- RGMII bank 2E and PHY `DVDD_RG` operate at 1.8 V.
- RX delay strap is high (about 2 ns); TX delay strap is low. This transmitter
therefore creates its own +90-degree RGMII transmit clock.
- J13/PMOD0 supports 3.3 V.
- Board oscillator U4 is 100 MHz on GPIOL_25_PLLIN1.
- Ethernet TX balls are TXCTL B20, TXC C17 and TXD0..3 B17,A16,A17,C19.
- MDC is B19, MDIO B14 and PHY reset D10.
- LED9..13 logical pins/balls are GPIOL_23/V4, GPIOL_24/U6,
GPIOT_N_50/L18, GPIOT_N_19/R20 and GPIOT_N_64_CLK15_N/E19.
The authoritative sources are the official kit page, development-kit user
guide, board schematic/BOM and Ti375 C529 pinout spreadsheet linked there.
## Verified by the installed Efinity 2026.1 device database
- U4/GPIOL_25_PLLIN1 connects to `PLL_BL0` external clock 1.
- A12 maps to the global-clock-capable `GPIOR_95_CLK12` resource; the RF clock
is used directly, without a sampling PLL.
- The 100 -> 100/125/125(+90°) MHz Ethernet PLL solution is legal for
Ti375C529 C4.
- The pin I/O-bank voltages used by `create_interface.py` pass Interface
Designer DRC.
## Assumptions supplied by the recorder specification
- MAX2771 clock nominally 32.736 MHz.
- One 4-bit sample is valid per selected clock edge.
- Data bits are recorded literally without signed conversion or reinterpretation.
- Earlier sample is normally packed in the high nibble.
- Ethernet destination/source/EtherType are broadcast,
`02:27:71:00:00:01`, and `0x88b5`.
## Wiring supplied for this Ti375 target
- RF clock: J13.10, A12, `GPIOR_95_CLK12`.
- RF data 0..3: J13.2/J13.3/J13.4/J13.7, G16/F16/F17/G17,
`GPIOR_89` through `GPIOR_92`.
- All five signals use `3.3_V_LVCMOS`.
## Must be confirmed from the actual hardware
- FPGA speed/temperature grade. The kit guide and installed Efinity example
use C4; current sales material may show a different ordering code. Read the
device marking and set `EFINIX_TIMING_MODEL` accordingly.
- The MAX2771 output electrical mode is compatible with 3.3 V LVCMOS at J13.
Do not connect a higher-voltage or differential output directly.
- Cable/adapter signal integrity is adequate at 32.736 MHz.
## Requires datasheet information or measurement
- MAX2771 clock-to-data minimum/maximum delay at the selected interface mode.
- Board/adapter trace skew and setup/hold margin at the Ti375 input register.
- Whether rising or falling-edge capture gives the intended word boundary.
- RGMII clock/data phase at the PHY pins. Confirm the nominal +2 ns relation
with an oscilloscope during initial bring-up if hardware acceptance requires
measured compliance.
Efinity’s generated `pt.sdc` input-delay values describe its internal interface
register path. They are tool-generated, not claimed MAX2771 timing numbers.
+154
View File
@@ -0,0 +1,154 @@
#!/usr/bin/env python3
"""Create the Ti375 C529 periphery with the supported Efinity Python API."""
import math
import os
import pprint
import sys
efinity_home = os.environ.get("EFINITY_HOME")
if not efinity_home:
raise SystemExit("EFINITY_HOME is not set; source $EFINITY_HOME/bin/setup.sh first")
sys.path.append(os.path.join(efinity_home, "pt", "bin"))
from api_service.design import DesignAPI # type: ignore # supplied by Efinity
PROJECT = os.environ.get("EFINITY_PROJECT", "ti375_max2771_recorder")
DEVICE = os.environ.get("EFINIX_DEVICE", "Ti375C529")
design = DesignAPI(is_verbose=True)
design.create(PROJECT, DEVICE, "./", overwrite=True)
def set_io(name, standard, drive=None, slew=None):
design.set_property(name, "IO_STANDARD", standard)
if drive is not None:
design.set_property(name, "DRIVE_STRENGTH", str(drive))
if slew is not None:
design.set_property(name, "SLEW_RATE", str(slew))
def output_gpio(name, pin, standard, drive=None, slew=None):
design.create_output_gpio(name)
design.assign_pkg_pin(name, pin)
set_io(name, standard, drive, slew)
# 100 MHz board oscillator on GPIOL_25_PLLIN1 (package ball U4). Using the
# left-side PLL keeps PLL_TR0 available for the MAX2771 clock on J13 pin 1.
design.create_block("eth_pll", block_type="PLL")
design.set_property("eth_pll", {"REFCLK_FREQ": "100.0"}, block_type="PLL")
design.gen_pll_ref_clock(
"eth_pll",
pll_res="PLL_BL0",
refclk_src="EXTERNAL",
refclk_name="clk100",
ext_refclk_no="1",
)
design.assign_pkg_pin("clk100", "U4")
set_io("clk100", "3.3_V_LVCMOS")
design.set_property("eth_pll", "LOCKED_PIN", "pll_locked", block_type="PLL")
design.set_property("eth_pll", "RSTN_PIN", "pll_resetn", block_type="PLL")
design.set_property("eth_pll", "CLKOUT0_PIN", "pll_dummy_clk", block_type="PLL")
design.set_property(
"eth_pll", {"CLKOUT1_EN": "1", "CLKOUT1_PIN": "eth_clk"}, block_type="PLL"
)
design.set_property(
"eth_pll", {"CLKOUT2_EN": "1", "CLKOUT2_PIN": "eth_clk90"}, block_type="PLL"
)
design.set_property("eth_pll", "FEEDBACK_MODE", "LOCAL", block_type="PLL")
design.set_property("eth_pll", "FEEDBACK_CLK", "CLK0", block_type="PLL")
target_freq = {
"CLKOUT0_FREQ": "100.0",
"CLKOUT0_PHASE": "0.0",
"CLKOUT1_FREQ": "125.0",
"CLKOUT1_PHASE": "0.0",
"CLKOUT2_FREQ": "125.0",
"CLKOUT2_PHASE": "90.0",
}
pll_results = design.auto_calc_pll_clock("eth_pll", target_freq)
if not pll_results:
raise SystemExit("Efinity could not find a legal 100 MHz -> 100/125/125 MHz PLL solution")
pprint.pprint(pll_results)
pll_props = design.get_property(
"eth_pll",
["CLKOUT0_FREQ", "CLKOUT1_FREQ", "CLKOUT2_FREQ", "CLKOUT2_PHASE"],
block_type="PLL",
)
for key, expected in (("CLKOUT0_FREQ", 100.0), ("CLKOUT1_FREQ", 125.0),
("CLKOUT2_FREQ", 125.0), ("CLKOUT2_PHASE", 90.0)):
if not math.isclose(float(pll_props[key]), expected, rel_tol=1.0e-6, abs_tol=1.0e-6):
raise SystemExit(f"PLL property {key} is {pll_props[key]}, expected {expected}")
# MAX2771 clock wiring supplied for this target: J13.10, package A12,
# GPIOR_95_CLK12. This is a dedicated global-clock-capable GPIO, so preserve
# the source-synchronous clock directly instead of passing it through a PLL.
design.create_input_clock_gpio("rf_clk_pmod")
design.assign_pkg_pin("rf_clk_pmod", "A12")
set_io("rf_clk_pmod", "3.3_V_LVCMOS")
design.create_input_gpio("rf_data_pmod", 3, 0)
for bit, pin in enumerate(("G16", "F16", "F17", "G17")):
design.assign_pkg_pin(f"rf_data_pmod[{bit}]", pin)
design.set_property("rf_data_pmod", "IN_REG", "DDIO_RESYNC_PIPE")
design.set_property("rf_data_pmod", "IN_CLK_PIN", "rf_clk_pmod")
design.set_property("rf_data_pmod", "IN_HI_PIN", "rf_data_pmod_HI")
design.set_property("rf_data_pmod", "IN_LO_PIN", "rf_data_pmod_LO")
set_io("rf_data_pmod", "3.3_V_LVCMOS")
# RTL8211FDI RGMII transmit side, all in bank 2E at 1.8 V.
design.create_output_gpio("eth_txd", 3, 0)
for bit, pin in enumerate(("B17", "A16", "A17", "C19")):
design.assign_pkg_pin(f"eth_txd[{bit}]", pin)
design.set_property("eth_txd", "OUT_REG", "DDIO_RESYNC")
design.set_property("eth_txd", "OUT_CLK_PIN", "eth_clk")
design.set_property("eth_txd", "OUT_HI_PIN", "eth_txd_HI")
design.set_property("eth_txd", "OUT_LO_PIN", "eth_txd_LO")
set_io("eth_txd", "1.8_V_LVCMOS", drive=16, slew=1)
design.create_output_gpio("eth_txctl")
design.assign_pkg_pin("eth_txctl", "B20")
design.set_property("eth_txctl", "OUT_REG", "DDIO_RESYNC")
design.set_property("eth_txctl", "OUT_CLK_PIN", "eth_clk")
design.set_property("eth_txctl", "OUT_HI_PIN", "eth_txctl_HI")
design.set_property("eth_txctl", "OUT_LO_PIN", "eth_txctl_LO")
set_io("eth_txctl", "1.8_V_LVCMOS", drive=16, slew=1)
design.create_output_gpio("eth_txc")
design.assign_pkg_pin("eth_txc", "C17")
design.set_property("eth_txc", "OUT_REG", "DDIO_RESYNC")
design.set_property("eth_txc", "OUT_CLK_PIN", "eth_clk90")
design.set_property("eth_txc", "OUT_HI_PIN", "eth_txc_HI")
design.set_property("eth_txc", "OUT_LO_PIN", "eth_txc_LO")
set_io("eth_txc", "1.8_V_LVCMOS", drive=16, slew=1)
output_gpio("eth_mdc", "B19", "1.8_V_LVCMOS", drive=8, slew=1)
output_gpio("eth_phyrst_n", "D10", "3.3_V_LVCMOS", drive=8, slew=0)
design.create_inout_gpio("eth_mdio")
design.assign_pkg_pin("eth_mdio", "B14")
design.set_property("eth_mdio", "IN_PIN", "eth_mdio_IN")
design.set_property("eth_mdio", "OUT_PIN", "eth_mdio_OUT")
design.set_property("eth_mdio", "OE_PIN", "eth_mdio_OE")
set_io("eth_mdio", "1.8_V_LVCMOS", drive=8, slew=0)
# LED9..LED13 span both 3.3 V and 1.8 V banks, so they are separate periphery
# blocks connected to members of the core-side user_led bus.
for name, bit, pin, standard in (
("led9", 0, "V4", "3.3_V_LVCMOS"),
("led10", 1, "U6", "3.3_V_LVCMOS"),
("led11", 2, "L18", "1.8_V_LVCMOS"),
("led12", 3, "R20", "1.8_V_LVCMOS"),
("led13", 4, "E19", "1.8_V_LVCMOS"),
):
design.create_output_gpio(name)
design.assign_pkg_pin(name, pin)
design.set_property(name, "OUT_PIN", f"user_led[{bit}]")
set_io(name, standard, drive=8, slew=0)
if not design.check_design():
raise SystemExit("Efinity Interface Designer DRC failed")
design.generate(enable_bitstream=True)
design.save()
@@ -0,0 +1,147 @@
`default_nettype none
// RTL8211FDI link qualification over IEEE 802.3 Clause-22 MDIO. The board
// straps the PHY for auto-negotiation and address 2. Realtek PHYSR is on page
// 0xa43, register 0x1a; it reports real-time link, resolved speed and duplex.
// Page 0 is restored after every poll so no hidden PHY state leaks outward.
module phy_init_rtl8211f #(
parameter int unsigned CLK_HZ = 125_000_000,
parameter int unsigned SETTLE_MS = 30,
parameter int unsigned POLL_MS = 100,
parameter logic [4:0] PHY_ADDRESS = 5'd2
) (
input wire logic clk,
input wire logic rst,
input wire logic phy_reset_complete,
output logic mdio_start,
output logic mdio_write,
output logic [4:0] mdio_phy_address,
output logic [4:0] mdio_register_address,
output logic [15:0] mdio_write_data,
input wire logic mdio_busy,
input wire logic mdio_done,
input wire logic [15:0] mdio_read_data,
output logic initialization_complete,
output logic link_ready,
output logic [63:0] link_down_events
);
localparam longint unsigned SETTLE_CYCLES = (CLK_HZ / 1000) * SETTLE_MS;
localparam longint unsigned POLL_CYCLES = (CLK_HZ / 1000) * POLL_MS;
localparam longint unsigned MAX_CYCLES =
(POLL_CYCLES > SETTLE_CYCLES) ? POLL_CYCLES : SETTLE_CYCLES;
localparam int unsigned TIMER_W = (MAX_CYCLES < 2) ? 1 : $clog2(MAX_CYCLES + 1);
typedef enum logic [3:0] {
S_WAIT_RESET, S_SETTLE,
S_START_PAGE, S_WAIT_PAGE,
S_START_PHYSR, S_WAIT_PHYSR,
S_START_RESTORE, S_WAIT_RESTORE,
S_POLL_WAIT
} state_t;
state_t state;
logic [TIMER_W-1:0] timer;
logic [15:0] physr;
logic qualified_link;
always_comb begin
mdio_write = (state == S_START_PAGE || state == S_WAIT_PAGE ||
state == S_START_RESTORE || state == S_WAIT_RESTORE);
mdio_phy_address = PHY_ADDRESS;
mdio_register_address = (state == S_START_PHYSR || state == S_WAIT_PHYSR)
? 5'h1a : 5'h1f;
mdio_write_data = (state == S_START_PAGE || state == S_WAIT_PAGE)
? 16'h0a43 : 16'h0000;
// PHYSR[5:4]=2'b10 is 1000 Mb/s, bit 3 full duplex, bit 2 link.
qualified_link = (physr[5:4] == 2'b10) && physr[3] && physr[2];
end
always_ff @(posedge clk) begin
if (rst) begin
state <= S_WAIT_RESET;
timer <= '0;
physr <= 16'd0;
mdio_start <= 1'b0;
initialization_complete <= 1'b0;
link_ready <= 1'b0;
link_down_events <= 64'd0;
end else begin
mdio_start <= 1'b0;
case (state)
S_WAIT_RESET: begin
link_ready <= 1'b0;
if (phy_reset_complete) begin
timer <= '0;
state <= S_SETTLE;
end
end
S_SETTLE: begin
if (timer == SETTLE_CYCLES-1) begin
timer <= '0;
state <= S_START_PAGE;
end else begin
timer <= timer + {{(TIMER_W-1){1'b0}}, 1'b1};
end
end
S_START_PAGE: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_PAGE;
end
end
S_WAIT_PAGE: if (mdio_done) state <= S_START_PHYSR;
S_START_PHYSR: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_PHYSR;
end
end
S_WAIT_PHYSR: begin
if (mdio_done) begin
physr <= mdio_read_data;
state <= S_START_RESTORE;
end
end
S_START_RESTORE: begin
if (!mdio_busy) begin
mdio_start <= 1'b1;
state <= S_WAIT_RESTORE;
end
end
S_WAIT_RESTORE: begin
if (mdio_done) begin
initialization_complete <= 1'b1;
if (link_ready && !qualified_link)
link_down_events <= link_down_events + 64'd1;
link_ready <= qualified_link;
timer <= '0;
state <= S_POLL_WAIT;
end
end
S_POLL_WAIT: begin
if (timer == POLL_CYCLES-1) begin
timer <= '0;
state <= S_START_PAGE;
end else begin
timer <= timer + {{(TIMER_W-1){1'b0}}, 1'b1};
end
end
default: state <= S_WAIT_RESET;
endcase
end
end
initial begin
if (CLK_HZ < 1000) $error("CLK_HZ must be at least 1000");
if (SETTLE_MS == 0 || POLL_MS == 0)
$error("SETTLE_MS and POLL_MS must be non-zero");
end
endmodule
`default_nettype wire
@@ -0,0 +1,31 @@
`default_nettype none
// Core-side half-cycle values for Efinity Interface Designer DDIO outputs.
// The physical GPIOs are configured as DDIO_RESYNC in interface/create_interface.py.
// *_HI is launched on the rising edge and *_LO on the falling edge.
module rgmii_tx_efinix (
input wire logic [7:0] gmii_txd,
input wire logic gmii_tx_en,
input wire logic gmii_tx_er,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO
);
always_comb begin
// RGMII: GMII bits 3:0 on the rising edge, bits 7:4 on falling.
eth_txd_HI = gmii_txd[3:0];
eth_txd_LO = gmii_txd[7:4];
eth_txctl_HI = gmii_tx_en;
eth_txctl_LO = gmii_tx_en ^ gmii_tx_er;
// These constants are clocked by the 90-degree PLL output in the
// periphery, producing the forwarded 125 MHz RGMII TX clock.
eth_txc_HI = 1'b1;
eth_txc_LO = 1'b0;
end
endmodule
`default_nettype wire
@@ -0,0 +1,142 @@
`default_nettype none
// Hardware-only Ethernet diagnostic. This top intentionally bypasses the RF
// capture/FIFO and supplies an endless, ordered 0..15 sample sequence to the
// production packetizer, MAC, FCS, RGMII and PHY-management blocks.
module ti375_ethernet_selftest_top (
input wire logic pll_dummy_clk,
input wire logic eth_clk,
input wire logic eth_clk90,
input wire logic pll_locked,
output logic pll_resetn,
input wire logic rf_clk_pmod,
input wire logic [3:0] rf_data_pmod_HI,
input wire logic [3:0] rf_data_pmod_LO,
output logic eth_mdc,
input wire logic eth_mdio_IN,
output logic eth_mdio_OUT,
output logic eth_mdio_OE,
output logic eth_phyrst_n,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO,
output logic [4:0] user_led
);
localparam int unsigned FIFO_DEPTH = 16384;
logic eth_rst;
logic phy_reset_complete;
logic mdio_start, mdio_write, mdio_busy, mdio_done;
logic [4:0] mdio_phy_address, mdio_register_address;
logic [15:0] mdio_write_data, mdio_read_data;
logic phy_init_complete, link_ready;
logic [63:0] link_down_events;
logic fifo_rd_en, fifo_rd_valid;
logic [68:0] fifo_rd_data;
logic [63:0] generated_sample_index;
logic mac_frame_start, mac_payload_valid, mac_payload_ready;
logic mac_busy, mac_frame_done;
logic [10:0] mac_payload_length;
logic [7:0] mac_payload_data;
logic [31:0] packet_sequence;
logic frame_activity_pulse;
logic [7:0] gmii_txd;
logic gmii_tx_en, gmii_tx_er;
logic [63:0] frames_transmitted, payload_bytes_transmitted;
logic [22:0] heartbeat;
logic activity_toggle;
assign pll_resetn = 1'b1;
reset_sync u_eth_reset (.clk(eth_clk), .arst(!pll_locked), .srst(eth_rst));
phy_reset_ctrl #(.CLK_HZ(125_000_000), .RESET_LOW_MS(10)) u_phy_reset (
.clk(eth_clk), .rst(eth_rst), .phy_reset_n(eth_phyrst_n),
.reset_complete(phy_reset_complete)
);
phy_init_rtl8211f u_phy_init (
.clk(eth_clk), .rst(eth_rst), .phy_reset_complete(phy_reset_complete),
.mdio_start(mdio_start), .mdio_write(mdio_write),
.mdio_phy_address(mdio_phy_address),
.mdio_register_address(mdio_register_address), .mdio_write_data(mdio_write_data),
.mdio_busy(mdio_busy), .mdio_done(mdio_done), .mdio_read_data(mdio_read_data),
.initialization_complete(phy_init_complete), .link_ready(link_ready),
.link_down_events(link_down_events)
);
mdio_master #(.MDC_DIV(25)) u_mdio (
.clk(eth_clk), .rst(eth_rst), .start(mdio_start),
.command_write(mdio_write), .phy_address(mdio_phy_address),
.register_address(mdio_register_address), .write_data(mdio_write_data),
.busy(mdio_busy), .done(mdio_done), .read_data(mdio_read_data),
.read_valid(), .mdc(eth_mdc), .mdio_i(eth_mdio_IN),
.mdio_o(eth_mdio_OUT), .mdio_oe(eth_mdio_OE)
);
// One-cycle read response that has the same entry format as max2771_capture:
// {discontinuity, absolute sample index, literal four-bit sample}.
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
fifo_rd_valid <= 1'b0;
fifo_rd_data <= 69'd0;
generated_sample_index <= 64'd0;
heartbeat <= 23'd0;
activity_toggle <= 1'b0;
end else begin
fifo_rd_valid <= fifo_rd_en;
if (fifo_rd_en) begin
fifo_rd_data <= {1'b0, generated_sample_index,
generated_sample_index[3:0]};
generated_sample_index <= generated_sample_index + 64'd1;
end
heartbeat <= heartbeat + 23'd1;
if (frame_activity_pulse)
activity_toggle <= !activity_toggle;
end
end
ethernet_packetizer #(
.FIFO_DEPTH(FIFO_DEPTH), .FULL_SAMPLES(2936),
.EARLIER_SAMPLE_HIGH_NIBBLE(1'b1)
) u_packetizer (
.clk(eth_clk), .rst(eth_rst), .link_ready(link_ready),
.dropped_samples_low(32'd0), .fifo_empty(1'b0),
.fifo_level(15'd16384), .fifo_rd_en(fifo_rd_en),
.fifo_rd_data(fifo_rd_data), .fifo_rd_valid(fifo_rd_valid),
.mac_frame_start(mac_frame_start), .mac_payload_length(mac_payload_length),
.mac_payload_data(mac_payload_data), .mac_payload_valid(mac_payload_valid),
.mac_payload_ready(mac_payload_ready), .mac_busy(mac_busy),
.mac_frame_done(mac_frame_done), .packet_sequence(packet_sequence),
.frame_activity_pulse(frame_activity_pulse)
);
ethernet_tx_mac u_mac (
.clk(eth_clk), .rst(eth_rst), .frame_start(mac_frame_start),
.payload_length(mac_payload_length), .payload_data(mac_payload_data),
.payload_valid(mac_payload_valid), .payload_ready(mac_payload_ready),
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.busy(mac_busy), .frame_done(mac_frame_done),
.frames_transmitted(frames_transmitted),
.payload_bytes_transmitted(payload_bytes_transmitted)
);
rgmii_tx_efinix u_rgmii_tx (
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.eth_txd_HI(eth_txd_HI), .eth_txd_LO(eth_txd_LO),
.eth_txctl_HI(eth_txctl_HI), .eth_txctl_LO(eth_txctl_LO),
.eth_txc_HI(eth_txc_HI), .eth_txc_LO(eth_txc_LO)
);
always_comb begin
user_led[0] = pll_locked || (!pll_locked && (pll_dummy_clk ^ eth_clk90));
user_led[1] = link_ready;
user_led[2] = heartbeat[22] || (!pll_locked &&
(rf_clk_pmod ^ (^rf_data_pmod_HI) ^
(^rf_data_pmod_LO)));
user_led[3] = activity_toggle;
user_led[4] = 1'b0;
end
endmodule
`default_nettype wire
@@ -0,0 +1,202 @@
`default_nettype none
module ti375_max2771_recorder_top #(
parameter bit CAPTURE_RISING = 1'b1,
parameter bit EARLIER_SAMPLE_HIGH_NIBBLE = 1'b1,
parameter int unsigned FIFO_DEPTH = 16384
) (
// Core clocks and status generated by the Efinity periphery PLL.
input wire logic pll_dummy_clk,
input wire logic eth_clk,
input wire logic eth_clk90,
input wire logic pll_locked,
output logic pll_resetn,
input wire logic rf_clk_pmod,
input wire logic [3:0] rf_data_pmod_HI,
input wire logic [3:0] rf_data_pmod_LO,
output logic eth_mdc,
input wire logic eth_mdio_IN,
output logic eth_mdio_OUT,
output logic eth_mdio_OE,
output logic eth_phyrst_n,
output logic [3:0] eth_txd_HI,
output logic [3:0] eth_txd_LO,
output logic eth_txctl_HI,
output logic eth_txctl_LO,
output logic eth_txc_HI,
output logic eth_txc_LO,
output logic [4:0] user_led
);
localparam int unsigned FIFO_LEVEL_W = $clog2(FIFO_DEPTH) + 1;
logic eth_rst, rf_rst;
logic [3:0] selected_rf_data;
logic rf_fifo_clk;
logic fifo_w_en, fifo_full;
logic [68:0] fifo_w_data;
(* keep = "true" *) logic [FIFO_LEVEL_W-1:0] fifo_w_level, fifo_r_level;
(* keep = "true" *) logic [63:0] samples_seen, samples_captured, samples_dropped;
logic [63:0] dropped_samples_eth;
logic loss_latched_rf, loss_sync1, loss_sync2;
logic sample_activity_rf, sample_activity_eth;
logic fifo_rd_en, fifo_rd_valid, fifo_empty;
logic [68:0] fifo_rd_data;
(* keep = "true" *) logic [63:0] fifo_overflow_attempts, fifo_underflow_attempts;
logic phy_reset_complete;
logic mdio_start, mdio_write, mdio_busy, mdio_done, mdio_read_valid;
logic [4:0] mdio_phy_address, mdio_register_address;
logic [15:0] mdio_write_data, mdio_read_data;
logic phy_init_complete, link_ready;
(* keep = "true" *) logic [63:0] link_down_events;
logic mac_frame_start, mac_payload_valid, mac_payload_ready, mac_busy, mac_frame_done;
logic [10:0] mac_payload_length;
logic [7:0] mac_payload_data;
logic [7:0] gmii_txd;
logic gmii_tx_en, gmii_tx_er;
(* keep = "true" *) logic [63:0] frames_transmitted, payload_bytes_transmitted;
logic [31:0] packet_sequence;
logic frame_activity_pulse;
logic [23:0] sample_led_stretch;
logic [12:0] frame_activity_divider;
logic frame_activity_toggle;
// Efinity requires CLKOUT0 to stay within 4x of the reference clock. It is
// a 100 MHz housekeeping output. The pre-lock LED probe below also keeps
// both PLL clock interface ports present for generated SDC constraints.
assign pll_resetn = 1'b1;
assign selected_rf_data = CAPTURE_RISING ? rf_data_pmod_HI : rf_data_pmod_LO;
reset_sync u_eth_reset (.clk(eth_clk), .arst(!pll_locked), .srst(eth_rst));
// Both physical edges are captured in Titanium GPIO DDIO registers. The
// selected half-cycle is then passed through the common capture/index block.
max2771_capture #(.CAPTURE_RISING(1'b1)) u_capture (
.rf_clk_pmod(rf_clk_pmod), .rst(rf_rst), .rf_data_pmod(selected_rf_data),
.fifo_full(fifo_full), .fifo_w_clk(rf_fifo_clk), .fifo_w_en(fifo_w_en),
.fifo_w_data(fifo_w_data), .samples_seen(samples_seen),
.samples_captured(samples_captured), .samples_dropped(samples_dropped),
.loss_latched(loss_latched_rf), .sample_activity_pulse(sample_activity_rf)
);
reset_sync u_rf_reset (
.clk(rf_fifo_clk), .arst(!pll_locked), .srst(rf_rst)
);
async_fifo_gray #(.DATA_WIDTH(69), .DEPTH(FIFO_DEPTH)) u_sample_fifo (
.w_clk(rf_fifo_clk), .w_rst(rf_rst), .w_en(fifo_w_en), .w_data(fifo_w_data),
.w_full(fifo_full), .w_level(fifo_w_level),
.overflow_attempt_count(fifo_overflow_attempts),
.r_clk(eth_clk), .r_rst(eth_rst), .r_en(fifo_rd_en), .r_data(fifo_rd_data),
.r_valid(fifo_rd_valid), .r_empty(fifo_empty), .r_level(fifo_r_level),
.underflow_attempt_count(fifo_underflow_attempts)
);
gray_counter_sync #(.WIDTH(64)) u_drop_sync (
.src_clk(rf_fifo_clk), .src_rst(rf_rst), .src_binary(samples_dropped),
.dst_clk(eth_clk), .dst_rst(eth_rst), .dst_binary(dropped_samples_eth)
);
pulse_sync u_sample_pulse_sync (
.src_clk(rf_fifo_clk), .src_rst(rf_rst), .src_pulse(sample_activity_rf),
.dst_clk(eth_clk), .dst_rst(eth_rst), .dst_pulse(sample_activity_eth)
);
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
loss_sync1 <= 1'b0;
loss_sync2 <= 1'b0;
end else begin
loss_sync1 <= loss_latched_rf;
loss_sync2 <= loss_sync1;
end
end
phy_reset_ctrl #(.CLK_HZ(125_000_000), .RESET_LOW_MS(10)) u_phy_reset (
.clk(eth_clk), .rst(eth_rst), .phy_reset_n(eth_phyrst_n),
.reset_complete(phy_reset_complete)
);
phy_init_rtl8211f u_phy_init (
.clk(eth_clk), .rst(eth_rst), .phy_reset_complete(phy_reset_complete),
.mdio_start(mdio_start), .mdio_write(mdio_write),
.mdio_phy_address(mdio_phy_address),
.mdio_register_address(mdio_register_address), .mdio_write_data(mdio_write_data),
.mdio_busy(mdio_busy), .mdio_done(mdio_done), .mdio_read_data(mdio_read_data),
.initialization_complete(phy_init_complete), .link_ready(link_ready),
.link_down_events(link_down_events)
);
mdio_master #(.MDC_DIV(25)) u_mdio (
.clk(eth_clk), .rst(eth_rst), .start(mdio_start),
.command_write(mdio_write), .phy_address(mdio_phy_address),
.register_address(mdio_register_address), .write_data(mdio_write_data),
.busy(mdio_busy), .done(mdio_done), .read_data(mdio_read_data),
.read_valid(mdio_read_valid), .mdc(eth_mdc),
.mdio_i(eth_mdio_IN), .mdio_o(eth_mdio_OUT), .mdio_oe(eth_mdio_OE)
);
ethernet_packetizer #(
.FIFO_DEPTH(FIFO_DEPTH), .FULL_SAMPLES(2936),
.EARLIER_SAMPLE_HIGH_NIBBLE(EARLIER_SAMPLE_HIGH_NIBBLE)
) u_packetizer (
.clk(eth_clk), .rst(eth_rst), .link_ready(link_ready),
.dropped_samples_low(dropped_samples_eth[31:0]),
.fifo_empty(fifo_empty), .fifo_level(fifo_r_level),
.fifo_rd_en(fifo_rd_en), .fifo_rd_data(fifo_rd_data),
.fifo_rd_valid(fifo_rd_valid), .mac_frame_start(mac_frame_start),
.mac_payload_length(mac_payload_length), .mac_payload_data(mac_payload_data),
.mac_payload_valid(mac_payload_valid), .mac_payload_ready(mac_payload_ready),
.mac_busy(mac_busy), .mac_frame_done(mac_frame_done),
.packet_sequence(packet_sequence), .frame_activity_pulse(frame_activity_pulse)
);
ethernet_tx_mac u_mac (
.clk(eth_clk), .rst(eth_rst), .frame_start(mac_frame_start),
.payload_length(mac_payload_length), .payload_data(mac_payload_data),
.payload_valid(mac_payload_valid), .payload_ready(mac_payload_ready),
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.busy(mac_busy), .frame_done(mac_frame_done),
.frames_transmitted(frames_transmitted),
.payload_bytes_transmitted(payload_bytes_transmitted)
);
rgmii_tx_efinix u_rgmii_tx (
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.eth_txd_HI(eth_txd_HI), .eth_txd_LO(eth_txd_LO),
.eth_txctl_HI(eth_txctl_HI), .eth_txctl_LO(eth_txctl_LO),
.eth_txc_HI(eth_txc_HI), .eth_txc_LO(eth_txc_LO)
);
always_ff @(posedge eth_clk) begin
if (eth_rst) begin
sample_led_stretch <= 24'd0;
frame_activity_divider <= 13'd0;
frame_activity_toggle <= 1'b0;
end else begin
if (sample_activity_eth) sample_led_stretch <= 24'hffffff;
else if (sample_led_stretch != 24'd0)
sample_led_stretch <= sample_led_stretch - 24'd1;
if (frame_activity_pulse) begin
if (frame_activity_divider == 13'd4095) begin
frame_activity_divider <= 13'd0;
frame_activity_toggle <= !frame_activity_toggle;
end else begin
frame_activity_divider <= frame_activity_divider + 13'd1;
end
end
end
end
always_comb begin
// Ti375 C529 has five verified user LEDs (LED9 through LED13).
user_led[0] = (pll_locked && !eth_rst) ||
(!pll_locked && (pll_dummy_clk ^ eth_clk90));
user_led[1] = link_ready;
// The pre-lock term keeps both physical DDIO capture edges in the
// implemented design even when CAPTURE_RISING is a constant parameter.
user_led[2] = (sample_led_stretch != 24'd0) ||
(!pll_locked && (^rf_data_pmod_LO));
user_led[3] = !fifo_empty && (frame_activity_toggle || !link_ready);
user_led[4] = loss_sync2;
end
endmodule
`default_nettype wire
+75
View File
@@ -0,0 +1,75 @@
#!/usr/bin/env bash
set -euo pipefail
target_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
build_dir=${EFINIX_BUILD_DIR:-"$target_dir/build"}
project=${EFINITY_PROJECT:-ti375_max2771_recorder}
constraint_file=${EFINITY_CONSTRAINT_FILE:-"$target_dir/constraints/ti375_max2771_recorder.sdc"}
if [[ -z ${EFINITY_HOME:-} ]]; then
echo "ERROR: set EFINITY_HOME to the Efinity installation directory" >&2
echo "Example: export EFINITY_HOME=/opt/efinity/2025.2" >&2
exit 2
fi
if [[ ! -f "$EFINITY_HOME/bin/setup.sh" ]]; then
echo "ERROR: $EFINITY_HOME/bin/setup.sh not found" >&2
exit 2
fi
# shellcheck disable=SC1090
export PYTHONPATH=${PYTHONPATH:-}
source "$EFINITY_HOME/bin/setup.sh"
mkdir -p "$build_dir"
export EFINITY_BUILD_DIR="$build_dir"
export EFINITY_PROJECT="$project"
export EFINIX_DEVICE=${EFINIX_DEVICE:-Ti375C529}
export EFINIX_TIMING_MODEL=${EFINIX_TIMING_MODEL:-C4}
rf_clock_period_ns=${RF_CLOCK_PERIOD_NS:-30.547}
if [[ ! $rf_clock_period_ns =~ ^[0-9]+([.][0-9]+)?$ ]]; then
echo "ERROR: RF_CLOCK_PERIOD_NS must be a positive numeric period in ns" >&2
exit 2
fi
cd "$build_dir"
"$EFINITY_HOME/bin/python3" "$target_dir/scripts/generate_project.py"
"$EFINITY_HOME/bin/python3" "$target_dir/interface/create_interface.py"
pt_sdc="$build_dir/outflow/$project.pt.sdc"
if [[ ! -f "$pt_sdc" ]]; then
echo "ERROR: Interface Designer did not generate $pt_sdc" >&2
exit 1
fi
{
# A direct GCLK input has no frequency property in Interface Designer, so
# Efinity emits this documented placeholder for the user clock period.
sed "s/<USER_PERIOD>/$rf_clock_period_ns/g" "$pt_sdc"
sed -n '1,$p' "$constraint_file"
} > "$build_dir/${project}_merged.sdc"
if grep -q '<USER_PERIOD>' "$build_dir/${project}_merged.sdc"; then
echo "ERROR: unresolved RF input-clock period in merged SDC" >&2
exit 1
fi
"$EFINITY_HOME/bin/python3" "$EFINITY_HOME/scripts/efx_run.py" \
"$build_dir/$project.xml" --flow compile
"$EFINITY_HOME/bin/python3" "$target_dir/scripts/check_timing.py" \
"$build_dir/outflow/$project.timing.rpt"
cdc_report="$build_dir/outflow/$project.cdc.rpt"
if [[ ! -f "$cdc_report" ]] || ! grep -q "No Synchronizer warnings to report" "$cdc_report"; then
echo "ERROR: Efinity CDC/synchronizer report is absent or contains warnings" >&2
exit 1
fi
if grep -Eq "No ports matched|No clocks matched|No valid pin\(s\) found for clock" \
"$build_dir/outflow/$project.log"; then
echo "ERROR: major SDC coverage warning found in Efinity log" >&2
exit 1
fi
if [[ ! -f "$build_dir/outflow/$project.bit" ]]; then
echo "ERROR: expected JTAG bitstream was not generated" >&2
exit 1
fi
echo "Bitstream: $build_dir/outflow/$project.bit"
@@ -0,0 +1,8 @@
#!/usr/bin/env bash
set -euo pipefail
target_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
export EFINITY_PROJECT=ti375_ethernet_selftest
export EFINITY_TOP=ti375_ethernet_selftest_top
export EFINITY_BUILD_DIR=${EFINITY_SELFTEST_BUILD_DIR:-"$target_dir/build_selftest"}
exec "$target_dir/scripts/build_efinity.sh"
+35
View File
@@ -0,0 +1,35 @@
#!/usr/bin/env python3
"""Fail conservatively on Efinity timing violations or absent clock coverage."""
import re
import sys
from pathlib import Path
if len(sys.argv) != 2:
raise SystemExit("usage: check_timing.py OUTflow/project.timing.rpt")
path = Path(sys.argv[1])
if not path.is_file():
raise SystemExit(f"timing report not found: {path}")
text = path.read_text(encoding="utf-8", errors="replace")
if re.search(r"\b(?:VIOLATED|timing\s+not\s+met|negative\s+slack)\b", text, re.I):
raise SystemExit(f"timing violation reported in {path}")
lower = text.lower()
start = lower.find("clock relationship summary")
if start < 0:
raise SystemExit("Clock Relationship Summary is absent; cannot prove timing closure")
end_candidates = [x for x in (
lower.find("path details", start + 1),
lower.find("max critical", start + 1),
) if x >= 0]
summary = text[start:min(end_candidates) if end_candidates else len(text)]
# Relationship rows contain the setup/hold slack values. Reject any negative
# decimal in this summary; clock names on this design contain no negative nums.
if re.search(r"(?<![A-Za-z0-9_])-\d+(?:\.\d+)?", summary):
raise SystemExit("negative setup/hold slack in Clock Relationship Summary")
for required in ("eth_clk", "rf_clk_pmod"):
if required not in text:
raise SystemExit(f"required clock {required!r} is absent from timing report")
print(f"Timing report accepted: {path}")
+101
View File
@@ -0,0 +1,101 @@
#!/usr/bin/env python3
"""Generate an Efinity project XML without a GUI/project database dependency."""
import datetime
import os
from pathlib import Path
import xml.dom.minidom
import xml.etree.ElementTree as ET
TARGET = Path(__file__).resolve().parents[1]
ROOT = TARGET.parents[1]
BUILD = Path(os.environ.get("EFINIX_BUILD_DIR", TARGET / "build")).resolve()
PROJECT = os.environ.get("EFINITY_PROJECT", "ti375_max2771_recorder")
TOP = os.environ.get("EFINITY_TOP", "ti375_max2771_recorder_top")
DEVICE = os.environ.get("EFINIX_DEVICE", "Ti375C529")
TIMING_MODEL = os.environ.get("EFINIX_TIMING_MODEL", "C4")
EFINITY_HOME = Path(os.environ["EFINITY_HOME"]).resolve()
version_file = EFINITY_HOME / "scripts" / "sw_version.txt"
SW_VERSION = version_file.read_text(encoding="utf-8").splitlines()[0].strip()
sources = [
ROOT / "rtl" / "common" / "reset_sync.sv",
ROOT / "rtl" / "common" / "pulse_sync.sv",
ROOT / "rtl" / "common" / "gray_counter_sync.sv",
ROOT / "rtl" / "common" / "async_fifo_gray.sv",
ROOT / "rtl" / "common" / "max2771_capture.sv",
ROOT / "rtl" / "common" / "ethernet_crc32.sv",
ROOT / "rtl" / "common" / "ethernet_packetizer.sv",
ROOT / "rtl" / "common" / "ethernet_tx_mac.sv",
ROOT / "rtl" / "common" / "phy_reset_ctrl.sv",
ROOT / "rtl" / "common" / "mdio_master.sv",
TARGET / "rtl" / "phy_init_rtl8211f.sv",
TARGET / "rtl" / "rgmii_tx_efinix.sv",
TARGET / "rtl" / "ti375_max2771_recorder_top.sv",
TARGET / "rtl" / "ti375_ethernet_selftest_top.sv",
]
for source in sources:
if not source.is_file():
raise SystemExit(f"missing RTL source: {source}")
ns = "http://www.efinixinc.com/enf_proj"
root = ET.Element("efx:project", {
"xmlns:efx": ns,
"name": PROJECT,
"location": str(BUILD),
"sw_version": SW_VERSION,
"last_change_date": "Date : " + datetime.datetime.now().strftime("%Y-%m-%d %H:%M"),
})
device_info = ET.SubElement(root, "efx:device_info")
ET.SubElement(device_info, "efx:family", name="Titanium")
ET.SubElement(device_info, "efx:device", name=DEVICE)
ET.SubElement(device_info, "efx:timing_model", name=TIMING_MODEL)
design_info = ET.SubElement(root, "efx:design_info", {
"def_veri_version": "verilog_2k", "def_vhdl_version": "vhdl_2008"
})
ET.SubElement(design_info, "efx:top_module", name=TOP)
for source in sources:
ET.SubElement(design_info, "efx:design_file", {
"name": str(source), "version": "default", "library": "default"
})
constraint_info = ET.SubElement(root, "efx:constraint_info")
ET.SubElement(constraint_info, "efx:sdc_file", name=str(BUILD / f"{PROJECT}_merged.sdc"))
ET.SubElement(root, "efx:misc_info")
ET.SubElement(root, "efx:ip_info")
def add_param(parent, name, value, value_type="e_string"):
ET.SubElement(parent, "efx:param", name=name, value=str(value), value_type=value_type)
synth = ET.SubElement(root, "efx:synthesis", tool_name="efx_map")
add_param(synth, "work_dir", "work_syn")
add_param(synth, "mode", "speed", "e_option")
add_param(synth, "infer-clk-enable", "3", "e_option")
add_param(synth, "infer-sync-set-reset", "1", "e_option")
add_param(synth, "bram_output_regs_packing", "1", "e_option")
add_param(synth, "retiming", "1", "e_option")
add_param(synth, "seq_opt", "1", "e_option")
pnr = ET.SubElement(root, "efx:place_and_route", tool_name="efx_pnr")
add_param(pnr, "work_dir", "work_pnr")
pgm = ET.SubElement(root, "efx:bitstream_generation", tool_name="efx_pgm")
add_param(pgm, "mode", "active", "e_string")
add_param(pgm, "width", "1", "e_string")
add_param(pgm, "oscillator_clock_divider", "DIV8", "e_option")
add_param(pgm, "spi_low_power_mode", "on", "e_bool")
add_param(pgm, "io_weak_pullup", "on", "e_bool")
add_param(pgm, "enable_roms", "smart", "e_option")
add_param(pgm, "bitstream_compression", "on", "e_bool")
add_param(pgm, "generate_bit", "on", "e_bool")
add_param(pgm, "generate_hex", "on", "e_bool")
ET.SubElement(root, "efx:debugger")
ET.SubElement(root, "efx:security")
BUILD.mkdir(parents=True, exist_ok=True)
raw = ET.tostring(root, encoding="utf-8")
pretty = xml.dom.minidom.parseString(raw).toprettyxml(indent=" ")
(BUILD / f"{PROJECT}.xml").write_text(pretty, encoding="utf-8")
print(BUILD / f"{PROJECT}.xml")
+21
View File
@@ -0,0 +1,21 @@
#!/usr/bin/env bash
set -euo pipefail
target_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
build_dir=${EFINITY_BUILD_DIR:-"$target_dir/build"}
project=${EFINITY_PROJECT:-ti375_max2771_recorder}
if [[ -z ${EFINITY_HOME:-} || ! -f "$EFINITY_HOME/bin/setup.sh" ]]; then
echo "ERROR: set EFINITY_HOME to a valid Efinity installation" >&2
exit 2
fi
# shellcheck disable=SC1090
export PYTHONPATH=${PYTHONPATH:-}
source "$EFINITY_HOME/bin/setup.sh"
if [[ ! -f "$build_dir/outflow/$project.bit" ]]; then
echo "ERROR: build first; bitstream is absent" >&2
exit 1
fi
cd "$build_dir"
"$EFINITY_HOME/bin/python3" "$EFINITY_HOME/scripts/efx_run.py" \
"$build_dir/$project.xml" --flow program --pgm_opts mode=jtag
@@ -0,0 +1,7 @@
#!/usr/bin/env bash
set -euo pipefail
target_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
export EFINITY_PROJECT=ti375_ethernet_selftest
export EFINITY_BUILD_DIR=${EFINITY_SELFTEST_BUILD_DIR:-"$target_dir/build_selftest"}
exec "$target_dir/scripts/program_efinity.sh"
+36
View File
@@ -0,0 +1,36 @@
#!/usr/bin/env bash
set -euo pipefail
target_dir=$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)
repo_dir=$(cd "$target_dir/../.." && pwd)
sim_dir="$target_dir/build/questa"
mkdir -p "$sim_dir"
if [[ -d "$sim_dir/work" ]]; then
vdel -lib "$sim_dir/work" -all >/dev/null
fi
vlib "$sim_dir/work"
vlog -work "$sim_dir/work" -sv \
"$repo_dir/rtl/common/reset_sync.sv" \
"$repo_dir/rtl/common/pulse_sync.sv" \
"$repo_dir/rtl/common/gray_counter_sync.sv" \
"$repo_dir/rtl/common/async_fifo_gray.sv" \
"$repo_dir/rtl/common/max2771_capture.sv" \
"$repo_dir/rtl/common/ethernet_crc32.sv" \
"$repo_dir/rtl/common/ethernet_packetizer.sv" \
"$repo_dir/rtl/common/ethernet_tx_mac.sv" \
"$repo_dir/rtl/common/phy_reset_ctrl.sv" \
"$repo_dir/rtl/common/mdio_master.sv" \
"$target_dir/rtl/phy_init_rtl8211f.sv" \
"$target_dir/rtl/rgmii_tx_efinix.sv" \
"$target_dir/rtl/ti375_max2771_recorder_top.sv" \
"$target_dir/tb/tb_rgmii_tx_efinix.sv" \
"$target_dir/tb/tb_phy_init_rtl8211f.sv"
for test in tb_rgmii_tx_efinix tb_phy_init_rtl8211f; do
echo "== $test =="
vsim -c -lib "$sim_dir/work" "$test" -do 'run -all; quit -f'
done
echo "Efinix target unit tests passed. Running the shared recorder regression..."
"$repo_dir/scripts/run_common_sim.sh"
@@ -0,0 +1,93 @@
`timescale 1ns/1ps
`default_nettype none
module tb_phy_init_rtl8211f;
logic clk = 1'b0;
logic rst = 1'b1;
logic reset_complete = 1'b0;
logic start, write_cmd, busy = 1'b0, done = 1'b0;
logic [4:0] phy_addr, reg_addr;
logic [15:0] write_data, read_data = 16'd0;
logic init_complete, link_ready;
logic [63:0] link_down_events;
integer transaction = 0;
integer response_delay = 0;
always #5 clk = !clk;
phy_init_rtl8211f #(
.CLK_HZ(1000), .SETTLE_MS(1), .POLL_MS(1), .PHY_ADDRESS(5'd2)
) dut (
.clk(clk), .rst(rst), .phy_reset_complete(reset_complete),
.mdio_start(start), .mdio_write(write_cmd), .mdio_phy_address(phy_addr),
.mdio_register_address(reg_addr), .mdio_write_data(write_data),
.mdio_busy(busy), .mdio_done(done), .mdio_read_data(read_data),
.initialization_complete(init_complete), .link_ready(link_ready),
.link_down_events(link_down_events)
);
// Minimal transaction-level MDIO responder. First PHYSR poll resolves
// 1000/full; the second reports link down to exercise the event counter.
always_ff @(posedge clk) begin
done <= 1'b0;
if (start && !busy) begin
if (phy_addr !== 5'd2)
$fatal(1, "wrong PHY address");
busy <= 1'b1;
response_delay <= 2;
end else if (busy && response_delay != 0) begin
response_delay <= response_delay - 1;
end else if (busy) begin
busy <= 1'b0;
done <= 1'b1;
case (transaction)
0: begin
if (reg_addr !== 5'h1f || !write_cmd || write_data !== 16'h0a43)
$fatal(1, "expected PHYSR page-select write");
end
1: begin
if (reg_addr !== 5'h1a || write_cmd)
$fatal(1, "expected PHYSR read");
read_data <= 16'h002c; // 1000 Mb/s + full duplex + link
end
2: begin
if (reg_addr !== 5'h1f || !write_cmd || write_data !== 16'h0000)
$fatal(1, "expected page-zero restore");
end
3: begin
if (reg_addr !== 5'h1f || !write_cmd || write_data !== 16'h0a43)
$fatal(1, "expected second page-select write");
end
4: begin
if (reg_addr !== 5'h1a || write_cmd)
$fatal(1, "expected link-down PHYSR read");
read_data <= 16'h0028; // 1000 Mb/s + full duplex, link clear
end
default: begin
if (reg_addr !== 5'h1f || !write_cmd || write_data !== 16'h0000)
$fatal(1, "expected second page-zero restore");
end
endcase
transaction <= transaction + 1;
end
end
initial begin
repeat (3) @(posedge clk);
rst <= 1'b0;
reset_complete <= 1'b1;
wait (init_complete && link_ready);
if (link_down_events !== 64'd0) $fatal(1, "spurious link-down event");
wait (!link_ready);
if (link_down_events !== 64'd1) $fatal(1, "link-down event not counted");
$display("PASS tb_phy_init_rtl8211f");
$finish;
end
initial begin
#5000;
$fatal(1, "timeout");
end
endmodule
`default_nettype wire
@@ -0,0 +1,36 @@
`timescale 1ns/1ps
`default_nettype none
module tb_rgmii_tx_efinix;
logic [7:0] gmii_txd;
logic gmii_tx_en, gmii_tx_er;
logic [3:0] hi, lo;
logic ctl_hi, ctl_lo, txc_hi, txc_lo;
rgmii_tx_efinix dut (
.gmii_txd(gmii_txd), .gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
.eth_txd_HI(hi), .eth_txd_LO(lo),
.eth_txctl_HI(ctl_hi), .eth_txctl_LO(ctl_lo),
.eth_txc_HI(txc_hi), .eth_txc_LO(txc_lo)
);
initial begin
gmii_txd = 8'ha5; gmii_tx_en = 1'b1; gmii_tx_er = 1'b0; #1;
if (hi !== 4'h5 || lo !== 4'ha || ctl_hi !== 1'b1 || ctl_lo !== 1'b1)
$fatal(1, "RGMII normal-data mapping failed");
if (txc_hi !== 1'b1 || txc_lo !== 1'b0)
$fatal(1, "RGMII forwarded-clock values failed");
gmii_txd = 8'h3c; gmii_tx_en = 1'b1; gmii_tx_er = 1'b1; #1;
if (hi !== 4'hc || lo !== 4'h3 || ctl_hi !== 1'b1 || ctl_lo !== 1'b0)
$fatal(1, "RGMII TX error-control encoding failed");
gmii_tx_en = 1'b0; gmii_tx_er = 1'b0; #1;
if (ctl_hi !== 1'b0 || ctl_lo !== 1'b0)
$fatal(1, "RGMII idle-control encoding failed");
$display("PASS tb_rgmii_tx_efinix");
$finish;
end
endmodule
`default_nettype wire