example/HTG940: Add example design for HTG940

Signed-off-by: Alex Forencich <alex@alexforencich.com>
This commit is contained in:
Alex Forencich
2025-02-20 10:20:15 -08:00
parent 152b5aeed5
commit 650da9c972
12 changed files with 1307 additions and 0 deletions

View File

@@ -92,6 +92,7 @@ To facilitate the dual-license model, contributions to the project can only be a
Example designs are provided for several different FPGA boards, showcasing many of the capabilities of this library. Building the example designs will require the appropriate vendor toolchain and may also require tool and IP licenses.
* Digilent Arty A7 (Xilinx Artix 7 XC7A35T)
* HiTech Global HTG-940 (Xilinx Virtex UltraScale+ XCVU9P/XCVU13P)
* Xilinx KC705 (Xilinx Kintex 7 XC7K325T)
* Xilinx KCU105 (Xilinx Kintex UltraScale XCKU040)
* Xilinx KR260 (Xilinx Kria K26 SoM / Zynq UltraScale+ XCK26)

View File

@@ -0,0 +1,36 @@
# Taxi Example Design for HTG-940
## Introduction
This example design targets the HiTech Global HTG-940 FPGA board.
The design places a looped-back MAC on the BASE-T port, as well as a looped-back UART on on the USB UART connection.
* USB UART
* Looped-back UART
* RJ-45 Ethernet ports with TI DP83867IRPAP PHY
* Looped-back MAC via RGMII
## Board details
* FPGA: xcvu9p-flgb2104-2-e
* 1000BASE-T PHY: TI DP83867IRPAP via RGMII
## Licensing
* Toolchain
* Vivado Enterprise (requires license)
* IP
* No licensed vendor IP or 3rd party IP
## How to build
Run `make` in the appropriate `fpga*` subdirectory to build the bitstream. Ensure that the Xilinx Vivado toolchain components are in PATH.
## How to test
Run `make program` to program the board with Vivado.
To test the looped-back UART, use any serial terminal software like minicom, screen, etc. The looped-back UART will echo typed text back without modification.
To test the looped-back MAC, it is recommended to use a network tester like the Viavi T-BERD 5800 that supports basic layer 2 tests with a loopback. Do not connect the looped-back MAC to a network as the reflected packets may cause problems.

View File

@@ -0,0 +1,153 @@
# SPDX-License-Identifier: MIT
###################################################################
#
# Xilinx Vivado FPGA Makefile
#
# Copyright (c) 2016-2025 Alex Forencich
#
###################################################################
#
# Parameters:
# FPGA_TOP - Top module name
# FPGA_FAMILY - FPGA family (e.g. VirtexUltrascale)
# FPGA_DEVICE - FPGA device (e.g. xcvu095-ffva2104-2-e)
# SYN_FILES - list of source files
# INC_FILES - list of include files
# XDC_FILES - list of timing constraint files
# XCI_FILES - list of IP XCI files
# IP_TCL_FILES - list of IP TCL files (sourced during project creation)
# CONFIG_TCL_FILES - list of config TCL files (sourced before each build)
#
# Note: both SYN_FILES and INC_FILES support file list files. File list
# files are files with a .f extension that contain a list of additional
# files to include, one path relative to the .f file location per line.
# The .f files are processed recursively, and then the complete file list
# is de-duplicated, with later files in the list taking precedence.
#
# Example:
#
# FPGA_TOP = fpga
# FPGA_FAMILY = VirtexUltrascale
# FPGA_DEVICE = xcvu095-ffva2104-2-e
# SYN_FILES = rtl/fpga.v
# XDC_FILES = fpga.xdc
# XCI_FILES = ip/pcspma.xci
# include ../common/vivado.mk
#
###################################################################
# phony targets
.PHONY: fpga vivado tmpclean clean distclean
# prevent make from deleting intermediate files and reports
.PRECIOUS: %.xpr %.bit %.bin %.ltx %.xsa %.mcs %.prm
.SECONDARY:
CONFIG ?= config.mk
-include $(CONFIG)
FPGA_TOP ?= fpga
PROJECT ?= $(FPGA_TOP)
XDC_FILES ?= $(PROJECT).xdc
# handle file list files
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
SYN_FILES := $(call uniq_base,$(call process_f_files,$(SYN_FILES)))
INC_FILES := $(call uniq_base,$(call process_f_files,$(INC_FILES)))
###################################################################
# Main Targets
#
# all: build everything (fpga)
# fpga: build FPGA config
# vivado: open project in Vivado
# tmpclean: remove intermediate files
# clean: remove output files and project files
# distclean: remove archived output files
###################################################################
all: fpga
fpga: $(PROJECT).bit
vivado: $(PROJECT).xpr
vivado $(PROJECT).xpr
tmpclean::
-rm -rf *.log *.jou *.cache *.gen *.hbs *.hw *.ip_user_files *.runs *.xpr *.html *.xml *.sim *.srcs *.str .Xil defines.v
-rm -rf create_project.tcl update_config.tcl run_synth.tcl run_impl.tcl generate_bit.tcl
clean:: tmpclean
-rm -rf *.bit *.bin *.ltx *.xsa program.tcl generate_mcs.tcl *.mcs *.prm flash.tcl
-rm -rf *_utilization.rpt *_utilization_hierarchical.rpt
distclean:: clean
-rm -rf rev
###################################################################
# Target implementations
###################################################################
# Vivado project file
# create fresh project if Makefile or IP files have changed
create_project.tcl: Makefile $(XCI_FILES) $(IP_TCL_FILES)
rm -rf defines.v
touch defines.v
for x in $(DEFS); do echo '`define' $$x >> defines.v; done
echo "create_project -force -part $(FPGA_PART) $(PROJECT)" > $@
echo "add_files -fileset sources_1 defines.v $(SYN_FILES)" >> $@
echo "set_property top $(FPGA_TOP) [current_fileset]" >> $@
echo "add_files -fileset constrs_1 $(XDC_FILES)" >> $@
for x in $(XCI_FILES); do echo "import_ip $$x" >> $@; done
for x in $(IP_TCL_FILES); do echo "source $$x" >> $@; done
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
# source config TCL scripts if any source file has changed
update_config.tcl: $(CONFIG_TCL_FILES) $(SYN_FILES) $(INC_FILES) $(XDC_FILES)
echo "open_project -quiet $(PROJECT).xpr" > $@
for x in $(CONFIG_TCL_FILES); do echo "source $$x" >> $@; done
$(PROJECT).xpr: create_project.tcl update_config.tcl
vivado -nojournal -nolog -mode batch $(foreach x,$?,-source $x)
# synthesis run
$(PROJECT).runs/synth_1/$(PROJECT).dcp: create_project.tcl update_config.tcl $(SYN_FILES) $(INC_FILES) $(XDC_FILES) | $(PROJECT).xpr
echo "open_project $(PROJECT).xpr" > run_synth.tcl
echo "reset_run synth_1" >> run_synth.tcl
echo "launch_runs -jobs 4 synth_1" >> run_synth.tcl
echo "wait_on_run synth_1" >> run_synth.tcl
vivado -nojournal -nolog -mode batch -source run_synth.tcl
# implementation run
$(PROJECT).runs/impl_1/$(PROJECT)_routed.dcp: $(PROJECT).runs/synth_1/$(PROJECT).dcp
echo "open_project $(PROJECT).xpr" > run_impl.tcl
echo "reset_run impl_1" >> run_impl.tcl
echo "launch_runs -jobs 4 impl_1" >> run_impl.tcl
echo "wait_on_run impl_1" >> run_impl.tcl
echo "open_run impl_1" >> run_impl.tcl
echo "report_utilization -file $(PROJECT)_utilization.rpt" >> run_impl.tcl
echo "report_utilization -hierarchical -file $(PROJECT)_utilization_hierarchical.rpt" >> run_impl.tcl
vivado -nojournal -nolog -mode batch -source run_impl.tcl
# output files (including potentially bit, bin, ltx, and xsa)
$(PROJECT).bit $(PROJECT).bin $(PROJECT).ltx $(PROJECT).xsa: $(PROJECT).runs/impl_1/$(PROJECT)_routed.dcp
echo "open_project $(PROJECT).xpr" > generate_bit.tcl
echo "open_run impl_1" >> generate_bit.tcl
echo "write_bitstream -force -bin_file $(PROJECT).runs/impl_1/$(PROJECT).bit" >> generate_bit.tcl
echo "write_debug_probes -force $(PROJECT).runs/impl_1/$(PROJECT).ltx" >> generate_bit.tcl
echo "write_hw_platform -fixed -force -include_bit $(PROJECT).xsa" >> generate_bit.tcl
vivado -nojournal -nolog -mode batch -source generate_bit.tcl
ln -f -s $(PROJECT).runs/impl_1/$(PROJECT).bit .
ln -f -s $(PROJECT).runs/impl_1/$(PROJECT).bin .
if [ -e $(PROJECT).runs/impl_1/$(PROJECT).ltx ]; then ln -f -s $(PROJECT).runs/impl_1/$(PROJECT).ltx .; fi
mkdir -p rev
COUNT=100; \
while [ -e rev/$(PROJECT)_rev$$COUNT.bit ]; \
do COUNT=$$((COUNT+1)); done; \
cp -pv $(PROJECT).runs/impl_1/$(PROJECT).bit rev/$(PROJECT)_rev$$COUNT.bit; \
cp -pv $(PROJECT).runs/impl_1/$(PROJECT).bin rev/$(PROJECT)_rev$$COUNT.bin; \
if [ -e $(PROJECT).runs/impl_1/$(PROJECT).ltx ]; then cp -pv $(PROJECT).runs/impl_1/$(PROJECT).ltx rev/$(PROJECT)_rev$$COUNT.ltx; fi; \
if [ -e $(PROJECT).xsa ]; then cp -pv $(PROJECT).xsa rev/$(PROJECT)_rev$$COUNT.xsa; fi

