From 21515f119f38a66d25d8fc438e0df9473330a525 Mon Sep 17 00:00:00 2001 From: Madeline Busig Date: Wed, 3 Dec 2025 02:45:10 -0800 Subject: [PATCH] Add script for packaging IP --- scripts/package_ip.sh | 15 +++ tcl/package_ip.tcl | 292 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 307 insertions(+) create mode 100755 scripts/package_ip.sh create mode 100644 tcl/package_ip.tcl diff --git a/scripts/package_ip.sh b/scripts/package_ip.sh new file mode 100755 index 0000000..3e96863 --- /dev/null +++ b/scripts/package_ip.sh @@ -0,0 +1,15 @@ +#!/usr/bin/bash + +THISSCRIPT=$(realpath $0) +PROJECT_ROOT=$(dirname $(dirname $THISSCRIPT)) +TCLSCRIPT="$PROJECT_ROOT/tcl/package_ip.tcl" + +if [ $# -lt 1 ]; then + echo "Usage: $0 PROJECT_NAME" + exit -1 +fi + +PROJNAME=$1 + +$VIVADO_ROOT/bin/vivado -mode batch -source "$TCLSCRIPT" -tclargs "$PROJNAME" + diff --git a/tcl/package_ip.tcl b/tcl/package_ip.tcl new file mode 100644 index 0000000..e113f41 --- /dev/null +++ b/tcl/package_ip.tcl @@ -0,0 +1,292 @@ +proc DictValueOr { dict key default } { + if { [dict exists $dict $key] } { + return [dict get $dict $key] + } else { + return $default + } +} + +# Returns the value after in the string after startword and a delimiter +# +# If the string's format does not match, returns empty string +# +# EX: [SplitSub foo.bar foo] => "bar" +# EX: [SplitSub foo.biz foo] => "biz" +# EX: [SplitSub foobar foo] => "" +# EX: [SplitSub buz.bar foo] => "" +proc SplitSub { str startword } { + if { [string first $startword $str] == 0 } { + set sepidx [string first "." "$str"] + + if { $sepidx == -1 } { + return "" + } + + set val [string range "$str" [expr $sepidx + 1] [string length "$str"]] + + return $val + } else { + return "" + } +} + +# Gets all the values whose key starts with 'startword.*', returns a +# dictionary with the same values, but the keys are instead '*'. +# +# EX: With the dictionary d = { param.foo one param.bar two biz three } +# +# Calling [GetSubValues $d "param"] would result in the dictionary: +# +# { foo one bar two } +proc GetSubValues {map subcat} { + set res_map [dict create] + + foreach {key val} $map { + set new_key [SplitSub $key $subcat] + + if { $new_key != "" } { + dict append res_map $new_key $val + } + } + + return $res_map +} + +proc InterfaceAddParameters {iface params} { + foreach {param_name val} $params { + puts "Setting parameter $param_name" + + ipx::add_bus_parameter $param_name $iface + set param [ipx::get_bus_parameters $param_name -of_objects $iface] + set_property value $val $param + } +} + +proc InterfaceAddPortMaps {iface port_maps} { + foreach {logical_port physical_port} $port_maps { + puts "Mapping logical port $logical_port" + + ipx::add_port_map $logical_port $iface + set port_map [ipx::get_port_maps $logical_port -of_objects $iface] + set_property physical_name $physical_port $port_map + } +} + +# Parse the interface configuration and create a new interface +# in the current core +# +# @param iface_name Name of the interface to crate +# @param iface_conf Dictionary containing the interface configuration, parsed +# from hog.tcl ReadConf +# +proc ParseInterfaceConf { iface_name iface_conf } { + puts "Parsing interface $iface_name" + + set iface_abstraction [dict get $iface_conf "INTERFACE"] + variable infer false + + if { [dict exists $iface_conf "INFER"] } { + set infer [dict get $iface_conf "INFER"] + } + + # Creating interface object + + if { $infer } { + puts "Inferring interface" + + set ports [split [dict get $iface_conf "PORTS"]] + + puts "Using ports: $ports" + + set result [ipx::infer_bus_interface $ports $iface_abstraction [ipx::current_core]] + + # Inferred name could differ from the desired name, so set it + set inferred_name [lindex $result 2] + set created_iface [ipx::get_bus_interfaces $inferred_name -of_objects [ipx::current_core]] + set_property NAME $iface_name $created_iface + } else { + puts "Manually creating interface" + + ipx::add_bus_interface $iface_name [ipx::current_core] + + set created_iface [ipx::get_bus_interfaces $iface_name -of_objects [ipx::current_core]] + + set_property ABSTRACTION_TYPE_VLNV $iface_abstraction $created_iface + + # We need to set BOTH the bus ABSTRACTION, and the bus TYPE. + # The type can be obtained from the abstraction, so it is what + # is provided in ip.conf. Vivado does not provide an easy way + # to get one from the other, so we load the interface IP and + # get it's property. + set bus_abstraction [lindex [ipx::get_ipfiles -type busabs $iface_abstraction] 0] + set bus_type_vlnv [get_property BUS_TYPE_VLNV $bus_abstraction] + + set_property BUS_TYPE_VLNV $bus_type_vlnv $created_iface + } + + set iface [ipx::get_bus_interfaces $iface_name -of_objects [ipx::current_core]] + + # Set interface properties + set_property DESCRIPTION [DictValueOr $iface_conf "DESCRIPTION" ""] $iface + set_property DISPLAY_NAME [DictValueOr $iface_conf "DISPLAY_NAME" ""] $iface + + # If no mode is explicitly specified, use the potentially inferred mode + if { [dict exists $iface_conf "MODE"] } { + set_property INTERFACE_MODE [dict get $iface_conf "MODE"] $iface + } + + set params [GetSubValues $iface_conf "PARAMETER"] + set port_maps [GetSubValues $iface_conf "PORT"] + + InterfaceAddParameters $iface $params + InterfaceAddPortMaps $iface $port_maps +} + +proc AddMemoryMapBlock {memmap block_name block_conf} { + set disp_name [DictValueOr $block_conf "DISPLAY_NAME" ""] + set description [DictValueOr $block_conf "DESCRIPTION" ""] + set base_addr [DictValueOr $block_conf "BASE_ADDRESS" "0"] + set range [DictValueOr $block_conf "RANGE" "4096"] + set width [DictValueOr $block_conf "WIDTH" "0"] + + set block [ipx::add_address_block $block_name $memmap] + + set_property DISPLAY_NAME $disp_name $block + set_property DESCRIPTION $description $block + set_property BASE_ADDRESS $base_addr $block + set_property RANGE $range $block + set_property WIDTH $width $block +} + +proc ParseMemoryMapConf {iface_name memmap_conf} { + set memmap_name "${iface_name}_mem" + set blocks [dict get $memmap_conf "BLOCKS"] + + set memmap [ipx::add_memory_map $memmap_name [ipx::current_core]] + set associated_iface [ipx::get_bus_interfaces $iface_name -of_objects [ipx::current_core]] + set_property SLAVE_MEMORY_MAP_REF $memmap_name $associated_iface + + foreach block_name $blocks { + puts "Parsing block $block_name of $memmap_name" + + set block_conf [GetSubValues $memmap_conf $block_name] + AddMemoryMapBlock $memmap $block_name $block_conf + } +} + +set gitroot [exec git rev-parse --show-toplevel] +set hogroot "$gitroot/Hog" + +puts "Hog root: $hogroot" + +puts "Sourcing hog.tcl" + +# Using ReadConf +source -notrace "$hogroot/Tcl/hog.tcl" + +if { $argc < 1 } { + puts "Usage: vivado -mode batch -source package_ip.tcl -tclargs PROJECT_NAME" + exit -1 +} + +set proj_name [lindex $argv 0] +set tmp_proj_name "tmp_$proj_name" + +puts "Packaging $proj_name" + +set proj_top_dir "$gitroot/Top/$proj_name" +set proj_dir "$gitroot/Projects/$proj_name" +set proj_file "$proj_dir/$proj_name.xpr" + +set ip_conf_file "$proj_top_dir/ip.conf" + +puts "Reading configuration file $ip_conf_file" + +set ip_conf [ReadConf "$ip_conf_file"] + +puts "IP Configuration: $ip_conf" + +if { [dict exists $ip_conf main] == 0 } { + puts "No main section in IP configuration!" + exit -2 +} + +set ip_root "$gitroot/[dict get $ip_conf main ROOT]" +set ip_vendor [dict get $ip_conf main VENDOR] +set ip_library [dict get $ip_conf main LIBRARY] +set ip_taxonomy [dict get $ip_conf main TAXONOMY] + +puts "Opening project $proj_file" +open_project "$proj_file" + +puts "Packaging project" + +ipx::package_project -root_dir "$ip_root" -vendor "$ip_vendor" -library "$ip_library" -taxonomy "$ip_taxonomy" -import_files -set_current false + +set ip_component_file "$ip_root/component.xml" + +puts "Unloading core" + +ipx::unload_core "$ip_component_file" +ipx::edit_ip_in_project -name "$tmp_proj_name" -upgrade true -directory "$ip_root" "$ip_component_file" +update_compile_order -fileset sources_1 + +current_project "$tmp_proj_name" + +# Remove inferred interfaces and memory maps. Start from a blank slate. + +puts "Removing inferred interfaces" + +foreach iface [ipx::get_bus_interfaces -of_objects [ipx::current_core]] { + set iface_name [lindex $iface 2] + puts "Removing inferred bus interface $iface_name" + ipx::remove_bus_interface $iface_name [ipx::current_core] +} + +foreach memmap [ipx::get_memory_maps -of_objects [ipx::current_core]] { + set memmap_name [lindex $memmap 2] + puts "Removing inferred memory map $memmap_name" + ipx::remove_memory_map $memmap_name [ipx::current_core] +} + +puts "Parsing interfaces" + +foreach {key val} $ip_conf { + if { [string first "interface" "$key"] == 0 } { + set sepidx [string first "." "$key"] + set iface_name [string range "$key" [expr $sepidx + 1] [string length "$key"]] + + ParseInterfaceConf "$iface_name" $val + } else { + puts "$key is not an interface" + } +} + +puts "Parsing memory maps" + +set memmaps [GetSubValues $ip_conf "memmap"] + +puts "Memmaps: $memmaps" + +foreach {associated_iface memmap_conf} $memmaps { + puts "Parsing memory map for iface $associated_iface, conf: $memmap_conf" + ParseMemoryMapConf $associated_iface $memmap_conf +} + +puts "Packaging IP" + +# Increment revision +set rev [get_property core_revision [ipx::current_core]] +incr rev +set_property core_revision $rev [ipx::current_core] + +ipx::update_source_project_archive -component [ipx::current_core] +ipx::create_xgui_files [ipx::current_core] +ipx::update_checksums [ipx::current_core] +ipx::check_integrity [ipx::current_core] +ipx::save_core [ipx::current_core] + +close_project -delete + +puts "Done" +