Implement generic MAX2771 raw Ethernet recorder

This commit is contained in:
onur
2026-08-01 01:15:47 +03:00
commit 79d07fdda7
57 changed files with 4810 additions and 0 deletions
+10
View File
@@ -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
+104
View File
@@ -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
+85
View File
@@ -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
+39
View File
@@ -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
+93
View File
@@ -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]"
+24
View File
@@ -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