View File

@@ -0,0 +1,15 @@
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
#
# Ethernet constraints
# IDELAY from PHY chip (RGMII)
set_property DELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay phy_rxd_idelay_bit[*].idelay_inst}]
# MMCM phase (RGMII)
set_property CLKOUT1_PHASE 90 [get_cells clk_mmcm_inst]

View File

@@ -0,0 +1,122 @@
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
#
# XDC constraints for the HiTech Global HTG-9200 board
# part: xcvu9p-flgb2104-2-e
# part: xcvu13p-fhgb2104-2-e
# General configuration
set_property CFGBVS GND [current_design]
set_property CONFIG_VOLTAGE 1.8 [current_design]
set_property BITSTREAM.GENERAL.COMPRESS true [current_design]
set_property BITSTREAM.CONFIG.EXTMASTERCCLK_EN {DIV-1} [current_design]
set_property BITSTREAM.CONFIG.SPI_32BIT_ADDR YES [current_design]
set_property BITSTREAM.CONFIG.SPI_BUSWIDTH 8 [current_design]
set_property BITSTREAM.CONFIG.SPI_FALL_EDGE YES [current_design]
set_property BITSTREAM.CONFIG.OVERTEMPSHUTDOWN Enable [current_design]
# System clocks
# DDR4 clocks from U37 (200 MHz)
#set_property -dict {LOC BA34 IOSTANDARD DIFF_SSTL12} [get_ports sys_clk_ddr4_a_p]
#set_property -dict {LOC BB34 IOSTANDARD DIFF_SSTL12} [get_ports sys_clk_ddr4_a_n]
#create_clock -period 5.000 -name sys_clk_ddr4_a [get_ports sys_clk_ddr4_a_p]
# refclk from U39 (156.25 MHz)
set_property -dict {LOC AW28 IOSTANDARD LVDS} [get_ports ref_clk_p]
set_property -dict {LOC AY28 IOSTANDARD LVDS} [get_ports ref_clk_n]
create_clock -period 6.400 -name ref_clk [get_ports ref_clk_p]
# LEDs
set_property -dict {LOC AP28 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[0]}]
set_property -dict {LOC AN28 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[1]}]
set_property -dict {LOC AP26 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[2]}]
set_property -dict {LOC AP25 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[3]}]
set_property -dict {LOC AR28 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[4]}]
set_property -dict {LOC AR27 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[5]}]
set_property -dict {LOC AT28 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[6]}]
set_property -dict {LOC AR25 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports {led[7]}]
set_false_path -to [get_ports {led[*]}]
set_output_delay 0 [get_ports {led[*]}]
# Push buttons
set_property -dict {LOC AJ34 IOSTANDARD LVCMOS12} [get_ports {btn[0]}]
set_property -dict {LOC AK32 IOSTANDARD LVCMOS12} [get_ports {btn[1]}]
set_false_path -from [get_ports {btn[*]}]
set_input_delay 0 [get_ports {btn[*]}]
# DIP switches
set_property -dict {LOC BF33 IOSTANDARD LVCMOS12} [get_ports {sw[0]}]
set_property -dict {LOC AK27 IOSTANDARD LVCMOS12} [get_ports {sw[1]}]
set_property -dict {LOC AR32 IOSTANDARD LVCMOS12} [get_ports {sw[2]}]
set_property -dict {LOC AR31 IOSTANDARD LVCMOS12} [get_ports {sw[3]}]
set_property -dict {LOC AT32 IOSTANDARD LVCMOS12} [get_ports {sw[4]}]
set_property -dict {LOC AW30 IOSTANDARD LVCMOS12} [get_ports {sw[5]}]
set_property -dict {LOC BC32 IOSTANDARD LVCMOS12} [get_ports {sw[6]}]
set_property -dict {LOC BC33 IOSTANDARD LVCMOS12} [get_ports {sw[7]}]
set_false_path -from [get_ports {sw[*]}]
set_input_delay 0 [get_ports {sw[*]}]
# UART
set_property -dict {LOC R15 IOSTANDARD LVCMOS18} [get_ports uart_txd]
set_property -dict {LOC P15 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports uart_rxd]
set_property -dict {LOC L15 IOSTANDARD LVCMOS18} [get_ports uart_rts]
set_property -dict {LOC D14 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports uart_cts]
set_property -dict {LOC P16 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports uart_rst_n]
set_false_path -to [get_ports {uart_rxd uart_cts uart_rst_n}]
set_output_delay 0 [get_ports {uart_rxd uart_cts uart_rst_n}]
set_false_path -from [get_ports {uart_txd uart_rts}]
set_input_delay 0 [get_ports {uart_txd uart_rts}]
# I2C
set_property -dict {LOC AV28 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports i2c_main_scl]
set_property -dict {LOC AU27 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports i2c_main_sda]
set_property -dict {LOC AV27 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 8} [get_ports i2c_main_rst_n]
set_false_path -to [get_ports {i2c_main_sda i2c_main_scl i2c_main_rst_n}]
set_output_delay 0 [get_ports {i2c_main_sda i2c_main_scl i2c_main_rst_n}]
set_false_path -from [get_ports {i2c_main_sda i2c_main_scl}]
set_input_delay 0 [get_ports {i2c_main_sda i2c_main_scl}]
# Gigabit Ethernet RGMII PHY
set_property -dict {LOC G20 IOSTANDARD LVCMOS18} [get_ports {phy_rx_clk}] ;# from U2.43 // MAYBE
set_property -dict {LOC A20 IOSTANDARD LVCMOS18} [get_ports {phy_rxd[0]}] ;# from U2.44
set_property -dict {LOC D21 IOSTANDARD LVCMOS18} [get_ports {phy_rxd[1]}] ;# from U2.45
set_property -dict {LOC E21 IOSTANDARD LVCMOS18} [get_ports {phy_rxd[2]}] ;# from U2.46
set_property -dict {LOC C21 IOSTANDARD LVCMOS18} [get_ports {phy_rxd[3]}] ;# from U2.47
set_property -dict {LOC B21 IOSTANDARD LVCMOS18} [get_ports {phy_rx_ctl}] ;# from U2.53
set_property -dict {LOC B20 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_tx_clk}] ;# from U2.40
set_property -dict {LOC D20 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_txd[0]}] ;# from U2.38
set_property -dict {LOC A19 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_txd[1]}] ;# from U2.37
set_property -dict {LOC B19 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_txd[2]}] ;# from U2.36
set_property -dict {LOC E20 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_txd[3]}] ;# from U2.35
set_property -dict {LOC G21 IOSTANDARD LVCMOS18 SLEW FAST DRIVE 12} [get_ports {phy_tx_ctl}] ;# from U2.52
#set_property -dict {LOC G19 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 12} [get_ports {phy_mdio}] ;# from U2.21
#set_property -dict {LOC A18 IOSTANDARD LVCMOS18 SLEW SLOW DRIVE 12} [get_ports {phy_mdc}] ;# from U2.20
create_clock -period 8.000 -name {phy_rx_clk} [get_ports {phy_rx_clk}]
#set_false_path -to [get_ports {phy_mdio phy_mdc}]
#set_output_delay 0 [get_ports {phy_mdio phy_mdc}]
#set_false_path -from [get_ports {phy_mdio}]
#set_input_delay 0 [get_ports {phy_mdio}]
# QSPI flash
#set_property -dict {LOC AM26 IOSTANDARD LVCMOS18 DRIVE 8} [get_ports {qspi_1_dq[0]}]
#set_property -dict {LOC AN26 IOSTANDARD LVCMOS18 DRIVE 8} [get_ports {qspi_1_dq[1]}]
#set_property -dict {LOC AL25 IOSTANDARD LVCMOS18 DRIVE 8} [get_ports {qspi_1_dq[2]}]
#set_property -dict {LOC AM25 IOSTANDARD LVCMOS18 DRIVE 8} [get_ports {qspi_1_dq[3]}]
#set_property -dict {LOC BF27 IOSTANDARD LVCMOS18 DRIVE 8} [get_ports {qspi_1_cs_n}]
#set_false_path -to [get_ports {qspi_1_dq[*] qspi_1_cs}]
#set_output_delay 0 [get_ports {qspi_1_dq[*] qspi_1_cs}]
#set_false_path -from [get_ports {qspi_1_dq}]
#set_input_delay 0 [get_ports {qspi_1_dq}]

