Implement generic MAX2771 raw Ethernet recorder
This commit is contained in:
@@ -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."
|
||||
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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
@@ -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"
|
||||
Executable
+36
@@ -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
|
||||
@@ -0,0 +1,10 @@
|
||||
.PHONY: sim build program
|
||||
|
||||
sim:
|
||||
../../scripts/run_common_sim.sh
|
||||
|
||||
build:
|
||||
vivado -mode batch -source scripts/build_vivado.tcl
|
||||
|
||||
program:
|
||||
vivado -mode batch -source scripts/program_fpga.tcl
|
||||
@@ -0,0 +1,104 @@
|
||||
# Digilent Genesys 2 target
|
||||
|
||||
This directory contains only the Genesys 2 adaptation of the MAX2771 recorder.
|
||||
The protocol/capture/FIFO/MAC RTL comes from `../../rtl/common`; no portable
|
||||
module is copied here.
|
||||
|
||||
## Target-specific files
|
||||
|
||||
| File | Function |
|
||||
|---|---|
|
||||
| `rtl/max2771_recorder_top.sv` | connects common blocks, status counters and LEDs |
|
||||
| `rtl/ethernet_clock_gen_xilinx.sv` | 200 MHz differential input and primitive MMCM for 125 MHz/125 MHz +90° |
|
||||
| `rtl/rgmii_tx_xilinx.sv` | Kintex-7 ODDR implementation for TXD/TXCTL/TXC |
|
||||
| `rtl/phy_init_rtl8211e.sv` | polls Genesys RTL8211E PHY address 1 and gates 1000/full link |
|
||||
| `constraints/genesys2_max2771_recorder.xdc` | verified clock, JA, Ethernet and LED pins |
|
||||
| `scripts/build_vivado.tcl` | in-memory/non-project synthesis, P&R, reports and bitstream |
|
||||
| `scripts/program_fpga.tcl` | volatile JTAG configuration |
|
||||
|
||||
## Wiring
|
||||
|
||||
| Signal | FPGA ball | JA pin | Standard |
|
||||
|---|---:|---:|---|
|
||||
| `rf_clk_pmod` | U27 | JA[0] | LVCMOS33 |
|
||||
| `rf_data_pmod[0]` | U28 | JA[1] | LVCMOS33 |
|
||||
| `rf_data_pmod[1]` | T26 | JA[2] | LVCMOS33 |
|
||||
| `rf_data_pmod[2]` | T27 | JA[3] | LVCMOS33 |
|
||||
| `rf_data_pmod[3]` | T22 | JA[4] | LVCMOS33 |
|
||||
|
||||
Connect a common ground and use only a 3.3-V-compatible digital source.
|
||||
|
||||
## Simulation
|
||||
|
||||
From the repository root:
|
||||
|
||||
```bash
|
||||
make genesys2-sim
|
||||
```
|
||||
|
||||
This runs the common FIFO/protocol/MAC regression. Xilinx primitives are
|
||||
guarded by `SYNTHESIS`, so vendor simulation libraries are not required for the
|
||||
common regression.
|
||||
|
||||
## Build
|
||||
|
||||
```bash
|
||||
make genesys2-build
|
||||
```
|
||||
|
||||
Equivalent direct command:
|
||||
|
||||
```bash
|
||||
vivado -mode batch -source targets/genesys2/scripts/build_vivado.tcl
|
||||
```
|
||||
|
||||
Output:
|
||||
|
||||
```text
|
||||
build/genesys2/vivado/max2771_recorder.bit
|
||||
build/genesys2/reports/
|
||||
```
|
||||
|
||||
The build uses part `xc7k325tffg900-2`, top `max2771_recorder_top`, creates an
|
||||
in-memory project, fails on negative setup/hold slack or unexpected major CDC
|
||||
warnings and uses no block design or generated IP. A clean Vivado 2026.1 build
|
||||
on 1 August 2026 completed synthesis, placement, routing, DRC and bitstream
|
||||
generation with +0.212 ns worst setup slack and +0.090 ns worst hold slack.
|
||||
|
||||
## Programming
|
||||
|
||||
Connect the Digilent JTAG USB port and run:
|
||||
|
||||
```bash
|
||||
make genesys2-program
|
||||
```
|
||||
|
||||
This configures the FPGA volatile SRAM. It does not write configuration flash.
|
||||
Power cycling removes the design.
|
||||
|
||||
## Clocking and RGMII
|
||||
|
||||
The 200 MHz differential oscillator drives an MMCM with a 1000 MHz VCO. It
|
||||
generates 125 MHz at 0° for the MAC/data ODDRs and 125 MHz at +90° for the TXC
|
||||
ODDR. The resulting nominal +2 ns clock shift is required because the board
|
||||
straps RTL8211E transmit delay off.
|
||||
|
||||
PHY reset remains asserted for 10 ms, register access waits another 30 ms, and
|
||||
transmit starts only after PHY-specific status reports resolved 1000 Mb/s/full
|
||||
duplex link. Ethernet receive is not implemented.
|
||||
|
||||
## LEDs
|
||||
|
||||
| LED | Meaning |
|
||||
|---:|---|
|
||||
| 0 | MMCM locked/system ready |
|
||||
| 1 | qualified 1000/full link |
|
||||
| 2 | RF sample activity |
|
||||
| 3 | FIFO non-empty |
|
||||
| 4 | sample loss/overflow latched |
|
||||
| 5 | frame activity |
|
||||
| 6 | first PHY status read completed |
|
||||
| 7 | Ethernet-domain heartbeat |
|
||||
|
||||
See [hardware assumptions](docs/hardware_assumptions.md) and
|
||||
[timing constraints](docs/timing_constraints.md) before hardware sign-off.
|
||||
@@ -0,0 +1,75 @@
|
||||
###############################################################################
|
||||
# Digilent Genesys 2 / MAX2771 raw recorder
|
||||
# Board pins copied from Digilent Genesys-2-Master.xdc, repository commit
|
||||
# 00a3404901f35aa9567b01ecb3f2c233b6efe9f4. FPGA: xc7k325tffg900-2.
|
||||
###############################################################################
|
||||
|
||||
# 200 MHz differential system oscillator.
|
||||
set_property -dict {PACKAGE_PIN AD12 IOSTANDARD LVDS} [get_ports sysclk_p]
|
||||
set_property -dict {PACKAGE_PIN AD11 IOSTANDARD LVDS} [get_ports sysclk_n]
|
||||
create_clock -name sys_clk_200 -period 5.000 [get_ports sysclk_p]
|
||||
|
||||
# Active-low CPU reset button.
|
||||
set_property -dict {PACKAGE_PIN R19 IOSTANDARD LVCMOS33 PULLUP TRUE} [get_ports cpu_resetn]
|
||||
|
||||
# MAX2771 on JA. This mapping is supplied by the design specification and also
|
||||
# matches the official Digilent JA pin locations.
|
||||
set_property -dict {PACKAGE_PIN U27 IOSTANDARD LVCMOS33} [get_ports rf_clk_pmod]
|
||||
set_property -dict {PACKAGE_PIN U28 IOSTANDARD LVCMOS33} [get_ports {rf_data_pmod[0]}]
|
||||
set_property -dict {PACKAGE_PIN T26 IOSTANDARD LVCMOS33} [get_ports {rf_data_pmod[1]}]
|
||||
set_property -dict {PACKAGE_PIN T27 IOSTANDARD LVCMOS33} [get_ports {rf_data_pmod[2]}]
|
||||
set_property -dict {PACKAGE_PIN T22 IOSTANDARD LVCMOS33} [get_ports {rf_data_pmod[3]}]
|
||||
create_clock -name rf_sample_clk -period 30.547 -waveform {0.000 15.2735} [get_ports rf_clk_pmod]
|
||||
|
||||
# MAX2771 clock-to-data timing is not supplied by the board manual or by the
|
||||
# project specification. Do not invent it. Before hardware sign-off, replace
|
||||
# RF_INPUT_MAX_NS and RF_INPUT_MIN_NS below with limits derived from the selected
|
||||
# MAX2771 output mode plus measured cable/PMOD skew, then uncomment the commands.
|
||||
# set_input_delay -clock rf_sample_clk -max RF_INPUT_MAX_NS [get_ports {rf_data_pmod[*]}]
|
||||
# set_input_delay -clock rf_sample_clk -min RF_INPUT_MIN_NS [get_ports {rf_data_pmod[*]}]
|
||||
|
||||
# Realtek RTL8211E-VL management and transmit-side RGMII, Bank 33 at 1.5 V.
|
||||
set_property -dict {PACKAGE_PIN AF12 IOSTANDARD LVCMOS15 SLEW SLOW} [get_ports eth_mdc]
|
||||
set_property -dict {PACKAGE_PIN AG12 IOSTANDARD LVCMOS15 PULLUP TRUE} [get_ports eth_mdio]
|
||||
set_property -dict {PACKAGE_PIN AH24 IOSTANDARD LVCMOS33 SLEW SLOW} [get_ports eth_phyrst_n]
|
||||
set_property -dict {PACKAGE_PIN AE10 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports eth_txck]
|
||||
set_property -dict {PACKAGE_PIN AJ12 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports {eth_txd[0]}]
|
||||
set_property -dict {PACKAGE_PIN AK11 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports {eth_txd[1]}]
|
||||
set_property -dict {PACKAGE_PIN AJ11 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports {eth_txd[2]}]
|
||||
set_property -dict {PACKAGE_PIN AK10 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports {eth_txd[3]}]
|
||||
set_property -dict {PACKAGE_PIN AK14 IOSTANDARD LVCMOS15 SLEW FAST} [get_ports eth_txctl]
|
||||
|
||||
# The MMCM VCO is 1000 MHz (200 MHz * 5). CLKOUT0 and CLKOUT1 are 125 MHz;
|
||||
# CLKOUT1 has +90 degrees = +2.000 ns. Vivado derives internal MMCM clocks.
|
||||
# This explicit forwarded-clock definition constrains the PHY interface.
|
||||
create_generated_clock -name rgmii_txc \
|
||||
-source [get_pins u_clock_gen/u_mmcm/CLKOUT1] -divide_by 1 [get_ports eth_txck]
|
||||
create_generated_clock -name mdio_mdc \
|
||||
-source [get_pins u_clock_gen/u_mmcm/CLKOUT0] -divide_by 50 [get_ports eth_mdc]
|
||||
|
||||
# RGMII v2.0 source-synchronous budget: the receiver requires 1.0 ns setup and
|
||||
# 1.0 ns hold around the delayed TXC edge. The +2 ns FPGA clock phase centers the
|
||||
# data eye because the board straps RTL8211E TXDLY off.
|
||||
set_output_delay -clock rgmii_txc -max 1.000 [get_ports {eth_txd[*] eth_txctl}]
|
||||
set_output_delay -clock rgmii_txc -min -1.000 [get_ports {eth_txd[*] eth_txctl}]
|
||||
|
||||
# All RF<->Ethernet crossings are structurally confined to u_sample_fifo Gray
|
||||
# pointers, u_drop_sync Gray counter, u_sample_pulse_sync, and reset synchronizers.
|
||||
# There is intentionally no ordinary logic path between these asynchronous clocks.
|
||||
set_clock_groups -asynchronous \
|
||||
-group [get_clocks rf_sample_clk] \
|
||||
-group [get_clocks -include_generated_clocks sys_clk_200]
|
||||
|
||||
# Force the four MAX2771 sampling registers and all RGMII DDR registers into IOBs.
|
||||
set_property IOB TRUE [get_cells -hier -filter {NAME =~ *u_capture/sample_iob_reg*}]
|
||||
set_property IOB TRUE [get_cells -hier -filter {NAME =~ *u_rgmii_tx*g_txd_oddr*u_oddr* || NAME =~ *u_rgmii_tx*u_ctl_oddr* || NAME =~ *u_rgmii_tx*u_txc_oddr*}]
|
||||
|
||||
# Eight verified user LEDs.
|
||||
set_property -dict {PACKAGE_PIN T28 IOSTANDARD LVCMOS33} [get_ports {led[0]}]
|
||||
set_property -dict {PACKAGE_PIN V19 IOSTANDARD LVCMOS33} [get_ports {led[1]}]
|
||||
set_property -dict {PACKAGE_PIN U30 IOSTANDARD LVCMOS33} [get_ports {led[2]}]
|
||||
set_property -dict {PACKAGE_PIN U29 IOSTANDARD LVCMOS33} [get_ports {led[3]}]
|
||||
set_property -dict {PACKAGE_PIN V20 IOSTANDARD LVCMOS33} [get_ports {led[4]}]
|
||||
set_property -dict {PACKAGE_PIN V26 IOSTANDARD LVCMOS33} [get_ports {led[5]}]
|
||||
set_property -dict {PACKAGE_PIN W24 IOSTANDARD LVCMOS33} [get_ports {led[6]}]
|
||||
set_property -dict {PACKAGE_PIN W23 IOSTANDARD LVCMOS33} [get_ports {led[7]}]
|
||||
@@ -0,0 +1,48 @@
|
||||
# Hardware information and assumptions
|
||||
|
||||
## Verified board information
|
||||
|
||||
The pin source is Digilent's official [`Genesys-2-Master.xdc`](https://github.com/Digilent/digilent-xdc/blob/00a3404901f35aa9567b01ecb3f2c233b6efe9f4/Genesys-2-Master.xdc), commit `00a3404901f35aa9567b01ecb3f2c233b6efe9f4`. The official board definition names part `xc7k325tffg900-2` and the system oscillator as 200 MHz. The [Genesys 2 reference manual](https://digilent.com/reference/_media/reference/programmable-logic/genesys-2/genesys2_rm.pdf) identifies the PHY as RTL8211E-VL.
|
||||
|
||||
| Function | FPGA pin | Standard | Notes |
|
||||
|---|---|---|---|
|
||||
| `sysclk_p/n` | AD12 / AD11 | LVDS | 200 MHz differential oscillator |
|
||||
| `cpu_resetn` | R19 | LVCMOS33 | active-low board reset |
|
||||
| MDC / MDIO | AF12 / AG12 | LVCMOS15 | PHY address strapped to 1 |
|
||||
| RGMII TXC | AE10 | LVCMOS15 | Bank 33, 1.5 V |
|
||||
| RGMII TXD[0:3] | AJ12, AK11, AJ11, AK10 | LVCMOS15 | in vector order |
|
||||
| RGMII TXCTL | AK14 | LVCMOS15 | no TX error is generated |
|
||||
| PHY reset | AH24 | LVCMOS33 | active low |
|
||||
| PHY INTB / PMEB | AK16 / AK15 | LVCMOS18 | open-drain; not used by this design |
|
||||
| RGMII RXC | AG10 | LVCMOS15 | receive path not implemented |
|
||||
| RGMII RXCTL | AH11 | LVCMOS15 | receive path not implemented |
|
||||
| RGMII RXD[0:3] | AJ14, AH14, AK13, AJ13 | LVCMOS15 | receive path not implemented |
|
||||
| LEDs[0:7] | T28, V19, U30, U29, V20, V26, W24, W23 | LVCMOS33 | user status LEDs |
|
||||
|
||||
The reference manual states that the board straps auto-negotiation on, advertises 10/100/1000 modes, selects PHY address `00001`, and selects no PHY-internal RGMII RX or TX delay. It also shows a 25 MHz PHY crystal; the FPGA does not supply the PHY reference clock.
|
||||
|
||||
The RTL8211E datasheet requires PHYRSTB low for at least 10 ms and a further 30 ms before register access. The RTL implements both intervals. Link gating polls PHY-specific status register `0x11`; transmit is enabled only when bits `[15:14]=10` (1000 Mb/s), bit 13 (full duplex), bit 11 (resolved), and bit 10 (real-time link) are all set.
|
||||
|
||||
## Assumptions from the specification
|
||||
|
||||
- `rf_clk_pmod` is nominally 32.736 MHz.
|
||||
- A four-bit word is valid on every configured sampling edge.
|
||||
- Default capture is rising-edge and default packing is earlier-sample-high-nibble.
|
||||
- JA is a 3.3 V interface and the MAX2771-side signals presented to JA are electrically compatible with LVCMOS33.
|
||||
- Ethernet destination is broadcast and source is the fixed locally administered address `02:27:71:00:00:01`.
|
||||
|
||||
## MAX2771 datasheet confirmation required
|
||||
|
||||
- Confirm the selected MAX2771 digital-output mode really produces one complete four-bit word per RF clock edge.
|
||||
- Confirm output voltage, drive strength, clock polarity, setup, hold, and clock-to-output limits for the exact supply and load.
|
||||
- Confirm whether the default rising capture edge is the center of the valid data eye. Change `CAPTURE_RISING` only after that check.
|
||||
- Confirm whether the four bits represent the desired channel/order. RTL records `[3:0]` literally and deliberately does not reinterpret it.
|
||||
|
||||
## Measurement required
|
||||
|
||||
- Measure MAX2771 clock-to-data timing at the Genesys 2 JA pins, including cable/adapter skew. Put the resulting min/max values into the commented `set_input_delay` commands in the XDC.
|
||||
- Measure the four data-line skews if wiring is not length matched.
|
||||
- Verify the forwarded RGMII TX clock is centered approximately 2.0 ns after TXD/TXCTL transitions at the PHY pins. The MMCM provides the nominal shift; board trace and I/O delay still deserve hardware measurement.
|
||||
- Confirm clean 3.3 V levels, ground return, edge quality, and absence of overshoot at JA before long captures.
|
||||
|
||||
No unverified board pin has been inserted into the constraints.
|
||||
@@ -0,0 +1,42 @@
|
||||
# Timing constraints and closure
|
||||
|
||||
## Clock plan
|
||||
|
||||
| Clock | Source | Frequency / period | Use |
|
||||
|---|---|---|---|
|
||||
| `sys_clk_200` | AD12/AD11 LVDS oscillator | 200 MHz / 5.000 ns | MMCM input |
|
||||
| `clk125_i` | MMCM VCO 1000 MHz, divide 8 | 125 MHz / 8.000 ns, 0° | packetizer, MAC, MDIO, RGMII data ODDR |
|
||||
| `clk125_90_i` | same MMCM output divide | 125 MHz / 8.000 ns, +90° | forwarded RGMII TXC ODDR |
|
||||
| `rgmii_txc` | ODDR from `clk125_90_i` | 125 MHz, +2.000 ns | PHY source-synchronous clock |
|
||||
| `mdio_mdc` | counter from 125 MHz | 2.5 MHz | Clause-22 management |
|
||||
| `rf_sample_clk` | U27 | 32.736 MHz / 30.547 ns | MAX2771 capture and FIFO write |
|
||||
|
||||
The RTL8211E is strapped with TX delay disabled. Therefore the FPGA creates RGMII-ID source-clock skew: GMII/RGMII data changes from ODDRs clocked at 0°, and TXC changes from an ODDR clocked at +90°. At 125 MHz, 90° is 2.000 ns. RGMII TX output delays use the standard 1.0 ns setup/1.0 ns hold receive budget around that centered clock edge.
|
||||
|
||||
## CDC exceptions
|
||||
|
||||
The RF and Ethernet clocks are asynchronous. The only crossings are:
|
||||
|
||||
- FIFO write/read Gray pointers, each through two `ASYNC_REG` stages;
|
||||
- the Gray-coded cumulative drop counter, through two `ASYNC_REG` stages;
|
||||
- a toggle-based sample-activity indicator, through two `ASYNC_REG` stages;
|
||||
- reset synchronizers with asynchronous assertion and synchronous deassertion.
|
||||
|
||||
The XDC asynchronous clock grouping is valid because no ordinary data/control logic crosses those groups. The multi-bit FIFO data is stored in true dual-clock block RAM and is read only under synchronized pointer control; it is not sampled as a combinational bus crossing.
|
||||
|
||||
Vivado reports CDC-6 for the three intentionally Gray-coded buses even when it recognizes their two ASYNC_REG stages. The build script accepts only named FIFO/drop-counter instances and fails for any other Warning or Critical CDC object.
|
||||
|
||||
## MAX2771 input delay boundary
|
||||
|
||||
The clock is constrained to 30.547 ns and the input registers are forced into IOBs. The project does **not** claim RF input setup/hold sign-off yet: neither the task input nor the Genesys 2 documentation provides MAX2771 `tCO`, cable delay, or inter-bit skew. The XDC contains conspicuous placeholder commands rather than fabricated numbers.
|
||||
|
||||
Before hardware sign-off:
|
||||
|
||||
1. derive earliest/latest data arrival at each FPGA pin from the MAX2771 datasheet;
|
||||
2. add measured cable/adapter and bit-to-bit skew;
|
||||
3. replace the placeholder max/min values in the XDC;
|
||||
4. rerun `make genesys2-build` and inspect
|
||||
`build/genesys2/reports/timing_post_route.rpt`;
|
||||
5. verify the eye with an oscilloscope or logic analyzer at JA.
|
||||
|
||||
The current 2026.1 build routes successfully with setup WNS `+0.212 ns` and hold WNS `+0.090 ns` for constrained paths. These numbers do not replace the missing external RF input-delay model.
|
||||
@@ -0,0 +1,54 @@
|
||||
`default_nettype none
|
||||
|
||||
// Genesys 2: 200 MHz differential oscillator -> 1000 MHz MMCM VCO ->
|
||||
// 125 MHz GMII clock and a +90 degree (2.000 ns) RGMII TX clock.
|
||||
module ethernet_clock_gen (
|
||||
input wire logic sysclk_p,
|
||||
input wire logic sysclk_n,
|
||||
input wire logic reset,
|
||||
output logic eth_clk,
|
||||
output logic eth_clk90,
|
||||
output logic locked
|
||||
);
|
||||
`ifdef SYNTHESIS
|
||||
wire logic clk200;
|
||||
wire logic clkfb;
|
||||
wire logic clkfb_buf;
|
||||
wire logic clk125_i;
|
||||
wire logic clk125_90_i;
|
||||
wire logic mmcm_locked;
|
||||
|
||||
IBUFDS #(.DIFF_TERM("FALSE"), .IBUF_LOW_PWR("TRUE")) u_sysclk_ibuf (
|
||||
.I(sysclk_p), .IB(sysclk_n), .O(clk200)
|
||||
);
|
||||
MMCME2_BASE #(
|
||||
.BANDWIDTH("OPTIMIZED"),
|
||||
.CLKIN1_PERIOD(5.000),
|
||||
.DIVCLK_DIVIDE(1),
|
||||
.CLKFBOUT_MULT_F(5.000),
|
||||
.CLKOUT0_DIVIDE_F(8.000),
|
||||
.CLKOUT0_PHASE(0.000),
|
||||
.CLKOUT1_DIVIDE(8),
|
||||
.CLKOUT1_PHASE(90.000),
|
||||
.STARTUP_WAIT("FALSE")
|
||||
) u_mmcm (
|
||||
.CLKIN1(clk200), .CLKFBIN(clkfb_buf), .RST(reset), .PWRDWN(1'b0),
|
||||
.CLKFBOUT(clkfb), .CLKOUT0(clk125_i), .CLKOUT1(clk125_90_i),
|
||||
.LOCKED(mmcm_locked),
|
||||
.CLKOUT0B(), .CLKOUT1B(), .CLKOUT2(), .CLKOUT2B(), .CLKOUT3(),
|
||||
.CLKOUT3B(), .CLKOUT4(), .CLKOUT5(), .CLKOUT6(), .CLKFBOUTB()
|
||||
);
|
||||
BUFG u_fb_buf (.I(clkfb), .O(clkfb_buf));
|
||||
BUFG u_clk_buf (.I(clk125_i), .O(eth_clk));
|
||||
BUFG u_clk90_buf (.I(clk125_90_i), .O(eth_clk90));
|
||||
assign locked = mmcm_locked;
|
||||
`else
|
||||
// Top-level behavioral simulation supplies a 125 MHz clock on sysclk_p.
|
||||
// Unit tests use GMII directly; this branch avoids a UNISIM dependency.
|
||||
assign eth_clk = sysclk_p;
|
||||
assign eth_clk90 = sysclk_p;
|
||||
assign locked = !reset;
|
||||
`endif
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,172 @@
|
||||
`default_nettype none
|
||||
|
||||
module max2771_recorder_top #(
|
||||
parameter bit CAPTURE_RISING = 1'b1,
|
||||
parameter bit EARLIER_SAMPLE_HIGH_NIBBLE = 1'b1,
|
||||
parameter int unsigned FIFO_DEPTH = 16384
|
||||
) (
|
||||
input wire logic sysclk_p,
|
||||
input wire logic sysclk_n,
|
||||
input wire logic cpu_resetn,
|
||||
input wire logic rf_clk_pmod,
|
||||
input wire logic [3:0] rf_data_pmod,
|
||||
|
||||
output logic eth_mdc,
|
||||
inout wire logic eth_mdio,
|
||||
output logic eth_phyrst_n,
|
||||
output logic eth_txck,
|
||||
output logic [3:0] eth_txd,
|
||||
output logic eth_txctl,
|
||||
output logic [7:0] led
|
||||
);
|
||||
localparam int unsigned FIFO_LEVEL_W = $clog2(FIFO_DEPTH) + 1;
|
||||
logic eth_clk, eth_clk90, mmcm_locked;
|
||||
logic eth_rst, rf_rst;
|
||||
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;
|
||||
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 mdio_i, mdio_o, mdio_oe;
|
||||
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, frame_led_stretch;
|
||||
logic [26:0] heartbeat;
|
||||
|
||||
ethernet_clock_gen u_clock_gen (
|
||||
.sysclk_p(sysclk_p), .sysclk_n(sysclk_n), .reset(!cpu_resetn),
|
||||
.eth_clk(eth_clk), .eth_clk90(eth_clk90), .locked(mmcm_locked)
|
||||
);
|
||||
reset_sync u_eth_reset (.clk(eth_clk), .arst(!cpu_resetn || !mmcm_locked), .srst(eth_rst));
|
||||
|
||||
max2771_capture #(.CAPTURE_RISING(CAPTURE_RISING)) u_capture (
|
||||
.rf_clk_pmod(rf_clk_pmod), .rst(rf_rst), .rf_data_pmod(rf_data_pmod),
|
||||
.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(!cpu_resetn || !mmcm_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)
|
||||
);
|
||||
|
||||
phy_reset_ctrl u_phy_reset (
|
||||
.clk(eth_clk), .rst(eth_rst), .phy_reset_n(eth_phyrst_n),
|
||||
.reset_complete(phy_reset_complete)
|
||||
);
|
||||
phy_init 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 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(mdio_i), .mdio_o(mdio_o), .mdio_oe(mdio_oe)
|
||||
);
|
||||
`ifdef SYNTHESIS
|
||||
IOBUF u_mdio_iobuf (.I(mdio_o), .O(mdio_i), .T(!mdio_oe), .IO(eth_mdio));
|
||||
`else
|
||||
assign eth_mdio = mdio_oe ? mdio_o : 1'bz;
|
||||
assign mdio_i = eth_mdio;
|
||||
`endif
|
||||
|
||||
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 u_rgmii_tx (
|
||||
.gmii_clk(eth_clk), .tx_clk90(eth_clk90), .gmii_txd(gmii_txd),
|
||||
.gmii_tx_en(gmii_tx_en), .gmii_tx_er(gmii_tx_er),
|
||||
.rgmii_txc(eth_txck), .rgmii_txd(eth_txd), .rgmii_tx_ctl(eth_txctl)
|
||||
);
|
||||
|
||||
always_ff @(posedge eth_clk) begin
|
||||
if (eth_rst) begin
|
||||
sample_led_stretch <= 24'd0;
|
||||
frame_led_stretch <= 24'd0;
|
||||
heartbeat <= 27'd0;
|
||||
end else begin
|
||||
heartbeat <= heartbeat + 27'd1;
|
||||
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) frame_led_stretch <= 24'hffffff;
|
||||
else if (frame_led_stretch != 24'd0) frame_led_stretch <= frame_led_stretch - 24'd1;
|
||||
end
|
||||
end
|
||||
|
||||
always_comb begin
|
||||
led[0] = mmcm_locked && !eth_rst;
|
||||
led[1] = link_ready;
|
||||
led[2] = (sample_led_stretch != 24'd0);
|
||||
led[3] = !fifo_empty;
|
||||
led[4] = loss_latched_rf;
|
||||
led[5] = (frame_led_stretch != 24'd0);
|
||||
led[6] = phy_init_complete;
|
||||
led[7] = heartbeat[26];
|
||||
end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,85 @@
|
||||
`default_nettype none
|
||||
|
||||
// The Genesys 2 straps the RTL8211E-VL for auto-negotiation and PHY address 1.
|
||||
// After the mandatory post-reset settling time, this controller polls PHYSR
|
||||
// (register 0x11) and permits transmit only for resolved 1000 Mb/s full duplex.
|
||||
module phy_init #(
|
||||
parameter int unsigned CLK_HZ = 125_000_000,
|
||||
parameter int unsigned SETTLE_MS = 30,
|
||||
parameter int unsigned POLL_MS = 100
|
||||
) (
|
||||
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 int unsigned TIMER_W = $clog2(((POLL_CYCLES > SETTLE_CYCLES) ? POLL_CYCLES : SETTLE_CYCLES) + 1);
|
||||
typedef enum logic [2:0] {I_WAIT_RESET, I_SETTLE, I_START, I_WAIT_MDIO, I_POLL_WAIT} istate_t;
|
||||
istate_t state;
|
||||
logic [TIMER_W-1:0] timer;
|
||||
logic new_link_ready;
|
||||
|
||||
always_comb begin
|
||||
mdio_write = 1'b0;
|
||||
mdio_phy_address = 5'd1;
|
||||
mdio_register_address = 5'h11;
|
||||
mdio_write_data = 16'd0;
|
||||
new_link_ready = (mdio_read_data[15:14] == 2'b10) &&
|
||||
mdio_read_data[13] && mdio_read_data[11] && mdio_read_data[10];
|
||||
end
|
||||
|
||||
always_ff @(posedge clk) begin
|
||||
if (rst) begin
|
||||
state <= I_WAIT_RESET;
|
||||
timer <= '0;
|
||||
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)
|
||||
I_WAIT_RESET: begin
|
||||
link_ready <= 1'b0;
|
||||
if (phy_reset_complete) begin timer <= '0; state <= I_SETTLE; end
|
||||
end
|
||||
I_SETTLE: begin
|
||||
if (timer == SETTLE_CYCLES-1) begin timer <= '0; state <= I_START; end
|
||||
else timer <= timer + {{(TIMER_W-1){1'b0}},1'b1};
|
||||
end
|
||||
I_START: begin
|
||||
if (!mdio_busy) begin mdio_start <= 1'b1; state <= I_WAIT_MDIO; end
|
||||
end
|
||||
I_WAIT_MDIO: begin
|
||||
if (mdio_done) begin
|
||||
initialization_complete <= 1'b1;
|
||||
if (link_ready && !new_link_ready)
|
||||
link_down_events <= link_down_events + 64'd1;
|
||||
link_ready <= new_link_ready;
|
||||
timer <= '0;
|
||||
state <= I_POLL_WAIT;
|
||||
end
|
||||
end
|
||||
I_POLL_WAIT: begin
|
||||
if (timer == POLL_CYCLES-1) begin timer <= '0; state <= I_START; end
|
||||
else timer <= timer + {{(TIMER_W-1){1'b0}},1'b1};
|
||||
end
|
||||
default: state <= I_WAIT_RESET;
|
||||
endcase
|
||||
end
|
||||
end
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,39 @@
|
||||
`default_nettype none
|
||||
|
||||
module rgmii_tx (
|
||||
input wire logic gmii_clk,
|
||||
input wire logic tx_clk90,
|
||||
input wire logic [7:0] gmii_txd,
|
||||
input wire logic gmii_tx_en,
|
||||
input wire logic gmii_tx_er,
|
||||
output logic rgmii_txc,
|
||||
output logic [3:0] rgmii_txd,
|
||||
output logic rgmii_tx_ctl
|
||||
);
|
||||
`ifdef SYNTHESIS
|
||||
genvar i;
|
||||
generate for (i = 0; i < 4; i = i + 1) begin : g_txd_oddr
|
||||
ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) u_oddr (
|
||||
.C(gmii_clk), .CE(1'b1), .D1(gmii_txd[i]), .D2(gmii_txd[i+4]),
|
||||
.Q(rgmii_txd[i]), .R(1'b0), .S(1'b0)
|
||||
);
|
||||
end endgenerate
|
||||
ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) u_ctl_oddr (
|
||||
.C(gmii_clk), .CE(1'b1), .D1(gmii_tx_en), .D2(gmii_tx_en ^ gmii_tx_er),
|
||||
.Q(rgmii_tx_ctl), .R(1'b0), .S(1'b0)
|
||||
);
|
||||
ODDR #(.DDR_CLK_EDGE("SAME_EDGE"), .INIT(1'b0), .SRTYPE("SYNC")) u_txc_oddr (
|
||||
.C(tx_clk90), .CE(1'b1), .D1(1'b1), .D2(1'b0),
|
||||
.Q(rgmii_txc), .R(1'b0), .S(1'b0)
|
||||
);
|
||||
`else
|
||||
// Edge-select behavioral model; hardware uses the ODDR primitives above.
|
||||
always_comb begin
|
||||
rgmii_txd = gmii_clk ? gmii_txd[3:0] : gmii_txd[7:4];
|
||||
rgmii_tx_ctl = gmii_clk ? gmii_tx_en : (gmii_tx_en ^ gmii_tx_er);
|
||||
rgmii_txc = tx_clk90;
|
||||
end
|
||||
`endif
|
||||
endmodule
|
||||
|
||||
`default_nettype wire
|
||||
@@ -0,0 +1,93 @@
|
||||
# Non-project Vivado build for the Genesys 2 MAX2771 recorder.
|
||||
# Run from repository root:
|
||||
# vivado -mode batch -source targets/genesys2/scripts/build_vivado.tcl
|
||||
|
||||
set script_dir [file normalize [file dirname [info script]]]
|
||||
set target_dir [file normalize [file join $script_dir ..]]
|
||||
set root_dir [file normalize [file join $target_dir .. ..]]
|
||||
set build_dir [file join $root_dir build genesys2 vivado]
|
||||
set report_dir [file join $root_dir build genesys2 reports]
|
||||
file mkdir $build_dir
|
||||
file mkdir $report_dir
|
||||
|
||||
set part xc7k325tffg900-2
|
||||
set top max2771_recorder_top
|
||||
create_project -in_memory -part $part
|
||||
set_property target_language Verilog [current_project]
|
||||
set_property default_lib work [current_project]
|
||||
set_property verilog_define {SYNTHESIS=1} [current_fileset]
|
||||
|
||||
set common_rtl [lsort [glob -nocomplain [file join $root_dir rtl common *.sv]]]
|
||||
set target_rtl [lsort [glob -nocomplain [file join $target_dir rtl *.sv]]]
|
||||
set rtl_files [concat $common_rtl $target_rtl]
|
||||
if {[llength $rtl_files] == 0} { error "No RTL files found" }
|
||||
read_verilog -sv $rtl_files
|
||||
read_xdc [file join $target_dir constraints genesys2_max2771_recorder.xdc]
|
||||
|
||||
synth_design -top $top -part $part -flatten_hierarchy rebuilt
|
||||
write_checkpoint -force [file join $build_dir post_synth.dcp]
|
||||
report_utilization -file [file join $report_dir post_synth_utilization.rpt]
|
||||
report_clocks -file [file join $report_dir clocks.rpt]
|
||||
report_clock_utilization -file [file join $report_dir clock_utilization.rpt]
|
||||
check_timing -verbose -file [file join $report_dir check_timing_post_synth.rpt]
|
||||
report_cdc -details -file [file join $report_dir cdc_post_synth.rpt]
|
||||
|
||||
# Fail for registers lacking a clock. I/O delay omissions are separately and
|
||||
# deliberately documented because the MAX2771 tCO/board skew is not yet known.
|
||||
set no_clock_text [check_timing -override_defaults no_clock -return_string]
|
||||
if {[regexp -nocase {([1-9][0-9]*) register[^\n]*no clock} $no_clock_text]} {
|
||||
error "Unconstrained sequential clocks detected; see check_timing_post_synth.rpt"
|
||||
}
|
||||
|
||||
# Vivado exposes CDC violations as objects after report_cdc. Any Critical or
|
||||
# Warning CDC not waived by structural ASYNC_REG recognition stops the build.
|
||||
if {[llength [info commands get_cdc_violations]] != 0} {
|
||||
set major_cdc [get_cdc_violations -quiet -filter {SEVERITY == "Critical" || SEVERITY == "Warning"}]
|
||||
set unexpected_cdc 0
|
||||
foreach violation $major_cdc {
|
||||
# CDC-6 is Vivado's expected classification for a multi-bit Gray bus
|
||||
# whose first two destination stages carry ASYNC_REG. Only the three
|
||||
# named Gray synchronizers are accepted; every other warning is fatal.
|
||||
set description [report_property -return_string $violation]
|
||||
if {![regexp {u_sample_fifo/(wgray_rsync1|rgray_wsync1)|u_drop_sync/gray_sync1} $description]} {
|
||||
puts stderr "Unexpected CDC violation:\n$description"
|
||||
set unexpected_cdc 1
|
||||
}
|
||||
}
|
||||
if {$unexpected_cdc} { error "Major unexpected CDC warnings detected; see cdc_post_synth.rpt" }
|
||||
}
|
||||
|
||||
opt_design
|
||||
place_design
|
||||
phys_opt_design
|
||||
write_checkpoint -force [file join $build_dir post_place.dcp]
|
||||
report_timing_summary -delay_type min_max -report_unconstrained -check_timing_verbose \
|
||||
-file [file join $report_dir timing_post_place.rpt]
|
||||
|
||||
route_design
|
||||
phys_opt_design
|
||||
write_checkpoint -force [file join $build_dir post_route.dcp]
|
||||
report_timing_summary -delay_type min_max -report_unconstrained -check_timing_verbose \
|
||||
-file [file join $report_dir timing_post_route.rpt]
|
||||
report_timing -delay_type max -max_paths 50 -sort_by group \
|
||||
-file [file join $report_dir setup_paths.rpt]
|
||||
report_timing -delay_type min -max_paths 50 -sort_by group \
|
||||
-file [file join $report_dir hold_paths.rpt]
|
||||
report_utilization -hierarchical -file [file join $report_dir utilization_post_route.rpt]
|
||||
report_methodology -file [file join $report_dir methodology_post_route.rpt]
|
||||
report_drc -file [file join $report_dir drc_post_route.rpt]
|
||||
|
||||
set setup_paths [get_timing_paths -quiet -delay_type max -max_paths 1]
|
||||
set hold_paths [get_timing_paths -quiet -delay_type min -max_paths 1]
|
||||
if {[llength $setup_paths] == 0 || [llength $hold_paths] == 0} {
|
||||
error "Timing paths unavailable; clocks may be unconstrained"
|
||||
}
|
||||
set setup_slack [get_property SLACK [lindex $setup_paths 0]]
|
||||
set hold_slack [get_property SLACK [lindex $hold_paths 0]]
|
||||
if {$setup_slack < 0.0 || $hold_slack < 0.0} {
|
||||
error "Timing failed: setup WNS=$setup_slack ns, hold WNS=$hold_slack ns"
|
||||
}
|
||||
|
||||
write_bitstream -force [file join $build_dir max2771_recorder.bit]
|
||||
puts "BUILD PASSED: setup WNS=$setup_slack ns, hold WNS=$hold_slack ns"
|
||||
puts "Bitstream: [file join $build_dir max2771_recorder.bit]"
|
||||
@@ -0,0 +1,24 @@
|
||||
# Run: vivado -mode batch -source targets/genesys2/scripts/program_fpga.tcl -tclargs [bitstream]
|
||||
set script_dir [file normalize [file dirname [info script]]]
|
||||
set target_dir [file normalize [file join $script_dir ..]]
|
||||
set root_dir [file normalize [file join $target_dir .. ..]]
|
||||
if {$argc > 0} {
|
||||
set bitstream [file normalize [lindex $argv 0]]
|
||||
} else {
|
||||
set bitstream [file join $root_dir build genesys2 vivado max2771_recorder.bit]
|
||||
}
|
||||
if {![file exists $bitstream]} { error "Bitstream not found: $bitstream" }
|
||||
|
||||
open_hw_manager
|
||||
connect_hw_server
|
||||
open_hw_target
|
||||
set devices [get_hw_devices -quiet -filter {PART =~ "xc7k325t*"}]
|
||||
if {[llength $devices] == 0} { error "No xc7k325t JTAG device found" }
|
||||
set device [lindex $devices 0]
|
||||
current_hw_device $device
|
||||
refresh_hw_device -update_hw_probes false $device
|
||||
set_property PROGRAM.FILE $bitstream $device
|
||||
program_hw_devices $device
|
||||
refresh_hw_device $device
|
||||
puts "Programmed $device with $bitstream"
|
||||
close_hw_manager
|
||||
Reference in New Issue
Block a user