From 1574b569e3cb61253d3f719284185da5e29a80bf Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Tue, 28 Jan 2020 10:35:02 -0500 Subject: [PATCH 01/16] Fixed issue in setup.py --- setup.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/setup.py b/setup.py index b2670d2a..d7d7bdb0 100644 --- a/setup.py +++ b/setup.py @@ -23,7 +23,7 @@ class directoryValidator(Validator): def validate(self, document): if not document.text=='': - if not os.path.isdir("/home/el"): + if not os.path.isdir(document.text): raise ValidationError( message='Please enter valid directory for galapagos path', cursor_position=len(document.text) From cd2fe8e881c8aa86b4a0f5118580fc75a43f2c0c Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Tue, 28 Jan 2020 15:40:01 -0500 Subject: [PATCH 02/16] Fixed TCL scripts in shells to support Vivado 2018.3 --- shells/Makefile | 8 +++- shells/sidewinder/tclScripts/shell_bd.tcl | 38 ++++++++++++++++-- shells/tclScripts/make_shell.tcl | 2 +- shells/tclScripts/shell_procs.tcl | 8 +++- shells/zedboard/tclScripts/shell_bd.tcl | 48 +++++++++++++++-------- 5 files changed, 79 insertions(+), 25 deletions(-) diff --git a/shells/Makefile b/shells/Makefile index 01fd00a8..39f05207 100644 --- a/shells/Makefile +++ b/shells/Makefile @@ -13,20 +13,24 @@ guard-%: all: example synth = 0 +# This file does not exist... pr_tcl = ./hlsTest/dma_example/tclScripts/pr_bd.tcl dma_example: vivado_hls ./hlsTest/dma_example/generate_hls.tcl vivado -mode gui -source ./tclScripts/make_shell.tcl -tclargs --project_name dma_example --pr_tcl ${pr_tcl} --start_synth ${synth} +# None of these files exist hlsExample: mkdir -p hlsBuild vivado_hls ./hlsTest/generate_hls.tcl ./hlsTest/generate.sh dma_example -example: hlsExample +example: mkdir -p projects - vivado -mode tcl -source ./tclScripts/make_shell.tcl -tclargs --project_name example --pr_tcl ${pr_tcl} --start_synth ${synth} + vivado -mode batch -source ./tclScripts/make_shell.tcl -tclargs --project_name example --start_synth ${synth} + # Get rid of references to nonexistent files + # vivado -mode tcl -source ./tclScripts/make_shell.tcl -tclargs --project_name example --pr_tcl ${pr_tcl} --start_synth ${synth} app_dev: guard-PROJECTNAME mkdir -p projects diff --git a/shells/sidewinder/tclScripts/shell_bd.tcl b/shells/sidewinder/tclScripts/shell_bd.tcl index 708b1abd..acb09226 100644 --- a/shells/sidewinder/tclScripts/shell_bd.tcl +++ b/shells/sidewinder/tclScripts/shell_bd.tcl @@ -120,6 +120,34 @@ namespace eval 2018.2 { " } +namespace eval 2018.3 { + set ip_list "\ + xilinx.com:ip:axi_bram_ctrl:4.1\ + xilinx.com:ip:axi_gpio:2.0\ + xilinx.com:ip:blk_mem_gen:8.4\ + xilinx.com:ip:clk_wiz:6.0\ + xilinx.com:ip:xlconstant:1.1\ + xilinx.com:ip:system_ila:1.1\ + xilinx.com:ip:util_vector_logic:2.0\ + xilinx.com:ip:vio:3.0\ + xilinx.com:ip:util_ds_buf:2.1\ + xilinx.com:ip:xdma:4.1\ + xilinx.com:ip:mdm:3.2\ + xilinx.com:ip:microblaze:11.0\ + xilinx.com:ip:proc_sys_reset:5.0\ + xilinx.com:ip:ddr4:2.2\ + xilinx.com:ip:axi_10g_ethernet:3.1\ + dlyma.org:dlyma:network_packet_fifo_rx:1.1\ + dlyma.org:dlyma:network_packet_fifo_tx:1.1\ + xilinx.com:ip:axis_register_slice:1.1\ + xilinx.com:ip:lmb_bram_if_cntlr:4.0\ + xilinx.com:ip:lmb_v10:3.0\ + xilinx.com:ip:fifo_generator:13.2\ + xilinx.com:ip:zynq_ultra_ps_e:3.2\ + xilinx.com:ip:xxv_ethernet:2.5 + " +} + # defines get_design_name if { [info exists ::env(GALAPAGOS_PATH)] } { source ${::env(GALAPAGOS_PATH)}/shells/tclScripts/utilities.tcl @@ -132,6 +160,7 @@ if { [info exists ::env(GALAPAGOS_PATH)] } { } +# ADD CLAUSE HERE TO SUPPORT NEW VERSIONS # determine Vivado version set current_vivado_version [version -short] if { [string first 2017.2 $current_vivado_version] != -1 } { @@ -142,6 +171,9 @@ if { [string first 2017.2 $current_vivado_version] != -1 } { set version 2018.1 } elseif { [string first 2018.2 $current_vivado_version] != -1 } { set version 2018.2 +} elseif { [string first 2018.3 $current_vivado_version] != -1 } { + # MM Jan 28/2020: Added support for 2018.3 + set version 2018.3 } else { puts "" catch {common::send_msg_id "BD_TCL-109" "ERROR" "Unsupported Vivado version:\ @@ -1083,9 +1115,9 @@ connect_bd_net [get_bd_pins proc_sys_reset_0/peripheral_aresetn] [get_bd_pins ax # Restore current instance current_bd_instance $oldCurInst - # regenerate_bd_layout - # save_bd_design - # validate_bd_design + regenerate_bd_layout + save_bd_design + validate_bd_design } # End of create_root_design() diff --git a/shells/tclScripts/make_shell.tcl b/shells/tclScripts/make_shell.tcl index 735956ac..2341fdd9 100644 --- a/shells/tclScripts/make_shell.tcl +++ b/shells/tclScripts/make_shell.tcl @@ -126,7 +126,7 @@ if {! [catch {glob $shell_path/srcs/*} yikes] } { } create_bd_design "shell" -# open_bd_design $project_path/$project_name.srcs/sources_1/bd/shell/shell.bd +open_bd_design $project_path/$project_name.srcs/sources_1/bd/shell/shell.bd set ret_val [source $shell_path/tclScripts/shell_bd.tcl] if { $ret_val != 0 } { puts "Error in shell_bd script" diff --git a/shells/tclScripts/shell_procs.tcl b/shells/tclScripts/shell_procs.tcl index 92cf5b35..259b4695 100644 --- a/shells/tclScripts/shell_procs.tcl +++ b/shells/tclScripts/shell_procs.tcl @@ -553,8 +553,12 @@ proc create_hier_eth10G_zu { parentCell nameHier } { connect_bd_net -net util_vector_logic_1_Res [get_bd_pins util_vector_logic_1/Res] [get_bd_pins util_vector_logic_2/Op1] connect_bd_net -net util_vector_logic_2_Res [get_bd_pins ext_reset_n] [get_bd_pins util_vector_logic_2/Res] connect_bd_net -net xlconstant_0_dout [get_bd_pins CONST_5/dout] [get_bd_pins xxv_ethernet_0/rxoutclksel_in_0] [get_bd_pins xxv_ethernet_0/txoutclksel_in_0] - connect_bd_net -net xxv_ethernet_0_rx_clk_out_0 [get_bd_pins axis_clock_converter_0/s_axis_aclk] [get_bd_pins bit_synchronizer_0/sync_clk] [get_bd_pins xxv_ethernet_0/rx_clk_out_0] - connect_bd_net -net xxv_ethernet_0_tx_clk_out_1 [get_bd_pins tx_clk_out_0] [get_bd_pins axis_clock_converter_0/m_axis_aclk] [get_bd_pins axis_register_slice_0/aclk] [get_bd_pins axis_register_slice_1/aclk] [get_bd_pins xxv_ethernet_0/rx_core_clk_0] [get_bd_pins xxv_ethernet_0/tx_clk_out_0] + # + # MM Jan 28/2020: I don't know how this ever worked before, but this uses the wrong clock for rx_core_clk + connect_bd_net -net xxv_ethernet_0_rx_clk_out_0 [get_bd_pins axis_clock_converter_0/s_axis_aclk] [get_bd_pins bit_synchronizer_0/sync_clk] [get_bd_pins xxv_ethernet_0/rx_core_clk_0] [get_bd_pins xxv_ethernet_0/rx_clk_out_0] + connect_bd_net -net xxv_ethernet_0_tx_clk_out_1 [get_bd_pins tx_clk_out_0] [get_bd_pins axis_clock_converter_0/m_axis_aclk] [get_bd_pins axis_register_slice_0/aclk] [get_bd_pins axis_register_slice_1/aclk] [get_bd_pins xxv_ethernet_0/tx_clk_out_0] + + # connect_bd_net -net xxv_ethernet_0_user_rx_reset_0 [get_bd_pins util_vector_logic_1/Op1] [get_bd_pins xxv_ethernet_0/user_rx_reset_0] connect_bd_net -net xxv_ethernet_0_user_tx_reset_0 [get_bd_pins util_vector_logic_1/Op2] [get_bd_pins xxv_ethernet_0/user_tx_reset_0] diff --git a/shells/zedboard/tclScripts/shell_bd.tcl b/shells/zedboard/tclScripts/shell_bd.tcl index 16b2a571..d985e281 100644 --- a/shells/zedboard/tclScripts/shell_bd.tcl +++ b/shells/zedboard/tclScripts/shell_bd.tcl @@ -8,7 +8,7 @@ proc get_script_folder {} { variable script_folder set script_folder [_tcl::get_script_folder] -set supported_versions {2017.4} +set supported_versions {2017.4 2018.3} namespace eval 2017.4 { set ip_list "\ @@ -22,6 +22,18 @@ namespace eval 2017.4 { " } +namespace eval 2018.3 { + set ip_list "\ + xilinx.com:ip:axi_bram_ctrl:4.1\ + xilinx.com:ip:axi_dma:7.1\ + xilinx.com:ip:axi_gpio:2.0\ + xilinx.com:ip:blk_mem_gen:8.4\ + xilinx.com:ip:proc_sys_reset:5.0\ + xilinx.com:ip:processing_system7:5.5\ + xilinx.com:ip:xlconcat:2.1\ + " +} + if { [info exists ::env(GALAPAGOS_PATH)] } { set root_path ${::env(GALAPAGOS_PATH)}/shells } elseif [info exists ::env(SHELLS_PATH)] { @@ -68,6 +80,9 @@ if { $nRet != 0 } { # Check IPs set list_check_ips [puts [subst $\{[subst ${version}::ip_list]\}]] +puts "MARCO SEZ" +puts [subst $\{[subst ${version}::ip_list]\}] +puts "MARCO NO LONGER SEZ" set list_ips_missing "" common::send_msg_id "BD_TCL-006" "INFO" "Checking if the following IPs exist in the project's IP catalog: $list_check_ips ." foreach ip_vlnv $list_check_ips { @@ -190,19 +205,19 @@ proc create_root_design { parentCell } { set sw [ create_bd_port -dir I -from 7 -to 0 sw ] # Create instance: axi_bram_ctrl_0, and set properties - set axi_bram_ctrl_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.0 axi_bram_ctrl_0 ] + set axi_bram_ctrl_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl axi_bram_ctrl_0 ] set_property -dict [ list \ CONFIG.SINGLE_PORT_BRAM {1} \ ] $axi_bram_ctrl_0 # Create instance: axi_bram_ctrl_1, and set properties - set axi_bram_ctrl_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.0 axi_bram_ctrl_1 ] + set axi_bram_ctrl_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl axi_bram_ctrl_1 ] set_property -dict [ list \ CONFIG.SINGLE_PORT_BRAM {1} \ ] $axi_bram_ctrl_1 # Create instance: axi_dma_0, and set properties - set axi_dma_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_dma:7.1 axi_dma_0 ] + set axi_dma_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_dma axi_dma_0 ] set_property -dict [ list \ CONFIG.c_include_mm2s_dre {1} \ CONFIG.c_include_s2mm_dre {1} \ @@ -215,14 +230,14 @@ proc create_root_design { parentCell } { ] $axi_dma_0 # Create instance: axi_gpio_0, and set properties - set axi_gpio_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_0 ] + set axi_gpio_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_0 ] set_property -dict [ list \ CONFIG.C_ALL_OUTPUTS {1} \ CONFIG.C_GPIO_WIDTH {8} \ ] $axi_gpio_0 # Create instance: axi_gpio_1, and set properties - set axi_gpio_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_1 ] + set axi_gpio_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_1 ] set_property -dict [ list \ CONFIG.C_ALL_INPUTS {1} \ CONFIG.C_ALL_OUTPUTS {0} \ @@ -231,7 +246,7 @@ proc create_root_design { parentCell } { ] $axi_gpio_1 # Create instance: axi_gpio_2, and set properties - set axi_gpio_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_2 ] + set axi_gpio_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_2 ] set_property -dict [ list \ CONFIG.C_ALL_INPUTS {1} \ CONFIG.C_ALL_OUTPUTS {0} \ @@ -240,41 +255,41 @@ proc create_root_design { parentCell } { ] $axi_gpio_2 # Create instance: axi_interconnect_1, and set properties - set axi_interconnect_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_1 ] + set axi_interconnect_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_1 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {2} \ ] $axi_interconnect_1 # Create instance: axi_interconnect_2, and set properties - set axi_interconnect_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_2 ] + set axi_interconnect_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_2 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_2 # Create instance: axi_interconnect_3, and set properties - set axi_interconnect_3 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_3 ] + set axi_interconnect_3 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_3 ] set_property -dict [ list \ CONFIG.NUM_MI {2} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_3 # Create instance: axi_interconnect_4, and set properties - set axi_interconnect_4 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_4 ] + set axi_interconnect_4 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_4 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_4 # Create instance: axi_interconnect_5, and set properties - set axi_interconnect_5 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_5 ] + set axi_interconnect_5 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_5 ] set_property -dict [ list \ CONFIG.NUM_MI {4} \ ] $axi_interconnect_5 # Create instance: blk_mem_gen_0, and set properties - set blk_mem_gen_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 blk_mem_gen_0 ] + set blk_mem_gen_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen blk_mem_gen_0 ] set_property -dict [ list \ CONFIG.Enable_B {Use_ENB_Pin} \ CONFIG.Memory_Type {True_Dual_Port_RAM} \ @@ -285,10 +300,10 @@ proc create_root_design { parentCell } { ] $blk_mem_gen_0 # Create instance: proc_sys_reset_0, and set properties - set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 proc_sys_reset_0 ] + set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset proc_sys_reset_0 ] # Create instance: ps7_0, and set properties - set ps7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 ps7_0 ] + set ps7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7 ps7_0 ] set_property -dict [ list \ CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {666.666687} \ CONFIG.PCW_ACT_CAN_PERIPHERAL_FREQMHZ {10.000000} \ @@ -689,7 +704,7 @@ proc create_root_design { parentCell } { ] $ps7_0 # Create instance: xlconcat_1, and set properties - set xlconcat_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 xlconcat_1 ] + set xlconcat_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat xlconcat_1 ] set_property -dict [ list \ CONFIG.NUM_PORTS {4} \ ] $xlconcat_1 @@ -731,7 +746,6 @@ proc create_root_design { parentCell } { connect_bd_net -net ps7_0_FCLK_CLK0 [get_bd_ports CLK_DATA] [get_bd_pins axi_bram_ctrl_0/s_axi_aclk] [get_bd_pins axi_bram_ctrl_1/s_axi_aclk] [get_bd_pins axi_dma_0/m_axi_mm2s_aclk] [get_bd_pins axi_dma_0/m_axi_s2mm_aclk] [get_bd_pins axi_dma_0/m_axi_sg_aclk] [get_bd_pins axi_dma_0/s_axi_lite_aclk] [get_bd_pins axi_gpio_0/s_axi_aclk] [get_bd_pins axi_gpio_1/s_axi_aclk] [get_bd_pins axi_gpio_2/s_axi_aclk] [get_bd_pins axi_interconnect_1/ACLK] [get_bd_pins axi_interconnect_1/M00_ACLK] [get_bd_pins axi_interconnect_1/S00_ACLK] [get_bd_pins axi_interconnect_1/S01_ACLK] [get_bd_pins axi_interconnect_2/ACLK] [get_bd_pins axi_interconnect_2/M00_ACLK] [get_bd_pins axi_interconnect_2/S00_ACLK] [get_bd_pins axi_interconnect_3/ACLK] [get_bd_pins axi_interconnect_3/M00_ACLK] [get_bd_pins axi_interconnect_3/M01_ACLK] [get_bd_pins axi_interconnect_3/S00_ACLK] [get_bd_pins axi_interconnect_4/ACLK] [get_bd_pins axi_interconnect_4/M00_ACLK] [get_bd_pins axi_interconnect_4/S00_ACLK] [get_bd_pins axi_interconnect_5/ACLK] [get_bd_pins axi_interconnect_5/M00_ACLK] [get_bd_pins axi_interconnect_5/M01_ACLK] [get_bd_pins axi_interconnect_5/M02_ACLK] [get_bd_pins axi_interconnect_5/M03_ACLK] [get_bd_pins axi_interconnect_5/S00_ACLK] [get_bd_pins proc_sys_reset_0/slowest_sync_clk] [get_bd_pins ps7_0/FCLK_CLK0] [get_bd_pins ps7_0/M_AXI_GP0_ACLK] [get_bd_pins ps7_0/M_AXI_GP1_ACLK] [get_bd_pins ps7_0/S_AXI_HP0_ACLK] [get_bd_pins ps7_0/S_AXI_HP1_ACLK] [get_bd_pins ps7_0/S_AXI_HP2_ACLK] connect_bd_net -net ps7_0_FCLK_RESET0_N [get_bd_ports ARESETN] [get_bd_pins proc_sys_reset_0/ext_reset_in] [get_bd_pins ps7_0/FCLK_RESET0_N] connect_bd_net -net xlconcat_1_dout [get_bd_pins ps7_0/IRQ_F2P] [get_bd_pins xlconcat_1/dout] - connect_bd_net [get_bd_pins xlconcat_1/dout] [get_bd_pins ps7_0/IRQ_F2P # Create address segments create_bd_addr_seg -range 0x00002000 -offset 0x40000000 [get_bd_addr_spaces axi_dma_0/Data_SG] [get_bd_addr_segs axi_bram_ctrl_1/S_AXI/Mem0] SEG_axi_bram_ctrl_1_Mem0 From 530ad819f78f712b7f51f1ea9175d6eb038bdec3 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Tue, 28 Jan 2020 18:27:31 -0500 Subject: [PATCH 03/16] Added Vivado 2018.3 support for pynq --- shells/Makefile | 3 ++ shells/pynq-z2/tclScripts/shell_bd.tcl | 44 ++++++++++++++++---------- 2 files changed, 31 insertions(+), 16 deletions(-) diff --git a/shells/Makefile b/shells/Makefile index 39f05207..0c7f0c3d 100644 --- a/shells/Makefile +++ b/shells/Makefile @@ -16,6 +16,7 @@ synth = 0 # This file does not exist... pr_tcl = ./hlsTest/dma_example/tclScripts/pr_bd.tcl +# This target is completely broken dma_example: vivado_hls ./hlsTest/dma_example/generate_hls.tcl vivado -mode gui -source ./tclScripts/make_shell.tcl -tclargs --project_name dma_example --pr_tcl ${pr_tcl} --start_synth ${synth} @@ -32,9 +33,11 @@ example: # Get rid of references to nonexistent files # vivado -mode tcl -source ./tclScripts/make_shell.tcl -tclargs --project_name example --pr_tcl ${pr_tcl} --start_synth ${synth} +# I think this is the target that gets called when you build a project app_dev: guard-PROJECTNAME mkdir -p projects vivado -mode gui -source ./tclScripts/make_shell.tcl -tclargs --project_name ${PROJECTNAME} + # Why do we always make a projects directory? clean: rm -rf projects/project_name diff --git a/shells/pynq-z2/tclScripts/shell_bd.tcl b/shells/pynq-z2/tclScripts/shell_bd.tcl index 7642bddb..e8ab8b1f 100644 --- a/shells/pynq-z2/tclScripts/shell_bd.tcl +++ b/shells/pynq-z2/tclScripts/shell_bd.tcl @@ -10,7 +10,7 @@ set script_folder [_tcl::get_script_folder] set_param synth.vivado.filterDuplicatedIPFiles 0 -set supported_versions {2017.4 2018.1 2018.2} +set supported_versions {2017.4 2018.1 2018.2 2018.3} namespace eval 2017.4 { set ip_list "\ @@ -48,6 +48,18 @@ namespace eval 2018.2 { " } +namespace eval 2018.3 { + set ip_list "\ + xilinx.com:ip:axi_bram_ctrl\ + xilinx.com:ip:axi_dma\ + xilinx.com:ip:axi_gpio\ + xilinx.com:ip:blk_mem_gen\ + xilinx.com:ip:proc_sys_reset\ + xilinx.com:ip:processing_system7\ + xilinx.com:ip:xlconcat\ + " +} + if { [info exists ::env(GALAPAGOS_PATH)] } { set root_path ${::env(GALAPAGOS_PATH)}/shells @@ -217,19 +229,19 @@ proc create_root_design { parentCell } { set sw [ create_bd_port -dir I -from 1 -to 0 sw ] # Create instance: axi_bram_ctrl_0, and set properties - set axi_bram_ctrl_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.0 axi_bram_ctrl_0 ] + set axi_bram_ctrl_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl axi_bram_ctrl_0 ] set_property -dict [ list \ CONFIG.SINGLE_PORT_BRAM {1} \ ] $axi_bram_ctrl_0 # Create instance: axi_bram_ctrl_1, and set properties - set axi_bram_ctrl_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl:4.0 axi_bram_ctrl_1 ] + set axi_bram_ctrl_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_bram_ctrl axi_bram_ctrl_1 ] set_property -dict [ list \ CONFIG.SINGLE_PORT_BRAM {1} \ ] $axi_bram_ctrl_1 # Create instance: axi_dma_0, and set properties - set axi_dma_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_dma:7.1 axi_dma_0 ] + set axi_dma_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_dma axi_dma_0 ] set_property -dict [ list \ CONFIG.c_include_mm2s_dre {1} \ CONFIG.c_include_s2mm_dre {1} \ @@ -242,14 +254,14 @@ proc create_root_design { parentCell } { ] $axi_dma_0 # Create instance: axi_gpio_0, and set properties - set axi_gpio_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_0 ] + set axi_gpio_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_0 ] set_property -dict [ list \ CONFIG.C_ALL_OUTPUTS {1} \ CONFIG.C_GPIO_WIDTH {4} \ ] $axi_gpio_0 # Create instance: axi_gpio_1, and set properties - set axi_gpio_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_1 ] + set axi_gpio_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_1 ] set_property -dict [ list \ CONFIG.C_ALL_INPUTS {1} \ CONFIG.C_ALL_OUTPUTS {0} \ @@ -258,7 +270,7 @@ proc create_root_design { parentCell } { ] $axi_gpio_1 # Create instance: axi_gpio_2, and set properties - set axi_gpio_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio:2.0 axi_gpio_2 ] + set axi_gpio_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_gpio axi_gpio_2 ] set_property -dict [ list \ CONFIG.C_ALL_INPUTS {1} \ CONFIG.C_ALL_OUTPUTS {0} \ @@ -267,41 +279,41 @@ proc create_root_design { parentCell } { ] $axi_gpio_2 # Create instance: axi_interconnect_1, and set properties - set axi_interconnect_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_1 ] + set axi_interconnect_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_1 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {2} \ ] $axi_interconnect_1 # Create instance: axi_interconnect_2, and set properties - set axi_interconnect_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_2 ] + set axi_interconnect_2 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_2 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_2 # Create instance: axi_interconnect_3, and set properties - set axi_interconnect_3 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_3 ] + set axi_interconnect_3 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_3 ] set_property -dict [ list \ CONFIG.NUM_MI {2} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_3 # Create instance: axi_interconnect_4, and set properties - set axi_interconnect_4 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_4 ] + set axi_interconnect_4 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_4 ] set_property -dict [ list \ CONFIG.NUM_MI {1} \ CONFIG.NUM_SI {1} \ ] $axi_interconnect_4 # Create instance: axi_interconnect_5, and set properties - set axi_interconnect_5 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect:2.1 axi_interconnect_5 ] + set axi_interconnect_5 [ create_bd_cell -type ip -vlnv xilinx.com:ip:axi_interconnect axi_interconnect_5 ] set_property -dict [ list \ CONFIG.NUM_MI {4} \ ] $axi_interconnect_5 # Create instance: blk_mem_gen_0, and set properties - set blk_mem_gen_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen:8.4 blk_mem_gen_0 ] + set blk_mem_gen_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:blk_mem_gen blk_mem_gen_0 ] set_property -dict [ list \ CONFIG.Enable_B {Use_ENB_Pin} \ CONFIG.Memory_Type {True_Dual_Port_RAM} \ @@ -312,10 +324,10 @@ proc create_root_design { parentCell } { ] $blk_mem_gen_0 # Create instance: proc_sys_reset_0, and set properties - set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset:5.0 proc_sys_reset_0 ] + set proc_sys_reset_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:proc_sys_reset proc_sys_reset_0 ] # Create instance: ps7_0, and set properties - set ps7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7:5.5 ps7_0 ] + set ps7_0 [ create_bd_cell -type ip -vlnv xilinx.com:ip:processing_system7 ps7_0 ] set_property -dict [ list \ CONFIG.PCW_ACT_APU_PERIPHERAL_FREQMHZ {650.000000} \ CONFIG.PCW_ACT_CAN0_PERIPHERAL_FREQMHZ {23.8095} \ @@ -1178,7 +1190,7 @@ proc create_root_design { parentCell } { ] $ps7_0 # Create instance: xlconcat_1, and set properties - set xlconcat_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat:2.1 xlconcat_1 ] + set xlconcat_1 [ create_bd_cell -type ip -vlnv xilinx.com:ip:xlconcat xlconcat_1 ] set_property -dict [ list \ CONFIG.NUM_PORTS {4} \ ] $xlconcat_1 From b88742f022f2d16d130f41a0dddab628774c6903 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Wed, 29 Jan 2020 09:47:26 -0500 Subject: [PATCH 04/16] Started working on Hello World example --- examples/hello_world/Makefile | 22 ++++++++++++++++++++++ examples/hello_world/README.txt | 22 ++++++++++++++++++++++ examples/hello_world/hello_world.cpp | 20 ++++++++++++++++++++ examples/hello_world/hello_world.tcl | 23 +++++++++++++++++++++++ examples/hello_world/world.cpp | 11 +++++++++++ examples/hello_world/world.tcl | 23 +++++++++++++++++++++++ 6 files changed, 121 insertions(+) create mode 100644 examples/hello_world/Makefile create mode 100644 examples/hello_world/README.txt create mode 100644 examples/hello_world/hello_world.cpp create mode 100644 examples/hello_world/hello_world.tcl create mode 100644 examples/hello_world/world.cpp create mode 100644 examples/hello_world/world.tcl diff --git a/examples/hello_world/Makefile b/examples/hello_world/Makefile new file mode 100644 index 00000000..d3ebbe03 --- /dev/null +++ b/examples/hello_world/Makefile @@ -0,0 +1,22 @@ +all: .hello_world .world middleware pick_up_vivados_dirty_socks + + +.hello_world: hello_world.cpp + vivado_hls hello_world.tcl + touch .hello_world + +.world: world.cpp + vivado_hls world.tcl + touch .world + + +middleware: + #TODO + +pick_up_vivados_dirty_socks: + rm -rf vivado* + +show_reports: + less $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/hello_world/solution1/syn/report/hello_world_csynth.rpt + less $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/world/solution1/syn/report/world_csynth.rpt + diff --git a/examples/hello_world/README.txt b/examples/hello_world/README.txt new file mode 100644 index 00000000..44e02e8c --- /dev/null +++ b/examples/hello_world/README.txt @@ -0,0 +1,22 @@ +===================== +GALAPAGOS HELLO WORLD +===================== + +This example contains: + + - hello_world.cpp, a Galapagos HLS kernel with DEST = 1 that contantly + broadcasts a message to the kernel with DEST = 2 + + - world.cpp, a Galapagos HLS kernel with DEST = 2 that ignores all + incoming messages + + - generate_ips.tcl, a script that invokes Vivado HLS in order to compile + the HLS code into Verilog + + - logical.xml, the logical file + + - mapping.xml, the mapping file + + - A Makefile + + diff --git a/examples/hello_world/hello_world.cpp b/examples/hello_world/hello_world.cpp new file mode 100644 index 00000000..4abf1445 --- /dev/null +++ b/examples/hello_world/hello_world.cpp @@ -0,0 +1,20 @@ +#include "galapagos_packet.h" + +using gp = galapagos::stream_packet<64>; + +void hello_world(hls::stream *out) { + #pragma HLS INTERFACE axis register both port=out + #pragma HLS INTERFACE ap_ctrl_none port=return + + gp tmp; + + tmp.dest = 2; + tmp.data = 0xFEEDBADBEEF2BABE; + tmp.id = 1; + tmp.last = 1; + tmp.keep = 0xFFFF; + + if(!out->full()) out->write(tmp); + + +} diff --git a/examples/hello_world/hello_world.tcl b/examples/hello_world/hello_world.tcl new file mode 100644 index 00000000..a737ad73 --- /dev/null +++ b/examples/hello_world/hello_world.tcl @@ -0,0 +1,23 @@ +set galapagos_path $::env(GALAPAGOS_PATH) +set board_name $::env(GALAPAGOS_BOARD_NAME) +set part_name $::env(GALAPAGOS_PART) + +set src_path_root $galapagos_path/examples/hello_world + +# Does this still work? We'll find out, I guess... +cd $galapagos_path/hlsBuild/${board_name}/ip + +open_project hello_world +set_top hello_world +open_solution "solution1" +set_part ${part_name} +#csynth path +add_files $src_path_root/hello_world.cpp -cflags "-I $galapagos_path/middleware/include -I $galapagos_path/middleware/CPP_lib/Galapagos_lib" +create_clock -period 250MHz -name default +config_interface -expose_global +csynth_design +export_design -format ip_catalog +close_project + +quit + diff --git a/examples/hello_world/world.cpp b/examples/hello_world/world.cpp new file mode 100644 index 00000000..6129ebe5 --- /dev/null +++ b/examples/hello_world/world.cpp @@ -0,0 +1,11 @@ +#include "galapagos_packet.h" + +using gp = galapagos::stream_packet<64>; + +void world(hls::stream *in) { + #pragma HLS INTERFACE axis register both port=in + #pragma HLS INTERFACE ap_ctrl_none port=return + + gp tmp; + if (!in->empty()) in->read(tmp); +} diff --git a/examples/hello_world/world.tcl b/examples/hello_world/world.tcl new file mode 100644 index 00000000..9b0cbfa2 --- /dev/null +++ b/examples/hello_world/world.tcl @@ -0,0 +1,23 @@ +set galapagos_path $::env(GALAPAGOS_PATH) +set board_name $::env(GALAPAGOS_BOARD_NAME) +set part_name $::env(GALAPAGOS_PART) + +set src_path_root $galapagos_path/examples/hello_world + +# Does this still work? We'll find out, I guess... +cd $galapagos_path/hlsBuild/${board_name}/ip + +open_project world +set_top world +open_solution "solution1" +set_part ${part_name} +#csynth path +add_files $src_path_root/world.cpp -cflags "-I $galapagos_path/middleware/include -I $galapagos_path/middleware/CPP_lib/Galapagos_lib" +create_clock -period 250MHz -name default +config_interface -expose_global +csynth_design +export_design -format ip_catalog +close_project + +quit + From a7a58028e8ff836024c6bc252fe24940b2270a9d Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Wed, 29 Jan 2020 16:24:05 -0500 Subject: [PATCH 05/16] Almost done getting hello world to work --- examples/hello_world/Makefile | 7 +++-- examples/hello_world/README.txt | 4 +-- examples/hello_world/logical.xml | 51 ++++++++++++++++++++++++++++++++ examples/hello_world/mapping.xml | 16 ++++++++++ middleware/Makefile | 2 +- 5 files changed, 75 insertions(+), 5 deletions(-) create mode 100644 examples/hello_world/logical.xml create mode 100644 examples/hello_world/mapping.xml diff --git a/examples/hello_world/Makefile b/examples/hello_world/Makefile index d3ebbe03..a0cfe90c 100644 --- a/examples/hello_world/Makefile +++ b/examples/hello_world/Makefile @@ -10,8 +10,11 @@ all: .hello_world .world middleware pick_up_vivados_dirty_socks touch .world -middleware: - #TODO +middleware: logical.xml mapping.xml + export LOGICALFILE=$(shell realpath logical.xml) + export MAPFILE=$(shell realpath mapping.xml) + export PROJECTNAME=hello_world + make -C$(GALAPAGOS_PATH) middleware pick_up_vivados_dirty_socks: rm -rf vivado* diff --git a/examples/hello_world/README.txt b/examples/hello_world/README.txt index 44e02e8c..0d9c742f 100644 --- a/examples/hello_world/README.txt +++ b/examples/hello_world/README.txt @@ -10,8 +10,8 @@ This example contains: - world.cpp, a Galapagos HLS kernel with DEST = 2 that ignores all incoming messages - - generate_ips.tcl, a script that invokes Vivado HLS in order to compile - the HLS code into Verilog + - hello_world.tcl and world.tcl. Scripts that invoke Vivado HLS to compile + hello_world.cpp and world.cpp into Verilog - logical.xml, the logical file diff --git a/examples/hello_world/logical.xml b/examples/hello_world/logical.xml new file mode 100644 index 00000000..a135f519 --- /dev/null +++ b/examples/hello_world/logical.xml @@ -0,0 +1,51 @@ + + + + + 64 + 1 + 1 + 16 + + + + + hello_world + + xilinx.com + hls + 1.0 + 1 + 1 + + + ap_clk + ap_rst_n + + global + out_r + + + + + world + + xilinx.com + hls + 1.0 + 2 + 1 + + + ap_clk + ap_rst_n + + global + in_r + + + + diff --git a/examples/hello_world/mapping.xml b/examples/hello_world/mapping.xml new file mode 100644 index 00000000..42e76ab3 --- /dev/null +++ b/examples/hello_world/mapping.xml @@ -0,0 +1,16 @@ + + + + hw + raw + sidewinder + 01:23:45:67:89:AB + 10.10.84.234 + + + 1 + 2 + + + + diff --git a/middleware/Makefile b/middleware/Makefile index ae4a3bc0..b1b18ac2 100644 --- a/middleware/Makefile +++ b/middleware/Makefile @@ -24,7 +24,7 @@ python_path = $(middleware_path)/python all: middleware hlsmiddleware middleware: guard-LOGICALFILE guard-MAPFILE guard-PROJECTNAME ${LOGICALFILE} ${MAPFILE} - python3.5 ${python_path}/globalFPGAParser.py --logicalFile=${LOGICALFILE} \ + python ${python_path}/globalFPGAParser.py --logicalFile=${LOGICALFILE} \ --mapFile=${MAPFILE} --projectName=${PROJECTNAME} chmod +x $(GALAPAGOS_PATH)/projects/$(PROJECTNAME)/createCluster.sh From 54628e4748d5d350c142dddae97bc785bd5068b4 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Wed, 29 Jan 2020 19:19:34 -0500 Subject: [PATCH 06/16] Finished hello_world example --- examples/hello_world/mapping.xml | 1 + middleware/python/cluster.py | 2 ++ 2 files changed, 3 insertions(+) diff --git a/examples/hello_world/mapping.xml b/examples/hello_world/mapping.xml index 42e76ab3..3ccc85fb 100644 --- a/examples/hello_world/mapping.xml +++ b/examples/hello_world/mapping.xml @@ -1,3 +1,4 @@ + diff --git a/middleware/python/cluster.py b/middleware/python/cluster.py index 02e8cc17..c3215cf3 100644 --- a/middleware/python/cluster.py +++ b/middleware/python/cluster.py @@ -107,6 +107,8 @@ def __init__(self, name, kernel_file, map_file, mode='file'): if(mode=='file'): logical_dict = self.getDict(kernel_file)['cluster']['kernel'] map_dict = self.getDict(map_file)['cluster']['node'] + if not isinstance(map_dict, list): + map_dict = [map_dict] else: logical_dict = kernel_file['cluster']['kernel'] map_dict = map_file['cluster']['node'] From 5aeaa8b6d760935c06fa0097c4ab55b091acbb09 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Wed, 29 Jan 2020 20:40:10 -0500 Subject: [PATCH 07/16] Started adding documentation --- documentation/README.txt | 20 +++++ documentation/galapagos_flow.txt | 122 +++++++++++++++++++++++++++++++ 2 files changed, 142 insertions(+) create mode 100644 documentation/README.txt create mode 100644 documentation/galapagos_flow.txt diff --git a/documentation/README.txt b/documentation/README.txt new file mode 100644 index 00000000..889b3c24 --- /dev/null +++ b/documentation/README.txt @@ -0,0 +1,20 @@ +These files were written by Marco Merlini (marco.merlini@utoronto.ca), who is +not the official maintainer of this repository. They are a summary of his +personal notes from using Galapagos. + +For Galapagos users: + + galapagos_flow.txt explains the design flow of using Galapagos + + +For Galapagos developers: + + file_reference.txt tries to disambiguate the utility of each script + packaged in this reposiitory + + adding_vivado_versions.txt gives a general method for extending Galapagos + to work with new versions of Vivado. Your mileage may vary. + + shell_development.txt explains how to add support for new boards in + Galapagos + diff --git a/documentation/galapagos_flow.txt b/documentation/galapagos_flow.txt new file mode 100644 index 00000000..dedf4ef9 --- /dev/null +++ b/documentation/galapagos_flow.txt @@ -0,0 +1,122 @@ +=========================== +DEFINITIONS AND ASSUMPTIONS +=========================== + +In this file, $GALAPDIR will refer to the root directory of the Galapagos +repository. (Note: this is not an environment variable used by Galapagos; it is +a placeholder for you to replace yourself) + +$BOARD will refer to the name of the currently targeted board. + +All commands are assumed to be run from inside $GALAPDIR (unless otherwise +stated). + +A "kernel" means a single user module in the Galapagos cluster, analogous to a +Rank in MPI. + + +======== +OVERVIEW +======== + +The following numbered list explains the brief details of using Galapagos. More +details are provided in the later sections of this document. There is also a +quick blurb on writing kernels in HLS. + +When first installing Galapagos, + + 1) Run `$GALAPDIR/build.sh` and answer all its questions + +For each board you wish to use, + + 2) Use `galapagos_update_board ` to select the board, and + + 3) Run `make middleware` from inside $GALAPDIR + +To create a Galapagos project, + + 4) Package kernels as IPs and place them in $GALAPDIR/hlsBuild/$BOARD/ip, + where $BOARD is the board that the IP is targeted for. + -> Use this directory even if your kernel is not written in HLS + + 5) Write a logical file and a mapping file (details provided below) + + 6) Set the LOGICALFILE and MAPFILE environment variables to be the full + path of your logical file and mapping file (repsectively). Also set the + PROJECTNAME environment variable to be the desired project name + + 7) Run `make middleware` from inside $GALAPDIR + + 8) If there were no errors, Galapagos will have created a directory called + $GALAPDIR/projects/$PROJECTNAME which contains a number of TCL scripts + and a bash script called createCluster.sh. Simply run this shell script + and wait for Vivado to produce your bitstream(s). + + +=============================== +WHEN FIRST INSTALLING GALAPAGOS +=============================== + +For the time being, Galapagos works by reading a number of environment +variables. Run + + $ cd $GALAPDIR + $ source build.sh + +This will bring up a snazzy menu. Answer its questions. Don't worry about +making a mistake; you can always try this again if it doesn't work. + +Once you have finished answering the questions, Galapgos will have created a +new file in your home folder. You can take a look at it: + + $ less ~/.galapagos + +This file simply sets Galapagos's environment variables using the answers you +provided earlier. It also defines two bash shell functions: +galapagos-update-board and galapagos-update-version. The first function allows +you ask Galapagos to target a different board without re-running build.sh, and +the second lets you select a different Vivado version. + +Finally, note that build.sh adds a line to your .bashrc: + + $ less ~/.bashrc + +This ensures that .galapagos is called whenever you open a new bash shell. + + +============================== +FOR EACH BOARD YOU PLAN TO USE +============================== + +Galapagos uses a number of custom IP cores written in HLS. These must be +synthesized and packaged as IPs before you can start making your own projects. + +Run the following from inside $GALAPDIR: + + $ galapagos-update-board + $ make hlsmiddleware + +Currently, Galapagos supports the following choices for : + + - zynq-p2 + - zedboard + - sidewinder + - adm-8k5 + - adm-8k5-debug + + +====================== +WRITING KERNELS IN HLS +====================== + +Include the header file in $GALAPDIR/middleware/include/galapagos_packet.h + +The following is a skeleton of an HLS Galapagos kernel: + + (TODO) + + +===================== +CREATING YOUR PROJECT +===================== + From 2cfbe978fb8086b3858b368f5fb5f366c9f1ad05 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Fri, 31 Jan 2020 13:32:44 -0500 Subject: [PATCH 08/16] Added an AXI Stream flit counter IP --- middleware/hls/axistreamstats/Makefile | 43 +++++++ middleware/hls/axistreamstats/README.txt | 4 + .../hls/axistreamstats/axistreamstats.v | 107 ++++++++++++++++++ middleware/hls/axistreamstats/ip_maker.tcl | 36 ++++++ 4 files changed, 190 insertions(+) create mode 100644 middleware/hls/axistreamstats/Makefile create mode 100644 middleware/hls/axistreamstats/README.txt create mode 100644 middleware/hls/axistreamstats/axistreamstats.v create mode 100644 middleware/hls/axistreamstats/ip_maker.tcl diff --git a/middleware/hls/axistreamstats/Makefile b/middleware/hls/axistreamstats/Makefile new file mode 100644 index 00000000..6c85ca6f --- /dev/null +++ b/middleware/hls/axistreamstats/Makefile @@ -0,0 +1,43 @@ +# This file is from tpdp/helpful_scripts/packaging_custom_ip: +# https://github.com/UofT-HPRC/tpdp/tree/master/helpful_scripts/packaging_custom_ip + +# I didn't want to rock the boat too much, so I'm just using Galapagos's +# environment variables for compatibility. However, it would be much better to +# use a more robust method + + +# Edit the following four variables and run `make`. +# After that, follow the instructions in ip_maker.tcl +# +# Alternatively, you could set any of these variables on the command line: +# +# $ make src_dir=/path/to/my/src dst_dir=/path/to/my/output +# +# Just make sure that you don't include a trailing slash on your directories + +dst_dir=$(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME) +src_dir=. +ip_name=axistreamstats +part_no=$(GALAPAGOS_PART) + + + +# Makes Makefile easier to read +out_dir=${dst_dir}/${ip_name} + +all: clean + rm -rf ${out_dir} + mkdir -p ${out_dir}/src + cp $(shell find ${src_dir} -name "*.v" -o -name "*vh" -o -name "*sv") ${out_dir}/src + # Remember to follow the instructions in ip_maker.tcl! + vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} + # Vivado leaves a bunch of crap laying around that no one cares about. So + # delete it. + rm -rf ${ip_name}_tmp_proj + rm -f *log + rm -rf .Xil + rm -f vivado* +clean: + rm -rf ${out_dir} + rm -rf ${ip_name}_tmp_proj + diff --git a/middleware/hls/axistreamstats/README.txt b/middleware/hls/axistreamstats/README.txt new file mode 100644 index 00000000..62f33550 --- /dev/null +++ b/middleware/hls/axistreamstats/README.txt @@ -0,0 +1,4 @@ +A simple test to see if I can figure out how to add new IPs in the Galapagos +middleware. + +This is a simple flit/packet counter for AXI Stream diff --git a/middleware/hls/axistreamstats/axistreamstats.v b/middleware/hls/axistreamstats/axistreamstats.v new file mode 100644 index 00000000..86a8af76 --- /dev/null +++ b/middleware/hls/axistreamstats/axistreamstats.v @@ -0,0 +1,107 @@ +`timescale 1ns / 1ps + +`define NO_RESET 0 +`define ACTIVE_HIGH 1 +`define ACTIVE_LOW 2 + +`define genif generate if +`define else_genif end else if +`define endgen end endgenerate + +module axistreamstats #( + parameter DATA_WIDTH = 32, + parameter CNT_WIDTH = 32, + parameter RESET_TYPE = `ACTIVE_LOW +) ( + input wire clk, + //Depending on RESET_TYPE, one or neither of these ports are used + input wire rst, + input wire rstn, + + //Input AXI Stream. This is just so Vivado makes it an interface and keeps + //the block diagram simple + input wire [DATA_WIDTH-1:0] in_TDATA, + input wire in_TVALID, + output wire in_TREADY, + input wire [DATA_WIDTH/8 -1:0] in_TKEEP, + input wire in_TDEST, + input wire in_TID, + input wire in_TLAST, + + //Output AXI Stream. This is just so Vivado makes it an interface and keeps + //the block diagram simple + output wire [DATA_WIDTH-1:0] out_TDATA, + output wire out_TVALID, + input wire out_TREADY, + output wire [DATA_WIDTH/8 -1:0] out_TKEEP, + output wire out_TDEST, + output wire out_TID, + output wire out_TLAST, + + //The flit and packet counts + output wire [CNT_WIDTH-1:0] flit_cnt, + output wire [CNT_WIDTH-1:0] pkt_cnt +); + + //First, directly connect the AXI Streams + assign out_TDATA = in_TDATA; + assign out_TVALID = in_TVALID; + assign in_TREADY = out_TREADY; + assign out_TKEEP = in_TKEEP; + assign out_TDEST = in_TDEST; + assign out_TID = in_TID; + assign out_TLAST = in_TLAST; + + //A few helper signals + wire flit_vld; + assign flit_vld = in_TVALID && in_TREADY; + + wire flit_last; + assign flit_last = flit_vld && in_TLAST; + + //Now do the packet counting + reg [CNT_WIDTH-1:0] flit_cnt_r = 0; + reg [CNT_WIDTH-1:0] pkt_cnt_r = 0; + +`genif (RESET_TYPE == `NO_RESET) begin + + always @(posedge clk) begin + flit_cnt_r <= flit_cnt_r + flit_vld; + pkt_cnt_r <= pkt_cnt_r + flit_last; + end + +`else_genif (RESET_TYPE == `ACTIVE_HIGH) begin + + always @(posedge clk) begin + if (rst) begin + flit_cnt_r <= 0; + pkt_cnt_r <= 0; + end else begin + flit_cnt_r <= flit_cnt_r + flit_vld; + pkt_cnt_r <= pkt_cnt_r + flit_last; + end + end + +`else_genif (RESET_TYPE == `ACTIVE_LOW) begin + + always @(posedge clk) begin + if (!rstn) begin + flit_cnt_r <= 0; + pkt_cnt_r <= 0; + end else begin + flit_cnt_r <= flit_cnt_r + flit_vld; + pkt_cnt_r <= pkt_cnt_r + flit_last; + end + end + +`endgen + +endmodule + +`undef genif +`undef else_genif +`undef endgen + +`undef NO_RESET +`undef ACTIVE_HIGH +`undef ACTIVE_LOW diff --git a/middleware/hls/axistreamstats/ip_maker.tcl b/middleware/hls/axistreamstats/ip_maker.tcl new file mode 100644 index 00000000..edf7b03d --- /dev/null +++ b/middleware/hls/axistreamstats/ip_maker.tcl @@ -0,0 +1,36 @@ +# Call as: +# vivado -mode tcl -nolog -nojournal -source scripts/ip_package.tcl -tclargs $out_dir $ip_name $part_name + + +# INSTRUCTIONS +# ------------ +# The first time you run this tcl, it will open the Vivado GUI in the IP +# packaging mode. Go ahead and edit your IP the way you normally would, taking +# care to copy and paste all the TCL commands that Vivado generates into the +# location labeled below. +# When you're finished, remove the "start_gui" line and uncomment all the lines +# at the end of this script + + +start_gui; # Remove this line after copying TCL commands (see below) + +set out_dir [lindex $argv 0] +set ip_name [lindex $argv 1] +set part_name [lindex $argv 2] +set project_name ${ip_name}_tmp_proj +create_project ${project_name} ${project_name} -part ${part_name} +add_files ${out_dir}/src +ipx::package_project -root_dir ${out_dir} -vendor Marco_Merlini -library fpga_bpf -taxonomy /UserIP + +###PUT YOUR COMMANDS HERE + +# Uncomment these lines after copying TCL commands from gui + +###ipx::create_xgui_files [ipx::current_core] +###ipx::update_checksums [ipx::current_core] +###ipx::save_core [ipx::current_core] +###close_project +###exit + + + From d8982c48bd2024fd7eb59d134a62b88ebd429b38 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Fri, 31 Jan 2020 13:46:18 -0500 Subject: [PATCH 09/16] Fixed a few minor errors in IP packaging scripts --- documentation/galapagos_flow.txt | 2 +- middleware/hls/axistreamstats/Makefile | 3 --- middleware/hls/axistreamstats/ip_maker.tcl | 27 ++++++++++++++++------ 3 files changed, 21 insertions(+), 11 deletions(-) diff --git a/documentation/galapagos_flow.txt b/documentation/galapagos_flow.txt index dedf4ef9..8d83fca8 100644 --- a/documentation/galapagos_flow.txt +++ b/documentation/galapagos_flow.txt @@ -31,7 +31,7 @@ For each board you wish to use, 2) Use `galapagos_update_board ` to select the board, and - 3) Run `make middleware` from inside $GALAPDIR + 3) Run `make hlsmiddleware` from inside $GALAPDIR To create a Galapagos project, diff --git a/middleware/hls/axistreamstats/Makefile b/middleware/hls/axistreamstats/Makefile index 6c85ca6f..c8fe22f8 100644 --- a/middleware/hls/axistreamstats/Makefile +++ b/middleware/hls/axistreamstats/Makefile @@ -29,10 +29,7 @@ all: clean rm -rf ${out_dir} mkdir -p ${out_dir}/src cp $(shell find ${src_dir} -name "*.v" -o -name "*vh" -o -name "*sv") ${out_dir}/src - # Remember to follow the instructions in ip_maker.tcl! vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} - # Vivado leaves a bunch of crap laying around that no one cares about. So - # delete it. rm -rf ${ip_name}_tmp_proj rm -f *log rm -rf .Xil diff --git a/middleware/hls/axistreamstats/ip_maker.tcl b/middleware/hls/axistreamstats/ip_maker.tcl index edf7b03d..588e8013 100644 --- a/middleware/hls/axistreamstats/ip_maker.tcl +++ b/middleware/hls/axistreamstats/ip_maker.tcl @@ -20,17 +20,30 @@ set part_name [lindex $argv 2] set project_name ${ip_name}_tmp_proj create_project ${project_name} ${project_name} -part ${part_name} add_files ${out_dir}/src -ipx::package_project -root_dir ${out_dir} -vendor Marco_Merlini -library fpga_bpf -taxonomy /UserIP +ipx::package_project -root_dir ${out_dir} -vendor mmerlini.ca -library galapagos -taxonomy /UserIP -###PUT YOUR COMMANDS HERE +set_property tooltip {Width of AXI Stream channel} [ipgui::get_guiparamspec -name "DATA_WIDTH" -component [ipx::current_core] ] +set_property widget {textEdit} [ipgui::get_guiparamspec -name "DATA_WIDTH" -component [ipx::current_core] ] + +set_property display_name {Counter Width} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] +set_property tooltip {Number of bits in packet/flit count} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] +set_property widget {textEdit} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] + +set_property widget {comboBox} [ipgui::get_guiparamspec -name "RESET_TYPE" -component [ipx::current_core] ] +set_property value_validation_type pairs [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] +set_property value_validation_pairs {{No reset} 0 {Active high} 1 {Active low} 2} [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] + +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 1} [ipx::get_bus_interfaces rst -of_objects [ipx::current_core]] + +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 2} [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] # Uncomment these lines after copying TCL commands from gui -###ipx::create_xgui_files [ipx::current_core] -###ipx::update_checksums [ipx::current_core] -###ipx::save_core [ipx::current_core] -###close_project -###exit +ipx::create_xgui_files [ipx::current_core] +ipx::update_checksums [ipx::current_core] +ipx::save_core [ipx::current_core] +close_project +exit From 648824527140e38a4d70463e58a27c4365c190e6 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Fri, 31 Jan 2020 15:45:49 -0500 Subject: [PATCH 10/16] Fixed a few nits --- examples/hello_world/Makefile | 25 +++++++++++++++++++++---- examples/hello_world/README.txt | 4 ++++ 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/examples/hello_world/Makefile b/examples/hello_world/Makefile index a0cfe90c..a37a39db 100644 --- a/examples/hello_world/Makefile +++ b/examples/hello_world/Makefile @@ -1,25 +1,42 @@ -all: .hello_world .world middleware pick_up_vivados_dirty_socks - +all: hello_world world middleware pick_up_vivados_dirty_socks +hello_world: .hello_world .hello_world: hello_world.cpp vivado_hls hello_world.tcl touch .hello_world +world: .world .world: world.cpp vivado_hls world.tcl touch .world - -middleware: logical.xml mapping.xml +middleware: .middleware +.middleware: logical.xml mapping.xml .hello_world .world export LOGICALFILE=$(shell realpath logical.xml) export MAPFILE=$(shell realpath mapping.xml) export PROJECTNAME=hello_world make -C$(GALAPAGOS_PATH) middleware + touch .middleware pick_up_vivados_dirty_socks: + # Cleaning up after Vivado... rm -rf vivado* show_reports: less $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/hello_world/solution1/syn/report/hello_world_csynth.rpt less $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/world/solution1/syn/report/world_csynth.rpt +clean: + rm -rf $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/hello_world/ + rm -rf $(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip/world/ + rm -rf $(GALAPAGOS_PATH)/projects/hello_world + rm -rf .world + rm -rf .hello_world + rm -rf .middleware + rm -rf vivado* + + +# Very bizarre! GNU Make will silently add calls to g++ if you have cpp files +# in your directory (even if none of your rules or dependencies mention it). +# This is because of some implicit rules... 30 minutes I found this solution +.SUFFIXES: diff --git a/examples/hello_world/README.txt b/examples/hello_world/README.txt index 0d9c742f..650dbaa0 100644 --- a/examples/hello_world/README.txt +++ b/examples/hello_world/README.txt @@ -20,3 +20,7 @@ This example contains: - A Makefile +Make sure you have followed the Galapagos setup instructions (see +documentation/galapagos_flow.txt). This Makefile will generate a Vivado project +in projects/hello_world/0/ + From 1a30b03f59e79362d4487da66efc7d86886acb9a Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Fri, 31 Jan 2020 15:47:30 -0500 Subject: [PATCH 11/16] Oops, forgot to comment out start_gui --- middleware/hls/axistreamstats/ip_maker.tcl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/middleware/hls/axistreamstats/ip_maker.tcl b/middleware/hls/axistreamstats/ip_maker.tcl index 588e8013..d998e09d 100644 --- a/middleware/hls/axistreamstats/ip_maker.tcl +++ b/middleware/hls/axistreamstats/ip_maker.tcl @@ -12,7 +12,7 @@ # at the end of this script -start_gui; # Remove this line after copying TCL commands (see below) +# start_gui; # Remove this line after copying TCL commands (see below) set out_dir [lindex $argv 0] set ip_name [lindex $argv 1] From bfc39ca73fc2df6f269265dd46338aef1be04927 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Fri, 31 Jan 2020 16:30:15 -0500 Subject: [PATCH 12/16] Fixed a few more bugs --- middleware/hls/axistreamstats/axistreamstats.v | 4 ++++ middleware/python/tclFileGenerator.py | 1 + 2 files changed, 5 insertions(+) diff --git a/middleware/hls/axistreamstats/axistreamstats.v b/middleware/hls/axistreamstats/axistreamstats.v index 86a8af76..c3209214 100644 --- a/middleware/hls/axistreamstats/axistreamstats.v +++ b/middleware/hls/axistreamstats/axistreamstats.v @@ -95,6 +95,10 @@ module axistreamstats #( end `endgen + + //And of course, don't forget to assign the outputs! + assign flit_cnt = flit_cnt_r; + assign pkt_cnt = pkt_cnt_r; endmodule diff --git a/middleware/python/tclFileGenerator.py b/middleware/python/tclFileGenerator.py index 7d8820a5..21d3dba1 100644 --- a/middleware/python/tclFileGenerator.py +++ b/middleware/python/tclFileGenerator.py @@ -2031,4 +2031,5 @@ def makeTCLFiles(fpga, projectName, output_path, sim): tclMain.addSource(galapagos_path + '/middleware/tclScripts/custom/' + fpga['custom'] + '.tcl') tclMain.tprint('validate_bd_design') + tclMain.tprint('save_bd_design') tclMain.close() From ef2b073e90f5632ed4cbb1a4a82ca697028aaa1e Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Sat, 29 Feb 2020 11:01:04 -0500 Subject: [PATCH 13/16] Copied in certain Verilog cores from https://github.com/esophagus-now/ye_olde_verilogge --- .gitignore | 3 +- .../Makefile | 32 +- middleware/hls/axis_unconcat/axis_unconcat.v | 198 +++++++ middleware/hls/axis_unconcat/ip_maker.tcl | 170 ++++++ middleware/hls/axistreamstats/README.txt | 4 - .../hls/axistreamstats/axistreamstats.v | 111 ---- middleware/hls/axistreamstats/ip_maker.tcl | 49 -- middleware/hls/buffered_handshake/Makefile | 53 ++ middleware/hls/buffered_handshake/README.txt | 507 ++++++++++++++++ middleware/hls/buffered_handshake/bhand.gtkw | 47 ++ middleware/hls/buffered_handshake/bhand.v | 197 +++++++ .../hls/buffered_handshake/bhand_drivers.mem | 35 ++ middleware/hls/buffered_handshake/bhand_tb.v | 81 +++ .../hls/buffered_handshake/ip_maker.tcl | 43 ++ middleware/hls/dbg_guv/Makefile | 61 ++ middleware/hls/dbg_guv/addcore.tcl | 240 ++++++++ middleware/hls/dbg_guv/axis_governor/Makefile | 42 ++ .../dbg_guv/axis_governor/axis_governor.gtkw | 70 +++ .../hls/dbg_guv/axis_governor/axis_governor.v | 152 +++++ .../axis_governor/axis_governor_drivers.mem | 1 + .../dbg_guv/axis_governor/axis_governor_tb.v | 153 +++++ .../axis_governor/design_docs/arbitration.txt | 376 ++++++++++++ .../design_docs/early_attempts/gov.circ | 222 +++++++ .../design_docs/early_attempts/gov2.circ | 219 +++++++ .../design_docs/early_attempts/gov3.circ | 263 +++++++++ .../axis_governor/design_docs/gov4.circ | 261 +++++++++ .../axis_governor/design_docs/governor.png | Bin 0 -> 16319 bytes .../axis_governor/design_docs/governor.xlsx | Bin 0 -> 104690 bytes .../hls/dbg_guv/axis_headerizer/Makefile | 31 + .../axis_headerizer/axis_headerizer.gtkw | 52 ++ .../dbg_guv/axis_headerizer/axis_headerizer.v | 70 +++ .../axis_headerizer/axis_headerizer_tb.v | 74 +++ middleware/hls/dbg_guv/dbg_guv.gtkw | 144 +++++ middleware/hls/dbg_guv/dbg_guv.v | 547 ++++++++++++++++++ middleware/hls/dbg_guv/dbg_guv_drivers.mem | 66 +++ middleware/hls/dbg_guv/dbg_guv_tb.v | 228 ++++++++ middleware/hls/dbg_guv/ip_maker.tcl | 79 +++ middleware/hls/marcos_macros/README.txt | 75 +++ middleware/hls/marcos_macros/macros.vh | 223 +++++++ middleware/hls/rr4/Makefile | 54 ++ middleware/hls/rr4/ip_maker.tcl | 61 ++ middleware/hls/rr4/rr4.gtkw | 72 +++ middleware/hls/rr4/rr4.v | 207 +++++++ middleware/hls/rr4/rr4_drivers.mem | 1 + middleware/hls/rr4/rr4_tb.v | 190 ++++++ middleware/hls/rr4/rr_tree.tcl | 36 ++ 46 files changed, 5618 insertions(+), 182 deletions(-) rename middleware/hls/{axistreamstats => axis_unconcat}/Makefile (58%) create mode 100644 middleware/hls/axis_unconcat/axis_unconcat.v create mode 100644 middleware/hls/axis_unconcat/ip_maker.tcl delete mode 100644 middleware/hls/axistreamstats/README.txt delete mode 100644 middleware/hls/axistreamstats/axistreamstats.v delete mode 100644 middleware/hls/axistreamstats/ip_maker.tcl create mode 100644 middleware/hls/buffered_handshake/Makefile create mode 100644 middleware/hls/buffered_handshake/README.txt create mode 100644 middleware/hls/buffered_handshake/bhand.gtkw create mode 100644 middleware/hls/buffered_handshake/bhand.v create mode 100644 middleware/hls/buffered_handshake/bhand_drivers.mem create mode 100644 middleware/hls/buffered_handshake/bhand_tb.v create mode 100644 middleware/hls/buffered_handshake/ip_maker.tcl create mode 100644 middleware/hls/dbg_guv/Makefile create mode 100644 middleware/hls/dbg_guv/addcore.tcl create mode 100644 middleware/hls/dbg_guv/axis_governor/Makefile create mode 100644 middleware/hls/dbg_guv/axis_governor/axis_governor.gtkw create mode 100644 middleware/hls/dbg_guv/axis_governor/axis_governor.v create mode 100644 middleware/hls/dbg_guv/axis_governor/axis_governor_drivers.mem create mode 100644 middleware/hls/dbg_guv/axis_governor/axis_governor_tb.v create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/arbitration.txt create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov.circ create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov2.circ create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov3.circ create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/gov4.circ create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/governor.png create mode 100644 middleware/hls/dbg_guv/axis_governor/design_docs/governor.xlsx create mode 100644 middleware/hls/dbg_guv/axis_headerizer/Makefile create mode 100644 middleware/hls/dbg_guv/axis_headerizer/axis_headerizer.gtkw create mode 100644 middleware/hls/dbg_guv/axis_headerizer/axis_headerizer.v create mode 100644 middleware/hls/dbg_guv/axis_headerizer/axis_headerizer_tb.v create mode 100644 middleware/hls/dbg_guv/dbg_guv.gtkw create mode 100644 middleware/hls/dbg_guv/dbg_guv.v create mode 100644 middleware/hls/dbg_guv/dbg_guv_drivers.mem create mode 100644 middleware/hls/dbg_guv/dbg_guv_tb.v create mode 100644 middleware/hls/dbg_guv/ip_maker.tcl create mode 100644 middleware/hls/marcos_macros/README.txt create mode 100644 middleware/hls/marcos_macros/macros.vh create mode 100644 middleware/hls/rr4/Makefile create mode 100644 middleware/hls/rr4/ip_maker.tcl create mode 100644 middleware/hls/rr4/rr4.gtkw create mode 100644 middleware/hls/rr4/rr4.v create mode 100644 middleware/hls/rr4/rr4_drivers.mem create mode 100644 middleware/hls/rr4/rr4_tb.v create mode 100644 middleware/hls/rr4/rr_tree.tcl diff --git a/.gitignore b/.gitignore index 1ded8d62..fb50e188 100644 --- a/.gitignore +++ b/.gitignore @@ -6,4 +6,5 @@ my_init.sh vivado_* vivado.log vivado.jou - +examples/hello_world/.* +tags diff --git a/middleware/hls/axistreamstats/Makefile b/middleware/hls/axis_unconcat/Makefile similarity index 58% rename from middleware/hls/axistreamstats/Makefile rename to middleware/hls/axis_unconcat/Makefile index c8fe22f8..318635d2 100644 --- a/middleware/hls/axistreamstats/Makefile +++ b/middleware/hls/axis_unconcat/Makefile @@ -1,11 +1,3 @@ -# This file is from tpdp/helpful_scripts/packaging_custom_ip: -# https://github.com/UofT-HPRC/tpdp/tree/master/helpful_scripts/packaging_custom_ip - -# I didn't want to rock the boat too much, so I'm just using Galapagos's -# environment variables for compatibility. However, it would be much better to -# use a more robust method - - # Edit the following four variables and run `make`. # After that, follow the instructions in ip_maker.tcl # @@ -15,26 +7,32 @@ # # Just make sure that you don't include a trailing slash on your directories -dst_dir=$(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME) +dst_dir=$(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip src_dir=. -ip_name=axistreamstats +ip_name=axis_unconcat part_no=$(GALAPAGOS_PART) - # Makes Makefile easier to read out_dir=${dst_dir}/${ip_name} -all: clean +default: ip + +clean: + rm -rf ${out_dir} + rm -rf ${ip_name}_tmp_proj + +# Packages into a Vivado IP +ip: clean rm -rf ${out_dir} mkdir -p ${out_dir}/src - cp $(shell find ${src_dir} -name "*.v" -o -name "*vh" -o -name "*sv") ${out_dir}/src + cp axis_unconcat.v ../marcos_macros/macros.vh ${out_dir}/src vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} rm -rf ${ip_name}_tmp_proj rm -f *log rm -rf .Xil rm -f vivado* -clean: - rm -rf ${out_dir} - rm -rf ${ip_name}_tmp_proj - + +syntax_check: + iverilog -DICARUS_VERILOG -I../marcos_macros -E $(MODULE).v + rm -rf a.out diff --git a/middleware/hls/axis_unconcat/axis_unconcat.v b/middleware/hls/axis_unconcat/axis_unconcat.v new file mode 100644 index 00000000..4367fcca --- /dev/null +++ b/middleware/hls/axis_unconcat/axis_unconcat.v @@ -0,0 +1,198 @@ +`timescale 1ns / 1ps + +/* + +The counterpart to axis_concat.v + + +Each side channel has an "in enable" and an "out enable". This means: + +in | out | Meaning +------------------------------------ + d | 0 | The side channel is not present at the output + 0 | 1 | The side channel is split out of output TDATA + 1 | 1 | The side channel is present at input, and not split out of output TDATA + +*/ + +`include "macros.vh" + +/* + +Unfortunately, due to problems with Vivado, I have to move all the +automatically derived parameters into macros instead of using localparam + +Specifically, this happens because you can't use localparam to set a module's +port width, even if you use the K&R-style Verilog syntax: + + module my_thing # ( + parameter W = 2 + ) (a, b); + localparam WW = W+W; + input wire [W -1:0] a; + output wire [WW -1:0] b; + + endmodule + +Vivado chokes on it. + +So there is no choice but to use ugly `define statements... and to make things +worse, you can't do + x ? 1 : 0 +Vivado only understands + (x != 0) ? 1 : 0 +*/ + +//Derived parameters. Do not set manually! +// +//The "SPLIT_*_WIDTH" parameters mean how many additional bits are present in +//the output TDATA +// +//The "SAFE_*_WIDTH" parameters are an ugly hack. See, if I just did +//something like +// input wire [DEST_WIDTH - 1 :0] +//but DEST_WIDTH was 0, then we would get a Verilog error. Although I plan +//to have the packaged IP hide the ability to edit the width if you disable +//the port, I don't want obscure errors if the user typed in 0 before +//disabling. +// +//The "PASSTHRU_*" indicate that a side channel shouldnot be concatted with +//TDATA; instead, it is given its own output +// +`define SPLIT_LAST (((IN_ENABLE_LAST == 0) && (OUT_ENABLE_LAST != 0)) ? 1 : 0) +`define SPLIT_KEEP (((IN_ENABLE_KEEP == 0) && (OUT_ENABLE_KEEP != 0)) ? 1 : 0) +`define SPLIT_DEST (((IN_ENABLE_DEST == 0) && (OUT_ENABLE_DEST != 0)) ? 1 : 0) +`define SPLIT_ID (((IN_ENABLE_ID == 0) && (OUT_ENABLE_ID != 0)) ? 1 : 0) +`define SPLIT_USER (((IN_ENABLE_USER == 0) && (OUT_ENABLE_USER != 0)) ? 1 : 0) + +`define SPLIT_LAST_WIDTH (`SPLIT_LAST) //For consistency + +`define KEEP_WIDTH ((DATA_WIDTH+7)/8) +`define SPLIT_KEEP_WIDTH (`SPLIT_KEEP * `KEEP_WIDTH) +`define IN_SAFE_KEEP_WIDTH ((IN_ENABLE_KEEP != 0) ? `KEEP_WIDTH : 1) +`define OUT_SAFE_KEEP_WIDTH ((OUT_ENABLE_KEEP != 0) ? `KEEP_WIDTH : 1) + +`define SPLIT_DEST_WIDTH (`SPLIT_DEST * DEST_WIDTH) +`define IN_SAFE_DEST_WIDTH ((IN_ENABLE_DEST != 0) ? DEST_WIDTH : 1) +`define OUT_SAFE_DEST_WIDTH ((OUT_ENABLE_DEST != 0) ? DEST_WIDTH : 1) + +`define SPLIT_ID_WIDTH (`SPLIT_ID * ID_WIDTH) +`define IN_SAFE_ID_WIDTH ((IN_ENABLE_ID != 0) ? ID_WIDTH : 1) +`define OUT_SAFE_ID_WIDTH ((OUT_ENABLE_ID != 0) ? ID_WIDTH : 1) + +`define SPLIT_USER_WIDTH (`SPLIT_USER * USER_WIDTH) +`define IN_SAFE_USER_WIDTH ((IN_ENABLE_USER != 0) ? USER_WIDTH : 1) +`define OUT_SAFE_USER_WIDTH ((OUT_ENABLE_USER != 0) ? USER_WIDTH : 1) + + +`define PASSTHRU_LAST (((IN_ENABLE_LAST != 0) && (OUT_ENABLE_LAST != 0)) ? 1 : 0) +`define PASSTHRU_KEEP (((IN_ENABLE_KEEP != 0) && (OUT_ENABLE_KEEP != 0)) ? 1 : 0) +`define PASSTHRU_DEST (((IN_ENABLE_DEST != 0) && (OUT_ENABLE_DEST != 0)) ? 1 : 0) +`define PASSTHRU_ID (((IN_ENABLE_ID != 0) && (OUT_ENABLE_ID != 0)) ? 1 : 0) +`define PASSTHRU_USER (((IN_ENABLE_USER != 0) && (OUT_ENABLE_USER != 0)) ? 1 : 0) + +`define WIDTH_U (`SPLIT_USER_WIDTH) +`define WIDTH_IU (`SPLIT_ID_WIDTH + `WIDTH_U) +`define WIDTH_DIU (`SPLIT_DEST_WIDTH + `WIDTH_IU) +`define WIDTH_KDIU (`SPLIT_KEEP_WIDTH + `WIDTH_DIU) +`define WIDTH_LKDIU (`SPLIT_LAST_WIDTH + `WIDTH_KDIU) +`define WIDTH_DLKDIU (DATA_WIDTH + `WIDTH_LKDIU) + +module axis_unconcat # ( + parameter DATA_WIDTH = 32, + + parameter IN_ENABLE_KEEP = 0, + parameter OUT_ENABLE_KEEP = 0, + + parameter IN_ENABLE_LAST = 1, + parameter OUT_ENABLE_LAST = 1, + + parameter IN_ENABLE_DEST = 0, + parameter OUT_ENABLE_DEST = 0, + parameter DEST_WIDTH = 16, + + parameter IN_ENABLE_ID = 0, + parameter OUT_ENABLE_ID = 0, + parameter ID_WIDTH = 16, + + parameter IN_ENABLE_USER = 0, + parameter OUT_ENABLE_USER = 0, + parameter USER_WIDTH = 16 +) ( + input wire clk, //Dummy clock to get rid of Vivado's annoying warning + + input wire [DATA_WIDTH + + `SPLIT_LAST_WIDTH + + `SPLIT_KEEP_WIDTH + + `SPLIT_DEST_WIDTH + + `SPLIT_ID_WIDTH + + `SPLIT_USER_WIDTH + -1:0] left_TDATA, + input wire left_TVALID, + output wire left_TREADY, + input wire left_TLAST, + input wire [`IN_SAFE_KEEP_WIDTH -1:0] left_TKEEP, + input wire [`IN_SAFE_DEST_WIDTH -1:0] left_TDEST, + input wire [`IN_SAFE_ID_WIDTH -1:0] left_TID, + input wire [`IN_SAFE_USER_WIDTH -1:0] left_TUSER, + + output wire [DATA_WIDTH-1:0] right_TDATA, + output wire right_TVALID, + input wire right_TREADY, + output wire right_TLAST, + output wire [`OUT_SAFE_KEEP_WIDTH -1:0] right_TKEEP, + output wire [`OUT_SAFE_DEST_WIDTH -1:0] right_TDEST, + output wire [`OUT_SAFE_ID_WIDTH -1:0] right_TID, + output wire [`OUT_SAFE_USER_WIDTH -1:0] right_TUSER +); + +assign right_TDATA = left_TDATA[`WIDTH_DLKDIU -1 -: DATA_WIDTH]; + +`genif (`SPLIT_LAST) begin + assign right_TLAST = left_TDATA[`WIDTH_LKDIU -1 -: 1]; +`endgen + +`genif (`SPLIT_KEEP) begin + assign right_TKEEP = left_TDATA[`WIDTH_KDIU -1 -: `KEEP_WIDTH]; +`endgen + +`genif (`SPLIT_DEST) begin + assign right_TDEST = left_TDATA[`WIDTH_DIU -1 -: DEST_WIDTH]; +`endgen + +`genif (`SPLIT_ID) begin + assign right_TID = left_TDATA[`WIDTH_IU -1 -: ID_WIDTH]; +`endgen + +`genif (`SPLIT_USER) begin + assign right_TUSER = left_TDATA[`WIDTH_U -1 -: USER_WIDTH]; +`endgen + +assign right_TVALID = left_TVALID; +assign left_TREADY = right_TREADY; + +`genif (`PASSTHRU_LAST) begin + assign right_TLAST = left_TLAST; +`endgen + +`genif (`PASSTHRU_KEEP) begin + assign right_TKEEP = left_TKEEP; +`endgen + +`genif (`PASSTHRU_DEST) begin + assign right_TDEST = left_TDEST; +`endgen + +`genif (`PASSTHRU_ID) begin + assign right_TID = left_TID; +`endgen + +`genif (`PASSTHRU_USER) begin + assign right_TUSER = left_TUSER; +`endgen + + + + +endmodule + diff --git a/middleware/hls/axis_unconcat/ip_maker.tcl b/middleware/hls/axis_unconcat/ip_maker.tcl new file mode 100644 index 00000000..e4ef0755 --- /dev/null +++ b/middleware/hls/axis_unconcat/ip_maker.tcl @@ -0,0 +1,170 @@ +# Call as: +# vivado -mode tcl -nolog -nojournal -source scripts/ip_package.tcl -tclargs $out_dir $ip_name $part_name + +set out_dir [lindex $argv 0] +set ip_name [lindex $argv 1] +set part_name [lindex $argv 2] +set project_name ${ip_name}_tmp_proj +create_project ${project_name} ${project_name} -part ${part_name} +add_files ${out_dir}/src +ipx::package_project -root_dir ${out_dir} -vendor mmerlini -library yov -taxonomy /UserIP + +# Set conditional portds in AXI Streams. I hope this plays nicely with the rest +# of Vivado! +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.IN_ENABLE_ID')) = 1} [ipx::get_ports left_TID -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.IN_ENABLE_DEST')) = 1} [ipx::get_ports left_TDEST -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.IN_ENABLE_KEEP')) = 1} [ipx::get_ports left_TKEEP -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.IN_ENABLE_LAST')) = 1} [ipx::get_ports left_TLAST -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.IN_ENABLE_USER')) = 1} [ipx::get_ports left_TUSER -of_objects [ipx::current_core]] +set_property driver_value 0 [ipx::get_ports right_TID -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.OUT_ENABLE_ID')) = 1} [ipx::get_ports right_TID -of_objects [ipx::current_core]] +set_property driver_value 0 [ipx::get_ports right_TDEST -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.OUT_ENABLE_DEST')) = 1} [ipx::get_ports right_TDEST -of_objects [ipx::current_core]] +set_property driver_value 0 [ipx::get_ports right_TKEEP -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.OUT_ENABLE_KEEP')) = 1} [ipx::get_ports right_TKEEP -of_objects [ipx::current_core]] +set_property driver_value 0 [ipx::get_ports right_TLAST -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.OUT_ENABLE_LAST')) = 1} [ipx::get_ports right_TLAST -of_objects [ipx::current_core]] +set_property driver_value 0 [ipx::get_ports right_TUSER -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.OUT_ENABLE_USER')) = 1} [ipx::get_ports right_TUSER -of_objects [ipx::current_core]] + +# OUT_ENABLE_LAST +set_property display_name {Enable TLAST output} [ipgui::get_guiparamspec -name "OUT_ENABLE_LAST" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "OUT_ENABLE_LAST" -component [ipx::current_core] ] +set_property widget {checkBox} [ipgui::get_guiparamspec -name "OUT_ENABLE_LAST" -component [ipx::current_core] ] +set_property value false [ipx::get_user_parameters OUT_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value false [ipx::get_hdl_parameters OUT_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_user_parameters OUT_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_hdl_parameters OUT_ENABLE_LAST -of_objects [ipx::current_core]] + +# OUT_ENABLE_KEEP +set_property display_name {Enable TKEEP output} [ipgui::get_guiparamspec -name "OUT_ENABLE_KEEP" -component [ipx::current_core] ] +set_property tooltip {Enable TKEEP output} [ipgui::get_guiparamspec -name "OUT_ENABLE_KEEP" -component [ipx::current_core] ] +set_property widget {checkBox} [ipgui::get_guiparamspec -name "OUT_ENABLE_KEEP" -component [ipx::current_core] ] +set_property value false [ipx::get_user_parameters OUT_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value false [ipx::get_hdl_parameters OUT_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_user_parameters OUT_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_hdl_parameters OUT_ENABLE_KEEP -of_objects [ipx::current_core]] + +# OUT_ENABLE_DEST +set_property display_name {Enable TDEST output} [ipgui::get_guiparamspec -name "OUT_ENABLE_DEST" -component [ipx::current_core] ] +set_property tooltip {Enable TDEST output} [ipgui::get_guiparamspec -name "OUT_ENABLE_DEST" -component [ipx::current_core] ] +set_property widget {checkBox} [ipgui::get_guiparamspec -name "OUT_ENABLE_DEST" -component [ipx::current_core] ] +set_property value false [ipx::get_user_parameters OUT_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value false [ipx::get_hdl_parameters OUT_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_user_parameters OUT_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_hdl_parameters OUT_ENABLE_DEST -of_objects [ipx::current_core]] + +# OUT_ENABLE_ID +set_property display_name {Enable TID output} [ipgui::get_guiparamspec -name "OUT_ENABLE_ID" -component [ipx::current_core] ] +set_property tooltip {Enable TID output} [ipgui::get_guiparamspec -name "OUT_ENABLE_ID" -component [ipx::current_core] ] +set_property widget {checkBox} [ipgui::get_guiparamspec -name "OUT_ENABLE_ID" -component [ipx::current_core] ] +set_property value false [ipx::get_user_parameters OUT_ENABLE_ID -of_objects [ipx::current_core]] +set_property value false [ipx::get_hdl_parameters OUT_ENABLE_ID -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_user_parameters OUT_ENABLE_ID -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_hdl_parameters OUT_ENABLE_ID -of_objects [ipx::current_core]] + +# OUT_ENABLE_USER +set_property display_name {Enable TUSER output} [ipgui::get_guiparamspec -name "OUT_ENABLE_USER" -component [ipx::current_core] ] +set_property tooltip {Enable TUSER output} [ipgui::get_guiparamspec -name "OUT_ENABLE_USER" -component [ipx::current_core] ] +set_property widget {checkBox} [ipgui::get_guiparamspec -name "OUT_ENABLE_USER" -component [ipx::current_core] ] +set_property value false [ipx::get_user_parameters OUT_ENABLE_USER -of_objects [ipx::current_core]] +set_property value false [ipx::get_hdl_parameters OUT_ENABLE_USER -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_user_parameters OUT_ENABLE_USER -of_objects [ipx::current_core]] +set_property value_format bool [ipx::get_hdl_parameters OUT_ENABLE_USER -of_objects [ipx::current_core]] + +# IN_ENABLE_LAST +set_property display_name {TLAST source} [ipgui::get_guiparamspec -name "IN_ENABLE_LAST" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "IN_ENABLE_LAST" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "IN_ENABLE_LAST" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters IN_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters IN_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters IN_ENABLE_LAST -of_objects [ipx::current_core]] +set_property value_validation_pairs {{Input TLAST} 1 {Split out of TDATA} 0} [ipx::get_user_parameters IN_ENABLE_LAST -of_objects [ipx::current_core]] +set_property enablement_tcl_expr {$OUT_ENABLE_LAST != 0} [ipx::get_user_parameters IN_ENABLE_LAST -of_objects [ipx::current_core]] + +# IN_ENABLE_KEEP +set_property display_name {TKEEP source} [ipgui::get_guiparamspec -name "IN_ENABLE_KEEP" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "IN_ENABLE_KEEP" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "IN_ENABLE_KEEP" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters IN_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters IN_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters IN_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property value_validation_pairs {{Input TKEEP} 1 {Split out of TDATA} 0} [ipx::get_user_parameters IN_ENABLE_KEEP -of_objects [ipx::current_core]] +set_property enablement_tcl_expr {$OUT_ENABLE_KEEP != 0} [ipx::get_user_parameters IN_ENABLE_KEEP -of_objects [ipx::current_core]] + +# IN_ENABLE_DEST +set_property display_name {TDEST source} [ipgui::get_guiparamspec -name "IN_ENABLE_DEST" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "IN_ENABLE_DEST" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "IN_ENABLE_DEST" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters IN_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters IN_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters IN_ENABLE_DEST -of_objects [ipx::current_core]] +set_property value_validation_pairs {{Input TDEST} 1 {Split out of TDATA} 0} [ipx::get_user_parameters IN_ENABLE_DEST -of_objects [ipx::current_core]] +set_property enablement_tcl_expr {$OUT_ENABLE_DEST != 0} [ipx::get_user_parameters IN_ENABLE_DEST -of_objects [ipx::current_core]] + +# IN_ENABLE_ID +set_property display_name {TID source} [ipgui::get_guiparamspec -name "IN_ENABLE_ID" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "IN_ENABLE_ID" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "IN_ENABLE_ID" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters IN_ENABLE_ID -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters IN_ENABLE_ID -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters IN_ENABLE_ID -of_objects [ipx::current_core]] +set_property value_validation_pairs {{Input TID} 1 {Split out of TDATA} 0} [ipx::get_user_parameters IN_ENABLE_ID -of_objects [ipx::current_core]] +set_property enablement_tcl_expr {$OUT_ENABLE_ID != 0} [ipx::get_user_parameters IN_ENABLE_ID -of_objects [ipx::current_core]] + +# IN_ENABLE_USER +set_property display_name {TUSER source} [ipgui::get_guiparamspec -name "IN_ENABLE_USER" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "IN_ENABLE_USER" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "IN_ENABLE_USER" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters IN_ENABLE_USER -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters IN_ENABLE_USER -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters IN_ENABLE_USER -of_objects [ipx::current_core]] +set_property value_validation_pairs {{Input TUSER} 1 {Split out of TDATA} 0} [ipx::get_user_parameters IN_ENABLE_USER -of_objects [ipx::current_core]] +set_property enablement_tcl_expr {$OUT_ENABLE_USER != 0} [ipx::get_user_parameters IN_ENABLE_USER -of_objects [ipx::current_core]] + +# DEST_WIDTH +set_property enablement_tcl_expr {$OUT_ENABLE_DEST != 0} [ipx::get_user_parameters DEST_WIDTH -of_objects [ipx::current_core]] + +# ID_WIDTH +set_property enablement_tcl_expr {$OUT_ENABLE_ID != 0} [ipx::get_user_parameters ID_WIDTH -of_objects [ipx::current_core]] + +# USER WIDTH +set_property enablement_tcl_expr {$OUT_ENABLE_USER != 0} [ipx::get_user_parameters USER_WIDTH -of_objects [ipx::current_core]] + +# Get the GUI menu to look nice +ipgui::add_group -name {TLAST} -component [ipx::current_core] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core] ] -display_name {TLAST} +ipgui::move_group -component [ipx::current_core] -order 1 [ipgui::get_groupspec -name "TLAST" -component [ipx::current_core]] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "IN_ENABLE_LAST" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TLAST" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "OUT_ENABLE_LAST" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TLAST" -component [ipx::current_core]] +ipgui::add_group -name {TKEEP} -component [ipx::current_core] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core] ] -display_name {TKEEP} +ipgui::move_group -component [ipx::current_core] -order 1 [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TLAST" -component [ipx::current_core]] +ipgui::move_group -component [ipx::current_core] -order 2 [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TLAST" -component [ipx::current_core]] +ipgui::move_group -component [ipx::current_core] -order 2 [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "OUT_ENABLE_KEEP" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "IN_ENABLE_KEEP" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] +ipgui::add_group -name {TDEST} -component [ipx::current_core] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core] ] -display_name {TDEST} +ipgui::move_group -component [ipx::current_core] -order 3 [ipgui::get_groupspec -name "TDEST" -component [ipx::current_core]] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "OUT_ENABLE_DEST" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TDEST" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 1 [ipgui::get_guiparamspec -name "IN_ENABLE_KEEP" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TKEEP" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "IN_ENABLE_DEST" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TDEST" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 1 [ipgui::get_guiparamspec -name "IN_ENABLE_DEST" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TDEST" -component [ipx::current_core]] +ipgui::add_group -name {TID} -component [ipx::current_core] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core] ] -display_name {TID} +ipgui::move_group -component [ipx::current_core] -order 4 [ipgui::get_groupspec -name "TID" -component [ipx::current_core]] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "IN_ENABLE_ID" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TID" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "ID_WIDTH" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TID" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "OUT_ENABLE_ID" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TID" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 1 [ipgui::get_guiparamspec -name "DEST_WIDTH" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TDEST" -component [ipx::current_core]] +ipgui::add_group -name {TUSER} -component [ipx::current_core] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core] ] -display_name {TUSER} +ipgui::move_group -component [ipx::current_core] -order 5 [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] -parent [ipgui::get_pagespec -name "Page 0" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "OUT_ENABLE_USER" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "IN_ENABLE_USER" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 1 [ipgui::get_guiparamspec -name "IN_ENABLE_USER" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 0 [ipgui::get_guiparamspec -name "USER_WIDTH" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] +ipgui::move_param -component [ipx::current_core] -order 1 [ipgui::get_guiparamspec -name "USER_WIDTH" -component [ipx::current_core]] -parent [ipgui::get_groupspec -name "TUSER" -component [ipx::current_core]] + + +ipx::create_xgui_files [ipx::current_core] +ipx::update_checksums [ipx::current_core] +ipx::save_core [ipx::current_core] +close_project +exit diff --git a/middleware/hls/axistreamstats/README.txt b/middleware/hls/axistreamstats/README.txt deleted file mode 100644 index 62f33550..00000000 --- a/middleware/hls/axistreamstats/README.txt +++ /dev/null @@ -1,4 +0,0 @@ -A simple test to see if I can figure out how to add new IPs in the Galapagos -middleware. - -This is a simple flit/packet counter for AXI Stream diff --git a/middleware/hls/axistreamstats/axistreamstats.v b/middleware/hls/axistreamstats/axistreamstats.v deleted file mode 100644 index c3209214..00000000 --- a/middleware/hls/axistreamstats/axistreamstats.v +++ /dev/null @@ -1,111 +0,0 @@ -`timescale 1ns / 1ps - -`define NO_RESET 0 -`define ACTIVE_HIGH 1 -`define ACTIVE_LOW 2 - -`define genif generate if -`define else_genif end else if -`define endgen end endgenerate - -module axistreamstats #( - parameter DATA_WIDTH = 32, - parameter CNT_WIDTH = 32, - parameter RESET_TYPE = `ACTIVE_LOW -) ( - input wire clk, - //Depending on RESET_TYPE, one or neither of these ports are used - input wire rst, - input wire rstn, - - //Input AXI Stream. This is just so Vivado makes it an interface and keeps - //the block diagram simple - input wire [DATA_WIDTH-1:0] in_TDATA, - input wire in_TVALID, - output wire in_TREADY, - input wire [DATA_WIDTH/8 -1:0] in_TKEEP, - input wire in_TDEST, - input wire in_TID, - input wire in_TLAST, - - //Output AXI Stream. This is just so Vivado makes it an interface and keeps - //the block diagram simple - output wire [DATA_WIDTH-1:0] out_TDATA, - output wire out_TVALID, - input wire out_TREADY, - output wire [DATA_WIDTH/8 -1:0] out_TKEEP, - output wire out_TDEST, - output wire out_TID, - output wire out_TLAST, - - //The flit and packet counts - output wire [CNT_WIDTH-1:0] flit_cnt, - output wire [CNT_WIDTH-1:0] pkt_cnt -); - - //First, directly connect the AXI Streams - assign out_TDATA = in_TDATA; - assign out_TVALID = in_TVALID; - assign in_TREADY = out_TREADY; - assign out_TKEEP = in_TKEEP; - assign out_TDEST = in_TDEST; - assign out_TID = in_TID; - assign out_TLAST = in_TLAST; - - //A few helper signals - wire flit_vld; - assign flit_vld = in_TVALID && in_TREADY; - - wire flit_last; - assign flit_last = flit_vld && in_TLAST; - - //Now do the packet counting - reg [CNT_WIDTH-1:0] flit_cnt_r = 0; - reg [CNT_WIDTH-1:0] pkt_cnt_r = 0; - -`genif (RESET_TYPE == `NO_RESET) begin - - always @(posedge clk) begin - flit_cnt_r <= flit_cnt_r + flit_vld; - pkt_cnt_r <= pkt_cnt_r + flit_last; - end - -`else_genif (RESET_TYPE == `ACTIVE_HIGH) begin - - always @(posedge clk) begin - if (rst) begin - flit_cnt_r <= 0; - pkt_cnt_r <= 0; - end else begin - flit_cnt_r <= flit_cnt_r + flit_vld; - pkt_cnt_r <= pkt_cnt_r + flit_last; - end - end - -`else_genif (RESET_TYPE == `ACTIVE_LOW) begin - - always @(posedge clk) begin - if (!rstn) begin - flit_cnt_r <= 0; - pkt_cnt_r <= 0; - end else begin - flit_cnt_r <= flit_cnt_r + flit_vld; - pkt_cnt_r <= pkt_cnt_r + flit_last; - end - end - -`endgen - - //And of course, don't forget to assign the outputs! - assign flit_cnt = flit_cnt_r; - assign pkt_cnt = pkt_cnt_r; - -endmodule - -`undef genif -`undef else_genif -`undef endgen - -`undef NO_RESET -`undef ACTIVE_HIGH -`undef ACTIVE_LOW diff --git a/middleware/hls/axistreamstats/ip_maker.tcl b/middleware/hls/axistreamstats/ip_maker.tcl deleted file mode 100644 index d998e09d..00000000 --- a/middleware/hls/axistreamstats/ip_maker.tcl +++ /dev/null @@ -1,49 +0,0 @@ -# Call as: -# vivado -mode tcl -nolog -nojournal -source scripts/ip_package.tcl -tclargs $out_dir $ip_name $part_name - - -# INSTRUCTIONS -# ------------ -# The first time you run this tcl, it will open the Vivado GUI in the IP -# packaging mode. Go ahead and edit your IP the way you normally would, taking -# care to copy and paste all the TCL commands that Vivado generates into the -# location labeled below. -# When you're finished, remove the "start_gui" line and uncomment all the lines -# at the end of this script - - -# start_gui; # Remove this line after copying TCL commands (see below) - -set out_dir [lindex $argv 0] -set ip_name [lindex $argv 1] -set part_name [lindex $argv 2] -set project_name ${ip_name}_tmp_proj -create_project ${project_name} ${project_name} -part ${part_name} -add_files ${out_dir}/src -ipx::package_project -root_dir ${out_dir} -vendor mmerlini.ca -library galapagos -taxonomy /UserIP - -set_property tooltip {Width of AXI Stream channel} [ipgui::get_guiparamspec -name "DATA_WIDTH" -component [ipx::current_core] ] -set_property widget {textEdit} [ipgui::get_guiparamspec -name "DATA_WIDTH" -component [ipx::current_core] ] - -set_property display_name {Counter Width} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] -set_property tooltip {Number of bits in packet/flit count} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] -set_property widget {textEdit} [ipgui::get_guiparamspec -name "CNT_WIDTH" -component [ipx::current_core] ] - -set_property widget {comboBox} [ipgui::get_guiparamspec -name "RESET_TYPE" -component [ipx::current_core] ] -set_property value_validation_type pairs [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] -set_property value_validation_pairs {{No reset} 0 {Active high} 1 {Active low} 2} [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] - -set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 1} [ipx::get_bus_interfaces rst -of_objects [ipx::current_core]] - -set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 2} [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] - -# Uncomment these lines after copying TCL commands from gui - -ipx::create_xgui_files [ipx::current_core] -ipx::update_checksums [ipx::current_core] -ipx::save_core [ipx::current_core] -close_project -exit - - - diff --git a/middleware/hls/buffered_handshake/Makefile b/middleware/hls/buffered_handshake/Makefile new file mode 100644 index 00000000..b53bee61 --- /dev/null +++ b/middleware/hls/buffered_handshake/Makefile @@ -0,0 +1,53 @@ +# Edit the following four variables and run `make`. +# After that, follow the instructions in ip_maker.tcl +# +# Alternatively, you could set any of these variables on the command line: +# +# $ make src_dir=/path/to/my/src dst_dir=/path/to/my/output +# +# Just make sure that you don't include a trailing slash on your directories + +dst_dir=$(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip +src_dir=. +ip_name=bhand +part_no=$(GALAPAGOS_PART) + +# Makes Makefile easier to read +out_dir=${dst_dir}/${ip_name} + +MODULE := bhand + +# By default, package as IP +default: ip + +tb: $(MODULE).vcd + +$(MODULE).vcd: $(MODULE).vvp + vvp $(MODULE).vvp + +$(MODULE).vvp: $(MODULE).v $(MODULE)_tb.v $(MODULE)_drivers.mem + iverilog -DICARUS_VERILOG -o $(MODULE).vvp $(MODULE)_tb.v + +open: $(MODULE).vcd + gtkwave $(MODULE).vcd --autosavename & + +clean: + rm -rf $(MODULE).vvp + rm -rf $(MODULE).vcd + rm -rf ${out_dir} + rm -rf ${ip_name}_tmp_proj + +# Packages into a Vivado IP +ip: clean + rm -rf ${out_dir} + mkdir -p ${out_dir}/src + cp bhand.v ${out_dir}/src + vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} + rm -rf ${ip_name}_tmp_proj + rm -f *log + rm -rf .Xil + rm -f vivado* + +force: + touch $(MODULE)_tb.v + make diff --git a/middleware/hls/buffered_handshake/README.txt b/middleware/hls/buffered_handshake/README.txt new file mode 100644 index 00000000..7bb4fae3 --- /dev/null +++ b/middleware/hls/buffered_handshake/README.txt @@ -0,0 +1,507 @@ +=============================== +WHAT IS "BUFFERED HANDSHAKING"? +=============================== + +This is a term I learned from Dan Gisselquist: + + https://zipcpu.com/blog/2017/08/14/strategies-for-pipelining.html + +Some friends of mine mentioned that they learned a similar technique in their +undergrad comp arch class. However, a Google search for "buffered handshake" +doesn't turn up anything other than the article I just linked, so the technique +must have a different name elsewhere. + +Basically, when you have a pipelined design that needs to handle backpressure, +you need a way to propagate that pressure backwards. If you're not careful and +just have a global (i.e. combinational) ready signal, then you can fail timing. +Buffered handshaking is a way of addressing this problem. + +Note: this is exactly the same thing as an AXI Stream register slice in one of +the modes you can choose. + +======= +DETAILS +======= + +The general idea is something like this: we will delay data and valid in the +forward direction, and delay ready in the backward direction: + + +-----------+ +-----------+ +-----------+ + ------->|data data|------->|data data|------->|data data|-------> + ------->|vld vld|------->|vld vld|------->|vld vld|-------> + <-------|rdy rdy|<-------|rdy rdy|<-------|rdy rdy|<------- + |> | |> | |> | + +-----------+ +-----------+ +-----------+ + A B C + +Here's the problem: consider stage B. If its RHS ready goes low, it will take +one cycle before its LHS ready goes low. That means that B needs a _second +register_ in order to save the incoming data _without overwriting_ the data +that is already inside it. + +The next few plates (mostly copied from Dan Gisselquist's article and converted +to use ready+valid signalling) will illustrate the general idea. + + +------------------------------------------------ +-----------------------------------------+ + | Legend | +t = 0: Initial state. The value "a" is input. |-----------------------------------------| + | | + +-+ +-+ +-+ | +-----------------------+ | + | | | | | | | |Data saved in extra reg| | + +-+ ---> |-| |-| |-| | ---> |-----------------------| ---> vld | + |a| <--- | | <--- | | <--- | | <--- | <--- |Data for this stage | <--- rdy | + +-+ +-+ +-+ +-+ | +-----------------------+ | + Input 1 2 3 | | + | If arrow is present, signal is asserted | +------------------------------------------------ +-----------------------------------------+ +t = 1: "a" was accepted, and "b" is input. + + +-+ +-+ +-+ + | | | | | | + +-+ ---> |-| ---> |-| |-| + |b| <--- |a| <--- | | <--- | | <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 2: "b" was accepted, and "c" is input. + + +-+ +-+ +-+ + | | | | | | + +-+ ---> |-| ---> |-| ---> |-| + |c| <--- |b| <--- |a| <--- | | <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 3: "c" was accepted, and "d" is input. Because RHS ready and valid are +high, the value "a" will be output on this cycle. + + +-+ +-+ +-+ + | | | | | | + +-+ ---> |-| ---> |-| ---> |-| ---> + |d| <--- |c| <--- |b| <--- |a| <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 3: "d" was accepted, and "e" is input. RHS ready went low, so "b" will not +be output. However, note that stage 3's ready is still high; this means c must +be saved, since stage 2 will discard it on this cycle. + + +-+ +-+ +-+ + | | | | | | + +-+ ---> |-| ---> |-| ---> |-| ---> + |e| <--- |d| <--- |c| <--- |b| + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 4: "e" was accepted, and "f" is input. RHS ready still low, so "b" will not +be output. Now, the low ready signal has propagated backwards through stage 3, +so "d" will not be read. However, note that stage 2's ready is still high; this +means e must be saved, since stage 1 will discard it on this cycle. + + +-+ +-+ +-+ + | | | | |c| + +-+ ---> |-| ---> |-| ---> |-| ---> + |f| <--- |e| <--- |d| |b| + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 5: "f" was accepted, and "g" is input. RHS ready went high again, so "b" +will be output. Now, the low ready signals have propagated backwards through +stages 2 and 3, so "d" and "f" will not be read. However, note that stage 1's +ready is still high; this means g must be saved, since the input will discard +it on this cycle. + + +-+ +-+ +-+ + | | |e| |c| + +-+ ---> |-| ---> |-| ---> |-| ---> + |g| <--- |f| |d| |b| <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 6: "g" was accepted, and "h" is input. RHS ready is still high, so "c" will +be output. Now, the low ready signals have propagated backwards through stages +1 and 2, so "f" and "h" will not be read. At this point, the input side is +receiving backpressure. + + +-+ +-+ +-+ + |g| |e| | | + +-+ ---> |-| ---> |-| ---> |-| ---> + |h| |f| |d| <--- |c| <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 7: RHS ready is still high, so "d" will be output. The low ready signals +have propagated backwards through stages 1 and 2, so "f" and "h" will not be +read. At this point, the input side is receiving backpressure. + + +-+ +-+ +-+ + |g| | | | | + +-+ ---> |-| ---> |-| ---> |-| ---> + |h| |f| <--- |e| <--- |d| <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ +t = 8: RHS ready is still high, so "d" will be output. All the low ready +signals have "bubbled out" of the pipeline, and we're back to normal operation. +Note that the output ready was low for two cycles, and (after a delay) the +input was also low for two cycles. This is no accident. + + +-+ +-+ +-+ + | | | | | | + +-+ ---> |-| ---> |-| ---> |-| ---> + |h| <--- |g| <--- |f| <--- |e| <--- + +-+ +-+ +-+ +-+ + Input 1 2 3 +------------------------------------------------------------------------------ + + +============== +IMPLEMENTATION +============== + +A buffered handshake can really be thought of as a two-element FIFO: + + +-----------+ + ------->|data data|------> + ------->|vld vld|------> + <-------|rdy rdy|<------ + |> | + +-----------+ + +A good question is "why can't we use a one-element FIFO?" The answer is because +a one-element FIFO will have a combinational path between the right-hand ready +signal and the left-hand ready signal. This happens when the FIFO is full: if +the output side is ready (meaning the FIFO's value will be read on this cycle), +then the input side is ready (meaning a new value can enter), but if the output +side is not ready, then the input side is not ready. + + That explanation is not 100% complete: you could make the same argument + about a two-element FIFO as well (i.e. when it is full, a combinational + path exists between right-side ready and left-side ready). We actually do + not permit a write into the FIFO when it is full _even if a value will exit + on this cycle_. + + One last thing: why can't we do this with a one-element FIFO (i.e. + disallow inputting when full)? You can, but then it is impossible to + maintain line rate, since the single element will have to be filled and + emptied for every data beat. + +Because of the (small) fixed FIFO size, we don't need to keep track of read and +write pointers. Instead, we'll do something a little more ad-hoc: start by +making a one-element FIFO and add a few things until we're done. + +One-element FIFO +---------------- + +This is about as simple as I can make it: + + module fifo_single ( + input wire clk, + + input wire [7:0] idata, + input wire idata_vld, + output wire idata_rdy, + + output wire [7:0] odata, + output wire odata_vld, + input wire odata_rdy + ); + + //Some helper signals for neatening up the code + wire shift_in; + assign shift_in = idata_vld && idata_rdy; + + wire shift_out; + assign shift_out = odata_vld && odata_rdy; + + + + //Internal registers and signals for FIFO element + reg [7:0] mem = 0; + reg mem_vld = 0; + wire mem_rdy; + + //We are ready if FIFO is empty, or if the value is leaving on this cycle + assign mem_rdy = !mem_vld || (odata_vld && odata_rdy); + + //We will enable writing into mem if it is ready, and if the input is valid + wire mem_en; + assign mem_en = mem_rdy && idata_vld; + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + mem <= idata; + end + + //mem_vld's next value + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end + + + + //Actually wire up module outputs + assign idata_rdy = mem_rdy; + assign odata = mem; + assign odata_vld = mem_vld; + + endmodule + +Verilog is so hard to read... I'm not really sure what to do about it... + + +Converting into a buffered handshake +------------------------------------ + +We're pretty much already finished. We only need to make four changes: + + - Add a second FIFO element + - Load second element when original element is full + - Allow loading the original element from the second element (when needed) + - Calculate the idata_rdy signal based on whether the extra register is free + +The text below is the buffered handshake module. Lines beginning with '#' are +modifications of the fifo_single module, and lines beginning with '>' are +additions. (Note: a two-column diff is provided below; this is only here in +case you prefer using a narrow screen) + + #module buffered_handshake ( + input wire clk, + + input wire [7:0] idata, + input wire idata_vld, + output wire idata_rdy, + + output wire [7:0] odata, + output wire odata_vld, + input wire odata_rdy + ); + + //Some helper signals for neatening up the code + wire shift_in; + assign shift_in = idata_vld && idata_rdy; + + wire shift_out; + assign shift_out = odata_vld && odata_rdy; + + + + > //Forward-declare this signal since extra_mem needs it + > reg mem_vld = 0; + > + > + > + > //Internal registers and signals for extra element + > reg [7:0] extra_mem = 0; + > reg extra_mem_vld = 0; + > wire extra_mem_rdy; + > + > //Unlike a regular FIFO, we are only ready if empty: + > assign extra_mem_rdy = (extra_mem_vld == 0); + > + > //We will enable writing into extra mem if a new element is shifting in AND + > //mem is full AND mem will not be read on this cycle + > wire extra_mem_en; + > assign extra_mem_en = shift_in && mem_vld && !shift_out; + > + > always @(posedge clk) begin + > //extra_mem's next value + > if (extra_mem_en) begin + > extra_mem <= idata; + > end + > + > //extra_mem_vld's next value + > if (extra_mem_en) begin + > extra_mem_vld <= 1; + > end else if (shift_out) begin + > extra_mem_vld <= 0; + > end + > end + > + > + > + //Internal registers and signals for FIFO element + reg [7:0] mem = 0; + # //reg mem_vld = 0; //moved + wire mem_rdy; + + //We are ready if FIFO is empty, or if the value is leaving on this cycle + assign mem_rdy = !mem_vld || (odata_vld && odata_rdy); + + //We will enable writing into mem if it is ready, and if the input is valid + wire mem_en; + # assign mem_en = mem_rdy && (idata_vld || extra_mem_vld); + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + # mem <= extra_mem_vld ? extra_mem : idata; + end + + //mem_vld's next value + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end + + + + //Actually wire up module outputs + # assign idata_rdy = extra_mem_rdy; + assign odata = mem; + assign odata_vld = mem_vld; + + endmodule + + + + + + +As promised, here is the two-column diff: + + module fifo_single ( # module buffered_handshake ( + input wire clk, + + input wire [7:0] idata, + input wire idata_vld, + output wire idata_rdy, + + output wire [7:0] odata, + output wire odata_vld, + input wire odata_rdy + ); + + //Some helper signals for neatening up the code + wire shift_in; + assign shift_in = idata_vld && idata_rdy; + + wire shift_out; + assign shift_out = odata_vld && odata_rdy; + + + + > //Forward-declare this signal since extra_mem needs it + > reg mem_vld = 0; + > + > + > + > //Internal registers and signals for extra element + > reg [7:0] extra_mem = 0; + > reg extra_mem_vld = 0; + > wire extra_mem_rdy; + > + > //Unlike a regular FIFO, we are only ready if empty: + > assign extra_mem_rdy = (extra_mem_vld == 0); + > + > //We will enable writing into extra mem if a new element is shifting in AND + > //mem is full AND mem will not be read on this cycle + > wire extra_mem_en; + > assign extra_mem_en = shift_in && mem_vld && !shift_out; + > + > always @(posedge clk) begin + > //extra_mem's next value + > if (extra_mem_en) begin + > extra_mem <= idata; + > end + > + > //extra_mem_vld's next value + > if (extra_mem_en) begin + > extra_mem_vld <= 1; + > end else if (shift_out) begin + > extra_mem_vld <= 0; + > end + > end + > + > + > + //Internal registers and signals for FIFO element + reg [7:0] mem = 0; + reg mem_vld = 0; # //reg mem_vld = 0; //moved + wire mem_rdy; + + //We are ready if FIFO is empty, or if the value is leaving on this cycle + assign mem_rdy = !mem_vld || (odata_vld && odata_rdy); + + //We will enable writing into mem if it is ready, and if the input is valid + wire mem_en; + assign mem_en = mem_rdy && idata_vld; # assign mem_en = mem_rdy && (idata_vld || extra_mem_vld); + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + mem <= idata; # mem <= extra_mem_vld ? extra_mem : idata; + end + + //mem_vld's next value + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end + + + + //Actually wire up module outputs + assign idata_rdy = mem_rdy; # assign idata_rdy = extra_mem_rdy; + assign odata = mem; + assign odata_vld = mem_vld; + + endmodule + + +============= +COUNTING MODE +============= + +One last thing: in some cases, I need to count how many cycles a value has been +in a pipeline. To do this, I can use almost the exact same code, except every +time I assign a value to mem or extra_mem, I increment it first (not forgetting +the case when the value doesn't change!) + +So, taking the extra_mem code, + + always @(posedge clk) begin + //extra_mem's next value + if (extra_mem_en) begin + extra_mem <= idata; + end + end + +we simply change it to say: + + always @(posedge clk) begin + //extra_mem's next value + if (extra_mem_en) begin + extra_mem <= idata + 1; + end else begin + extra_mem <= extra_mem + 1; + end + +And similarly for the mem code, take + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + mem <= extra_mem_vld ? extra_mem : idata; + end + end + +and change it to + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + mem <= extra_mem_vld ? extra_mem + 1 : idata + 1; + end else begin + mem <= mem + 1; + end + end diff --git a/middleware/hls/buffered_handshake/bhand.gtkw b/middleware/hls/buffered_handshake/bhand.gtkw new file mode 100644 index 00000000..c4eeca02 --- /dev/null +++ b/middleware/hls/buffered_handshake/bhand.gtkw @@ -0,0 +1,47 @@ +[*] +[*] GTKWave Analyzer v3.3.102 (w)1999-2019 BSI +[*] Thu Nov 14 15:54:21 2019 +[*] +[dumpfile] "/home/mahkoe/research/fpga-bpf/sources/generic/buffered_handshake/bhand.vcd" +[dumpfile_mtime] "Thu Nov 14 15:52:51 2019" +[dumpfile_size] 4742 +[savefile] "/home/mahkoe/research/fpga-bpf/sources/generic/buffered_handshake/bhand.gtkw" +[timestart] 0 +[size] 1916 1060 +[pos] -1 -1 +*-15.632915 335000 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] bhand_tb. +[treeopen] bhand_tb.DUT. +[sst_width] 214 +[signals_width] 174 +[sst_expanded] 1 +[sst_vpaned_height] 610 +@28 +bhand_tb.DUT.clk +bhand_tb.rst +@200 +-input side +@22 +bhand_tb.idata[7:0] +@28 +bhand_tb.idata_vld +bhand_tb.idata_rdy +@200 +-output_side +@22 +bhand_tb.odata[7:0] +@28 +bhand_tb.odata_vld +bhand_tb.odata_rdy +@23 +bhand_tb.DUT.ocount[3:0] +@200 +-bhand internals +@22 +bhand_tb.DUT.extra_mem[7:0] +bhand_tb.DUT.mem[7:0] +@28 +bhand_tb.DUT.extra_mem_vld +bhand_tb.DUT.mem_vld +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/middleware/hls/buffered_handshake/bhand.v b/middleware/hls/buffered_handshake/bhand.v new file mode 100644 index 00000000..3b4f31fb --- /dev/null +++ b/middleware/hls/buffered_handshake/bhand.v @@ -0,0 +1,197 @@ +`ifndef BHAND_INCLUDE_GUARD +`define BHAND_INCLUDE_GUARD 1 + +`timescale 1ns / 1ps +/* +bhand.v + +Implements a buffered handshake. Can control whether or not the core has reset +signals and of which polarity. (Unfortunately, it makes the Verilog harder to +read...) + +*/ + +`define genif generate if +`define else_genif end else if +`define endgen end endgenerate + +`define NO_RESET 0 +`define ACTIVE_HIGH 1 +`define ACTIVE_LOW 2 + +module bhand # ( + parameter DATA_WIDTH = 8, + parameter RESET_TYPE = `ACTIVE_HIGH +) ( + input wire clk, + //Reset is used according to the RESET_TYPE parameter + input wire rst, + + //Connect your input stream signals here + //For example, + // .idata({in_TDATA, in_TKEEP, in_TDEST, in_TID, in_TLAST}), + // .idata_vld(in_TVALID), + // .idata_rdy(in_TREADY) + // + //Keep in mind that idata_rdy is an output of this module + input wire [DATA_WIDTH-1:0] idata, + input wire idata_vld, + output wire idata_rdy, + + //Connect your output stream signals here + //For example, + // .odata({out_TDATA, out_TKEEP, out_TDEST, out_TID, out_TLAST}), + // .odata_vld(out_TVALID), + // .odata_rdy(out_TREADY) + // + //Keep in mind that odata_rdy is an input of this module + output wire [DATA_WIDTH-1:0] odata, + output wire odata_vld, + input wire odata_rdy +); + + //Some helper signals for neatening up the code + wire shift_in; + assign shift_in = idata_vld && idata_rdy; + + wire shift_out; + assign shift_out = odata_vld && odata_rdy; + + //Forward-declare this signal since extra_mem needs it + reg mem_vld = 0; + + //Internal registers and signals for extra element + reg [DATA_WIDTH-1:0] extra_mem = 0; + reg extra_mem_vld = 0; + wire extra_mem_rdy; + + //Unlike a regular FIFO, we are only ready if empty: + assign extra_mem_rdy = (extra_mem_vld == 0); + + //We will enable writing into extra mem if a new element is shifting in AND + //mem is full AND mem will not be read on this cycle + wire extra_mem_en; + assign extra_mem_en = shift_in && mem_vld && !shift_out; + +`genif (RESET_TYPE == `NO_RESET) begin + + always @(posedge clk) begin + //extra_mem_vld's next value + if (extra_mem_en) begin + extra_mem_vld <= 1; + end else if (shift_out) begin + extra_mem_vld <= 0; + end + end + +`else_genif (RESET_TYPE == `ACTIVE_HIGH) begin + + always @(posedge clk) begin + if (rst) begin + extra_mem_vld <= 0; + end else begin + if (extra_mem_en) begin + extra_mem_vld <= 1; + end else if (shift_out) begin + extra_mem_vld <= 0; + end + end + end + +`else_genif (RESET_TYPE == `ACTIVE_LOW) begin + + always @(posedge clk) begin + if (!rst) begin + extra_mem_vld <= 0; + end else begin + if (extra_mem_en) begin + extra_mem_vld <= 1; + end else if (shift_out) begin + extra_mem_vld <= 0; + end + end + end + +`endgen + + //extra_mem's next value + always @(posedge clk) begin + if (extra_mem_en) begin + extra_mem <= idata; + end + end + + + //Internal registers and signals for FIFO element + reg [DATA_WIDTH-1:0] mem = 0; + //reg mem_vld = 0; //moved + wire mem_rdy; + + //We are ready if FIFO is empty, or if the value is leaving on this cycle + assign mem_rdy = !mem_vld || (odata_vld && odata_rdy); + + //We will enable writing into mem if it is ready, and if the input is valid + wire mem_en; + assign mem_en = mem_rdy && (idata_vld || extra_mem_vld); + +`genif (RESET_TYPE == `NO_RESET) begin + always @(posedge clk) begin + //mem_vld's next value + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end +end else if (RESET_TYPE == `ACTIVE_HIGH) begin + always @(posedge clk) begin + //mem_vld's next value + if (rst) begin + mem_vld <= 0; + end else begin + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end + end +end else if (RESET_TYPE == `ACTIVE_LOW) begin + always @(posedge clk) begin + //mem_vld's next value + if (!rst) begin + mem_vld <= 0; + end else begin + if (mem_en) begin + mem_vld <= 1; + end else if (shift_out) begin + mem_vld <= 0; + end + end + end +`endgen + + always @(posedge clk) begin + //mem's next value + if (mem_en) begin + mem <= extra_mem_vld ? extra_mem : idata; + end + end + + //Actually wire up module outputs + assign idata_rdy = extra_mem_rdy; + assign odata = mem; + assign odata_vld = mem_vld; + +endmodule + + +`undef NO_RESET +`undef ACTIVE_HIGH +`undef ACTIVE_LOW + +`undef genif +`undef else_genif +`undef endgen + +`endif diff --git a/middleware/hls/buffered_handshake/bhand_drivers.mem b/middleware/hls/buffered_handshake/bhand_drivers.mem new file mode 100644 index 00000000..94847706 --- /dev/null +++ b/middleware/hls/buffered_handshake/bhand_drivers.mem @@ -0,0 +1,35 @@ +idata idata_vld odata_rdy rst +00 0 1 0 +DE 1 1 0 +AD 1 1 0 +00 0 1 0 +BE 1 1 0 +EF 1 1 0 +00 0 0 0 +FE 1 0 0 +ED 1 1 0 +ED 1 1 0 +BE 1 1 0 +00 0 1 0 +EF 1 1 0 +00 0 1 0 +00 0 1 1 +00 0 1 0 +DE 1 1 0 +AD 1 1 1 +00 0 1 0 +BE 1 1 0 +00 0 0 0 +00 0 0 0 +00 0 0 0 +00 0 0 0 +EF 1 1 0 +00 0 0 0 +FE 1 0 0 +ED 1 1 0 +ED 1 1 0 +BE 1 1 0 +00 0 1 0 +EF 1 1 0 +00 0 1 0 +00 0 1 0 diff --git a/middleware/hls/buffered_handshake/bhand_tb.v b/middleware/hls/buffered_handshake/bhand_tb.v new file mode 100644 index 00000000..4db21a7c --- /dev/null +++ b/middleware/hls/buffered_handshake/bhand_tb.v @@ -0,0 +1,81 @@ +`timescale 1ns / 1ps + +/* +bhand_tb.v + +A testbench for the buffered handshake +*/ + +`ifdef ICARUS_VERILOG +`include "bhand.v" +`endif + +`define DATA_WIDTH 8 +`define COUNT_WIDTH 4 +`define ENABLE_COUNT 1 + +module bhand_tb; + reg clk; + reg rst; + reg [`DATA_WIDTH-1:0] idata; + reg idata_vld; + wire idata_rdy; + wire [`DATA_WIDTH-1:0] odata; + wire odata_vld; + reg odata_rdy; + + integer fd, dummy; + + initial begin + $dumpfile("bhand.vcd"); + $dumpvars; + $dumplimit(512000); + + clk <= 0; + rst <= 0; + idata <= 0; + idata_vld <= 0; + odata_rdy <= 0; + + fd = $fopen("bhand_drivers.mem", "r"); + if (fd == 0) begin + $display("Could not open file"); + $finish; + end + + while ($fgetc(fd) != "\n") begin + if ($feof(fd)) begin + $display("Error: file is in incorrect format"); + $finish; + end + end + end + + always #5 clk <= ~clk; + + always @(posedge clk) begin + if ($feof(fd)) begin + $display("Reached end of drivers file"); + #20 + $finish; + end + + #0.01 + dummy = $fscanf(fd, "%x%b%b%b", idata, idata_vld, odata_rdy, rst); + end + + bhand # ( + .DATA_WIDTH(`DATA_WIDTH), + .RESET_TYPE(1) + ) DUT ( + .clk(clk), + .rst(rst), + .idata(idata), + .idata_vld(idata_vld), + .idata_rdy(idata_rdy), + .odata(odata), + .odata_vld(odata_vld), + .odata_rdy(odata_rdy) + ); + +endmodule diff --git a/middleware/hls/buffered_handshake/ip_maker.tcl b/middleware/hls/buffered_handshake/ip_maker.tcl new file mode 100644 index 00000000..72521dd0 --- /dev/null +++ b/middleware/hls/buffered_handshake/ip_maker.tcl @@ -0,0 +1,43 @@ +# Call as: +# vivado -mode tcl -nolog -nojournal -source scripts/ip_package.tcl -tclargs $out_dir $ip_name $part_name + +set out_dir [lindex $argv 0] +set ip_name [lindex $argv 1] +set part_name [lindex $argv 2] +set project_name ${ip_name}_tmp_proj +create_project ${project_name} ${project_name} -part ${part_name} +add_files ${out_dir}/src +ipx::package_project -root_dir ${out_dir} -vendor mmerlini -library yov -taxonomy /UserIP + +# Fix up 'rst' interface +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 1} [ipx::get_bus_interfaces rst -of_objects [ipx::current_core]] +ipx::add_bus_parameter POLARITY [ipx::get_bus_interfaces rst -of_objects [ipx::current_core]] +set_property VALUE ACTIVE_HIGH [ipx::get_bus_parameters POLARITY -of_objects [ipx::get_bus_interfaces rst -of_objects [ipx::current_core]]] + +# Add 'rstn' interface +ipx::add_bus_interface rstn [ipx::current_core] +set_property abstraction_type_vlnv xilinx.com:signal:reset_rtl:1.0 [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] +set_property bus_type_vlnv xilinx.com:signal:reset:1.0 [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] +set_property enablement_dependency {spirit:decode(id('MODELPARAM_VALUE.RESET_TYPE')) = 2} [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] +ipx::add_port_map RST [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] +set_property physical_name rst [ipx::get_port_maps RST -of_objects [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]]] +ipx::add_bus_parameter POLARITY [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]] +set_property VALUE ACTIVE_LOW [ipx::get_bus_parameters POLARITY -of_objects [ipx::get_bus_interfaces rstn -of_objects [ipx::current_core]]] + +# Do up 'RESET_TYPE' configuration parameter +set_property display_name {Reset type} [ipgui::get_guiparamspec -name "RESET_TYPE" -component [ipx::current_core] ] +set_property tooltip {} [ipgui::get_guiparamspec -name "RESET_TYPE" -component [ipx::current_core] ] +set_property widget {comboBox} [ipgui::get_guiparamspec -name "RESET_TYPE" -component [ipx::current_core] ] +set_property value 0 [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] +set_property value 0 [ipx::get_hdl_parameters RESET_TYPE -of_objects [ipx::current_core]] +set_property value_validation_type pairs [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] +set_property value_validation_pairs {None 0 {Active high} 1 {Active low} 2} [ipx::get_user_parameters RESET_TYPE -of_objects [ipx::current_core]] + +ipx::create_xgui_files [ipx::current_core] +ipx::update_checksums [ipx::current_core] +ipx::save_core [ipx::current_core] +close_project +exit + + + diff --git a/middleware/hls/dbg_guv/Makefile b/middleware/hls/dbg_guv/Makefile new file mode 100644 index 00000000..8008a40f --- /dev/null +++ b/middleware/hls/dbg_guv/Makefile @@ -0,0 +1,61 @@ +# Edit the following four variables and run `make`. +# After that, follow the instructions in ip_maker.tcl +# +# Alternatively, you could set any of these variables on the command line: +# +# $ make src_dir=/path/to/my/src dst_dir=/path/to/my/output +# +# Just make sure that you don't include a trailing slash on your directories + +dst_dir=$(GALAPAGOS_PATH)/hlsBuild/$(GALAPAGOS_BOARD_NAME)/ip +src_dir=. +ip_name=dbg_guv +part_no=$(GALAPAGOS_PART) + + + +# Makes Makefile easier to read +out_dir=${dst_dir}/${ip_name} + +# These variables are for running the testbench +MODULE := dbg_guv + +# By default, package as an ip +default: ip + +# This is to run the testbench +tb: $(MODULE).vcd + +# This opens the testbench in gtkwave +open: $(MODULE).vcd + gtkwave $(MODULE).vcd --autosavename & + +# Compile the Verilog into Icarus's special format +$(MODULE).vvp: $(MODULE).v $(MODULE)_tb.v $(MODULE)_drivers.mem + iverilog -DICARUS_VERILOG -Iaxis_governor/ -I../marcos_macros/ -Iaxis_headerizer/ -o $(MODULE).vvp $(MODULE)_tb.v + +# Run the Verilog simulator +$(MODULE).vcd: $(MODULE).vvp + vvp $(MODULE).vvp + +clean: + rm -rf $(MODULE).vvp + rm -rf $(MODULE).vcd + rm -rf ${out_dir} + rm -rf ${ip_name}_tmp_proj + +# Packages into a Vivado IP +ip: clean + rm -rf ${out_dir} + mkdir -p ${out_dir}/src + cp dbg_guv.v axis_governor/axis_governor.v ../marcos_macros/macros.vh axis_headerizer/axis_headerizer.v ${out_dir}/src + vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} + rm -rf ${ip_name}_tmp_proj + rm -f *log + rm -rf .Xil + rm -f vivado* + +syntax_check: + iverilog -DICARUS_VERILOG -Iaxis_governor/ -I../marcos_macros/ -Iaxis_headerizer/ -E $(MODULE).v + rm -rf a.out + diff --git a/middleware/hls/dbg_guv/addcore.tcl b/middleware/hls/dbg_guv/addcore.tcl new file mode 100644 index 00000000..42a3a181 --- /dev/null +++ b/middleware/hls/dbg_guv/addcore.tcl @@ -0,0 +1,240 @@ +# These procs are used to automatically insert dbg_guvs into a design + +# Replaces the bd_intf_net n with a dbg_guv core named "inst" +proc add_dbg_core_to_net {n inst} { + # Get the two endpoints of this net + set pins [get_bd_intf_pins -of_objects $n -quiet] + set ports [get_bd_intf_ports -of_objects $n -quiet] + + set npins [llength $pins] + set nports [llength $ports] + + # Check if it has the right number of endpoints + if {[expr $npins + $nports] != 2} { + puts "Warning: invalid net" + return -1 + } + + # Name the two endpoints left and right + if {$npins == 2} { + set left [lindex $pins 0] + set right [lindex $pins 1] + } elseif {$npins == 1} { + set left [lindex $pins 0] + set right [lindex $ports 0] + } else { + set left [lindex $ports 0] + set right [lindex $ports 1] + } + + # Double-check that they are in fact AXI Stream + if {[string compare [get_property VLNV $left] "xilinx.com:interface:axis_rtl:1.0"] != 0} { + puts "Warning: this is not an AXI Stream interface" + return -1 + } + if {[string compare [get_property VLNV $right] "xilinx.com:interface:axis_rtl:1.0"] != 0} { + puts "Warning: this is not an AXI Stream interface" + return -1 + } + + # Quit early if there is already a dbg_guv at one endpoint of this net + if {$npins > 0} { + set left_vlnv [get_property VLNV [get_bd_cells -of_objects $left]] + if {$left_vlnv == "mmerlini:yov:dbg_guv:1.0"} { + puts "INFO: net already has a dbg_guv" + return 0 + } + } + + if {$npins == 2} { + set right_vlnv [get_property VLNV [get_bd_cells -of_objects $right]] + if {$right_vlnv == "mmerlini:yov:dbg_guv:1.0"} { + puts "INFO: net already has a dbg_guv" + return 0 + } + } + + # Delete the original net + delete_bd_objs $n + + # Instantiate the dbg_guv + set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv $inst] + + # Connect the dbg_guv to the loose endpoints + if ![string compare [get_property MODE $left] Master] { + connect_bd_intf_net -intf_net GUV_${inst}_mst $g/out $right + connect_bd_intf_net -intf_net GUV_${inst}_slv $left $g/in + } else { + connect_bd_intf_net -intf_net GUV_${inst}_mst $g/out $left + connect_bd_intf_net -intf_net GUV_${inst}_slv $right $g/in + } + + return $g +} + +# Searches current BD to get next ID to use for a dbg_guv +proc get_next_dbg_core_id {} { + set cells [get_bd_cells -hierarchical -filter {VLNV == mmerlini:yov:dbg_guv:1.0} -quiet] + set next_id 0 + foreach c $cells { + set name [get_property NAME $c] + if {[string first "GUV_" $name] == 0} { + set idnum [string range $name 4 [string length $name]] + if [string is integer $idnum] { + set next_id [expr max($next_id,$idnum+1)] + } + } + } + return $next_id +} + +# Given a list of AXI Stream masters (with only TDATA, TREADY, TVALID, and TLAST) +# hooks up an automatically sized arbiter tree +# TODO?: Special case for n=1 +proc rr_tree {msts} { + startgroup + set nnodes [expr ([llength $msts]+1)/3] + + set slvs {} + set nodes {} + while {$nnodes > 0} { + set node [create_bd_cell -vlnv Marco_Merlini:fpga_bpf:rr4 -name node_$nnodes] + + set_property CONFIG.RESET_TYPE 0 $node + + lappend nodes $node + lappend slvs [get_bd_intf_pins $node/s0] + lappend slvs [get_bd_intf_pins $node/s1] + lappend slvs [get_bd_intf_pins $node/s2] + lappend slvs [get_bd_intf_pins $node/s3] + incr nnodes -1 + if {$nnodes > 0} { + lappend msts [get_bd_intf_pins $node/o ] + } + } + + while {[llength $msts] > 0 && [llength $slvs] > 0} { + connect_bd_intf_net [lindex $msts 0] [lindex $slvs 0] + set msts [lreplace $msts 0 0] + set slvs [lreplace $slvs 0 0] + } + + if {[llength $nodes] > 0} { + # The user is not allowed to use this name + set h [create_bd_cell -type hier -name DBG_GUV_TREE] + + move_bd_cells $h $nodes + + create_bd_intf_pin -mode Master -vlnv xilinx.com:interface:axis_rtl:1.0 DBG_GUV_TREE/o + connect_bd_intf_net [get_bd_intf_pins DBG_GUV_TREE/o] [get_bd_intf_pins DBG_GUV_TREE/node_1/o] + create_bd_pin -dir I DBG_GUV_TREE/clk + + connect_bd_net [list [get_bd_pins /DBG_GUV_TREE/*/clk] [get_bd_pins /DBG_GUV_TREE/clk]] + } + + endgroup +} + + +proc del_dbg_core {c} { + startgroup + + set left [get_bd_intf_nets -of_objects [get_bd_intf_pins $c/in]] + set mst [get_bd_intf_pins -of_objects $left -filter "PATH !~ $c/in" -quiet] + if {[llength $mst] == 0} { + set mst [get_bd_intf_ports -of_objects $left -filter "PATH !~ $c/in" -quiet] + } + + set right [get_bd_intf_nets -of_objects [get_bd_intf_pins $c/out]] + set slv [get_bd_intf_pins -of_objects $right -filter "PATH !~ $c/out" -quiet] + if {[llength $slv] == 0} { + set slv [get_bd_intf_ports -of_objects $right -filter "PATH !~ $c/in" -quiet] + } + + delete_bd_objs [get_bd_intf_nets $left] [get_bd_intf_nets $right] + delete_bd_objs $c + + connect_bd_intf_net $mst $slv + + highlight_objects -color_index 3 [get_bd_intf_nets -of_objects $mst] + + endgroup +} + +proc del_all_dbg_cores {} { + startgroup + delete_bd_objs [get_bd_cells DBG_GUV_TREE -quiet] -quiet + set dbg_guvs [get_bd_cells -filter {VLNV =~ "*dbg_guv*"} -quiet] + foreach d $dbg_guvs { + set nets [get_bd_intf_nets -of_objects $d -quiet] + delete_bd_objs [get_bd_intf_nets $d/log_catted -quiet] -quiet + delete_bd_objs [get_bd_intf_nets $d/cmd_out -quiet] -quiet + del_dbg_core $d + } + endgroup +} + +proc add_dbg_core_to_highlighted {{safe_mode 1}} { + startgroup + + if {$safe_mode} { + del_all_dbg_cores + } + + # Stores the list of log outputs to run through the arbiter tree + set log_outs {} + + # Stores the previous cmd_out + set last_cmd {} + + + # Get all highlihgted nets + set nets [get_bd_intf_nets [get_highlighted_objects]] + + foreach n $nets { + # Choose an ID. get_next_dbg_core_id guarantees it will be unique, and + # if some dbg_guvs don't get added (e.g. if the highlighted net was + # invalid), it also makes sure that IDs don't get skipped + set next_id [get_next_dbg_core_id] + + # g holds a reference to the newly create dbg_guv cell + set g [add_dbg_core_to_net $n GUV_$next_id] + + set_property CONFIG.ADDR $next_id $g + + # Add $g/log to the list of log outputs + lappend log_outs $g/log_catted + + # If last_cmd is not empty, connect its cmd_out to $g/cmd_in + if {[llength $last_cmd] == 1} { + connect_bd_intf_net [get_bd_intf_pins $last_cmd] [get_bd_intf_pins $g/cmd_in] + } + + # Update last_cmd + set last_cmd $g/cmd_out + } + + # Put in the arbiter tree + rr_tree $log_outs + + # Connect up the clocks + # TODO: If I ever plan to allow multiple clock domains, this will have to + # change + connect_bd_net [list [get_bd_pins -of_objects [get_bd_cells -hierarchical -filter {VLNV == mmerlini:yov:dbg_guv:1.0}] -filter {NAME == clk}] [get_bd_pins /DBG_GUV_TREE/clk]] + endgroup +} + +proc del_highlighted_dbg_cores {} { + startgroup + set cores [get_highlighted_objects] + foreach c $cores { + if {[get_property VLNV $c] != "mmerlini:yov:dbg_guv:1.0"} { + puts "Warning, trying to delete an IP which is not a dbg_guv" + continue + } + + del_dbg_core $c + + } + endgroup +} diff --git a/middleware/hls/dbg_guv/axis_governor/Makefile b/middleware/hls/dbg_guv/axis_governor/Makefile new file mode 100644 index 00000000..26693923 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/Makefile @@ -0,0 +1,42 @@ +# These variables are for running the testbench +MODULE := axis_governor + +# By default, package as an ip +default: + echo "Warning: axis_governor is not meant to be used as a separate IP in Galapagos" + echo "Please see https://github.com/esophagus-now/ye_olde_verilogge" + +# This is to run the testbench +tb: $(MODULE).vcd + +# This opens the testbench in gtkwave +open: $(MODULE).vcd + gtkwave $(MODULE).vcd --autosavename & + +# Compile the Verilog into Icarus's special format +$(MODULE).vvp: $(MODULE).v $(MODULE)_tb.v $(MODULE)_drivers.mem + iverilog -DICARUS_VERILOG -o $(MODULE).vvp $(MODULE)_tb.v + +# Run the Verilog simulator +$(MODULE).vcd: $(MODULE).vvp + vvp $(MODULE).vvp + +clean: + rm -rf $(MODULE).vvp + rm -rf $(MODULE).vcd + +# Packages into a Vivado IP +ip: clean + rm -rf ${out_dir} + mkdir -p ${out_dir}/src + cp $(shell find ${src_dir} -name "*.v" -o -name "*vh" -o -name "*sv" | grep -v "tb") ${out_dir}/src + vivado -nolog -nojournal -notrace -mode batch -source ip_maker.tcl -tclargs ${out_dir} ${ip_name} ${part_no} + rm -rf ${ip_name}_tmp_proj + rm -f *log + rm -rf .Xil + rm -f vivado* + +syntax_check: + iverilog -DICARUS_VERILOG -E $(MODULE).v + rm -rf a.out + diff --git a/middleware/hls/dbg_guv/axis_governor/axis_governor.gtkw b/middleware/hls/dbg_guv/axis_governor/axis_governor.gtkw new file mode 100644 index 00000000..a6b37623 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/axis_governor.gtkw @@ -0,0 +1,70 @@ +[*] +[*] GTKWave Analyzer v3.3.103 (w)1999-2019 BSI +[*] Sat Feb 8 16:49:06 2020 +[*] +[dumpfile] "/home/mahkoe/research/galapagos/middleware/hls/axis_governor/axis_governor.vcd" +[dumpfile_mtime] "Sat Feb 8 16:32:37 2020" +[dumpfile_size] 32134 +[savefile] "/home/mahkoe/research/galapagos/middleware/hls/axis_governor/axis_governor.gtkw" +[timestart] 4798000 +[size] 1916 1060 +[pos] -1 -541 +*-15.412784 4495010 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 +[treeopen] testbench_template. +[treeopen] testbench_template.DUT. +[sst_width] 233 +[signals_width] 158 +[sst_expanded] 1 +[sst_vpaned_height] 575 +@28 +testbench_template.clk +testbench_template.pause +testbench_template.drop +testbench_template.log +@29 +testbench_template.inject +@800200 +-in +@24 +[color] 2 +testbench_template.in_TDATA[7:0] +@28 +[color] 2 +testbench_template.in_TVALID +[color] 2 +testbench_template.in_TREADY +@1000200 +-in +@800200 +-log +@24 +[color] 3 +testbench_template.log_TDATA[7:0] +@28 +testbench_template.log_TVALID +testbench_template.log_TREADY +@1000200 +-log +@800200 +-inj +@24 +[color] 2 +testbench_template.inj_TDATA[7:0] +@28 +[color] 2 +testbench_template.inj_TVALID +[color] 2 +testbench_template.inj_TREADY +@1000200 +-inj +@800200 +-out +@24 +testbench_template.out_TDATA[7:0] +@28 +testbench_template.out_TVALID +testbench_template.out_TREADY +@1000200 +-out +[pattern_trace] 1 +[pattern_trace] 0 diff --git a/middleware/hls/dbg_guv/axis_governor/axis_governor.v b/middleware/hls/dbg_guv/axis_governor/axis_governor.v new file mode 100644 index 00000000..52abbe69 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/axis_governor.v @@ -0,0 +1,152 @@ +`timescale 1ns / 1ps + +/* +This module is an AXI Stream passthrough, but with options to modify the +underlying stream. Supported operations: + + - Injecting flits + - Forcing ready to low (i.e. pausing) + - Copying flits to another interface (i.e. logging) + - Dropping flits (while potentially logging them) + +This module is designed to be used inside of an FSM which can, among other +things, accept commands to allow single-stepping, running to a "breakpoint" or +"watchpoint", repackage logged streams to move the side channels into TDATA, +etc. For now I just wanted to simulate this. + +This module requires 2 LUTs for the glue logic, and whatever LUTs/MUXes are +needed for the multiplexers at the output + +*/ + +//TODO: Find out how to selectively disable certain side channels +//Default widths correspond to Galapagos defaults +module axis_governor #( + parameter DATA_WIDTH = 64, + parameter DEST_WIDTH = 16, + parameter ID_WIDTH = 16 +) ( + input wire clk, + + //Input AXI Stream. + input wire [DATA_WIDTH-1:0] in_TDATA, + input wire in_TVALID, + output wire in_TREADY, + input wire [DATA_WIDTH/8 -1:0] in_TKEEP, + input wire [DEST_WIDTH -1:0] in_TDEST, + input wire [ID_WIDTH -1:0] in_TID, + input wire in_TLAST, + + //Inject AXI Stream. + input wire [DATA_WIDTH-1:0] inj_TDATA, + input wire inj_TVALID, + output wire inj_TREADY, + input wire [DATA_WIDTH/8 -1:0] inj_TKEEP, + input wire [DEST_WIDTH -1:0] inj_TDEST, + input wire [ID_WIDTH -1:0] inj_TID, + input wire inj_TLAST, + + //Output AXI Stream. + output wire [DATA_WIDTH-1:0] out_TDATA, + output wire out_TVALID, + input wire out_TREADY, + output wire [DATA_WIDTH/8 -1:0] out_TKEEP, + output wire [DEST_WIDTH -1:0] out_TDEST, + output wire [ID_WIDTH -1:0] out_TID, + output wire out_TLAST, + + //Log AXI Stream. + output wire [DATA_WIDTH-1:0] log_TDATA, + output wire log_TVALID, + input wire log_TREADY, + output wire [DATA_WIDTH/8 -1:0] log_TKEEP, + output wire [DEST_WIDTH -1:0] log_TDEST, + output wire [ID_WIDTH -1:0] log_TID, + output wire log_TLAST, + + //Control signals + input wire pause, + input wire drop, + input wire log_en + //input wire inject //Not needed; this is determined from inj_TVALID +); + //Perform the tricky management of valids and readies. + //This module is defined lower down in this file + axis_governor_glue_log glue( + .in_vld(in_TVALID), + .out_rdy(out_TREADY), + .log_en(log_en), + .log_rdy(log_TREADY), + .inj_vld(inj_TVALID), + .pause(pause), + .drop(drop), + + .in_rdy(in_TREADY), + .out_vld(out_TVALID), + .log_vld(log_TVALID), + .inj_rdy(inj_TREADY) + ); + + //Connect remaining AXI Stream signals + assign out_TDATA = inj_TVALID ? inj_TDATA : in_TDATA; + assign out_TKEEP = inj_TVALID ? inj_TKEEP : in_TKEEP; + assign out_TDEST = inj_TVALID ? inj_TDEST : in_TDEST; + assign out_TID = inj_TVALID ? inj_TID : in_TID; + assign out_TLAST = inj_TVALID ? inj_TLAST : in_TLAST; + + assign log_TDATA = in_TDATA; + assign log_TKEEP = in_TKEEP; + assign log_TDEST = in_TDEST; + assign log_TID = in_TID; + assign log_TLAST = in_TLAST; +endmodule + + +//This glue logic was NOT easy to come up with. In the end I did a 128-entry +//truth table. There was no other way to be sure I covered every corner case. +//At some point I may attach the truth table with this code. +module axis_governor_glue_log ( + input wire in_vld, + input wire out_rdy, + input wire log_en, + input wire log_rdy, + input wire inj_vld, + input wire pause, + input wire drop, + + output wire in_rdy, + output wire out_vld, + output wire log_vld, + output wire inj_rdy +); + + //(~inj_vld && out_rdy) means the slave is ready to receive a flit from the + //master. OR'ing this with drop means the slave (and injector) cannot + //backpressure the master if drop is high. + // + //(log_rdy || ~log_en) means that the logger is not backpressuring the + //master + // + //Finally, in_rd is forced low if we are paused + assign in_rdy = ~pause && (log_rdy || ~log_en) && (drop || (~inj_vld && out_rdy)); + + //If the injector has valid data, it takes precedence + //The other condition means that the master is trying to send something to + //the slave, and that the logger is not backpressuring the master + assign out_vld = inj_vld || (in_vld && ~drop && ~pause && (~log_en || log_rdy)); + + //Note: this next line is written this way to let Vivado synthesize the logic + //in simple LUTs. However, it is better understood as + // + //assign log_vld = log_en && in_vld && s_rdy; + // + //Also note that log_vld depends on log_rdy. Technically, this violates the + //AXI Stream specification. In order for this to work, you need to connect + //the log AXI Stream channel to a core which does not have a combinational + //path from ready to valid (a register slice, for example) + assign log_vld = log_en && ~pause && in_vld && (drop || (~inj_vld && out_rdy)); + + //At least this is simple... + assign inj_rdy = out_rdy; + +endmodule diff --git a/middleware/hls/dbg_guv/axis_governor/axis_governor_drivers.mem b/middleware/hls/dbg_guv/axis_governor/axis_governor_drivers.mem new file mode 100644 index 00000000..f4f08092 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/axis_governor_drivers.mem @@ -0,0 +1 @@ +Nothing to see here... diff --git a/middleware/hls/dbg_guv/axis_governor/axis_governor_tb.v b/middleware/hls/dbg_guv/axis_governor/axis_governor_tb.v new file mode 100644 index 00000000..8aa65ea3 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/axis_governor_tb.v @@ -0,0 +1,153 @@ +`timescale 1ns / 1ps + +/* +testbench_template.v + +Replace innards with desired logic +*/ + +`include "axis_governor.v" + +module testbench_template # ( + parameter DATA_WIDTH = 8, + parameter DEST_WIDTH = 16, + parameter ID_WIDTH = 16 +); + reg clk = 0; + //Input AXI Stream. + reg [DATA_WIDTH-1:0] in_TDATA = 1; + reg in_TVALID = 0; + wire in_TREADY; + reg [DATA_WIDTH/8 -1:0] in_TKEEP = 0; + reg [DEST_WIDTH -1:0] in_TDEST = 0; + reg [ID_WIDTH -1:0] in_TID = 0; + reg in_TLAST = 0; + + //Inject AXI Stream. + reg [DATA_WIDTH-1:0] inj_TDATA = 0; + reg inj_TVALID = 0; + wire inj_TREADY; + reg [DATA_WIDTH/8 -1:0] inj_TKEEP = 0; + reg [DEST_WIDTH -1:0] inj_TDEST = 0; + reg [ID_WIDTH -1:0] inj_TID = 0; + reg inj_TLAST = 0; + + //Output AXI Stream. + wire [DATA_WIDTH-1:0] out_TDATA; + wire out_TVALID; + reg out_TREADY = 0; + wire [DATA_WIDTH/8 -1:0] out_TKEEP; + wire [DEST_WIDTH -1:0] out_TDEST; + wire [ID_WIDTH -1:0] out_TID; + wire out_TLAST; + + //Log AXI Stream. + wire [DATA_WIDTH-1:0] log_TDATA; + wire log_TVALID; + reg log_TREADY = 0; + wire [DATA_WIDTH/8 -1:0] log_TKEEP; + wire [DEST_WIDTH -1:0] log_TDEST; + wire [ID_WIDTH -1:0] log_TID; + wire log_TLAST; + + //Control signals + wire pause; + wire drop; + wire log; + + //Simulation variables + wire inject; + reg [3:0] state = 0; + + assign pause = state[0]; + assign drop = state[1]; + assign log = state[2]; + assign inject = state[3]; + + integer fd, dummy; + + initial begin + $dumpfile("axis_governor.vcd"); + $dumpvars; + $dumplimit(512000); + + clk <= 0; + + repeat(16) begin + repeat(32) @(negedge clk); + state <= state + 1; + end + + #10 + $finish; + end + + always #5 clk <= ~clk; + + always @(posedge clk) begin + #0.01 //Not sure why Vivado doesn't need this... + in_TVALID <= $random; + inj_TVALID <= $random & inject; + out_TREADY <= $random; + log_TREADY <= $random; + + if (in_TREADY && in_TVALID) begin + in_TDATA <= in_TDATA + 2; + end + + if (inj_TREADY && inj_TVALID) begin + inj_TDATA <= inj_TDATA + 2; + end + end + +axis_governor #( + .DATA_WIDTH(DATA_WIDTH), + .DEST_WIDTH(DEST_WIDTH), + .ID_WIDTH(ID_WIDTH) +) DUT ( + .clk(clk), + + //Input AXI Stream. + .in_TDATA(in_TDATA), + .in_TVALID(in_TVALID), + .in_TREADY(in_TREADY), + .in_TKEEP(in_TKEEP), + .in_TDEST(in_TDEST), + .in_TID(in_TID), + .in_TLAST(in_TLAST), + + //Inject AXI Stream. + .inj_TDATA(inj_TDATA), + .inj_TVALID(inj_TVALID), + .inj_TREADY(inj_TREADY), + .inj_TKEEP(inj_TKEEP), + .inj_TDEST(inj_TDEST), + .inj_TID(inj_TID), + .inj_TLAST(inj_TLAST), + + //Output AXI Stream. + .out_TDATA(out_TDATA), + .out_TVALID(out_TVALID), + .out_TREADY(out_TREADY), + .out_TKEEP(out_TKEEP), + .out_TDEST(out_TDEST), + .out_TID(out_TID), + .out_TLAST(out_TLAST), + + //Log AXI Stream. + .log_TDATA(log_TDATA), + .log_TVALID(log_TVALID), + .log_TREADY(log_TREADY), + .log_TKEEP(log_TKEEP), + .log_TDEST(log_TDEST), + .log_TID(log_TID), + .log_TLAST(log_TLAST), + + //Control signals + .pause(pause), + .drop(drop), + .log_en(log) +); + + +endmodule diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/arbitration.txt b/middleware/hls/dbg_guv/axis_governor/design_docs/arbitration.txt new file mode 100644 index 00000000..b79a58a7 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/design_docs/arbitration.txt @@ -0,0 +1,376 @@ +=================================================== +HOW DOES THE ARBITRATION WORK IN THE AXIS GOVERNOR? +=================================================== + +Suppose I have an AXI Stream between two points: + + +-----------+ +-----------+ + | vld|------------------------------------>|vld | + | | | | + | Alex | | Brittany | + | | | | + | rdy|<------------------------------------|rdy | + +-----------+ +-----------+ + + +(Note: "Alice and Bob" have been used so often that I have named my two parties +differently) + +We will consider how we can add some logic to allow: + - Pausing transmission + - Forcing flits to be dropped + - Injecting flits + - Logging flits + +Our technique will be to do each of features separately, then to combine them. +After that, I will present the technique I used to verify that we (probably) +didn't miss any corner cases. + +======= +PAUSING +======= + +We will add an input signal which stops transmission if it is high, and has no +effect if it is low: + + pause + + + | __ + +-----------+ +-o| \ +-----------+ + | vld|----------------|--|__/------------->|vld | + | | | | | + | Alex | __ | | Brittany | + | | / |o-+ | | + | rdy|<---------\__|-----------------------|rdy | + +-----------+ +-----------+ + +(These are AND gates) + +If pause is low, Brittany.vld = Alex.vld and Alex.rdy = Brittany.rdy. However, +if pause is high, Alex.rdy is low and Brittany.vld is low, meaning that neither +will ever see a flit go by. + + +======== +DROPPING +======== + +Now we will add an input which causes Alex to think Brittany is always ready, +but Brittany thinks Alex is never valid (in other words, flits from Alex will +be dropped): + + + drop + + + | __ + +-----------+ +-o| \ +-----------+ + | vld|----------------|--|__/------------->|vld | + | | | | | + | Alex | ___ | | Brittany | + | | / /-+ | | + | rdy|<----------\__\----------------------|rdy | + +-----------+ +-----------+ + +(The top gate is an AND and the bottom is an OR) + + +========= +INJECTING +========= + +This is a little more complicated. We'll add an injection input (as an AXI +Stream master). If there is an injected flit we need to backpressure Alex, but +we still want transmissions to continue otherwise: + + ___ + +-----------+ +-\ \ +-----------+ + | vld|---------------|-/__/--------------->|vld | + | | | | | + | Alex | __ | | Brittany | + | | / |o-+ | | + | rdy|<--------\__|--|------+--------------|rdy | + +-----------+ | | +-----------+ + | | + | v + +------------+ + | vld rdy | + | | + | Inject | + | | + +------------+ + +Suppose Inject.vld is low; then (Brittany.vld = Alex.vld) and (Alex.rdy = +Brittany.rdy). This means that transmissions can continue if we're not +injecting anything. + +Suppose Inject.vld is high; then Alex.rdy is low (thus causing backpressure) +and Brittany.vld is high (which is correct, since Inject.vld is high and is the +source of flits for Brittany). + + +======= +LOGGING +======= + +NAÏVE METHOD +------------ + +This is the most complicated task. We will add a log output (as an AXI Stream +slave). Alex must be backpressured if either Brittany or the logging output is +not ready: + + +-----------+ +-----------+ + | vld|---------------+-------------------->|vld | + | | | | | + | Alex | | __ | Brittany | + | | | / |-+ | | + | rdy|<----------------\__|-|--------------|rdy | + +-----------+ | | +-----------+ + | | + v | + +------------+ + | vld rdy | + | | + | Log | + | | + +------------+ + +However, we run into trouble if we do this: if Alex is valid, then the logger +or Brittany will record a flit whenever they are ready. However, they must both +be ready before Alex will consider a flit to have been read. + +In other words, flits are copied if exactly one of Brittany or the logging +output is ready! + + +ELIMINATING INCORRECT FLIT COPIES +--------------------------------- + +To fix this, we should make sure that Brittany.vld and Log.vld are only high +when a flit is read from master: + + + __ + +-----------+ +-| \ +-----------+ + | vld|---|-|__/------+-------------------->|vld | + | | | | | | + | Alex | | | __ | Brittany | + | | | | / |-+ | | + | rdy|<--+-------------\__|-|--------------|rdy | + +-----------+ | | +-----------+ + | | + v | + +------------+ + | vld rdy | + | | + | Log | + | | + +------------+ + +This does techninically solve the problem, but we have now violated the AXI +Stream specification! In the spec, it is legal for a slave to have a +combinational path from its "valid" input to its "ready" output. By letting +Brittany.vld = (Alex.vld && Alex.rdy) = (Alex.vld && Brittany.rdy && Log.rdy) +we might make a combinational loop involving Brittany.vld and Brittany.rdy! + +And by the way, this same problem also exists for the Log output. + + +FIXING COMBINATIONAL LOOPS +-------------------------- + +The idea is this: Log is only valid if Alex is valid and Brittany is ready. +Likewise, Brittany is only valid if Alex is valid and the Log is ready. It's +actually easier to understand this reasoning if we had, say, five slaves. The +master should never send unless all five are ready, but each slave should never +receive unless the master is valid AND all the other slaves are ready. + +The final circuit is as follows: + __ + +-----------+ +-| \ +-----------+ + | vld|--------------+-------|-|__/-------->|vld | + | | | /--+ | | | + | Alex | --- | | | Brittany | + | | __ \_/ | | | | + | rdy|<------/ |----|---+--|--------------|rdy | + +-----------+ \__|----|------+ +-----------+ + | | + v | + +------------+ +------------------+ + | vld rdy | | a b | + | | | | | | + | Log | | --- | + | | | \_/ f = a AND b| + +------------+ | | | + | f | + +------------------+ + +This is much more subtle. If Log.rdy is low, then Alex.rdy is low and +Brittany.vld is low; this backpressures Alex and makes sure Brittany doesn't +read anything. + +If Log.rdy is high, then Alex.rdy = Brittany.rdy and Brittany.vld = Alex.vld, +which guarantees correct transmissions from Alex to Brittany. Furthermore, +Log.vld = (Alex.vld && Alex.rdy), which ensures that the logger only logs flits +that Alex transmitted (this is similar to a snooping interface). + +If you're like me, at this point you're thinking "well, I guess that makes +sense, but how do I know I didn't miss anything?". As far as I know, the only +way to be sure is to do a truth table and inspect every single circumstance +manually. See the section at the end of this document. + + +=================== +PUTTING IT TOGETHER +=================== + +This section presents how I chose to combine all these techniques. There are +actually several options, which could all be considered correct, though they +have different "precedence"; for example, if "drop" has higher precedence than +"pause", it means that flits from Alex will be dropped if both these signals +are asserted. On the other hand, if "pause" has higher precence, Alex will be +forcibly backpressured when both signals are asserted. + +Here is a high-level view of the AXI Stream governor: + + pause log drop + | | | + v v v + +-----------+ +---+ +-------------+ +---+ +-------------+ +-----------+ + | vld|-| P |-| |-| D |-| |-|vld | + | | | A | | Logging | | R | | Injection | | | + | Alex | | U | | arbitration | | O | | arbitration | | Brittany | + | | | S | | logic | | P | | logic | | | + | rdy|-| E |-| |-| |-| |-|rdy | + +-----------+ +---+ +-------------+ +---+ +-------------+ +-----------+ + | | | | + | | | | + +------------+ +------------+ + | vld rdy | | vld rdy | + | | | | + | Log | | Inject | + | | | | + +------------+ +------------+ + +The trick is to notice that each individual technique still looks like an AXI +Stream after it has been applied, so we can literally chain them together. My +choice was to let pause have highest precedence, and that injections should +still be honoured even when drop is high. Furthermore, logs can still occur +even if drop is high (and in fact, if drop is high, logs are not stopped even +if Brittany.rdy is low). + +The final circuit diagram is obtained by substituting in the individual +circuits. For completeness, I have drawn it up; please see governor.png in this +folder. It was generated by the excellent Logisim program using governor.circ. + +Finally, I went in by hand and wrote down the logic expressions for each +output, and I also simplified them slightly. Here are my results (copied +straight out of my Verilog implementation): + + //(~inj_vld && out_rdy) means the slave is ready to receive a flit from the + //master. OR'ing this with drop means the slave (and injector) cannot + //backpressure the master if drop is high. + // + //(log_rdy || ~log_en) means that the logger is not backpressuring the + //master + // + //Finally, in_rd is forced low if we are paused + assign in_rdy = ~pause && (log_rdy || ~log_en) && (drop || (~inj_vld && out_rdy)); + + //If the injector has valid data, it takes precedence + //The other condition means that the master is trying to send something to + //the slave, and that the logger is not backpressuring the master + assign out_vld = inj_vld || (in_vld && ~drop && ~pause && (~log_en || log_rdy)); + + //Note: this next line is written this way to let Vivado synthesize the logic + //in simple LUTs. However, it is better understood as + // + //assign log_vld = log_en && in_vld && m_rdy; + // + //Also note that log_vld depends on log_rdy. Technically, this violates the + //AXI Stream specification. In order for this to work, you need to connect + //the log AXI Stream channel to a core which does not have a combinational + //path from ready to valid (a register slice, for example) + assign log_vld = log_en && ~pause && in_vld && (drop || (~inj_vld && out_rdy)); + + //At least this is simple... + assign inj_rdy = out_rdy; + +The next section talks about how I verified this circuit (informally) and that +my logic expressions correctly matched my diagram. + +=========================== +HOW DO WE KNOW IF IT WORKS? +=========================== + +Once I drew up the circuit in Logisim, I asked Logisim to produce a truth table +for the circuit (Project -> Analyze Circuit). I then pasted this table directly +into a spreadsheet. + +Then, I came up with a list of errors. If any of these errors occur on any line +of the truth table, the circuit would have to be reviewed: + +-> badeat: Alex thinks a flit was consumed, but the governor was paused + +-> missdrop: Governor was dropping, but Brittany received a flit (and nothing + was injected) + +-> wrongdrop: Alex thinks a flit was consumed, but Brittany did not receive + anything, and the governor was not dropping + +-> badlog: The Logger read something, but (logging was off OR Alex did not send + anything) + +-> logcopy (special case of badlog): The Logger read something, but Alex did + not send anything. + +-> slvcopy: Brittany read something, but neither Alex nor the injector sent + anything + +-> misslog: Alex sent something, logging was on, but the logger did not read + anything + +-> missout: Brittany did not read anything, but ((Alex sent something AND we + weren't paused AND we're not dropping) OR (the injector sent something)) + +-> missinject (special case of missout): the injector send something but + Brittany didn't read anything + +-> injectclobber: Alex sent something, we're not paused, we're not dropping, + AND the injector sent something. + +-> badpause: Alex sent something, but we're paused + +-> wronginject: This turned out to be identical to missinject. I've just left + the description in here to better explain the attached spreadsheets + +I have no formal proof that these are all the errors that could happen, but I +can't think of any more, so that's good enough for me. + +By the way, the size of the truth table outputs is 128 by 4, or 512 cells. In +theory, for me to formally prove correctness, I would have to come up with a +formal specification for each of the output cells (either 1, 0, or Don't Care). +I decided not to do that. Who knows, maybe the above method is a more efficient +way of doing just that? + +EXPLANATION OF THE ATTACHED FILES AND SPREADSHEETS +-------------------------------------------------- + +First of all, notice that there is a gov.circ, gov2.circ, and gov3.circ in +early_attempts/. These were the attempts I did by just trying to draw the +circuit without really thinking about it in a structured way. In other words, +this whole discussion about combining the pause, drop, log, and inject +primitives (taking care of precedence) didn't occur to me until I had tried to +make the circuit twice and found errors. + +The first three sheets in the spreadsheet show the truth tables for my first +three attempts. The fourth one shows the final circuit that passed all my tests. + +The fifth spreadsheet was how I confirmed to myself that I had correctly +transcribed the logical formulas from the diagram. The truth table is the same +one from the correct circuit. + +In all the spreadsheets, immediately to the right of the (nicely coloured) +truth table, are little helper signals for each of the four AXI Streams. They +are simply the AND of valid and ready, indicating (perceived) flit +transmission. diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov.circ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov.circ new file mode 100644 index 00000000..5ed9edb7 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov.circ @@ -0,0 +1,222 @@ + + +This file is intended to be loaded by Logisim (http://www.cburch.com/logisim/). + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov2.circ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov2.circ new file mode 100644 index 00000000..677f386b --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov2.circ @@ -0,0 +1,219 @@ + + +This file is intended to be loaded by Logisim (http://www.cburch.com/logisim/). + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov3.circ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov3.circ new file mode 100644 index 00000000..58f43443 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/design_docs/early_attempts/gov3.circ @@ -0,0 +1,263 @@ + + +This file is intended to be loaded by Logisim (http://www.cburch.com/logisim/). + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/gov4.circ b/middleware/hls/dbg_guv/axis_governor/design_docs/gov4.circ new file mode 100644 index 00000000..0646a0b8 --- /dev/null +++ b/middleware/hls/dbg_guv/axis_governor/design_docs/gov4.circ @@ -0,0 +1,261 @@ + + +This file is intended to be loaded by Logisim (http://www.cburch.com/logisim/). + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/middleware/hls/dbg_guv/axis_governor/design_docs/governor.png b/middleware/hls/dbg_guv/axis_governor/design_docs/governor.png new file mode 100644 index 0000000000000000000000000000000000000000..4b513a6c7f747326613ea4d3473f96061738cea5 GIT binary patch literal 16319 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 0} { + set node [create_bd_cell -vlnv Marco_Merlini:fpga_bpf:rr4 -name node_$nnodes] + lappend nodes $node + lappend slvs [get_bd_intf_pins $node/s0] + lappend slvs [get_bd_intf_pins $node/s1] + lappend slvs [get_bd_intf_pins $node/s2] + lappend slvs [get_bd_intf_pins $node/s3] + incr nnodes -1 + if {$nnodes > 0} { + lappend msts [get_bd_intf_pins $node/o ] + } + } + + while {[llength $msts] > 0} { + connect_bd_intf_net [lindex $msts 0] [lindex $slvs 0] + set msts [lreplace $msts 0 0] + set slvs [lreplace $slvs 0 0] + } + + set h [create_bd_cell -type hier -name rr_tree] + + move_bd_cells $h $nodes + + create_bd_intf_pin -mode Master -vlnv xilinx.com:interface:axis_rtl:1.0 rr_tree/o + connect_bd_intf_net [get_bd_intf_pins rr_tree/o] [get_bd_intf_pins rr_tree/node_1/o] + + endgroup +} From 84782d10dbb00c40d7ad071d17ead5c48de842ef Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Sat, 29 Feb 2020 15:13:06 -0500 Subject: [PATCH 14/16] Half done automating dbg_guv insertion. TCL in block diagrams is such a piece of garbage! --- examples/hello_world/Makefile | 7 ++-- middleware/python/tclFileGenerator.py | 23 ++++++---- middleware/python/tclMe.py | 42 ++++++++++++++++++- .../add_dbg_cores.tcl} | 21 +++++++--- 4 files changed, 76 insertions(+), 17 deletions(-) rename middleware/{hls/dbg_guv/addcore.tcl => tclScripts/add_dbg_cores.tcl} (91%) diff --git a/examples/hello_world/Makefile b/examples/hello_world/Makefile index a37a39db..340e1f80 100644 --- a/examples/hello_world/Makefile +++ b/examples/hello_world/Makefile @@ -10,11 +10,12 @@ world: .world vivado_hls world.tcl touch .world +export LOGICALFILE=$(shell realpath logical.xml) +export MAPFILE=$(shell realpath mapping.xml) +export PROJECTNAME=hello_world + middleware: .middleware .middleware: logical.xml mapping.xml .hello_world .world - export LOGICALFILE=$(shell realpath logical.xml) - export MAPFILE=$(shell realpath mapping.xml) - export PROJECTNAME=hello_world make -C$(GALAPAGOS_PATH) middleware touch .middleware diff --git a/middleware/python/tclFileGenerator.py b/middleware/python/tclFileGenerator.py index 21d3dba1..54e13a9a 100644 --- a/middleware/python/tclFileGenerator.py +++ b/middleware/python/tclFileGenerator.py @@ -868,6 +868,8 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): Now that the kernels, Galapagos router, and memory controllers are instantiated, it's time to connect them all together. + MM Feb 29/2020: All AXI Stream wires to kernels are now highlighted + Args: tcl_user_app: a tclMe object (which contains references to the FPGA's node object and a handle to the output file) @@ -909,7 +911,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): # Connect it to the correct port on the AXI switch (NOT directly into # the Galapagos router; there is an AXI stream switch IP between # the router and the kernel(s) ) - tcl_user_app.makeConnection( + tcl_user_app.makeHighlightedConnection( 'intf', { 'name':'applicationRegion/input_switch', @@ -938,10 +940,11 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): 'port_name':'S01_AXIS' } ) - + + # Special case where there is only one kernel slave elif len(s_axis_array) == 1: if (sim == 1): - tcl_user_app.makeConnection( + tcl_user_app.makeHighlightedConnection( 'intf', { 'name':'applicationRegion/arbiter', @@ -970,7 +973,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): # there's no input switch in this case if tcl_user_app.fpga['comm'] not in ['raw', 'none']: if 'custom' not in tcl_user_app.fpga or tcl_user_app.fpga['custom'] != 'GAScore': - tcl_user_app.makeConnection( + tcl_user_app.makeHighlightedConnection( 'intf', { 'name':'applicationRegion/input_switch', @@ -1006,7 +1009,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): if tcl_user_app.fpga['comm'] not in ['raw', 'none']: instName = m_axis_array[0]['kernel_inst']['inst'] if 'custom' not in tcl_user_app.fpga or tcl_user_app.fpga['custom'] != 'GAScore': - tcl_user_app.makeConnection( + tcl_user_app.makeHighlightedConnection( 'intf', { 'name': instName, @@ -1023,7 +1026,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): for idx, m_axis in enumerate(m_axis_array): instName = m_axis['kernel_inst']['inst'] idx_str = "%02d"%idx - tcl_user_app.makeConnection( + tcl_user_app.makeHighlightedConnection( 'intf', { 'name': instName , @@ -1540,7 +1543,11 @@ def userApplicationRegion(outDir, fpga, sim): userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim) userApplicationRegionAssignAddresses(tcl_user_app, tcl_user_app.fpga['comm'] !='tcp' and tcl_user_app.fpga.address_space == 64) userApplicationLocalConnections(tcl_user_app) - + + galapagos_path = str(os.environ.get('GALAPAGOS_PATH')) + tcl_user_app.addSource(galapagos_path + '/middleware/tclScripts/add_dbg_cores.tcl') + tcl_user_app.tprint_raw('add_dbg_core_to_highlighted 0') + tcl_user_app.close() #return num_debug_interfaces @@ -2029,7 +2036,7 @@ def makeTCLFiles(fpga, projectName, output_path, sim): if 'custom' in fpga: tclMain.addSource(outDir + '/' + str(fpga['num']) + '_custom.tcl') tclMain.addSource(galapagos_path + '/middleware/tclScripts/custom/' + fpga['custom'] + '.tcl') - + tclMain.tprint('validate_bd_design') tclMain.tprint('save_bd_design') tclMain.close() diff --git a/middleware/python/tclMe.py b/middleware/python/tclMe.py index 4479c4eb..379ab0b7 100644 --- a/middleware/python/tclMe.py +++ b/middleware/python/tclMe.py @@ -142,6 +142,46 @@ def makeConnection(self, conn_type, source, sink): elif sink['type'] == 'pin': self.tprint_raw('get_bd_pins ', end = '') self.tprint_raw(sink['name'] + '/' + sink['port_name'] + ']') - + + # MM Feb 29 / 2020: Copy-pasted makeConnection and added a few lines to + # add highlighting. This was the easiest way to run a proc later on that + # adds debug cores + def makeHighlightedConnection(self, conn_type, source, sink): + if conn_type == 'net': + self.tprint('set g [connect_bd_net [', end = '') + elif conn_type == 'intf': + self.tprint('set g [connect_bd_intf_net [', end = '') + + if source['type'] == 'port': + self.tprint_raw('get_bd_ports ', end = '') + self.tprint_raw(source['port_name'] + '] [', end = '') + elif source['type'] == 'intf_port': + self.tprint_raw('get_bd_intf_ports ', end = '') + self.tprint_raw(source['port_name'] + '] [', end = '') + elif source['type'] == 'intf': + self.tprint_raw('get_bd_intf_pins ', end = '') + self.tprint_raw(source['name'] + '/' + source['port_name'] + '] [', end = '') + elif source['type'] == 'pin': + self.tprint_raw('get_bd_pins ', end = '') + self.tprint_raw(source['name'] + '/' + source['port_name'] + '] [', end = '') + + + + if sink['type'] == 'port': + self.tprint_raw('get_bd_ports ', end='') + self.tprint_raw(sink['port_name'] + ']') + elif sink['type'] == 'intf_port': + self.tprint_raw('get_bd_intf_ports ', end='') + self.tprint_raw(sink['port_name'] + ']') + elif sink['type'] == 'intf': + self.tprint_raw('get_bd_intf_pins ', end='') + self.tprint_raw(sink['name'] + '/' + sink['port_name'] + ']') + elif sink['type'] == 'pin': + self.tprint_raw('get_bd_pins ', end = '') + self.tprint_raw(sink['name'] + '/' + sink['port_name'] + ']') + + self.tprint_raw(']') + self.tprint_raw('highlight_objects -color_index 3 $g') + def close(self): self.fileHandle.close() diff --git a/middleware/hls/dbg_guv/addcore.tcl b/middleware/tclScripts/add_dbg_cores.tcl similarity index 91% rename from middleware/hls/dbg_guv/addcore.tcl rename to middleware/tclScripts/add_dbg_cores.tcl index 42a3a181..58afa2c9 100644 --- a/middleware/hls/dbg_guv/addcore.tcl +++ b/middleware/tclScripts/add_dbg_cores.tcl @@ -6,6 +6,17 @@ proc add_dbg_core_to_net {n inst} { set pins [get_bd_intf_pins -of_objects $n -quiet] set ports [get_bd_intf_ports -of_objects $n -quiet] + # For some RIDICULOUS reason, a hierarchy port is both a port and a pin! + # So we do a little band-aid fix here and remove any element from pins + # that also appears in ports + foreach p $ports { + # see https://stackoverflow.com/questions/5701947/tcl-remove-an-element-from-a-list + set idx [lsearch $pins $p] + if {$idx != -1} { + set pins [lreplace $pins $idx $idx] + } + } + set npins [llength $pins] set nports [llength $ports] @@ -58,15 +69,15 @@ proc add_dbg_core_to_net {n inst} { delete_bd_objs $n # Instantiate the dbg_guv - set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv $inst] + set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv applicationRegion/$inst] # Connect the dbg_guv to the loose endpoints if ![string compare [get_property MODE $left] Master] { - connect_bd_intf_net -intf_net GUV_${inst}_mst $g/out $right - connect_bd_intf_net -intf_net GUV_${inst}_slv $left $g/in + connect_bd_intf_net $g/out $right + connect_bd_intf_net $left $g/in } else { - connect_bd_intf_net -intf_net GUV_${inst}_mst $g/out $left - connect_bd_intf_net -intf_net GUV_${inst}_slv $right $g/in + connect_bd_intf_net $g/out $left + connect_bd_intf_net $right $g/in } return $g From e2844f87171f691789c8c7769cddcb1f3afd5221 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Tue, 3 Mar 2020 12:03:49 -0500 Subject: [PATCH 15/16] Almost done adding automatic debug cores. Still need to handle some corner cases when user defines custom packet format --- middleware/python/cluster.py | 57 ++++++++++++++++-- middleware/python/tclFileGenerator.py | 31 ++++++---- middleware/python/tclMe.py | 40 ++++++------- middleware/tclScripts/add_dbg_cores.tcl | 58 ++++++++++++++++--- .../tclScripts/replace_dbg_placeholder.tcl | 50 ++++++++++++++++ 5 files changed, 192 insertions(+), 44 deletions(-) create mode 100644 middleware/tclScripts/replace_dbg_placeholder.tcl diff --git a/middleware/python/cluster.py b/middleware/python/cluster.py index c3215cf3..9fa67351 100644 --- a/middleware/python/cluster.py +++ b/middleware/python/cluster.py @@ -8,6 +8,8 @@ import os import socket, struct import glob +# MM Mar 3 / 2020 Needed OrderedDict for compatibility with xmltodict output +from collections import OrderedDict class cluster(abstractDict): """ This class is the top-level interface to the myriad other objects used @@ -121,6 +123,11 @@ def __init__(self, name, kernel_file, map_file, mode='file'): if 'rep' in kern_dict: # Automatically gives sequential numbers base_num = int(kern_dict['num']) + # MM Mar 3 / 2020 Added error message if user tries to use + # number 0, which is reserved for the debug logic + if (base_num == 0): + raise Exception("ERROR: Kernel number 0 is reserved for debug logic. Please renumber your kernels\n\n") + for i in range(0, int(kern_dict['rep'])): kern_dict_local = copy.deepcopy(kern_dict) # Set number for this kernel instance @@ -184,15 +191,54 @@ def __init__(self, name, kernel_file, map_file, mode='file'): else: kern_dict_local['rep'] = 1 kern_dict_local['num'] = int(kern_dict_local['num']) + # MM Mar 3 / 2020 Added error message if user tries to use + # number 0, which is reserved for the debug logic + if (kern_dict_local['num'] == 0): + raise Exception("ERROR: Kernel number 0 is reserved for debug logic. Please renumber your kernels\n\n") + self.kernels.append(kernel(**kern_dict_local)) - + + # At this point, self.kernels contains a list of dicts with properly + # converted info about each kernel + ## Dumps kernel info to the screen (printf debugging) - #for kern in self.kernels: - # print("kernel object " + str(kern.data)) - + # print("KERNEL INFO") + # for kern in self.kernels: + # print("kernel object " + str(kern.data)) + + # MM Mar 3 / 2020 Add a placeholder kernel into each node. This causes + # the other Galapagos code to make the switches large enough and to + # route all debug traffic to the right place. Later, we will remove the + # the placeholder and replace it with the correct debug interfaces + + placeholder_dbg_kernel = { + 'name': 'dbg_guv', + 'num': 0, + '#text': 'dbg_guv', + 'rep': 1, + 'clk': ['clk'], + 'aresetn': [], # I hope that works... + 'vendor': 'mmerlini', + 'lib': 'yov', + 'version': '1.0', + 'id_port': None, + 'm_axi': None, + 's_axi': None, + 's_axis': [OrderedDict([('scope', 'global'), ('name', 'cmd_in')])], + 'm_axis': [OrderedDict([('scope', 'global'), ('name', 'log_catted')])], + 'wire_master': None, + 'wire_slave': None, + 'ip': None, # TODO: Figure out what this should be + 'mac': None, # ditto + 'const': None, + 'properties': None, + 'board': None + } + # Now deal with nodes (i.e. a CPU or an FPGA) self.nodes = [] for node_idx, node_dict in enumerate(map_dict): + # This basically copies the dictionary parsed from the tags into another dictionary, # but it does also check the fields to make sure they're all valid and that no mandatory info # is missing @@ -202,6 +248,9 @@ def __init__(self, name, kernel_file, map_file, mode='file'): # data formats node_inst['kernel'] = [] + # MM Mar 3 / 2020 Always put a placeholoder dbg_guv in each node + node_inst['kernel'].append(placeholder_dbg_kernel) + # for kmap_node in node_dict['kernel']: for kern_idx, kern in enumerate(self.kernels): diff --git a/middleware/python/tclFileGenerator.py b/middleware/python/tclFileGenerator.py index 54e13a9a..3fc7a477 100644 --- a/middleware/python/tclFileGenerator.py +++ b/middleware/python/tclFileGenerator.py @@ -911,7 +911,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): # Connect it to the correct port on the AXI switch (NOT directly into # the Galapagos router; there is an AXI stream switch IP between # the router and the kernel(s) ) - tcl_user_app.makeHighlightedConnection( + tcl_user_app.makeMarkedConnection( 'intf', { 'name':'applicationRegion/input_switch', @@ -944,7 +944,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): # Special case where there is only one kernel slave elif len(s_axis_array) == 1: if (sim == 1): - tcl_user_app.makeHighlightedConnection( + tcl_user_app.makeMarkedConnection( 'intf', { 'name':'applicationRegion/arbiter', @@ -973,7 +973,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): # there's no input switch in this case if tcl_user_app.fpga['comm'] not in ['raw', 'none']: if 'custom' not in tcl_user_app.fpga or tcl_user_app.fpga['custom'] != 'GAScore': - tcl_user_app.makeHighlightedConnection( + tcl_user_app.makeMarkedConnection( 'intf', { 'name':'applicationRegion/input_switch', @@ -1009,7 +1009,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): if tcl_user_app.fpga['comm'] not in ['raw', 'none']: instName = m_axis_array[0]['kernel_inst']['inst'] if 'custom' not in tcl_user_app.fpga or tcl_user_app.fpga['custom'] != 'GAScore': - tcl_user_app.makeHighlightedConnection( + tcl_user_app.makeMarkedConnection( 'intf', { 'name': instName, @@ -1026,7 +1026,7 @@ def userApplicationRegionKernelConnectSwitches(outDir, tcl_user_app, sim): for idx, m_axis in enumerate(m_axis_array): instName = m_axis['kernel_inst']['inst'] idx_str = "%02d"%idx - tcl_user_app.makeHighlightedConnection( + tcl_user_app.makeMarkedConnection( 'intf', { 'name': instName , @@ -1544,10 +1544,6 @@ def userApplicationRegion(outDir, fpga, sim): userApplicationRegionAssignAddresses(tcl_user_app, tcl_user_app.fpga['comm'] !='tcp' and tcl_user_app.fpga.address_space == 64) userApplicationLocalConnections(tcl_user_app) - galapagos_path = str(os.environ.get('GALAPAGOS_PATH')) - tcl_user_app.addSource(galapagos_path + '/middleware/tclScripts/add_dbg_cores.tcl') - tcl_user_app.tprint_raw('add_dbg_core_to_highlighted 0') - tcl_user_app.close() #return num_debug_interfaces @@ -1769,7 +1765,7 @@ def bridgeConnections(outDir, fpga, sim): else: instName = s_axis_array[0]['kernel_inst']['inst'] if tcl_bridge_connections.fpga['comm'] != 'none': - tcl_bridge_connections.makeConnection( + tcl_bridge_connections.makeMarkedConnection( 'intf', { 'name':'network/network_bridge_inst', @@ -1807,7 +1803,7 @@ def bridgeConnections(outDir, fpga, sim): else: instName = m_axis_array[0]['kernel_inst']['inst'] if tcl_bridge_connections.fpga['comm'] != 'none': - tcl_bridge_connections.makeConnection( + tcl_bridge_connections.makeMarkedConnection( 'intf', { 'name': instName, @@ -1987,6 +1983,19 @@ def bridgeConnections(outDir, fpga, sim): ) if tcl_bridge_connections.fpga['comm'] == 'none': tcl_custom.close() + + # MM Mar 2 / 2020 Added this call to one of my TCL scripts to wire up the + # custom debug cores + + galapagos_path = str(os.environ.get('GALAPAGOS_PATH')) + tcl_bridge_connections.addSource(galapagos_path + '/middleware/tclScripts/add_dbg_cores.tcl') + tcl_bridge_connections.tprint_raw('set g [add_dbg_core_to_list $marcos_list_of_dbg_nets 0]') + tcl_bridge_connections.tprint_raw('set first_cmd_in [lindex $g 0]') + tcl_bridge_connections.tprint_raw('set tree_out [lindex $g 1]') + + tcl_bridge_connections.addSource(galapagos_path + '/middleware/tclScripts/replace_dbg_placeholder.tcl') + tcl_bridge_connections.tprint_raw('replace_dbg_placeholder $first_cmd_in $tree_out') + tcl_bridge_connections.close() diff --git a/middleware/python/tclMe.py b/middleware/python/tclMe.py index 379ab0b7..58b3633f 100644 --- a/middleware/python/tclMe.py +++ b/middleware/python/tclMe.py @@ -143,29 +143,29 @@ def makeConnection(self, conn_type, source, sink): self.tprint_raw('get_bd_pins ', end = '') self.tprint_raw(sink['name'] + '/' + sink['port_name'] + ']') - # MM Feb 29 / 2020: Copy-pasted makeConnection and added a few lines to - # add highlighting. This was the easiest way to run a proc later on that - # adds debug cores - def makeHighlightedConnection(self, conn_type, source, sink): - if conn_type == 'net': - self.tprint('set g [connect_bd_net [', end = '') - elif conn_type == 'intf': - self.tprint('set g [connect_bd_intf_net [', end = '') - + # MM Mar 2 / 2020: Copy-pasted makeConnection and added a few lines to + # save nets into a list called "marcos_list_of_dbg_nets" + def makeMarkedConnection(self, conn_type, source, sink): + src_port = "" if source['type'] == 'port': - self.tprint_raw('get_bd_ports ', end = '') - self.tprint_raw(source['port_name'] + '] [', end = '') + src_port += 'get_bd_ports ' + src_port += source['port_name'] elif source['type'] == 'intf_port': - self.tprint_raw('get_bd_intf_ports ', end = '') - self.tprint_raw(source['port_name'] + '] [', end = '') + src_port += 'get_bd_intf_ports ' + src_port += source['port_name'] elif source['type'] == 'intf': - self.tprint_raw('get_bd_intf_pins ', end = '') - self.tprint_raw(source['name'] + '/' + source['port_name'] + '] [', end = '') + src_port += 'get_bd_intf_pins ' + src_port += source['name'] + '/' + source['port_name'] elif source['type'] == 'pin': - self.tprint_raw('get_bd_pins ', end = '') - self.tprint_raw(source['name'] + '/' + source['port_name'] + '] [', end = '') - + src_port += 'get_bd_pins ' + src_port += source['name'] + '/' + source['port_name'] + if conn_type == 'net': + self.tprint('connect_bd_net [', end='') + elif conn_type == 'intf': + self.tprint('connect_bd_intf_net [', end='') + + self.tprint_raw(src_port, end = '] [') if sink['type'] == 'port': self.tprint_raw('get_bd_ports ', end='') @@ -180,8 +180,8 @@ def makeHighlightedConnection(self, conn_type, source, sink): self.tprint_raw('get_bd_pins ', end = '') self.tprint_raw(sink['name'] + '/' + sink['port_name'] + ']') - self.tprint_raw(']') - self.tprint_raw('highlight_objects -color_index 3 $g') + self.tprint_raw('set g [get_bd_intf_nets -of_objects [' + src_port, end=']]\n') + self.tprint_raw('lappend marcos_list_of_dbg_nets $g') def close(self): self.fileHandle.close() diff --git a/middleware/tclScripts/add_dbg_cores.tcl b/middleware/tclScripts/add_dbg_cores.tcl index 58afa2c9..6f588506 100644 --- a/middleware/tclScripts/add_dbg_cores.tcl +++ b/middleware/tclScripts/add_dbg_cores.tcl @@ -2,6 +2,11 @@ # Replaces the bd_intf_net n with a dbg_guv core named "inst" proc add_dbg_core_to_net {n inst} { + puts "add_dbg_core_to_net args:" + puts "n = $n" + puts "inst = $inst" + puts "----" + # Get the two endpoints of this net set pins [get_bd_intf_pins -of_objects $n -quiet] set ports [get_bd_intf_ports -of_objects $n -quiet] @@ -69,7 +74,9 @@ proc add_dbg_core_to_net {n inst} { delete_bd_objs $n # Instantiate the dbg_guv - set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv applicationRegion/$inst] + # We do an UGLY hack to deal with Vivado's annoying rules about hierarchies + set prefix [lindex [regexp -inline {(.*)\/[^\/]*$} "[get_property PATH $n]"] 1] + set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv $prefix/$inst] # Connect the dbg_guv to the loose endpoints if ![string compare [get_property MODE $left] Master] { @@ -85,6 +92,8 @@ proc add_dbg_core_to_net {n inst} { # Searches current BD to get next ID to use for a dbg_guv proc get_next_dbg_core_id {} { + puts "get_next_dbg_core_id args:" + puts "----" set cells [get_bd_cells -hierarchical -filter {VLNV == mmerlini:yov:dbg_guv:1.0} -quiet] set next_id 0 foreach c $cells { @@ -101,8 +110,12 @@ proc get_next_dbg_core_id {} { # Given a list of AXI Stream masters (with only TDATA, TREADY, TVALID, and TLAST) # hooks up an automatically sized arbiter tree +# Returns a reference to the tree's final output # TODO?: Special case for n=1 proc rr_tree {msts} { + puts "rr_tree args:" + puts "msts = $msts" + puts "----" startgroup set nnodes [expr ([llength $msts]+1)/3] @@ -142,8 +155,9 @@ proc rr_tree {msts} { connect_bd_net [list [get_bd_pins /DBG_GUV_TREE/*/clk] [get_bd_pins /DBG_GUV_TREE/clk]] } - endgroup + + return [get_bd_intf_pins /DBG_GUV_TREE/o] } @@ -175,7 +189,7 @@ proc del_dbg_core {c} { proc del_all_dbg_cores {} { startgroup delete_bd_objs [get_bd_cells DBG_GUV_TREE -quiet] -quiet - set dbg_guvs [get_bd_cells -filter {VLNV =~ "*dbg_guv*"} -quiet] + set dbg_guvs [get_bd_cells -hierarchical -filter {VLNV =~ "*dbg_guv*"} -quiet] foreach d $dbg_guvs { set nets [get_bd_intf_nets -of_objects $d -quiet] delete_bd_objs [get_bd_intf_nets $d/log_catted -quiet] -quiet @@ -185,7 +199,20 @@ proc del_all_dbg_cores {} { endgroup } -proc add_dbg_core_to_highlighted {{safe_mode 1}} { +# Given a list of nets, instrument each one with a dbg_guv. This code handles a +# number of common issues, such as not adding dbg_guvs if there is already one +# there, checking if the net is actually an AXI Stream, and of course, the +# frustrating special cases of port vs. pin +# The safe_mode argument will, when set to 1, delete all existing debug infra +# before adding in new cores. I only made this a parameter so that I could +# disable it in Galapagos's automatic generator; you normally want this to be on +# Finally, this proc returns a list containing the first cmd_in in the dbg_guv +# daisy chain, and the last output of the arbiter tree +proc add_dbg_core_to_list {nets {safe_mode 1}} { + puts "add_dbg_core_to_list args:" + puts "nets = $nets" + puts "safe_mode = $safe_mode" + puts "----" startgroup if {$safe_mode} { @@ -198,9 +225,8 @@ proc add_dbg_core_to_highlighted {{safe_mode 1}} { # Stores the previous cmd_out set last_cmd {} - - # Get all highlihgted nets - set nets [get_bd_intf_nets [get_highlighted_objects]] + # Stores the first cmd_in in the daisy chain + set first_cmd_in {} foreach n $nets { # Choose an ID. get_next_dbg_core_id guarantees it will be unique, and @@ -210,6 +236,10 @@ proc add_dbg_core_to_highlighted {{safe_mode 1}} { # g holds a reference to the newly create dbg_guv cell set g [add_dbg_core_to_net $n GUV_$next_id] + if {$g == 0} { + # add_Dbg_core_to_net (correctly) did not add a dbg_guv + continue + } set_property CONFIG.ADDR $next_id $g @@ -219,6 +249,8 @@ proc add_dbg_core_to_highlighted {{safe_mode 1}} { # If last_cmd is not empty, connect its cmd_out to $g/cmd_in if {[llength $last_cmd] == 1} { connect_bd_intf_net [get_bd_intf_pins $last_cmd] [get_bd_intf_pins $g/cmd_in] + } else { + set first_cmd_in [get_bd_intf_pins $g/cmd_in] } # Update last_cmd @@ -226,13 +258,21 @@ proc add_dbg_core_to_highlighted {{safe_mode 1}} { } # Put in the arbiter tree - rr_tree $log_outs + set tree_out [rr_tree $log_outs] # Connect up the clocks # TODO: If I ever plan to allow multiple clock domains, this will have to # change - connect_bd_net [list [get_bd_pins -of_objects [get_bd_cells -hierarchical -filter {VLNV == mmerlini:yov:dbg_guv:1.0}] -filter {NAME == clk}] [get_bd_pins /DBG_GUV_TREE/clk]] + if {[llength $log_outs] > 1} { + connect_bd_net [list [get_bd_pins -of_objects [get_bd_cells -hierarchical -filter {VLNV == mmerlini:yov:dbg_guv:1.0}] -filter {NAME == clk}] [get_bd_pins /DBG_GUV_TREE/clk]] + } endgroup + + return [list $first_cmd_in $tree_out] +} + +proc add_dbg_core_to_highlighted {{safe_mode 1}} { + add_dbg_core_to_list [get_bd_intf_nets [get_highlighted_objects]] $safe_mode } proc del_highlighted_dbg_cores {} { diff --git a/middleware/tclScripts/replace_dbg_placeholder.tcl b/middleware/tclScripts/replace_dbg_placeholder.tcl new file mode 100644 index 00000000..b692a1e7 --- /dev/null +++ b/middleware/tclScripts/replace_dbg_placeholder.tcl @@ -0,0 +1,50 @@ +# This script is specific to Galapagos. + +# First of all, in the python middleware, I added a few lines that always put +# a placeholder dbg_guv IP in every node (with TDEST address 0). + +# Now that the placeholder is there and everything is wired up, it's time to +# replace it with what should go there. So this proc does three things: + +# 1. Save the port connected to placeholder/cmd_in as "in_port" and the port +# connected to placeholder/log_catted as "out_port" (see the code below) +# 2. Delete the placeholder core +# 3. Rewire in_port to connect to the golbal cmd_in of the dbg_guv daisy chain, +# and rewire out_port to connect to the output of the global dbg_guv log +# output + +proc replace_dbg_placeholder {first_cmd_in tree_out} { + puts "replace_dbg_placeholder args:" + puts "first_cmd_in = $first_cmd_in" + puts "tree_out = $tree_out" + puts "----" + # First, get a reference to the placeholder. This is really janky, but one + # consistent way to do is to assume that Galapagos named it "dbg_guv_inst_0" + set placeholder [get_bd_cells -hierarchical -filter {NAME == "dbg_guv_inst_0"}] + + # Get nets connected to the placeholder cell + set left [get_bd_intf_nets -of_objects [get_bd_intf_pins $placeholder/cmd_in]] + set right [get_bd_intf_nets -of_objects [get_bd_intf_pins $placeholder/log_catted]] + + # Now save references to the ports that were originally connected + set in_port [get_bd_intf_pins -of_objects $left -filter {PATH !~ *cmd_in}] + set out_port [get_bd_intf_pins -of_objects $right -filter {PATH !~ *log_catted}] + + # We can now safely delete the original placeholder + delete_bd_objs $placeholder + + # Rewire the input + connect_bd_intf_net [get_bd_intf_pins $first_cmd_in] $in_port + + # Rewire the output + # UGLY BAND-AID FIX: until I have a more robust method of handling generic + # choices for AXI Stream channel widths, I've hardcoded an axis_unconcat to + # bridge the gap here + set uncat [create_bd_cell -type ip -vlnv mmerlini:yov:axis_unconcat:1.0 axis_unconcat_0] + set_property -dict [list CONFIG.DATA_WIDTH {64} CONFIG.OUT_ENABLE_KEEP {true} CONFIG.IN_ENABLE_LAST {1} CONFIG.OUT_ENABLE_LAST {true}] $uncat + connect_bd_intf_net [get_bd_intf_pins /DBG_GUV_TREE/o] [get_bd_intf_pins $uncat/left] + connect_bd_net [get_bd_ports CLK] [get_bd_pins $uncat/clk] + connect_bd_intf_net [get_bd_intf_pins $uncat/right] [get_bd_intf_pins $out_port] + + # It's ugly, but it works! +} From c46f6e5f0a82c4a730c377348994d4afb9d62618 Mon Sep 17 00:00:00 2001 From: Marco Merlini Date: Tue, 3 Mar 2020 14:00:49 -0500 Subject: [PATCH 16/16] It's about time! It's not bulletproof, but automatic debug core addition should now work in most cases --- .gitignore | 1 + middleware/python/cluster.py | 3 + middleware/python/node.py | 5 ++ middleware/python/tclFileGenerator.py | 21 ++++++- middleware/tclScripts/add_dbg_cores.tcl | 62 +++++++++++++++---- .../tclScripts/replace_dbg_placeholder.tcl | 9 +-- 6 files changed, 81 insertions(+), 20 deletions(-) diff --git a/.gitignore b/.gitignore index fb50e188..d47b993f 100644 --- a/.gitignore +++ b/.gitignore @@ -8,3 +8,4 @@ vivado.log vivado.jou examples/hello_world/.* tags +*.pyc diff --git a/middleware/python/cluster.py b/middleware/python/cluster.py index 9fa67351..eae3586c 100644 --- a/middleware/python/cluster.py +++ b/middleware/python/cluster.py @@ -244,6 +244,9 @@ def __init__(self, name, kernel_file, map_file, mode='file'): # is missing node_inst = node(**node_dict) + # MM Mar 3 / 2020 Save a reference to the parent cluster in each node + node_inst.parent_cluster = self + # I'm fairly sure these next few lines of code are just converting # data formats node_inst['kernel'] = [] diff --git a/middleware/python/node.py b/middleware/python/node.py index 12671eec..27cb0c6b 100644 --- a/middleware/python/node.py +++ b/middleware/python/node.py @@ -13,6 +13,11 @@ class node(abstractDict): mandatory, but there are a whole bunch of optional ones too. """ + # MM Mar 3 / 2020 Added a reference to the parent cluster into node, since + # I needed access to the packet description in tclFileGenerator.py. This was + # done in cluster.py after a node object is constructed by simply accessing + # node.parent_cluster + def __init__(self, address_space=32, **kwargs): """ Fills underlying dict representation with kwargs. Since this uses the call diff --git a/middleware/python/tclFileGenerator.py b/middleware/python/tclFileGenerator.py index 3fc7a477..7b87ccf6 100644 --- a/middleware/python/tclFileGenerator.py +++ b/middleware/python/tclFileGenerator.py @@ -1989,7 +1989,26 @@ def bridgeConnections(outDir, fpga, sim): galapagos_path = str(os.environ.get('GALAPAGOS_PATH')) tcl_bridge_connections.addSource(galapagos_path + '/middleware/tclScripts/add_dbg_cores.tcl') - tcl_bridge_connections.tprint_raw('set g [add_dbg_core_to_list $marcos_list_of_dbg_nets 0]') + # Get the packet sizes. To avoid Verilog syntax errors, I intentionally make each width 1 if it was 0 + # (It's something I eventually want to fix, but Vivado's Verilog optimizer will remove the + # extra wires anyway) + c = fpga.parent_cluster + tdata_w = str(c.packet_data) + + tdest_w = c.packet_dest + if tdest_w == 0: + tdest_w = "1" + else: + tdest_w = str(tdest_w) + + tid_w = c.packet_id + if tid_w == 0: + tid_w = "1" + else: + tid_w = str(tid_w) + + # The last three positional arguments to this TCL function are the TDATA, TDEST, and TID widths + tcl_bridge_connections.tprint_raw('set g [add_dbg_core_to_list $marcos_list_of_dbg_nets 0 ' + tdata_w + ' ' + tdest_w + ' ' + tid_w + ']') tcl_bridge_connections.tprint_raw('set first_cmd_in [lindex $g 0]') tcl_bridge_connections.tprint_raw('set tree_out [lindex $g 1]') diff --git a/middleware/tclScripts/add_dbg_cores.tcl b/middleware/tclScripts/add_dbg_cores.tcl index 6f588506..592f980a 100644 --- a/middleware/tclScripts/add_dbg_cores.tcl +++ b/middleware/tclScripts/add_dbg_cores.tcl @@ -1,10 +1,13 @@ # These procs are used to automatically insert dbg_guvs into a design # Replaces the bd_intf_net n with a dbg_guv core named "inst" -proc add_dbg_core_to_net {n inst} { +proc add_dbg_core_to_net {n inst {tdata_width 64} {tdest_width 8} {tid_width 8}} { puts "add_dbg_core_to_net args:" puts "n = $n" puts "inst = $inst" + puts "tdata_width = $tdata_width" + puts "tdest_width = $tdest_width" + puts "tid_width = $tid_width" puts "----" # Get the two endpoints of this net @@ -77,7 +80,9 @@ proc add_dbg_core_to_net {n inst} { # We do an UGLY hack to deal with Vivado's annoying rules about hierarchies set prefix [lindex [regexp -inline {(.*)\/[^\/]*$} "[get_property PATH $n]"] 1] set g [create_bd_cell -vlnv mmerlini:yov:dbg_guv $prefix/$inst] - + + set_property -dict [list CONFIG.DATA_WIDTH $tdata_width CONFIG.DEST_WIDTH $tdest_width CONFIG.ID_WIDTH $tid_width] [get_bd_cells $g] + # Connect the dbg_guv to the loose endpoints if ![string compare [get_property MODE $left] Master] { connect_bd_intf_net $g/out $right @@ -111,12 +116,25 @@ proc get_next_dbg_core_id {} { # Given a list of AXI Stream masters (with only TDATA, TREADY, TVALID, and TLAST) # hooks up an automatically sized arbiter tree # Returns a reference to the tree's final output -# TODO?: Special case for n=1 -proc rr_tree {msts} { +proc rr_tree {msts {data_w 64}} { puts "rr_tree args:" puts "msts = $msts" + puts "data_w = $data_w" puts "----" + + # Quit gracefully if there is no tree to add + if {[llength $msts] == 0} { + return + } + startgroup + # We need to keep track of the port at the root of the arbitration tree + # If the user called this function with only one dbg_guv, then no tree will + # be generated. Otherwise, if we go on to generate a tree, this variable + # will be updated + set arbiter_out [lindex $msts 0] + + # Number of rr4 nodes needed to construct the tree set nnodes [expr ([llength $msts]+1)/3] set slvs {} @@ -124,7 +142,11 @@ proc rr_tree {msts} { while {$nnodes > 0} { set node [create_bd_cell -vlnv Marco_Merlini:fpga_bpf:rr4 -name node_$nnodes] + # Don't bother with reset lines. They only make timing harder to close, + # and for these little infrstructure IPs I don't really care set_property CONFIG.RESET_TYPE 0 $node + # Set the width + set_property CONFIG.DATA_WIDTH [expr $data_w + $data_w/8] $node lappend nodes $node lappend slvs [get_bd_intf_pins $node/s0] @@ -154,10 +176,21 @@ proc rr_tree {msts} { create_bd_pin -dir I DBG_GUV_TREE/clk connect_bd_net [list [get_bd_pins /DBG_GUV_TREE/*/clk] [get_bd_pins /DBG_GUV_TREE/clk]] + + # Update arbiter_out now that the tree is generated + set arbiter_out [get_bd_intf_pins /DBG_GUV_TREE/o] } endgroup - return [get_bd_intf_pins /DBG_GUV_TREE/o] + # UGLY BAND-AID FIX: until I have a more robust method of handling generic + # choices for AXI Stream channel widths, I've added in an axis_unconcat to + # bridge the gap here + set uncat [create_bd_cell -type ip -vlnv mmerlini:yov:axis_unconcat:1.0 axis_unconcat_0] + set_property -dict [list CONFIG.DATA_WIDTH $data_w CONFIG.OUT_ENABLE_KEEP {true} CONFIG.IN_ENABLE_LAST {1} CONFIG.OUT_ENABLE_LAST {true}] $uncat + connect_bd_intf_net [get_bd_intf_pins $arbiter_out] [get_bd_intf_pins $uncat/left] + connect_bd_net [get_bd_pins /DBG_GUV_TREE/clk] [get_bd_pins $uncat/clk] + + return [get_bd_intf_pins $uncat/right] } @@ -208,10 +241,13 @@ proc del_all_dbg_cores {} { # disable it in Galapagos's automatic generator; you normally want this to be on # Finally, this proc returns a list containing the first cmd_in in the dbg_guv # daisy chain, and the last output of the arbiter tree -proc add_dbg_core_to_list {nets {safe_mode 1}} { +proc add_dbg_core_to_list {nets {safe_mode 1} {tdata_width 64} {tdest_width 8} {tid_width 8}} { puts "add_dbg_core_to_list args:" puts "nets = $nets" puts "safe_mode = $safe_mode" + puts "tdata_width = $tdata_width" + puts "tdest_width = $tdest_width" + puts "tid_width = $tid_width" puts "----" startgroup @@ -235,9 +271,13 @@ proc add_dbg_core_to_list {nets {safe_mode 1}} { set next_id [get_next_dbg_core_id] # g holds a reference to the newly create dbg_guv cell - set g [add_dbg_core_to_net $n GUV_$next_id] + set g [add_dbg_core_to_net $n GUV_$next_id $tdata_width $tdest_width $tid_width] if {$g == 0} { - # add_Dbg_core_to_net (correctly) did not add a dbg_guv + # add_dbg_core_to_net (correctly) did not add a dbg_guv + continue + } elseif {$g == -1} { + # add_dbg_core_to_net encountered an error + puts "Warning: I can no longer guarantee that the debug cores will work! You may need to add them manually" continue } @@ -258,7 +298,7 @@ proc add_dbg_core_to_list {nets {safe_mode 1}} { } # Put in the arbiter tree - set tree_out [rr_tree $log_outs] + set tree_out [rr_tree $log_outs $tdata_width] # Connect up the clocks # TODO: If I ever plan to allow multiple clock domains, this will have to @@ -271,8 +311,8 @@ proc add_dbg_core_to_list {nets {safe_mode 1}} { return [list $first_cmd_in $tree_out] } -proc add_dbg_core_to_highlighted {{safe_mode 1}} { - add_dbg_core_to_list [get_bd_intf_nets [get_highlighted_objects]] $safe_mode +proc add_dbg_core_to_highlighted {{safe_mode 1} {tdata_width 64} {tdest_width 8} {tid_width 8}} { + add_dbg_core_to_list [get_bd_intf_nets [get_highlighted_objects]] $safe_mode $tdata_width $tdest_width $tid_width } proc del_highlighted_dbg_cores {} { diff --git a/middleware/tclScripts/replace_dbg_placeholder.tcl b/middleware/tclScripts/replace_dbg_placeholder.tcl index b692a1e7..d407781d 100644 --- a/middleware/tclScripts/replace_dbg_placeholder.tcl +++ b/middleware/tclScripts/replace_dbg_placeholder.tcl @@ -37,14 +37,7 @@ proc replace_dbg_placeholder {first_cmd_in tree_out} { connect_bd_intf_net [get_bd_intf_pins $first_cmd_in] $in_port # Rewire the output - # UGLY BAND-AID FIX: until I have a more robust method of handling generic - # choices for AXI Stream channel widths, I've hardcoded an axis_unconcat to - # bridge the gap here - set uncat [create_bd_cell -type ip -vlnv mmerlini:yov:axis_unconcat:1.0 axis_unconcat_0] - set_property -dict [list CONFIG.DATA_WIDTH {64} CONFIG.OUT_ENABLE_KEEP {true} CONFIG.IN_ENABLE_LAST {1} CONFIG.OUT_ENABLE_LAST {true}] $uncat - connect_bd_intf_net [get_bd_intf_pins /DBG_GUV_TREE/o] [get_bd_intf_pins $uncat/left] - connect_bd_net [get_bd_ports CLK] [get_bd_pins $uncat/clk] - connect_bd_intf_net [get_bd_intf_pins $uncat/right] [get_bd_intf_pins $out_port] + connect_bd_intf_net [get_bd_intf_pins $tree_out] [get_bd_intf_pins $out_port] # It's ugly, but it works! }