View File

@@ -0,0 +1,86 @@
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
#
# FPGA settings
FPGA_PART = xcvu13p-fhgb2104-2-e
FPGA_TOP = fpga
FPGA_ARCH = virtexuplus
# Files for synthesis
SYN_FILES = ../rtl/fpga.sv
SYN_FILES += ../rtl/fpga_core.sv
SYN_FILES += ../lib/taxi/rtl/eth/taxi_eth_mac_1g_rgmii_fifo.f
SYN_FILES += ../lib/taxi/rtl/lss/taxi_uart.f
SYN_FILES += ../lib/taxi/rtl/io/taxi_debounce_switch.sv
SYN_FILES += ../lib/taxi/rtl/sync/taxi_sync_reset.sv
SYN_FILES += ../lib/taxi/rtl/sync/taxi_sync_signal.sv
# XDC files
XDC_FILES = ../fpga.xdc
XDC_FILES += ../eth_rgmii.xdc
XDC_FILES += ../lib/taxi/syn/vivado/taxi_rgmii_phy_if.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_eth_mac_1g_rgmii.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_eth_mac_fifo.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_axis_async_fifo.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_sync_reset.tcl
# IP
#IP_TCL_FILES += ../ip/eth_xcvr_gt.tcl
# Configuration
#CONFIG_TCL_FILES = ./config.tcl
include ../common/vivado.mk
program: $(PROJECT).bit
echo "open_hw_manager" > program.tcl
echo "connect_hw_server" >> program.tcl
echo "open_hw_target" >> program.tcl
echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl
echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl
echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl
echo "program_hw_devices [current_hw_device]" >> program.tcl
echo "exit" >> program.tcl
vivado -nojournal -nolog -mode batch -source program.tcl
$(PROJECT)_primary.mcs $(PROJECT)_secondary.mcs $(PROJECT)_primary.prm $(PROJECT)_secondary.prm: $(PROJECT).bit
echo "write_cfgmem -force -format mcs -size 128 -interface SPIx8 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl
echo "exit" >> generate_mcs.tcl
vivado -nojournal -nolog -mode batch -source generate_mcs.tcl
mkdir -p rev
COUNT=100; \
while [ -e rev/$*_rev$$COUNT.bit ]; \
do COUNT=$$((COUNT+1)); done; \
COUNT=$$((COUNT-1)); \
for x in _primary.mcs _secondary.mcs _primary.prm _secondary.prm; \
do cp $*$$x rev/$*_rev$$COUNT$$x; \
echo "Output: rev/$*_rev$$COUNT$$x"; done;
flash: $(PROJECT)_primary.mcs $(PROJECT)_secondary.mcs $(PROJECT)_primary.prm $(PROJECT)_secondary.prm
echo "open_hw_manager" > flash.tcl
echo "connect_hw_server" >> flash.tcl
echo "open_hw_target" >> flash.tcl
echo "current_hw_device [lindex [get_hw_devices] 0]" >> flash.tcl
echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> flash.tcl
echo "create_hw_cfgmem -hw_device [current_hw_device] [lindex [get_cfgmem_parts {s25fl512s-spi-x1_x2_x4_x8}] 0]" >> flash.tcl
echo "current_hw_cfgmem -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM [current_hw_device]]" >> flash.tcl
echo "set_property PROGRAM.FILES [list \"$(PROJECT)_primary.mcs\" \"$(PROJECT)_secondary.mcs\"] [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.PRM_FILES [list \"$(PROJECT)_primary.prm\" \"$(PROJECT)_secondary.prm\"] [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.ERASE 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.CFG_PROGRAM 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.VERIFY 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.CHECKSUM 0 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.ADDRESS_RANGE {use_file} [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.UNUSED_PIN_TERMINATION {pull-none} [current_hw_cfgmem]" >> flash.tcl
echo "create_hw_bitstream -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM_BITFILE [current_hw_device]]" >> flash.tcl
echo "program_hw_devices [current_hw_device]" >> flash.tcl
echo "refresh_hw_device [current_hw_device]" >> flash.tcl
echo "program_hw_cfgmem -hw_cfgmem [current_hw_cfgmem]" >> flash.tcl
echo "boot_hw_device [current_hw_device]" >> flash.tcl
echo "exit" >> flash.tcl
vivado -nojournal -nolog -mode batch -source flash.tcl

View File

@@ -0,0 +1,86 @@
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
#
# FPGA settings
FPGA_PART = xcvu9p-flgb2104-2-e
FPGA_TOP = fpga
FPGA_ARCH = virtexuplus
# Files for synthesis
SYN_FILES = ../rtl/fpga.sv
SYN_FILES += ../rtl/fpga_core.sv
SYN_FILES += ../lib/taxi/rtl/eth/taxi_eth_mac_1g_rgmii_fifo.f
SYN_FILES += ../lib/taxi/rtl/lss/taxi_uart.f
SYN_FILES += ../lib/taxi/rtl/io/taxi_debounce_switch.sv
SYN_FILES += ../lib/taxi/rtl/sync/taxi_sync_reset.sv
SYN_FILES += ../lib/taxi/rtl/sync/taxi_sync_signal.sv
# XDC files
XDC_FILES = ../fpga.xdc
XDC_FILES += ../eth_rgmii.xdc
XDC_FILES += ../lib/taxi/syn/vivado/taxi_rgmii_phy_if.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_eth_mac_1g_rgmii.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_eth_mac_fifo.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_axis_async_fifo.tcl
XDC_FILES += ../lib/taxi/syn/vivado/taxi_sync_reset.tcl
# IP
#IP_TCL_FILES += ../ip/eth_xcvr_gt.tcl
# Configuration
#CONFIG_TCL_FILES = ./config.tcl
include ../common/vivado.mk
program: $(PROJECT).bit
echo "open_hw_manager" > program.tcl
echo "connect_hw_server" >> program.tcl
echo "open_hw_target" >> program.tcl
echo "current_hw_device [lindex [get_hw_devices] 0]" >> program.tcl
echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> program.tcl
echo "set_property PROGRAM.FILE {$(PROJECT).bit} [current_hw_device]" >> program.tcl
echo "program_hw_devices [current_hw_device]" >> program.tcl
echo "exit" >> program.tcl
vivado -nojournal -nolog -mode batch -source program.tcl
$(PROJECT)_primary.mcs $(PROJECT)_secondary.mcs $(PROJECT)_primary.prm $(PROJECT)_secondary.prm: $(PROJECT).bit
echo "write_cfgmem -force -format mcs -size 128 -interface SPIx8 -loadbit {up 0x0000000 $*.bit} -checksum -file $*.mcs" > generate_mcs.tcl
echo "exit" >> generate_mcs.tcl
vivado -nojournal -nolog -mode batch -source generate_mcs.tcl
mkdir -p rev
COUNT=100; \
while [ -e rev/$*_rev$$COUNT.bit ]; \
do COUNT=$$((COUNT+1)); done; \
COUNT=$$((COUNT-1)); \
for x in _primary.mcs _secondary.mcs _primary.prm _secondary.prm; \
do cp $*$$x rev/$*_rev$$COUNT$$x; \
echo "Output: rev/$*_rev$$COUNT$$x"; done;
flash: $(PROJECT)_primary.mcs $(PROJECT)_secondary.mcs $(PROJECT)_primary.prm $(PROJECT)_secondary.prm
echo "open_hw_manager" > flash.tcl
echo "connect_hw_server" >> flash.tcl
echo "open_hw_target" >> flash.tcl
echo "current_hw_device [lindex [get_hw_devices] 0]" >> flash.tcl
echo "refresh_hw_device -update_hw_probes false [current_hw_device]" >> flash.tcl
echo "create_hw_cfgmem -hw_device [current_hw_device] [lindex [get_cfgmem_parts {s25fl512s-spi-x1_x2_x4_x8}] 0]" >> flash.tcl
echo "current_hw_cfgmem -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM [current_hw_device]]" >> flash.tcl
echo "set_property PROGRAM.FILES [list \"$(PROJECT)_primary.mcs\" \"$(PROJECT)_secondary.mcs\"] [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.PRM_FILES [list \"$(PROJECT)_primary.prm\" \"$(PROJECT)_secondary.prm\"] [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.ERASE 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.CFG_PROGRAM 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.VERIFY 1 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.CHECKSUM 0 [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.ADDRESS_RANGE {use_file} [current_hw_cfgmem]" >> flash.tcl
echo "set_property PROGRAM.UNUSED_PIN_TERMINATION {pull-none} [current_hw_cfgmem]" >> flash.tcl
echo "create_hw_bitstream -hw_device [current_hw_device] [get_property PROGRAM.HW_CFGMEM_BITFILE [current_hw_device]]" >> flash.tcl
echo "program_hw_devices [current_hw_device]" >> flash.tcl
echo "refresh_hw_device [current_hw_device]" >> flash.tcl
echo "program_hw_cfgmem -hw_cfgmem [current_hw_cfgmem]" >> flash.tcl
echo "boot_hw_device [current_hw_device]" >> flash.tcl
echo "exit" >> flash.tcl
vivado -nojournal -nolog -mode batch -source flash.tcl

View File

@@ -0,0 +1 @@
../../../../

View File

@@ -0,0 +1,373 @@
// SPDX-License-Identifier: MIT
/*
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* FPGA top-level module
*/
module fpga #
(
// simulation (set to avoid vendor primitives)
parameter logic SIM = 1'b0,
// vendor ("GENERIC", "XILINX", "ALTERA")
parameter VENDOR = "XILINX",
// device family
parameter FAMILY = "zynquplus",
// Use 90 degree clock for RGMII transmit
parameter logic USE_CLK90 = 1'b0
)
(
/*
* Clock: 156.25 MHz LVDS
*/
input wire logic ref_clk_p,
input wire logic ref_clk_n,
/*
* GPIO
*/
input wire logic [1:0] btn,
input wire logic [7:0] sw,
output wire logic [7:0] led,
/*
* I2C for board management
*/
inout wire logic i2c_main_scl,
inout wire logic i2c_main_sda,
output wire logic i2c_main_rst_n,
/*
* UART: 115200 bps, 8N1
*/
output wire logic uart_rxd,
input wire logic uart_txd,
input wire logic uart_rts,
output wire logic uart_cts,
output wire logic uart_rst_n,
/*
* Ethernet: 1000BASE-T RGMII
*/
input wire logic phy_rx_clk,
input wire logic [3:0] phy_rxd,
input wire logic phy_rx_ctl,
output wire logic phy_tx_clk,
output wire logic [3:0] phy_txd,
output wire logic phy_tx_ctl
);
// Clock and reset
wire ref_clk_ibufg;
// Internal 125 MHz clock
wire clk_125mhz_mmcm_out;
wire clk90_125mhz_mmcm_out;
wire clk_125mhz_int;
wire clk90_125mhz_int;
wire rst_125mhz_int;
// Internal 312.5 MHz clock
wire clk_312mhz_mmcm_out;
wire clk_312mhz_int;
wire rst_312mhz_int;
wire mmcm_rst = ~btn[0];
wire mmcm_locked;
wire mmcm_clkfb;
IBUFGDS #(
.DIFF_TERM("FALSE"),
.IBUF_LOW_PWR("FALSE")
)
ref_clk_ibufg_inst (
.O (ref_clk_ibufg),
.I (ref_clk_p),
.IB (ref_clk_n)
);
// MMCM instance
MMCME4_BASE #(
// 156.25 MHz input
.CLKIN1_PERIOD(6.4),
.REF_JITTER1(0.010),
// 156.25 MHz input / 1 = 156.25 MHz PFD (range 10 MHz to 500 MHz)
.DIVCLK_DIVIDE(1),
// 156.25 MHz PFD * 8 = 1250 MHz VCO (range 800 MHz to 1600 MHz)
.CLKFBOUT_MULT_F(8),
.CLKFBOUT_PHASE(0),
// 1250 MHz / 10 = 125 MHz, 0 degrees
.CLKOUT0_DIVIDE_F(10),
.CLKOUT0_DUTY_CYCLE(0.5),
.CLKOUT0_PHASE(0),
// 1250 MHz / 10 = 125 MHz, 90 degrees
.CLKOUT1_DIVIDE(10),
.CLKOUT1_DUTY_CYCLE(0.5),
.CLKOUT1_PHASE(90),
// 1250 MHz / 4 = 312.5 MHz, 0 degrees
.CLKOUT2_DIVIDE(4),
.CLKOUT2_DUTY_CYCLE(0.5),
.CLKOUT2_PHASE(0),
// Not used
.CLKOUT3_DIVIDE(1),
.CLKOUT3_DUTY_CYCLE(0.5),
.CLKOUT3_PHASE(0),
// Not used
.CLKOUT4_DIVIDE(1),
.CLKOUT4_DUTY_CYCLE(0.5),
.CLKOUT4_PHASE(0),
.CLKOUT4_CASCADE("FALSE"),
// Not used
.CLKOUT5_DIVIDE(1),
.CLKOUT5_DUTY_CYCLE(0.5),
.CLKOUT5_PHASE(0),
// Not used
.CLKOUT6_DIVIDE(1),
.CLKOUT6_DUTY_CYCLE(0.5),
.CLKOUT6_PHASE(0),
// optimized bandwidth
.BANDWIDTH("OPTIMIZED"),
// don't wait for lock during startup
.STARTUP_WAIT("FALSE")
)
clk_mmcm_inst (
// 156.25 MHz input
.CLKIN1(ref_clk_ibufg),
// direct clkfb feeback
.CLKFBIN(mmcm_clkfb),
.CLKFBOUT(mmcm_clkfb),
.CLKFBOUTB(),
// 125 MHz, 0 degrees
.CLKOUT0(clk_125mhz_mmcm_out),
.CLKOUT0B(),
// 125 MHz, 90 degrees
.CLKOUT1(clk90_125mhz_mmcm_out),
.CLKOUT1B(),
// 312.5 MHz, 0 degrees
.CLKOUT2(clk_312mhz_mmcm_out),
.CLKOUT2B(),
// Not used
.CLKOUT3(),
.CLKOUT3B(),
// Not used
.CLKOUT4(),
// Not used
.CLKOUT5(),
// Not used
.CLKOUT6(),
// reset input
.RST(mmcm_rst),
// don't power down
.PWRDWN(1'b0),
// locked output
.LOCKED(mmcm_locked)
);
BUFG
clk_125mhz_bufg_inst (
.I(clk_125mhz_mmcm_out),
.O(clk_125mhz_int)
);
BUFG
clk90_125mhz_bufg_inst (
.I(clk90_125mhz_mmcm_out),
.O(clk90_125mhz_int)
);
BUFG
clk_312mhz_bufg_inst (
.I(clk_312mhz_mmcm_out),
.O(clk_312mhz_int)
);
taxi_sync_reset #(
.N(4)
)
sync_reset_125mhz_inst (
.clk(clk_125mhz_int),
.rst(~mmcm_locked),
.out(rst_125mhz_int)
);
taxi_sync_reset #(
.N(4)
)
sync_reset_312mhz_inst (
.clk(clk_312mhz_int),
.rst(~mmcm_locked),
.out(rst_312mhz_int)
);
// GPIO
wire btn_int;
wire [7:0] sw_int;
taxi_debounce_switch #(
.WIDTH(9),
.N(4),
.RATE(125000)
)
debounce_switch_inst (
.clk(clk_125mhz_int),
.rst(rst_125mhz_int),
.in({btn[1],
~sw}),
.out({btn_int,
sw_int})
);
wire uart_txd_int;
wire uart_rts_int;
taxi_sync_signal #(
.WIDTH(2),
.N(2)
)
sync_signal_inst (
.clk(clk_125mhz_int),
.in({uart_txd, uart_rts}),
.out({uart_txd_int, uart_rts_int})
);
wire i2c_scl_i;
wire i2c_scl_o;
wire i2c_scl_t;
wire i2c_sda_i;
wire i2c_sda_o;
wire i2c_sda_t;
assign i2c_scl_i = i2c_main_scl;
assign i2c_main_scl = i2c_scl_t ? 1'bz : i2c_scl_o;
assign i2c_sda_i = i2c_main_sda;
assign i2c_main_sda = i2c_sda_t ? 1'bz : i2c_sda_o;
assign i2c_main_rst_n = 1'b1;
// IODELAY elements for RGMII interface to PHY
wire [3:0] phy_rxd_int;
wire phy_rx_ctl_int;
IDELAYCTRL #(
.SIM_DEVICE("ULTRASCALE")
)
idelayctrl_inst (
.REFCLK(clk_312mhz_int),
.RST(rst_312mhz_int),
.RDY()
);
for (genvar n = 0; n < 4; n = n + 1) begin : phy_rxd_idelay_bit
IDELAYE3 #(
.DELAY_SRC("IDATAIN"),
.CASCADE("NONE"),
.DELAY_TYPE("FIXED"),
.DELAY_VALUE(0),
.REFCLK_FREQUENCY(312.5),
.DELAY_FORMAT("TIME"),
.UPDATE_MODE("SYNC"),
.SIM_DEVICE("ULTRASCALE_PLUS")
)
idelay_inst (
.CASC_IN(1'b0),
.CASC_RETURN(1'b0),
.CASC_OUT(),
.IDATAIN(phy_rxd[n]),
.DATAIN(1'b0),
.DATAOUT(phy_rxd_int[n]),
.CLK(1'b0),
.EN_VTC(1'b1),
.CE(1'b0),
.INC(1'b0),
.LOAD(1'b0),
.RST(1'b0),
.CNTVALUEIN(9'd0),
.CNTVALUEOUT()
);
end
IDELAYE3 #(
.DELAY_SRC("IDATAIN"),
.CASCADE("NONE"),
.DELAY_TYPE("FIXED"),
.DELAY_VALUE(0),
.REFCLK_FREQUENCY(312.5),
.DELAY_FORMAT("TIME"),
.UPDATE_MODE("SYNC"),
.SIM_DEVICE("ULTRASCALE_PLUS")
)
phy_rx_ctl_idelay (
.CASC_IN(1'b0),
.CASC_RETURN(1'b0),
.CASC_OUT(),
.IDATAIN(phy_rx_ctl),
.DATAIN(1'b0),
.DATAOUT(phy_rx_ctl_int),
.CLK(1'b0),
.EN_VTC(1'b1),
.CE(1'b0),
.INC(1'b0),
.LOAD(1'b0),
.RST(1'b0),
.CNTVALUEIN(9'd0),
.CNTVALUEOUT()
);
fpga_core #(
.SIM(SIM),
.VENDOR(VENDOR),
.FAMILY(FAMILY),
.USE_CLK90(USE_CLK90)
)
core_inst (
/*
* Clock: 125MHz
* Synchronous reset
*/
.clk(clk_125mhz_int),
.clk90(clk90_125mhz_int),
.rst(rst_125mhz_int),
/*
* GPIO
*/
.btn(btn_int),
.sw(sw_int),
.led(led),
/*
* UART: 115200 bps, 8N1
*/
.uart_rxd(uart_rxd),
.uart_txd(uart_txd_int),
.uart_rts(uart_rts_int),
.uart_cts(uart_cts),
.uart_rst_n(uart_rst_n),
/*
* Ethernet: 1000BASE-T RGMII
*/
.phy_rgmii_rx_clk(phy_rx_clk),
.phy_rgmii_rxd(phy_rxd),
.phy_rgmii_rx_ctl(phy_rx_ctl),
.phy_rgmii_tx_clk(phy_tx_clk),
.phy_rgmii_txd(phy_txd),
.phy_rgmii_tx_ctl(phy_tx_ctl)
);
endmodule
`resetall

View File

@@ -0,0 +1,176 @@
// SPDX-License-Identifier: MIT
/*
Copyright (c) 2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
*/
`resetall
`timescale 1ns / 1ps
`default_nettype none
/*
* FPGA core logic
*/
module fpga_core #
(
// simulation (set to avoid vendor primitives)
parameter logic SIM = 1'b0,
// vendor ("GENERIC", "XILINX", "ALTERA")
parameter VENDOR = "XILINX",
// device family
parameter FAMILY = "zynquplus",
// Use 90 degree clock for RGMII transmit
parameter logic USE_CLK90 = 1'b1
)
(
/*
* Clock: 125MHz
* Synchronous reset
*/
input wire logic clk,
input wire logic clk90,
input wire logic rst,
/*
* GPIO
*/
input wire logic btn,
input wire logic [7:0] sw,
output wire logic [7:0] led,
/*
* UART: 115200 bps, 8N1
*/
output wire logic uart_rxd,
input wire logic uart_txd,
input wire logic uart_rts,
output wire logic uart_cts,
output wire logic uart_rst_n,
/*
* Ethernet: 1000BASE-T RGMII
*/
input wire logic phy_rgmii_rx_clk,
input wire logic [3:0] phy_rgmii_rxd,
input wire logic phy_rgmii_rx_ctl,
output wire logic phy_rgmii_tx_clk,
output wire logic [3:0] phy_rgmii_txd,
output wire logic phy_rgmii_tx_ctl
);
assign led = sw;
// UART
assign uart_cts = 1'b1;
assign uart_rst_n = 1'b1;
taxi_axis_if #(.DATA_W(8)) axis_uart();
taxi_uart
uut (
.clk(clk),
.rst(rst),
/*
* AXI4-Stream input (sink)
*/
.s_axis_tx(axis_uart),
/*
* AXI4-Stream output (source)
*/
.m_axis_rx(axis_uart),
/*
* UART interface
*/
.rxd(uart_txd),
.txd(uart_rxd),
/*
* Status
*/
.tx_busy(),
.rx_busy(),
.rx_overrun_error(),
.rx_frame_error(),
/*
* Configuration
*/
.prescale(16'(125000000/115200/8))
);
// BASE-T PHY
taxi_axis_if #(.DATA_W(8), .ID_W(8)) axis_eth();
taxi_axis_if #(.DATA_W(96), .KEEP_W(1), .ID_W(8)) axis_tx_cpl();
taxi_eth_mac_1g_rgmii_fifo #(
.SIM(SIM),
.VENDOR(VENDOR),
.FAMILY(FAMILY),
.USE_CLK90(USE_CLK90),
.PADDING_EN(1),
.MIN_FRAME_LEN(64),
.TX_FIFO_DEPTH(16384),
.TX_FRAME_FIFO(1),
.RX_FIFO_DEPTH(16384),
.RX_FRAME_FIFO(1)
)
eth_mac_inst (
.gtx_clk(clk),
.gtx_clk90(clk90),
.gtx_rst(rst),
.logic_clk(clk),
.logic_rst(rst),
/*
* Transmit interface (AXI stream)
*/
.s_axis_tx(axis_eth),
.m_axis_tx_cpl(axis_tx_cpl),
/*
* Receive interface (AXI stream)
*/
.m_axis_rx(axis_eth),
/*
* RGMII interface
*/
.rgmii_rx_clk(phy_rgmii_rx_clk),
.rgmii_rxd(phy_rgmii_rxd),
.rgmii_rx_ctl(phy_rgmii_rx_ctl),
.rgmii_tx_clk(phy_rgmii_tx_clk),
.rgmii_txd(phy_rgmii_txd),
.rgmii_tx_ctl(phy_rgmii_tx_ctl),
/*
* Status
*/
.tx_error_underflow(),
.tx_fifo_overflow(),
.tx_fifo_bad_frame(),
.tx_fifo_good_frame(),
.rx_error_bad_frame(),
.rx_error_bad_fcs(),
.rx_fifo_overflow(),
.rx_fifo_bad_frame(),
.rx_fifo_good_frame(),
.link_speed(),
/*
* Configuration
*/
.cfg_ifg(8'd12),
.cfg_tx_enable(1'b1),
.cfg_rx_enable(1'b1)
);
endmodule
`resetall

View File

@@ -0,0 +1,52 @@
# SPDX-License-Identifier: MIT
#
# Copyright (c) 2020-2025 FPGA Ninja, LLC
#
# Authors:
# - Alex Forencich
TOPLEVEL_LANG = verilog
SIM ?= verilator
WAVES ?= 0
COCOTB_HDL_TIMEUNIT = 1ns
COCOTB_HDL_TIMEPRECISION = 1ps
DUT = fpga_core
COCOTB_TEST_MODULES = test_$(DUT)
COCOTB_TOPLEVEL = $(DUT)
MODULE = $(COCOTB_TEST_MODULES)
TOPLEVEL = $(COCOTB_TOPLEVEL)
VERILOG_SOURCES += ../../rtl/$(DUT).sv
VERILOG_SOURCES += ../../lib/taxi/rtl/eth/taxi_eth_mac_1g_rgmii_fifo.f
VERILOG_SOURCES += ../../lib/taxi/rtl/lss/taxi_uart.f
VERILOG_SOURCES += ../../lib/taxi/rtl/sync/taxi_sync_reset.sv
VERILOG_SOURCES += ../../lib/taxi/rtl/sync/taxi_sync_signal.sv
# handle file list files
process_f_file = $(call process_f_files,$(addprefix $(dir $1),$(shell cat $1)))
process_f_files = $(foreach f,$1,$(if $(filter %.f,$f),$(call process_f_file,$f),$f))
uniq_base = $(if $1,$(call uniq_base,$(foreach f,$1,$(if $(filter-out $(notdir $(lastword $1)),$(notdir $f)),$f,))) $(lastword $1))
VERILOG_SOURCES := $(call uniq_base,$(call process_f_files,$(VERILOG_SOURCES)))
# module parameters
export PARAM_SIM := "1'b1"
export PARAM_VENDOR := "\"XILINX\""
export PARAM_FAMILY := "\"virtexuplus\""
export PARAM_USE_CLK90 := "1'b1"
ifeq ($(SIM), icarus)
PLUSARGS += -fst
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-P $(COCOTB_TOPLEVEL).$(subst PARAM_,,$(v))=$($(v)))
else ifeq ($(SIM), verilator)
COMPILE_ARGS += $(foreach v,$(filter PARAM_%,$(.VARIABLES)),-G$(subst PARAM_,,$(v))=$($(v)))
ifeq ($(WAVES), 1)
COMPILE_ARGS += --trace-fst
VERILATOR_TRACE = 1
endif
endif
include $(shell cocotb-config --makefiles)/Makefile.sim

View File

@@ -0,0 +1,206 @@
#!/usr/bin/env python
# SPDX-License-Identifier: MIT
"""
Copyright (c) 2020-2025 FPGA Ninja, LLC
Authors:
- Alex Forencich
"""
import logging
import os
import cocotb_test.simulator
import cocotb
from cocotb.log import SimLog
from cocotb.clock import Clock
from cocotb.triggers import RisingEdge, Timer, Combine
from cocotbext.eth import GmiiFrame, RgmiiPhy
from cocotbext.uart import UartSource, UartSink
class TB:
def __init__(self, dut, speed=1000e6):
self.dut = dut
self.log = SimLog("cocotb.tb")
self.log.setLevel(logging.DEBUG)
self.baset_phy = RgmiiPhy(dut.phy_rgmii_txd, dut.phy_rgmii_tx_ctl, dut.phy_rgmii_tx_clk,
dut.phy_rgmii_rxd, dut.phy_rgmii_rx_ctl, dut.phy_rgmii_rx_clk, speed=speed)
self.uart_source = UartSource(dut.uart_txd, baud=115200, bits=8, stop_bits=1)
self.uart_sink = UartSink(dut.uart_rxd, baud=115200, bits=8, stop_bits=1)
cocotb.start_soon(self._run_clk())
async def init(self):
self.dut.rst.setimmediatevalue(0)
for k in range(10):
await RisingEdge(self.dut.clk)
self.dut.rst.value = 1
for k in range(10):
await RisingEdge(self.dut.clk)
self.dut.rst.value = 0
async def _run_clk(self):
t = Timer(2, 'ns')
while True:
self.dut.clk.value = 1
await t
self.dut.clk90.value = 1
await t
self.dut.clk.value = 0
await t
self.dut.clk90.value = 0
await t
async def uart_test(tb, source, sink):
tb.log.info("Test UART")
tx_data = b"FPGA Ninja"
tb.log.info("UART TX: %s", tx_data)
await source.write(tx_data)
rx_data = bytearray()
while len(rx_data) < len(tx_data):
rx_data.extend(await sink.read())
tb.log.info("UART RX: %s", rx_data)
tb.log.info("UART test done")
async def mac_test(tb, source, sink):
tb.log.info("Test MAC")
tb.log.info("Multiple small packets")
count = 64
pkts = [bytearray([(x+k) % 256 for x in range(60)]) for k in range(count)]
for p in pkts:
await source.send(GmiiFrame.from_payload(p))
for k in range(count):
rx_frame = await sink.recv()
tb.log.info("RX frame: %s", rx_frame)
assert rx_frame.get_payload() == pkts[k]
assert rx_frame.check_fcs()
assert rx_frame.error is None
tb.log.info("Multiple large packets")
count = 32
pkts = [bytearray([(x+k) % 256 for x in range(1514)]) for k in range(count)]
for p in pkts:
await source.send(GmiiFrame.from_payload(p))
for k in range(count):
rx_frame = await sink.recv()
tb.log.info("RX frame: %s", rx_frame)
assert rx_frame.get_payload() == pkts[k]
assert rx_frame.check_fcs()
assert rx_frame.error is None
tb.log.info("MAC test done")
@cocotb.test()
async def run_test(dut):
tb = TB(dut)
await tb.init()
tb.log.info("Start UART test")
uart_test_cr = cocotb.start_soon(uart_test(tb, tb.uart_source, tb.uart_sink))
tb.log.info("Start BASE-T MAC loopback test")
phy_test_cr = cocotb.start_soon(mac_test(tb, tb.baset_phy.rx, tb.baset_phy.tx))
await Combine(uart_test_cr, phy_test_cr)
await RisingEdge(dut.clk)
await RisingEdge(dut.clk)
# cocotb-test
tests_dir = os.path.abspath(os.path.dirname(__file__))
rtl_dir = os.path.abspath(os.path.join(tests_dir, '..', '..', 'rtl'))
lib_dir = os.path.abspath(os.path.join(rtl_dir, '..', 'lib'))
def process_f_files(files):
lst = {}
for f in files:
if f[-2:].lower() == '.f':
with open(f, 'r') as fp:
l = fp.read().split()
for f in process_f_files([os.path.join(os.path.dirname(f), x) for x in l]):
lst[os.path.basename(f)] = f
else:
lst[os.path.basename(f)] = f
return list(lst.values())
def test_fpga_core(request):
dut = "fpga_core"
module = os.path.splitext(os.path.basename(__file__))[0]
toplevel = dut
verilog_sources = [
os.path.join(rtl_dir, f"{dut}.sv"),
os.path.join(lib_dir, "taxi", "rtl", "eth", "taxi_eth_mac_1g_rgmii_fifo.f"),
os.path.join(lib_dir, "taxi", "rtl", "lss", "taxi_uart.f"),
os.path.join(lib_dir, "taxi", "rtl", "sync", "taxi_sync_reset.sv"),
os.path.join(lib_dir, "taxi", "rtl", "sync", "taxi_sync_signal.sv"),
]
verilog_sources = process_f_files(verilog_sources)
parameters = {}
parameters['SIM'] = "1'b1"
parameters['VENDOR'] = "\"XILINX\""
parameters['FAMILY'] = "\"virtexuplus\""
parameters['USE_CLK90'] = "1'b1"
extra_env = {f'PARAM_{k}': str(v) for k, v in parameters.items()}
sim_build = os.path.join(tests_dir, "sim_build",
request.node.name.replace('[', '-').replace(']', ''))
cocotb_test.simulator.run(
simulator="verilator",
python_search=[tests_dir],
verilog_sources=verilog_sources,
toplevel=toplevel,
module=module,
parameters=parameters,
sim_build=sim_build,
extra_env=extra_env,
)