diff --git a/.classpath b/.classpath index feb64e603..90ab0ce0c 100644 --- a/.classpath +++ b/.classpath @@ -17,7 +17,7 @@ - + @@ -33,12 +33,13 @@ - + - + + @@ -49,7 +50,7 @@ - + diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5c3a7cc89..06f25b588 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,7 +5,8 @@ on: pull_request: env: - RAPIDWRIGHT_VERSION: v2025.1.3-beta + RAPIDWRIGHT_VERSION: v2025.2.0-rc3-beta + jobs: build: diff --git a/LICENSE.TXT b/LICENSE.TXT index 62ea37d39..738e6771a 100755 --- a/LICENSE.TXT +++ b/LICENSE.TXT @@ -20,7 +20,7 @@ for details) RapidWright (Apache 2.0) - JOpt Simple (MIT License) - - Protocol Buffers 3.25.0 (Protocol Buffer License) + - Protocol Buffers 4.31.1 (Protocol Buffer License) - Kryo 5.2.1 (BSD 3-clause "New" or "Revised" License) - MinLog 1.3.1 (BSD 3-clause "New" or "Revised" License) - Reflectasm 1.11.9 (BSD 3-clause "New" or "Revised" License) @@ -82,6 +82,7 @@ RapidWright (Apache 2.0) - junit-team-junit 4.12 (Eclipse Public License 1.0) - json (2016-08-10) (unique license) - jeromq 0.5.2 (Mozilla Public License Version 2.0) + - jacl 1.4.1 (multiple licenses) - jgrapht-core 1.3.0 (LGPL 2.1 or EPL 2.1) - Source files available: http://www.rapidwright.io/docs/_static/jgrapht-core-1.3.0-src.zip - Cap'n Proto Java Runtime 0.1.13 (MIT License) @@ -101,9 +102,11 @@ RapidWright (Apache 2.0) - Opentest4j 1.2.0 (Apache 2.0) - JetBrains Java Annotations 20.1.0 (Apache 2.0) - jzlib 1.1.3 (BSD-style License) - - Apache Commons IO 2.11.0 (Apache 2.0) + - Apache Commons IO 2.20.0 (Apache 2.0) - Zstd-jni 1.5.5-1 (2-clause BSD) - Zstandard 1.5.5 (3-clause BSD or GPLv2) + - Google or-tools-java 9.14.6206 (Apache 2.0) + - Java Native Access jna-5.14.0 (Apache 2.0) @@ -429,7 +432,7 @@ IF LICENSEE DOES NOT AGREE TO ALL OF THE TERMS AND CONDITIONS OF THIS AGREEMENT, ************************ END LICENSE ************************ -**************************google-protobuf v3.25.0 - BSD-3-Clause*************************** +**************************google-protobuf v4.31.1 - BSD-3-Clause*************************** Copyright 2008 Google Inc. All rights reserved. @@ -8339,11 +8342,12 @@ Copyright 2013-2015 Sandstorm Development Group Inc. and contributors - Apiguardian API 1.1.0 (Apache 2.0) - Opentest4j 1.2.0 (Apache 2.0) - JetBrains Java Annotations 20.1.0 (Apache 2.0) - - Apache Commons IO 2.11.0 + - Apache Commons IO 2.20.0 *** Gradle Wrapper 7.4.2, Apiguardian API 1.1.0, Opentest4j 1.2.0, - JetBrains Java Annotations 20.1.0, Apache Commons IO 2.11.0 (Apache 2.0) **************************** + JetBrains Java Annotations 20.1.0, Apache Commons IO 2.20.0, + Google or-tools-java 9.14.6206, Java Native Access jna-5.14.0 (Apache 2.0) **************************** Apache License Version 2.0, January 2004 @@ -9070,3 +9074,214 @@ Public License instead of this License. ********************************* END LICENSE ********************************* + +********************** Jacl 1.4.1 (multiple licenses) **************** + +license.terms + +SUN MICROSYSTEMS, INC. THROUGH ITS SUN MICROSYSTEMS LABORATORIES +DIVISION ("SUN") WILL LICENSE THIS SOFTWARE AND THE ACCOMPANYING +DOCUMENTATION TO YOU (a "Licensee") ONLY ON YOUR ACCEPTANCE OF ALL +THE TERMS SET FORTH BELOW. + +Sun grants Licensee a non-exclusive, royalty-free right to download, +install, compile, use, copy and distribute the Software, modify or +otherwise create derivative works from the Software (each, a +"Modification") and distribute any Modification in source code and/or +binary code form to its customers with a license agreement containing +these terms and noting that the Software has been modified. The +Software is copyrighted by Sun and other third parties and Licensee +shall retain and reproduce all copyright and other notices presently +on the Software. As between Sun and Licensee, Sun is the sole owner of +all rights in and to the Software other than the limited rights +granted to Licensee herein; Licensee will own its Modifications, +expressly subject to Sun's continuing ownership of the +Software. Licensee will, at its expense, defend and indemnify Sun and +its licensors from and against any third party claims, including costs +and reasonable attorneys' fees, and be wholly responsible for any +liabilities arising out of or related to Licensee's development, use +or distribution of the Software or Modifications. Any distribution of +the Software and Modifications must comply with all applicable United +States export control laws. + +THE SOFTWARE IS BEING PROVIDED TO LICENSEE "AS IS" AND ALL EXPRESS OR +IMPLIED CONDITIONS AND WARRANTIES, INCLUDING ANY IMPLIED WARRANTY OF +MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE OR NON-INFRINGEMENT, +ARE DISCLAIMED. IN NO EVENT WILL SUN BE LIABLE HEREUNDER FOR ANY +DIRECT DAMAGES OR ANY INDIRECT, PUNITIVE, SPECIAL, INCIDENTAL OR +CONSEQUENTIAL DAMAGES OF ANY KIND. + +license.amd + +The following license terms apply to the TJC compiler source +and test files located in the src/tjc, src/tests/tjc, and +tests/tjc directories. + +© 2005 Advanced Micro Devices, Inc. All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that existing copyright notices +are retained in all copies, this notice is included verbatim in any +distributions, and the terms and conditions hererin are met. + +Use of the this software manifests acceptance of the terms of this +license by performance. + +The name of Advanced Micro Devices, Inc. may not be used to endorse or +promote products derived from this software without specific prior +written permission. + +THIS SOFTWARE IS PROVIDED BY ADVANCED MICRO DEVICES, INC. "AS IS" AND ANY +EXPRESS, IMPLIED OR STATUTORY WARRANTIES, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR +PURPOSE AND NON-INFRINGEMENT, OR THOSE ARISING FROM CUSTOM OF TRADE OR +COURSE OF USAGE ARE DISCLAIMED. + +IN NO EVENT SHALL ADVANCED MICRO DEVICES, INC. BE LIABLE FOR ANY DIRECT, +INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES +(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED, AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY +OUT OF THE USE OF THIS SOFTWARE ITS DOCUMENTATION OR ANY DERIVATIVES +THEREOF, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. BY USING THIS +SOFTWARE WITHOUT CHARGE, YOU ACCEPT THIS ALLOCATION OF RISK. THIS +DISCLAIMER OF LIABILITY CONSTITUTES AN ESSENTIAL PART OF THIS LICENSE. +ADVANCED MICRO DEVICES, INC. HAS NO OBLIGATION TO PROVIDE MAINTENANCE, +SUPPORT, UPDATES, ENHANCEMENTS, OR MODIFICATIONS OF THIS SOFTWARE. + +In the redistribution and use of this software, each party shall at all +times comply with all applicable governmental laws, statutes, ordinances, +rules, regulations, orders, and other requirements, including without +limitation such governmental requirements applicable to environmental +protection, health, safety, wages, hours, equal employment opportunity, +nondiscrimination, working conditions, import or export control, and +transportation. Without limiting the foregoing, each party shall adhere +to the U.S. Export Administration Regulations (EAR), currently found at +15 C.F.R. Sections 730 through 744, and, unless properly authorized by +the U.S. Government, shall not (1) export, re-export or release restricted +technology, software, or source code to a national of a country in Country +Groups D:1 or E:1, or (2) export to Country Groups D:1 or E:1 the direct +product of such technology or software, if such foreign produced direct +product is subject to national security controls as identified on the +Commerce Control List (currently found in Supplement 1 to Section 774 of EAR). +These export requirements shall survive any expiration or termination +of this agreement. + +license.itcl + +The following license terms apply to the Itcl source and +test files located in the src/itcl and tests/itcl directories. + + +This software is copyrighted by Cadence Design Systems, Inc., and other +parties. The following terms apply to all files associated with the +software unless explicitly disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +----------------------------------------------------------------------- + Following is the original agreement for the Tcl/Tk software from + Sun Microsystems. +----------------------------------------------------------------------- + +This software is copyrighted by the Regents of the University of +California, Sun Microsystems, Inc., and other parties. The following +terms apply to all files associated with the software unless explicitly +disclaimed in individual files. + +The authors hereby grant permission to use, copy, modify, distribute, +and license this software and its documentation for any purpose, provided +that existing copyright notices are retained in all copies and that this +notice is included verbatim in any distributions. No written agreement, +license, or royalty fee is required for any of the authorized uses. +Modifications to this software may be copyrighted by their authors +and need not follow the licensing terms described here, provided that +the new terms are clearly indicated on the first page of each file where +they apply. + +IN NO EVENT SHALL THE AUTHORS OR DISTRIBUTORS BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE, ITS DOCUMENTATION, OR ANY +DERIVATIVES THEREOF, EVEN IF THE AUTHORS HAVE BEEN ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + +THE AUTHORS AND DISTRIBUTORS SPECIFICALLY DISCLAIM ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. THIS SOFTWARE +IS PROVIDED ON AN "AS IS" BASIS, AND THE AUTHORS AND DISTRIBUTORS HAVE +NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, ENHANCEMENTS, OR +MODIFICATIONS. + +GOVERNMENT USE: If you are acquiring this software on behalf of the +U.S. government, the Government shall have only "Restricted Rights" +in the software and related documentation as defined in the Federal +Acquisition Regulations (FARs) in Clause 52.227.19 (c) (2). If you +are acquiring the software on behalf of the Department of Defense, the +software shall be classified as "Commercial Computer Software" and the +Government shall have only "Restricted Rights" as defined in Clause +252.227-7013 (c) (1) of DFARs. Notwithstanding the foregoing, the +authors grant the U.S. Government and others acting in its behalf +permission to use and distribute the software in accordance with the +terms specified in this license. + +license.ucb + +Portions of Jacl and Tcl Blend are +Copyright (c) 1997-1999 The Regents of the University of California. +All rights reserved. + +Permission is hereby granted, without written agreement and without +license or royalty fees, to use, copy, modify, and distribute this +software and its documentation for any purpose, provided that the above +copyright notice and the following two paragraphs appear in all copies +of this software. + +IN NO EVENT SHALL THE UNIVERSITY OF CALIFORNIA BE LIABLE TO ANY PARTY +FOR DIRECT, INDIRECT, SPECIAL, INCIDENTAL, OR CONSEQUENTIAL DAMAGES +ARISING OUT OF THE USE OF THIS SOFTWARE AND ITS DOCUMENTATION, EVEN IF +THE UNIVERSITY OF CALIFORNIA HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGE. + +THE UNIVERSITY OF CALIFORNIA SPECIFICALLY DISCLAIMS ANY WARRANTIES, +INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF +MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE SOFTWARE +PROVIDED HEREUNDER IS ON AN "AS IS" BASIS, AND THE UNIVERSITY OF +CALIFORNIA HAS NO OBLIGATION TO PROVIDE MAINTENANCE, SUPPORT, UPDATES, +ENHANCEMENTS, OR MODIFICATIONS. + + +********************************* END LICENSE ********************************* \ No newline at end of file diff --git a/common.gradle b/common.gradle index 97c0300d1..3531f40c3 100644 --- a/common.gradle +++ b/common.gradle @@ -38,15 +38,17 @@ dependencies { api 'org.capnproto:runtime:0.1.13' api 'net.sf.jopt-simple:jopt-simple:5.0.4' api 'org.python:jython-standalone:2.7.2' - api 'com.google.protobuf:protobuf-java:3.25.0' + api 'com.google.ortools:ortools-java:9.14.6206' + api 'com.google.protobuf:protobuf-java:4.31.1' api 'org.jetbrains:annotations:20.1.0' api 'org.zeromq:jeromq:0.5.2' api 'commons-cli:commons-cli:1.2' api 'org.json:json:20160810' api 'com.jcraft:jzlib:1.1.3' - api 'commons-io:commons-io:2.11.0' + api 'commons-io:commons-io:2.20.0' api 'com.xilinx.rapidwright:qtjambi-'+os+':4.5.2_01' api 'com.xilinx.rapidwright:jupyter-kernel-jsr223:1.0.1' + api 'com.xilinx.rapidwright:jacl:1.4.1' testFixturesApi 'org.junit.jupiter:junit-jupiter-api:5.7.1' testFixturesApi 'org.junit.jupiter:junit-jupiter-engine:5.7.1' testFixturesApi 'org.junit.jupiter:junit-jupiter-params:5.7.1' diff --git a/src/com/xilinx/rapidwright/MainEntrypoint.java b/src/com/xilinx/rapidwright/MainEntrypoint.java index 518877c52..edbec58cc 100644 --- a/src/com/xilinx/rapidwright/MainEntrypoint.java +++ b/src/com/xilinx/rapidwright/MainEntrypoint.java @@ -37,6 +37,7 @@ import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.blocks.PBlockGenerator; import com.xilinx.rapidwright.design.merge.MergeDesigns; +import com.xilinx.rapidwright.design.tools.ArrayBuilder; import com.xilinx.rapidwright.design.tools.CopyImplementation; import com.xilinx.rapidwright.design.tools.DesignObfuscator; import com.xilinx.rapidwright.design.tools.LUTTools; @@ -133,6 +134,7 @@ private static void addFunction(String name, MainStyleFunction func) { static { addFunction("AddSubGenerator", AddSubGenerator::main); + addFunction("ArrayBuilder", ArrayBuilder::main); addFunction("BlockCreator", BlockCreator::main); addFunction("BlockStitcher", BlockStitcher::main); addFunction("BlockUpdater", BlockUpdater::main); diff --git a/src/com/xilinx/rapidwright/debug/DotEdifDumper.java b/src/com/xilinx/rapidwright/debug/DotEdifDumper.java index f17cc5da9..e6eadcbea 100755 --- a/src/com/xilinx/rapidwright/debug/DotEdifDumper.java +++ b/src/com/xilinx/rapidwright/debug/DotEdifDumper.java @@ -63,7 +63,7 @@ protected Stream getPorts(EDIFCellInst edifCellInst) { protected Stream> getPortTemplates(EDIFCellInst edifCellInst) { return edifCellInst.getCellType().getPorts().stream().flatMap(p-> { if (p.isBus()) { - return Arrays.stream(p.getBitBlastedIndicies()).mapToObj(i -> new Pair<>(p, i)); + return Arrays.stream(p.getBitBlastedIndices()).mapToObj(i -> new Pair<>(p, i)); } return Stream.of(new Pair<>(p, 0)); }); diff --git a/src/com/xilinx/rapidwright/design/ClockTools.java b/src/com/xilinx/rapidwright/design/ClockTools.java new file mode 100644 index 000000000..25063c0e9 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/ClockTools.java @@ -0,0 +1,102 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, AMD Advanced Research and Development. + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.xilinx.rapidwright.design; + +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; + +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFHierPortInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; + +public class ClockTools { + + public static Map unisimFFs; + + public static Map unisimLatches; + + public static Map clbRegTypes; + + static { + unisimFFs = new HashMap<>(); + unisimFFs.put("FDRE", "C"); + unisimFFs.put("FDSE", "C"); + unisimFFs.put("FDCE", "C"); + unisimFFs.put("FDPE", "C"); + + unisimLatches = new HashMap<>(); + unisimLatches.put("LDCE", "G"); + unisimLatches.put("LDPE", "G"); + + clbRegTypes = new HashMap<>(); + clbRegTypes.putAll(unisimFFs); + clbRegTypes.putAll(unisimLatches); + } + + /** + * If the provided instance is a CLB register (flip-flop or latch), it will get + * the net of the connected net to the clock pin of the cell and return it. + * + * @param i The instance in question. + * @return The clock net connected to this instance's clock pin, or null if none + * is found. + */ + public static EDIFHierNet getClockNet(EDIFHierCellInst i) { + String clkInput = clbRegTypes.get(i.getCellName()); + EDIFHierPortInst portInst = clkInput != null ? i.getPortInst(clkInput) : null; + return portInst != null ? portInst.getHierarchicalNet() : null; + } + + /** + * Gets the clock net from the provided design. If the design has more than one + * net, it gets the net with the most CLB register fan out. + * + * @param design The design to query. + * @return The biggest CLB register fan out net in the design. + */ + public static EDIFHierNet getClockFromDesign(Design design) { + EDIFNetlist netlist = design.getNetlist(); + Map clockSinkCounts = new HashMap<>(); + for (EDIFHierCellInst i : netlist.getAllLeafHierCellInstances()) { + EDIFHierNet clk = ClockTools.getClockNet(i); + clk = netlist.getParentNet(clk); + if (clk != null) { + clockSinkCounts.compute(clk, (k, v) -> v == null ? 1 : 1 + v); + } + } + + int largestFanout = 0; + EDIFHierNet clkLargestFanout = null; + for (Entry e : clockSinkCounts.entrySet()) { + if (e.getValue() > largestFanout) { + largestFanout = e.getValue(); + clkLargestFanout = e.getKey(); + } + } + + return clkLargestFanout; + } + +} diff --git a/src/com/xilinx/rapidwright/design/NetTools.java b/src/com/xilinx/rapidwright/design/NetTools.java index c25e1ee00..0bda3e432 100644 --- a/src/com/xilinx/rapidwright/design/NetTools.java +++ b/src/com/xilinx/rapidwright/design/NetTools.java @@ -262,6 +262,52 @@ public static boolean hasClockSinks(Net net) { return false; } + /** + * Unroutes routed nets that have one or more overlapping or conflicting nodes + * with another route in the design. The choice between which of two or more + * nets gets unrouted is arbitrary and nets are unrouted until the set of routed + * nets do not overlap. + * + * @param design The design to evaluate for conflicting nodes. + * @return The list of nets that were unrouted. + */ + public static List unrouteNetsWithOverlappingNodes(Design design) { + List overlappingNets = getNetsWithOverlappingNodes(design); + for (Net net : overlappingNets) { + net.unroute(); + } + return overlappingNets; + } + + /** + * Returns a list of nets with overlapping nodes without unrouting them. + * + * @param design The design to evaluate for conflicting nodes. + * @return The list of nets that overlap. + */ + public static List getNetsWithOverlappingNodes(Design design) { + List overlappingNets = new ArrayList<>(); + Map used = new HashMap<>(); + for (Net net : design.getNets()) { + for (PIP pip : net.getPIPs()) { + for (Node node : new Node[] { pip.getStartNode(), pip.getEndNode() }) { + if (node == null) + continue; + Net existing = used.putIfAbsent(node, net); + if (existing != null && existing != net) { + for (PIP oldPip : new ArrayList<>(existing.getPIPs())) { + for (Node oldNode : new Node[] { oldPip.getStartNode(), oldPip.getEndNode() }) { + used.remove(oldNode); + } + } + overlappingNets.add(existing); + } + } + } + } + return overlappingNets; + } + /** * Returns a string representation of the net's routing tree using tree * characters (├─, └─). diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlock.java b/src/com/xilinx/rapidwright/design/blocks/PBlock.java index c52480b98..1e6b00561 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlock.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlock.java @@ -196,7 +196,7 @@ public ArrayList getTclConstraints() { ArrayList tcl = new ArrayList<>(); tcl.add("create_pblock " + name + (parent != null ? " -parent " + parent.getName() : "")); for (PBlockRange p : this) { - tcl.add("resize_pblock "+ name +" -add " + p.toString()); + tcl.add("resize_pblock [get_pblocks "+ name +"] -add " + p.toString()); } if (containRouting()) { tcl.add("set_property " + PblockProperty.CONTAIN_ROUTING + " 1 [get_pblocks " + name + "]"); diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java index 4b35c2088..8899996be 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockGenerator.java @@ -68,8 +68,8 @@ public class PBlockGenerator { public static final int LUTS_PER_CLE = 8; public static final int FF_PER_CLE = 16; - public static final float CLES_PER_BRAM = 5; - public static final float CLES_PER_DSP = 2.5f; + public static float CLES_PER_BRAM = 5; + public static float CLES_PER_DSP = 2.5f; public static int RAMLUTS_PER_CLE = 8; public static int CARRY_PER_CLE = 1; public static int SLICES_PER_TILE = 1; @@ -106,6 +106,7 @@ public class PBlockGenerator { int carryCount = 0; int bram18kCount = 0; int bram36kCount = 0; + int uramCount = 0; int tallestShape = 0; int widestShape = 0; int shapeArea = 0; @@ -151,14 +152,20 @@ private void getResourceUsages(String reportFileName) { lutCount = Integer.parseInt(line.split("\\s+")[4]); } else if (line.startsWith("| CLB Registers") || line.startsWith("| Slice Registers")) { regCount = Integer.parseInt(line.split("\\s+")[4]); + } else if (line.startsWith("| Registers")) { + regCount = Integer.parseInt(line.split("\\s+")[3]); } else if (line.startsWith("| LUT as Memory")) { lutRAMCount = Integer.parseInt(line.split("\\s+")[5]); - } else if (line.startsWith("| RAMB36/FIFO")) { + } else if (line.startsWith("| RAMB36/FIFO") || line.startsWith("| RAMB36E5")) { bram36kCount = Integer.parseInt(line.split("\\s+")[3]); - } else if (line.startsWith("| RAMB18")) { + } else if (line.startsWith("| RAMB18") || line.startsWith("| RAMB18E5*")) { bram18kCount = Integer.parseInt(line.split("\\s+")[3]); + } else if (line.startsWith("| URAM")) { + uramCount = Integer.parseInt(line.split("\\s+")[3]); } else if (line.startsWith("| DSPs")) { dspCount = Integer.parseInt(line.split("\\s+")[3]); + } else if (line.startsWith("| DSP Slices")) { + dspCount = Integer.parseInt(line.split("\\s+")[4]); } else if (line.startsWith("| CARRY")) { carryCount = Integer.parseInt(line.split("\\s+")[3]); } @@ -169,6 +176,10 @@ private void getResourceUsages(String reportFileName) { CARRY_PER_CLE = 2; RAMLUTS_PER_CLE = 4; SLICES_PER_TILE = 2; + } else if (dev.getSeries() == Series.Versal) { + CLES_PER_DSP = 1f; + CLES_PER_BRAM = 4f; + SLICES_PER_TILE = 2; } if (debug) { System.out.println("Parsed report: " + reportFileName); @@ -758,8 +769,15 @@ private ArrayList getCompatiblePatterns(int sliceColumns, int private String generatePblock(Site upperLeft, int columns, int rows) { String siteTypeName = upperLeft.getNameSpacePrefix(); - return siteTypeName+"X" + upperLeft.getInstanceX() + "Y" + (upperLeft.getInstanceY()-(rows-1)) + - ":"+siteTypeName+"X" + (upperLeft.getInstanceX()+columns-1) + "Y" + upperLeft.getInstanceY(); + int x = (upperLeft.getInstanceX() + columns - 1); + int y = (upperLeft.getInstanceY() + rows - 1); + String pblock = siteTypeName + "X" + upperLeft.getInstanceX() + "Y" + y + ":" + siteTypeName + "X" + x + "Y" + + upperLeft.getInstanceY(); + if (x < 0 || y < 0) { + throw new RuntimeException("ERROR: Invalid pblock generated: " + pblock); + } + + return pblock; } public ArrayList generatePBlockFromReport2(String reportFileName, String shapesReportFileName) { @@ -924,6 +942,13 @@ public ArrayList generatePBlockFromReport(String reportFileName, String } } + // Versal devices have a 50/50 mix of SLICEM and SLICEL with two SLICEs (one of + // each) in each tile + if (dev.getSeries() == Series.Versal) { + numSLICEColumns = numSLICEColumns + numSLICEMColumns; + numSLICEMColumns = 0; + } + // Fail safe in case we get too short, make sure shapes (carry chains,etc) can fit if (tallestShape > pblockCLEHeight) { pblockCLEHeight = tallestShape; @@ -1093,12 +1118,13 @@ public ArrayList generatePBlockFromReport(String reportFileName, String if (numSLICEColumns > 0 || numSLICEMColumns > 0) { int pIdx = 0; for (int i=0; pIdx < p.size(); i++) { - TileTypeEnum t = dev.getTile(row, col+i).getTileTypeEnum(); - if (Utils.isCLB(t)) { - upperLeft = dev.getTile(row - 4 /* TODO - Make Data Driven*/, col+i).getSites()[0]; + Tile tile = dev.getTile(row, col + i); + if (Utils.isCLB(tile.getTileTypeEnum())) { + upperLeft = tile.getSites()[0]; break; } - if (p.get(pIdx) == t) pIdx++; + if (p.get(pIdx) == tile.getTileTypeEnum()) + pIdx++; } sb.append(generatePblock(upperLeft, numSLICEColumns+numSLICEMColumns, numSLICERows)); } @@ -1136,44 +1162,42 @@ public ArrayList generatePBlockFromReport(String reportFileName, String } private int getTileRowInRegionBelow(int col, int row) { - Tile tmp = dev.getTile(row, col); + Tile refTile = null; + int targetCRColumn = dev.getNumOfClockRegionsColumns() / 2; for (int c = 0; c < dev.getColumns(); c++) { - if (Utils.isCLB(dev.getTile(row, c).getTileTypeEnum())) { - tmp = dev.getTile(row, c); + Tile tile = dev.getTile(row, c); + if (tile.getClockRegion().getColumn() == targetCRColumn && Utils.isCLB(tile.getTileTypeEnum())) { + refTile = tile; + break; } } int cleHeight = dev.getSeries().getCLEHeight(); - int sliceX = tmp.getSites()[0].getInstanceX(); - int sliceY = tmp.getSites()[0].getInstanceY() - cleHeight; + int sliceX = refTile.getSites()[0].getInstanceX(); + int sliceY = refTile.getSites()[0].getInstanceY() - cleHeight; Site tmpSite = dev.getSite("SLICE_X" + sliceX + "Y" + sliceY); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); if (dev.getNumOfSLRs() > 1) { - SLR master = null; - for (int i=0; i < dev.getNumOfSLRs(); i++) { - if (dev.getSLR(i).isMasterSLR()) { - master = dev.getSLR(i); - break; - } - } + SLR master = dev.getMasterSLR(); // Relocate to master SLR if necessary int slrCLBHeight = (dev.getNumOfClockRegionRows() / dev.getNumOfSLRs()) * cleHeight; // If we're below master, add - if (tmp.getRow() < master.getLowerRight().getRow()) { - while (tmp.getRow() < master.getLowerRight().getRow()) { + int lowerRightRow = master.getLowerRight().getRow(); + if (refTile.getRow() < lowerRightRow) { + while (refTile.getRow() < lowerRightRow && (tmpSite.getInstanceY() - slrCLBHeight >= 0)) { tmpSite = tmpSite.getNeighborSite(0, -slrCLBHeight); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); } } else { // Subtract - while (tmp.getRow() > master.getUpperLeft().getRow()) { + while (refTile.getRow() > master.getUpperLeft().getRow()) { tmpSite = tmpSite.getNeighborSite(0, slrCLBHeight); - tmp = tmpSite.getTile(); + refTile = tmpSite.getTile(); } } } - row = tmp.getRow(); + row = refTile.getRow(); return row; } diff --git a/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java b/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java index d4a2750e7..5a429dd0c 100644 --- a/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java +++ b/src/com/xilinx/rapidwright/design/blocks/PBlockRange.java @@ -105,7 +105,16 @@ public void setUpperRight(PBlockCorner upperRight) { this.upperRight = upperRight; } + private static String getPrefixedCornerName(PBlockCorner corner) { + if (corner instanceof ClockRegion) { + return CLOCK_REGION_RANGE_STR+"_"+corner.getName(); + } + return corner.getName(); + } public String toString() { + if (isClockRegionRange()) { + return CLOCK_REGION_RANGE_STR + "_" + lowerLeft.getName() + ":" + CLOCK_REGION_RANGE_STR + "_" + upperRight.getName(); + } return lowerLeft.getName() + ":" + upperRight.getName(); } diff --git a/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java b/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java index cc8bd9d5f..9f59931b4 100644 --- a/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java +++ b/src/com/xilinx/rapidwright/design/blocks/UtilizationType.java @@ -26,6 +26,14 @@ */ package com.xilinx.rapidwright.design.blocks; +import java.util.HashMap; +import java.util.HashSet; +import java.util.Map; +import java.util.Set; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; + /** * Different categories of fabric utilization. * @@ -39,17 +47,17 @@ public enum UtilizationType { REGS_AS_FFS("Regs as FF"), REGS_AS_LATCHES("Regs as Latch"), CARRY8S("CARRY8s"), - //F7_MUXES("F7 Muxes"), - //F8_MUXES("F8 Muxes"), - //F9_MUXES("F9 Muxes"), + F7_MUXES("F7 Muxes"), + F8_MUXES("F8 Muxes"), + F9_MUXES("F9 Muxes"), CLBS("CLBs"), CLBLS("CLBLs"), CLBMS("CLBMs"), - //LUT_FF_PAIRS("Lut/FF Pairs"), RAMB36S_FIFOS("RAMB36s/FIFOs"), RAMB18S("RAMB18s"), URAMS("URAMs"), DSPS("DSPs"), + BRAMS("BRAMS"), LOOKAHEAD8("LOOKAHEAD8"); private String name; @@ -64,4 +72,130 @@ public String getString() { return name; } + /** + * Calculates an estimated utilization for the given design's netlist. It + * doesn't take into account placement and only depends on the design's netlist + * for an estimate. + * + * @param design The design to query. + * @return A map of utilization types and their respective counts. + */ + public static Map computeUtilization(Design design) { + Map map = new HashMap<>(); + + design.getNetlist().collapseMacroUnisims(design.getSeries()); + + Set notTracked = new HashSet<>(); + + int[] lutCounts = new int[7]; + + for (EDIFHierCellInst i : design.getNetlist().getAllLeafHierCellInstances()) { + String type = i.getCellType().getName(); + switch (type) { + case "LUT1": + lutCounts[1]++; + break; + case "LUT2": + lutCounts[2]++; + break; + case "LUT3": + lutCounts[3]++; + break; + case "LUT4": + lutCounts[4]++; + break; + case "LUT5": + lutCounts[5]++; + break; + case "LUT6": + case "LUT6_2": + case "LUT6CY": + lutCounts[6]++; + break; + case "SRL16E": + case "SRLC16E": + case "SRLC32E": + case "RAMD64E": + case "RAMS64E": + case "RAMS64E1": + case "RAMD32M64": + case "RAMS32": + case "RAMD32": + case "RAMD64E5": + case "RAM32X1S": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAM32X1D": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 2 : (v + 2)); + break; + case "RAM32M": + case "RAM64M": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 4 : (v + 4)); + break; + case "RAM64X1S": + case "RAM32M16": + map.compute(LUTS_AS_MEMORY, (k, v) -> v == null ? 8 : (v + 8)); + break; + case "FDCE": + case "FDPE": + case "FDRE": + case "FDSE": + case "AND2B1L": + map.compute(REGS_AS_FFS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "LDCE": + case "LDPE": + map.compute(REGS_AS_LATCHES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "CARRY8": + case "LOOKAHEAD8": + map.compute(CARRY8S, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAMB36E2": + case "RAMB36E5_INT": + case "FIFO36E2": + map.compute(RAMB36S_FIFOS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "RAMB18E2": + case "RAMB18E5_INT": + map.compute(RAMB18S, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "URAM288": + case "URAM288_BASE": + case "URAM288E5": + map.compute(URAMS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "DSP_PREADD58": + case "DSP_FP_ADDER": + case "DSP48E2": + map.compute(DSPS, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF7": + map.compute(F7_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF8": + map.compute(F8_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + case "MUXF9": + map.compute(F9_MUXES, (k, v) -> v == null ? 1 : (v + 1)); + break; + default: + if (notTracked.add(type)) + System.out.println("Didn't count: " + type); + } + } + + design.getNetlist().expandMacroUnisims(design.getSeries()); + + // Calculate potential estimates for LUTs + int lutEstimate = 0; + for (int i = 1; i < 7; i++) { + lutEstimate += i == 3 ? lutCounts[i] / 2 : lutCounts[i]; + } + map.put(CLB_LUTS, lutEstimate + map.getOrDefault(LUTS_AS_MEMORY, 0)); + + map.put(CLB_REGS, map.getOrDefault(REGS_AS_FFS, 0) + map.getOrDefault(REGS_AS_LATCHES, 0)); + map.put(BRAMS, map.getOrDefault(RAMB36S_FIFOS, 0) + (map.getOrDefault(RAMB18S, 0) / 2)); + return map; + } } diff --git a/src/com/xilinx/rapidwright/design/compare/DesignComparator.java b/src/com/xilinx/rapidwright/design/compare/DesignComparator.java index 290687c40..a850a487e 100644 --- a/src/com/xilinx/rapidwright/design/compare/DesignComparator.java +++ b/src/com/xilinx/rapidwright/design/compare/DesignComparator.java @@ -270,6 +270,14 @@ public int compareSiteInsts(SiteInst gold, SiteInst test) { if (!Objects.equals(e.getValue().getType(), testCell.getType())) { addDiff(DesignDiffType.PLACED_CELL_TYPE, e.getValue(), testCell, gold, ""); } + + if (!Objects.equals(e.getValue().isBELFixed(), testCell.isBELFixed())) { + addDiff(DesignDiffType.PLACED_CELL_IS_BEL_FIXED, e.getValue(), testCell, gold, ""); + } + + if (!Objects.equals(e.getValue().isSiteFixed(), testCell.isSiteFixed())) { + addDiff(DesignDiffType.PLACED_CELL_IS_SITE_FIXED, e.getValue(), testCell, gold, ""); + } } for (Entry e : testMap.entrySet()) { Cell extraCell = e.getValue(); diff --git a/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java b/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java index 6ad0393b5..5d2b73662 100644 --- a/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java +++ b/src/com/xilinx/rapidwright/design/compare/DesignDiffType.java @@ -39,6 +39,8 @@ public enum DesignDiffType { PLACED_CELL_EXTRA, PLACED_CELL_TYPE, PLACED_CELL_NAME, + PLACED_CELL_IS_BEL_FIXED, + PLACED_CELL_IS_SITE_FIXED, SITEPIP_MISSING, SITEPIP_EXTRA, SITEPIP_INPIN_NAME, @@ -49,7 +51,7 @@ public enum DesignDiffType { NET_EXTRA, PIP_MISSING, PIP_EXTRA, - PIP_FLAGS; + PIP_FLAGS; private boolean isMissingType; @@ -63,7 +65,7 @@ public enum DesignDiffType { private static Set siteInstParentTypes = EnumSet.of(SITEINST_TYPE, PLACED_CELL_MISSING, PLACED_CELL_EXTRA, PLACED_CELL_TYPE, PLACED_CELL_NAME, SITEPIP_MISSING, SITEPIP_EXTRA, SITEPIP_INPIN_NAME, SITEWIRE_NET_MISSING, SITEWIRE_NET_EXTRA, - SITEWIRE_NET_NAME); + SITEWIRE_NET_NAME, PLACED_CELL_IS_BEL_FIXED, PLACED_CELL_IS_SITE_FIXED); private static Set netParentTypes = EnumSet.of(PIP_EXTRA, PIP_FLAGS, PIP_MISSING); diff --git a/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java new file mode 100644 index 000000000..37758db8c --- /dev/null +++ b/src/com/xilinx/rapidwright/design/tools/ArrayBuilder.java @@ -0,0 +1,860 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, AMD Advanced Research and Development. + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.tools; + +import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.LinkedHashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.Set; +import java.util.stream.Collectors; + +import com.xilinx.rapidwright.design.Cell; +import com.xilinx.rapidwright.design.ClockTools; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.Module; +import com.xilinx.rapidwright.design.ModuleInst; +import com.xilinx.rapidwright.design.Net; +import com.xilinx.rapidwright.design.NetTools; +import com.xilinx.rapidwright.design.NetType; +import com.xilinx.rapidwright.design.SiteInst; +import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.Unisim; +import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.blocks.PBlockGenerator; +import com.xilinx.rapidwright.design.blocks.PBlockRange; +import com.xilinx.rapidwright.device.BEL; +import com.xilinx.rapidwright.device.ClockRegion; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.device.Part; +import com.xilinx.rapidwright.device.PartNameTools; +import com.xilinx.rapidwright.device.Series; +import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.SiteTypeEnum; +import com.xilinx.rapidwright.device.Tile; +import com.xilinx.rapidwright.edif.EDIFCell; +import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFDirection; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.edif.EDIFNet; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPort; +import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.edif.EDIFValueType; +import com.xilinx.rapidwright.tests.CodePerfTracker; +import com.xilinx.rapidwright.util.FileTools; +import com.xilinx.rapidwright.util.MessageGenerator; +import com.xilinx.rapidwright.util.Pair; +import com.xilinx.rapidwright.util.PerformanceExplorer; +import com.xilinx.rapidwright.util.VivadoTools; + +import joptsimple.OptionParser; +import joptsimple.OptionSet; + +import static com.xilinx.rapidwright.util.Utils.isBRAM; +import static com.xilinx.rapidwright.util.Utils.isDSP; +import static com.xilinx.rapidwright.util.Utils.isSLICE; + +/** + * A Tool to optimize, place and route a kernel and then replicate its + * implementation in an array across the fabric. + */ +public class ArrayBuilder { + + private static final List KERNEL_DESIGN_OPTS = Arrays.asList("i", "input"); + private static final List OUTPUT_DESIGN_OPTS = Arrays.asList("o", "output"); + private static final List INPUT_EDIF_OPTS = Arrays.asList("e", "edif"); + private static final List UTILIZATION_OPTS = Arrays.asList("u", "utilization"); + private static final List SHAPES_OPTS = Arrays.asList("s", "shapes"); + private static final List PART_OPTS = Arrays.asList("p", "part"); + private static final List PBLOCK_OPTS = Arrays.asList("b", "pblock"); + private static final List HELP_OPTS = Arrays.asList("?", "h", "help"); + private static final List TARGET_CLK_PERIOD_OPTS = Arrays.asList("c", "clk-period"); + private static final List KERNEL_CLK_NAME_OPTS = Arrays.asList("n", "kernel-clk-name"); + private static final List TOP_CLK_NAME_OPTS = Arrays.asList("m", "top-clk-name"); + private static final List REUSE_RESULTS_OPTS = Arrays.asList("r", "reuse"); + private static final List SKIP_IMPL_OPTS = Arrays.asList("k", "skip-impl"); + private static final List LIMIT_INSTS_OPTS = Arrays.asList("l", "limit-inst-count"); + private static final List TOP_LEVEL_DESIGN_OPTS = Arrays.asList("t", "top-design"); + private static final List WRITE_PLACEMENT_OPTS = Collections.singletonList("write-placement"); + private static final List PLACEMENT_FILE_OPTS = Collections.singletonList("read-placement"); + private static final List PLACEMENT_GRID_OPTS = Collections.singletonList("write-placement-grid"); + private static final List OUT_OF_CONTEXT_OPTS = Collections.singletonList("out-of-context"); + + private Design design; + + private Design topDesign; + + private List pblocks; + + private double clkPeriod; + + private String kernelClkName; + + private String topClkName; + + private boolean skipImpl; + + private String outputName; + + private CodePerfTracker t; + + private int instCountLimit = Integer.MAX_VALUE; + + private String outputPlacementFileName; + + private String inputPlacementFileName; + + private String outputPlacementLocsFileName; + + private boolean outOfContext; + + public static final double DEFAULT_CLK_PERIOD_TARGET = 2.0; + + private OptionParser createOptionParser() { + + OptionParser p = new OptionParser() { + { + acceptsAll(KERNEL_DESIGN_OPTS, "Input Kernel Design (*.dcp or *.edf)").withRequiredArg(); + acceptsAll(OUTPUT_DESIGN_OPTS, "Output Array Design (default is 'array.dcp')").withRequiredArg(); + acceptsAll(PBLOCK_OPTS, "PBlock Constraint(s), separated with ';'").withRequiredArg(); + acceptsAll(INPUT_EDIF_OPTS, "Companion EDIF for DCP (*.edf)").withRequiredArg(); + acceptsAll(UTILIZATION_OPTS, "Vivado Generated Utilization Report").withRequiredArg(); + acceptsAll(SHAPES_OPTS, "Vivado Generated Shapes").withRequiredArg(); + acceptsAll(PART_OPTS, "Target AMD Part").withRequiredArg(); + acceptsAll(TARGET_CLK_PERIOD_OPTS, "Target Clock Period (ns)").withRequiredArg(); + acceptsAll(KERNEL_CLK_NAME_OPTS, "Kernel Clock Name").withRequiredArg(); + acceptsAll(TOP_CLK_NAME_OPTS, "Top Clock Name").withRequiredArg(); + acceptsAll(REUSE_RESULTS_OPTS, "Reuse Previous Implementation Results"); + acceptsAll(SKIP_IMPL_OPTS, "Skip Implementation of the Kernel"); + acceptsAll(LIMIT_INSTS_OPTS, "Limit number of instance copies").withRequiredArg(); + acceptsAll(WRITE_PLACEMENT_OPTS, "Write the chosen placement to the specified file").withRequiredArg(); + acceptsAll(PLACEMENT_FILE_OPTS, "Use placement specified in file").withRequiredArg(); + acceptsAll(PLACEMENT_GRID_OPTS, "Write grid of possible placement locations to specified file").withRequiredArg(); + acceptsAll(TOP_LEVEL_DESIGN_OPTS, "Top level design with blackboxes/kernel insts").withRequiredArg(); + acceptsAll(OUT_OF_CONTEXT_OPTS, "Specifies that the array will be compiled out of context"); + acceptsAll(HELP_OPTS, "Print this help message").forHelp(); + } + }; + + return p; + } + + public ArrayBuilder() { + + } + + public ArrayBuilder(CodePerfTracker t) { + this.t = t; + } + + private static void printHelp(OptionParser p) { + MessageGenerator.printHeader("ArrayBuilder"); + System.out.println("Generates an optimized, implemented array of the provided kernel."); + try { + p.printHelpOn(System.out); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public Device getDevice() { + return getKernelDesign().getDevice(); + } + + public void setKernelDesign(Design design) { + this.design = design; + } + + public Design getKernelDesign() { + return design; + } + + public Design getTopDesign() { + return topDesign; + } + + public void setTopDesign(Design topDesign) { + this.topDesign = topDesign; + } + + public void setPBlocks(List pblocks) { + this.pblocks = pblocks; + } + + public List getPBlocks() { + return pblocks == null ? Collections.emptyList() : pblocks; + } + + public void setClockPeriod(double clkPeriod) { + this.clkPeriod = clkPeriod; + } + + public double getClockPeriod() { + return clkPeriod; + } + + public void setKernelClockName(String clkName) { + this.kernelClkName = clkName; + } + + public String getKernelClockName() { + return kernelClkName; + } + + public void setTopClockName(String clkName) { + this.topClkName = clkName; + } + + public String getTopClockName() { + return topClkName; + } + + public boolean isSkipImpl() { + return skipImpl; + } + + public void setSkipImpl(boolean skipImpl) { + this.skipImpl = skipImpl; + } + + public String getOutputName() { + return outputName; + } + + public void setOutputName(String outputName) { + this.outputName = outputName; + } + + public int getInstCountLimit() { + return instCountLimit; + } + + public void setInstCountLimit(int instCountLimit) { + this.instCountLimit = instCountLimit; + } + + public String getOutputPlacementFileName() { + return outputPlacementFileName; + } + + public void setOutputPlacementFileName(String outputPlacementFileName) { + this.outputPlacementFileName = outputPlacementFileName; + } + + public String getInputPlacementFileName() { + return inputPlacementFileName; + } + + public void setInputPlacementFileName(String inputPlacementFileName) { + this.inputPlacementFileName = inputPlacementFileName; + } + + public String getOutputPlacementLocsFileName() { + return outputPlacementLocsFileName; + } + + public void setOutputPlacementLocsFileName(String outputPlacementLocsFileName) { + this.outputPlacementLocsFileName = outputPlacementLocsFileName; + } + + public boolean getOutOfContext() { + return outOfContext; + } + + public void setOutOfContext(boolean outOfContext) { + this.outOfContext = outOfContext; + } + + private void initializeArrayBuilder(OptionSet options) { + Path inputFile = null; + + setSkipImpl(options.has(SKIP_IMPL_OPTS.get(0))); + setOutOfContext(options.has(OUT_OF_CONTEXT_OPTS.get(0))); + + if (options.has(KERNEL_DESIGN_OPTS.get(0))) { + inputFile = Paths.get((String) options.valueOf(KERNEL_DESIGN_OPTS.get(0))); + if (inputFile.toString().endsWith(".dcp")) { + if (options.has(INPUT_EDIF_OPTS.get(0))) { + Path companionEDIF = Paths.get((String) options.valueOf(INPUT_EDIF_OPTS.get(0))); + setKernelDesign(Design.readCheckpoint(inputFile, companionEDIF, CodePerfTracker.SILENT)); + } else { + setKernelDesign(Design.readCheckpoint(inputFile)); + if (!design.getNetlist().getEncryptedCells().isEmpty()) { + System.out.println("Design has encrypted cells"); + } else { + System.out.println("Design does not have encrypted cells"); + } + } + + } else if (inputFile.toString().endsWith(".edf")) { + EDIFNetlist netlist = EDIFTools.readEdifFile(inputFile); + if (options.has(PART_OPTS.get(0))) { + Part part = PartNameTools.getPart((String) options.valueOf(PART_OPTS.get(0))); + EDIFTools.ensureCorrectPartInEDIF(netlist, part.toString()); + } + setKernelDesign(new Design(netlist)); + } + } else { + throw new RuntimeException("No input design found. " + + "Please specify an input kernel (*.dcp or *.edf) using options " + + KERNEL_DESIGN_OPTS); + } + assert (getKernelDesign() != null); + + if (options.has(PBLOCK_OPTS.get(0))) { + String pblockString = (String) options.valueOf(PBLOCK_OPTS.get(0)); + String[] pblockStrings = pblockString.split(";"); + List pblocks = new ArrayList(); + for (String str : pblockStrings) { + pblocks.add(new PBlock(getDevice(), str)); + } + setPBlocks(pblocks); + } else if (!skipImpl) { + PBlockGenerator pb = new PBlockGenerator(); + Path utilReport = null; + Path shapesReport = null; + if (options.has(UTILIZATION_OPTS.get(0)) && options.has(SHAPES_OPTS.get(0))) { + utilReport = Paths.get((String) options.valueOf(UTILIZATION_OPTS.get(0))); + shapesReport = Paths.get((String) options.valueOf(SHAPES_OPTS.get(0))); + } else { + utilReport = Paths.get("utilization.report"); + shapesReport = Paths.get("shapes.report"); + if (!inputFile.toString().endsWith(".dcp")) { + throw new RuntimeException( + "TODO - Implement support for util/shapes file " + "generation with EDIF input file."); + } + VivadoTools.getUtilizationAndShapesReport(inputFile, utilReport, shapesReport); + } + + List pblocks = pb.generatePBlockFromReport(utilReport.toString(), shapesReport.toString()); + List pblockObjects = new ArrayList(); + for (String s : pblocks) { + PBlock pblock = new PBlock(); + for (String range : s.split(" ")) { + pblock.add(new PBlockRange(getDevice(), range)); + } + pblockObjects.add(pblock); + } + setPBlocks(pblockObjects); + } + + for (int i = 0; i < getPBlocks().size(); i++) { + System.out.println("[INFO] PBlocks Set [" + i + "]: " + getPBlocks().get(i)); + } + + if (options.has(TARGET_CLK_PERIOD_OPTS.get(0))) { + setClockPeriod(Double.parseDouble((String) options.valueOf(TARGET_CLK_PERIOD_OPTS.get(0)))); + } else { + setClockPeriod(DEFAULT_CLK_PERIOD_TARGET); + System.out.println("[INFO] No clock period set, defaulting to: " + getClockPeriod() + "ns"); + } + + if (options.has(KERNEL_CLK_NAME_OPTS.get(0))) { + setKernelClockName(((String) options.valueOf(KERNEL_CLK_NAME_OPTS.get(0)))); + } else { + setKernelClockName(ClockTools.getClockFromDesign(getKernelDesign()).toString()); + } + + if (options.has(OUTPUT_DESIGN_OPTS.get(0))) { + setOutputName((String) options.valueOf(OUTPUT_DESIGN_OPTS.get(0))); + } else { + setOutputName("array.dcp"); + } + + if (options.has(LIMIT_INSTS_OPTS.get(0))) { + setInstCountLimit(Integer.parseInt((String) options.valueOf(LIMIT_INSTS_OPTS.get(0)))); + } + + if (options.has(TOP_LEVEL_DESIGN_OPTS.get(0))) { + Design d = Design.readCheckpoint((String) options.valueOf(TOP_LEVEL_DESIGN_OPTS.get(0))); + setTopDesign(d); + } + + if (options.has(TOP_CLK_NAME_OPTS.get(0))) { + setTopClockName(((String) options.valueOf(TOP_CLK_NAME_OPTS.get(0)))); + } else if (options.has(TOP_LEVEL_DESIGN_OPTS.get(0))) { + setTopClockName(ClockTools.getClockFromDesign(getTopDesign()).toString()); + } + + if (options.has(WRITE_PLACEMENT_OPTS.get(0))) { + setOutputPlacementFileName((String) options.valueOf(WRITE_PLACEMENT_OPTS.get(0))); + } + + if (options.has(PLACEMENT_FILE_OPTS.get(0))) { + setInputPlacementFileName((String) options.valueOf(PLACEMENT_FILE_OPTS.get(0))); + } + + if (options.has(PLACEMENT_GRID_OPTS.get(0))) { + setOutputPlacementLocsFileName((String) options.valueOf(PLACEMENT_GRID_OPTS.get(0))); + } + } + + public static void removeBUFGs(Design design) { + // Find BUFGs in the design and remove them + List bufgs = new ArrayList<>(); + for (Cell c : design.getCells()) { + if (c.getType().equals("BUFG") || c.getType().equals("BUFGCE")) { + bufgs.add(c); + } + } + + for (Cell bufg : bufgs) { + SiteInst si = bufg.getSiteInst(); + String inputSiteWire = bufg.getSiteWireNameFromLogicalPin("I"); + Net input = si.getNetFromSiteWire(inputSiteWire); + String outputSiteWire = bufg.getSiteWireNameFromLogicalPin("O"); + Net output = si.getNetFromSiteWire(outputSiteWire); + + // Remove BUFG + design.removeCell(bufg); + + design.removeSiteInst(bufg.getSiteInst()); + EDIFCellInst bufgInst = design.getTopEDIFCell().removeCellInst(bufg.getName()); + for (EDIFPortInst portInst : bufgInst.getPortInsts()) { + portInst.getNet().removePortInst(portInst); + } + EDIFNet clkin = design.getTopEDIFCell().getNet(input.getName()); + EDIFNet clk = design.getTopEDIFCell().getNet(output.getName()); + for (EDIFPortInst portInst : clkin.getPortInsts()) { + clk.addPortInst(portInst); + } + design.getTopEDIFCell().removeNet(clkin); + } + } + + public static List getMatchingModuleInstanceNames(Module m, Design array) { + List instNames = new ArrayList<>(); + EDIFCell modCellType = m.getNetlist().getTopCell(); + EDIFHierCellInst top = array.getNetlist().getTopHierCellInst(); + Queue q = new LinkedList<>(); + q.add(top); + while (!q.isEmpty()) { + EDIFHierCellInst curr = q.poll(); + if (curr.getCellType().matchesInterface(modCellType)) { + instNames.add(curr.getFullHierarchicalInstName()); + } else { + for (EDIFCellInst child : curr.getCellType().getCellInsts()) { + q.add(curr.getChild(child)); + } + } + } + return instNames; + } + + public static void writePlacementToFile(Map placementMap, String fileName) { + List lines = new ArrayList<>(); + Comparator comparator = new Comparator() { + @Override + public int compare(Site o1, Site o2) { + if (o1.getInstanceY() > o2.getInstanceY()) { + return -1; + } + + if (o1.getInstanceY() < o2.getInstanceY()) { + return 1; + } + + return Integer.compare(o1.getInstanceX(), o2.getInstanceX()); + } + }; + Map sortedMap = placementMap.entrySet().stream() + .sorted(Map.Entry.comparingByValue(comparator)) + .collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue, + (e1, e2) -> e1, LinkedHashMap::new)); + for (Map.Entry entry : sortedMap.entrySet()) { + lines.add(entry.getKey() + " " + entry.getValue()); + } + FileTools.writeLinesToTextFile(lines, fileName); + } + + public static Map readPlacementFromFile(String fileName) { + Map placementMap = new HashMap<>(); + List lines = FileTools.getLinesFromTextFile(fileName); + + for (String line : lines) { + String[] splitLine = line.split("\\s+"); + placementMap.put(splitLine[0], splitLine[1]); + } + + return placementMap; + } + + public static void writePlacementLocsToFile(List modules, String fileName) { + List lines = new ArrayList<>(); + Comparator comparator = (o1, o2) -> { + if (o1.getInstanceY() > o2.getInstanceY()) { + return -1; + } + + if (o1.getInstanceY() < o2.getInstanceY()) { + return 1; + } + + return Integer.compare(o1.getInstanceX(), o2.getInstanceX()); + }; + for (Module module : modules) { + lines.add(module.getName() + ":"); + List validPlacements = module.getAllValidPlacements().stream().sorted(comparator).collect(Collectors.toList()); + for (Site anchor : validPlacements) { + lines.add(anchor.getName()); + } + } + FileTools.writeLinesToTextFile(lines, fileName); + } + + public static void main(String[] args) { + CodePerfTracker t = new CodePerfTracker(ArrayBuilder.class.getName()); + t.start("Init"); + ArrayBuilder ab = new ArrayBuilder(t); + OptionParser p = ab.createOptionParser(); + OptionSet options = p.parse(args); + + if (options.has(HELP_OPTS.get(0))) { + printHelp(p); + return; + } + + // Load design and options + ab.initializeArrayBuilder(options); + + // Create a working directory for PerformanceExplorer + Path workDir = Paths.get("ArrayBuilder-" + FileTools.getTimeStamp().replace(" ", "-")); + boolean reuseResults = false; + if (options.has(REUSE_RESULTS_OPTS.get(0))) { + workDir = Paths.get((String) options.valueOf(REUSE_RESULTS_OPTS.get(0))); + reuseResults = true; + } + + List modules = new ArrayList<>(); + boolean unrouteStaticNets = false; + if (!ab.isSkipImpl()) { + t.stop().start("Implement Kernel"); + FileTools.makeDirs(workDir.toString()); + System.out.println("[INFO] Created work directory: " + workDir.toString()); + + // Initialize PerformanceExplorer + PerformanceExplorer pe = new PerformanceExplorer(ab.getKernelDesign(), workDir.toString(), + ab.getKernelClockName(), ab.getClockPeriod()); + + // Set PBlocks + Map pblocks = new HashMap<>(); + for (PBlock pb : ab.getPBlocks()) { + pblocks.put(pb, null); + } + pe.setPBlocks(pblocks); + pe.setAddEDIFAndMetadata(true); + pe.setReusePreviousResults(reuseResults); + pe.explorePerformance(); + + List> results = pe.getBestResultsPerPBlock(); + for (int i = 0; i < results.size(); i++) { + Pair result = results.get(i); + Path dcpPath = result.getFirst().resolve("routed.dcp"); + if (Files.exists(dcpPath)) { + System.out.println("Reading... " + dcpPath); + Design d = Design.readCheckpoint(dcpPath); + d.setName(d.getName() + "_" + i); + Module m = new Module(d, unrouteStaticNets); + modules.add(m); + m.setPBlock(pe.getPBlock(i)); + m.calculateAllValidPlacements(d.getDevice()); + } else { + System.err.println("Missing DCP Result: " + dcpPath); + } + System.out.println(result.getFirst() + " " + result.getSecond()); + } + } else /* skipImpl==true */ { + // Just use the design we loaded and replicate it + t.stop().start("Calculate Valid Placements"); + removeBUFGs(ab.getKernelDesign()); + Module m = new Module(ab.getKernelDesign(), unrouteStaticNets); + m.getNet(ab.getKernelClockName()).unroute(); + if (ab.getInputPlacementFileName() == null) { + m.calculateAllValidPlacements(ab.getDevice()); + } + if (!ab.getPBlocks().isEmpty()) { + m.setPBlock(ab.getPBlocks().get(0)); + } + modules.add(m); + } + t.stop().start("Place Instances"); + + Design array = null; + List modInstNames = null; + if (ab.getTopDesign() == null) { + array = new Design("array", ab.getKernelDesign().getPartName()); + } else { + array = ab.getTopDesign(); + // Find instances in existing design + modInstNames = getMatchingModuleInstanceNames(modules.get(0), array); + ab.setInstCountLimit(modInstNames.size()); + } + + if (ab.getOutputPlacementLocsFileName() != null) { + writePlacementLocsToFile(modules, ab.getOutputPlacementLocsFileName()); + } + + // Add encrypted cells from modules to array + for (Module module : modules) { + // Merge encrypted cells + List encryptedCells = module.getNetlist().getEncryptedCells(); + if (!encryptedCells.isEmpty()) { + System.out.println("Encrypted cells merged"); + array.getNetlist().addEncryptedCells(encryptedCells); + } + } + + int placed = 0; + Map newPlacementMap = new HashMap<>(); + if (ab.getInputPlacementFileName() != null) { + Map placementMap = readPlacementFromFile(ab.inputPlacementFileName); + System.out.println("Placing from specified file"); + for (Map.Entry entry : placementMap.entrySet()) { + String instName = entry.getKey(); + String anchorName = entry.getValue(); + EDIFHierCellInst hierInst = array.getNetlist().getHierCellInstFromName(instName); + if (hierInst == null) { + throw new RuntimeException("Instance name " + instName + " is invalid"); + } + if (modules.size() > 1) { + throw new RuntimeException("Manual placement does not work with automated implementation"); + } + Module module = modules.get(0); + ModuleInst curr = array.createModuleInst(instName, module); + + Site anchor = array.getDevice().getSite(anchorName); + + boolean wasPlaced = curr.place(anchor, true, false); + if (!wasPlaced) { + throw new RuntimeException("Unable to place cell " + instName + " at site " + anchor); + } + + if (straddlesClockRegion(curr)) { + curr.unplace(); + throw new RuntimeException("Chosen site anchor " + anchor + " straddles multiple clock regions"); + } + + newPlacementMap.put(curr, anchor); + placed++; + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); + } + } else { + ModuleInst curr = null; + int i = 0; + outer: for (Module module : modules) { + for (Site anchor : module.getAllValidPlacements()) { + if (curr == null) { + String instName = modInstNames == null ? ("inst_" + i) : modInstNames.get(i); + curr = array.createModuleInst(instName, module); + i++; + } + if (curr.place(anchor, true, false)) { + if (straddlesClockRegion(curr)) { + curr.unplace(); + continue; + } + + List overlapping = NetTools.getNetsWithOverlappingNodes(array); + if (!overlapping.isEmpty()) { + curr.unplace(); + continue; + } + + placed++; + newPlacementMap.put(curr, anchor); + System.out.println(" ** PLACED: " + placed + " " + anchor + " " + curr.getName()); + curr = null; + if (placed >= ab.getInstCountLimit()) { + break outer; + } + } + } + } + } + + if (ab.getOutputPlacementFileName() != null) { + writePlacementToFile(newPlacementMap, ab.outputPlacementFileName); + } + + List unrouted = NetTools.unrouteNetsWithOverlappingNodes(array); + if (!unrouted.isEmpty()) { + System.out.println("Found " + unrouted.size() + " overlapping nets, that were unrouted."); + } + + if (ab.isSkipImpl() && ab.getTopDesign() == null) { + EDIFCell top = array.getTopEDIFCell(); + EDIFHierNet clkNet = array.getNetlist().getHierNetFromName(ab.getKernelClockName()); + if (clkNet == null) { + // Create BUFG and clock net, then connect to all instances + Cell bufg = createBUFGCE(array, top, "bufg", array.getDevice().getSite("BUFGCE_X2Y0")); + Net clk = array.createNet(ab.getKernelClockName()); + clk.connect(bufg, "O"); + Net clkIn = array.createNet(ab.getKernelClockName() + "_in"); + clkIn.connect(bufg, "I"); + EDIFPort clkInPort = top.createPort(ab.getKernelClockName(), EDIFDirection.INPUT, 1); + clkIn.getLogicalNet().createPortInst(clkInPort); + EDIFNet logClkNet = clk.getLogicalNet(); + for (EDIFCellInst inst : top.getCellInsts()) { + EDIFPort port = inst.getPort(ab.getKernelClockName()); + if (port != null) { + logClkNet.createPortInst(port, inst); + } + } + } + + // Port up unconnected inputs + for (EDIFPort topPort : modules.get(0).getNetlist().getTopCell().getPorts()) { + if (topPort.isInput()) { + if (top.getPort(topPort.getName()) == null) { + EDIFPort port = top.createPort(topPort); + if (port.isBus()) { + for (int j = 0; j < port.getWidth(); j++) { + EDIFNet net = top.createNet(port.getPortInstNameFromPort(j)); + net.createPortInst(port, j); + for (ModuleInst mi : array.getModuleInsts()) { + net.createPortInst(port, j, mi.getCellInst()); + } + } + } else { + EDIFNet net = top.createNet(port.getName()); + net.createPortInst(port); + for (ModuleInst mi : array.getModuleInsts()) { + net.createPortInst(port, mi.getCellInst()); + } + } + } + } + } + + PerformanceExplorer.updateClockPeriodConstraint(array, ab.getKernelClockName(), ab.getClockPeriod()); + array.setDesignOutOfContext(true); + array.setAutoIOBuffers(false); + } + + Net gndNet = array.getNet(Net.GND_NET); + gndNet.unroute(); + Net vccNet = array.getNet(Net.VCC_NET); + vccNet.unroute(); + array.getNetlist().consolidateAllToWorkLibrary(); + + if (ab.getOutOfContext()) { + // Automatically find bounding PBlock based on used Slices, DSPs, and BRAMs + Set usedSites = new HashSet<>(); + for (SiteInst siteInst : array.getSiteInsts()) { + if (isSLICE(siteInst) || isBRAM(siteInst) || isDSP(siteInst)) { + usedSites.add(siteInst.getSite()); + } + } + PBlock pBlock = new PBlock(array.getDevice(), usedSites); + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsNearPins(array, ab.getTopClockName(), pBlock); + } + + t.stop().start("Write DCP"); + array.writeCheckpoint(ab.getOutputName()); + t.stop().printSummary(); + } + + public static Cell createBUFGCE(Design design, EDIFCell parent, String name, Site location) { + Cell bufgce = design.createAndPlaceCell(parent, name, Unisim.BUFGCE, location, location.getBEL("BUFCE")); + + bufgce.addProperty("CE_TYPE", "ASYNC", EDIFValueType.STRING); + + // Ensure a VCC cell source in the current cell + EDIFTools.getStaticNet(NetType.VCC, parent, design.getNetlist()); + + bufgce.getSiteInst().addSitePIP("CEINV", "CE_PREINV"); + bufgce.getSiteInst().addSitePIP("IINV", "I_PREINV"); + + if (design.getSeries() == Series.Versal) { + BEL ceinv = bufgce.getSite().getBEL("CEINV"); + bufgce.getSiteInst().routeIntraSiteNet(design.getVccNet(), ceinv.getPin("CE"), ceinv.getPin("CE_PREINV")); + design.getVccNet().addPin(new SitePinInst(false, "CE", bufgce.getSiteInst())); + } else if (design.getSeries() == Series.UltraScalePlus) { + // TODO + } + // Remove CE:VCC entry for CE:CE + bufgce.removePinMapping("CE"); + bufgce.addPinMapping("CE", "CE"); + + return bufgce; + } + + private static boolean straddlesClockRegion(ModuleInst mi) { + ClockRegion cr = mi.getAnchor().getSite().getClockRegion(); + for (SiteInst si : mi.getSiteInsts()) { + if (si.getSite().getClockRegion() != cr) { + return true; + } + } + return false; + } + + private static boolean straddlesClockRegionOrRCLK(ModuleInst mi) { + ClockRegion cr = mi.getAnchor().getSite().getClockRegion(); + int centerRow = getRCLKRowIndex(cr); + boolean inTop = false; + boolean inBot = false; + for (SiteInst si : mi.getSiteInsts()) { + inTop |= si.getTile().getRow() > centerRow; + inBot |= si.getTile().getRow() < centerRow; + if ((inTop && inBot) || si.getSite().getClockRegion() != cr) { + return true; + } + } + return false; + } + + private static int getRCLKRowIndex(ClockRegion cr) { + Tile center = cr.getApproximateCenter(); + int searchGridDim = 0; + outer: while (!center.getName().startsWith("RCLK_")) { + searchGridDim++; + for (int row = -searchGridDim; row < searchGridDim; row++) { + for (int col = -searchGridDim; col < searchGridDim; col++) { + Tile neighbor = center.getTileNeighbor(col, row); + if (neighbor != null) { + neighbor.getName().startsWith("RCLK_"); + center = neighbor; + break outer; + } + } + } + } + return center.getRow(); + } +} diff --git a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java index 7416ff84d..1aeffc219 100644 --- a/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java +++ b/src/com/xilinx/rapidwright/design/tools/InlineFlopTools.java @@ -22,13 +22,8 @@ package com.xilinx.rapidwright.design.tools; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.HashSet; -import java.util.Iterator; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; +import java.util.stream.Collectors; import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; @@ -38,16 +33,10 @@ import com.xilinx.rapidwright.design.SitePinInst; import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.design.blocks.PBlock; -import com.xilinx.rapidwright.device.BEL; -import com.xilinx.rapidwright.device.Series; -import com.xilinx.rapidwright.device.Site; +import com.xilinx.rapidwright.device.*; import com.xilinx.rapidwright.eco.ECOPlacementHelper; -import com.xilinx.rapidwright.edif.EDIFCell; -import com.xilinx.rapidwright.edif.EDIFCellInst; -import com.xilinx.rapidwright.edif.EDIFHierNet; -import com.xilinx.rapidwright.edif.EDIFNet; -import com.xilinx.rapidwright.edif.EDIFPort; -import com.xilinx.rapidwright.edif.EDIFPortInst; +import com.xilinx.rapidwright.edif.*; +import com.xilinx.rapidwright.placer.blockplacer.Point; import com.xilinx.rapidwright.util.Pair; import com.xilinx.rapidwright.util.StringTools; @@ -56,7 +45,7 @@ * This is targeted at kernel replication preparation prior to routing to ensure * that connections are routed outside of the pblock of the out-of-context * kernel. - * + * */ public class InlineFlopTools { @@ -66,49 +55,106 @@ public class InlineFlopTools { public static final String INLINE_SUFFIX = "_rw_inline_flop"; + private static final int MAX_FFS_PER_SLICE = 5; + private static final Set VALID_CENTROID_SITE_TYPES = + new HashSet<>(Arrays.asList(SiteTypeEnum.SLICEL, SiteTypeEnum.SLICEM)); + /** - * Add flip flops inline on all the top-level ports of an out-of-context design. + * Add flip-flops inline on all the top-level ports of an out-of-context design. * This is useful for out-of-context kernels prior to placement and routing so that * after the flops have been placed, the router is forced to route connections * of each of the ports to each of the flops. This can help alleviate congestion * when the kernels are placed/relocated in context. Note this assumes the * design is not implemented as in most contexts it will be placed and routed * immediately following this modification. - * + * + * @param design The design to modify + * @param clkNet Name of the clock net to use on which to add the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + */ + public static void createAndPlaceFlopsInlineOnTopPortsArbitrarily(Design design, String clkNet, PBlock keepOut) { + createAndPlaceFlopsInlineOnTopPorts(design, clkNet, keepOut, false); + } + + /** + * Add flip-flops inline on all the top-level ports of an out-of-context design. + * Flip-flop placements are chosen based on the centroid of net pins for each top-level + * I/O. This is useful for out-of-context kernels prior to final routing so that + * after the flops have been placed, the router is forced to route connections + * of each of the ports to each of the flops. Note this assumes the design is + * placed and potentially partially routed. + * * @param design The design to modify * @param clkNet Name of the clock net to use on which to add the flops * @param keepOut The pblock used to contain the kernel and the added flops will * not be placed inside this area. */ - public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut) { - assert (design.getSiteInsts().size() == 0); + public static void createAndPlaceFlopsInlineOnTopPortsNearPins(Design design, String clkNet, PBlock keepOut) { + createAndPlaceFlopsInlineOnTopPorts(design, clkNet, keepOut, true); + } + + /** + * Add flip-flops inline on all the top-level ports of an out-of-context design. + * This is useful for out-of-context kernels so that after the flops have been + * placed, the router is forced to route connections of each of the ports to + * each of the flops. This can help alleviate congestion when the kernels are + * placed/relocated in context. + * + * @param design The design to modify + * @param clkNet Name of the clock net to use on which to add the flops + * @param keepOut The pblock used to contain the kernel and the added flops will + * not be placed inside this area. + * @param centroidPlacement Places flip-flops based on the centroid of the top-level net pins. Should only be + * used if a placement already exists. + */ + private static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clkNet, PBlock keepOut, + boolean centroidPlacement) { + assert (design.getSiteInsts().isEmpty()); EDIFCell top = design.getTopEDIFCell(); Site start = keepOut.getAllSites("SLICE").iterator().next(); // TODO this is a bit wasteful boolean exclude = true; Iterator siteItr = ECOPlacementHelper.spiralOutFrom(start, keepOut, exclude).iterator(); siteItr.next(); // Skip the first site, as we are suggesting one inside the pblock - Net clk = design.getNet(clkNet); + EDIFHierNet clk = design.getNetlist().getHierNetFromName(clkNet); Set siteInstsToRoute = new HashSet<>(); for (EDIFPort port : top.getPorts()) { - // Don't flop the clock net if (port.getName().equals(clkNet)) { continue; } if (port.isBus()) { - for (int i : port.getBitBlastedIndicies()) { + for (int i : port.getBitBlastedIndices()) { EDIFPortInst inst = port.getInternalPortInstFromIndex(i); - Pair loc = nextAvailPlacement(design, siteItr); - Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); - siteInstsToRoute.add(flop.getSiteInst()); + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + if (centroidPlacement) { + netCentroidFlipFlopPlacement(design, inst, keepOut, clk, siteInstsToRoute); + } else { + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } } } else { EDIFPortInst inst = port.getInternalPortInst(); - Pair loc = nextAvailPlacement(design, siteItr); - Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); - siteInstsToRoute.add(flop.getSiteInst()); + if (inst != null) { + if (allLeavesAreIBUF(design, inst)) { + continue; + } + + if (centroidPlacement) { + netCentroidFlipFlopPlacement(design, inst, keepOut, clk, siteInstsToRoute); + } else { + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } } } for (SiteInst si : siteInstsToRoute) { @@ -116,20 +162,72 @@ public static void createAndPlaceFlopsInlineOnTopPorts(Design design, String clk } } + private static boolean allLeavesAreIBUF(Design design, EDIFPortInst inst) { + EDIFHierCellInst topInst = design.getNetlist().getTopHierCellInst(); + EDIFHierPortInst hierPortInst = new EDIFHierPortInst(topInst, inst); + List leafHierPortInsts = hierPortInst.getHierarchicalNet().getLeafHierPortInsts(); + + boolean allLeavesAreIBUF = !leafHierPortInsts.isEmpty(); + for (EDIFHierPortInst leafHierPortInst : leafHierPortInsts) { + allLeavesAreIBUF &= leafHierPortInst.getCellType().getName().equals("IBUF"); + } + return allLeavesAreIBUF; + } + + private static void netCentroidFlipFlopPlacement(Design design, EDIFPortInst inst, PBlock keepOut, EDIFHierNet clk, + Set siteInstsToRoute) { + EDIFHierCellInst topInst = design.getNetlist().getTopHierCellInst(); + EDIFHierPortInst hierPortInst = new EDIFHierPortInst(topInst, inst); + List leafHierPortInsts = hierPortInst.getHierarchicalNet().getLeafHierPortInsts(); + List points = new ArrayList<>(); + for (EDIFHierPortInst leafInst : leafHierPortInsts) { + Cell cell = design.getCell(leafInst.getFullHierarchicalInstName()); + if (cell != null && cell.isPlaced()) { + Tile t = cell.getTile(); + Point p = new Point(t.getColumn(), t.getRow()); + points.add(p); + } + } + + if (!points.isEmpty()) { + Site centroid = ECOPlacementHelper.getCentroidOfPoints(design.getDevice(), points, + VALID_CENTROID_SITE_TYPES); + Iterator siteItr = ECOPlacementHelper.spiralOutFrom(centroid, keepOut, true).iterator(); + siteItr.next(); + Pair loc = nextAvailPlacement(design, siteItr); + Cell flop = createAndPlaceFlopInlineOnTopPortInst(design, inst, loc, clk); + siteInstsToRoute.add(flop.getSiteInst()); + } + } + private static Pair nextAvailPlacement(Design design, Iterator itr) { while (itr.hasNext()) { Site curr = itr.next(); SiteInst candidate = design.getSiteInstFromSite(curr); - if (candidate == null) { - // Empty site, let's use it - return new Pair(curr, curr.getBEL("AFF")); + List usedFFs = new ArrayList<>(); + if (candidate != null) { + for (Cell c : candidate.getCells()) { + if (c.isPlaced() && c.getBEL().isFF() && !c.getBEL().isAnyIMR()) { + usedFFs.add(c.getBEL()); + } + } + } + if (usedFFs.size() < MAX_FFS_PER_SLICE) { + // There is an FF available, use one of them + List bels = Arrays.stream(curr.getBELs()).filter((BEL b) -> b.isFF() && !b.isAnyIMR()) + .collect(Collectors.toList()); + for (BEL b : bels) { + if (!usedFFs.contains(b)) { + return new Pair<>(curr, b); + } + } } } return null; } private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPortInst portInst, Pair loc, - Net clk) { + EDIFHierNet clk) { String name = portInst.getFullName() + INLINE_SUFFIX; Cell flop = design.createAndPlaceCell(design.getTopEDIFCell(), name, Unisim.FDRE, loc.getFirst(), loc.getSecond()); @@ -137,7 +235,13 @@ private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPor net.connect(flop, portInst.isInput() ? "D" : "Q"); design.getGndNet().connect(flop, "R"); design.getVccNet().connect(flop, "CE"); - clk.connect(flop, "C"); + EDIFHierCellInst flopHierCellInst = flop.getEDIFHierCellInst(); + EDIFHierPortInst clkHierPortInst = flopHierCellInst.getPortInst("C"); + if (clkHierPortInst == null) { + clk.getNet().createPortInst("C", flopHierCellInst.getInst()); + clkHierPortInst = flopHierCellInst.getPortInst("C"); + } + EDIFTools.connectPortInstsThruHier(clk, clkHierPortInst, name + "_clk"); EDIFNet origNet = portInst.getNet(); origNet.removePortInst(portInst); net.getLogicalNet().addPortInst(portInst); @@ -157,8 +261,8 @@ private static Cell createAndPlaceFlopInlineOnTopPortInst(Design design, EDIFPor /** * Removes the inline flops added by - * {@link #createAndPlaceFlopsInlineOnTopPorts(Design, String, PBlock)} - * + * {@link #createAndPlaceFlopsInlineOnTopPorts(Design, String, PBlock, boolean)} + * * @param design The current design from which to remove the flops */ public static void removeInlineFlops(Design design) { @@ -168,7 +272,7 @@ public static void removeInlineFlops(Design design) { Net vcc = design.getVccNet(); Set vccPins = new HashSet<>(); pinsToRemove.put(vcc, vccPins); - String[] staticPins = new String[] { "CKEN1", design.getSeries() == Series.Versal ? "RST" : "SRST1" }; + String[] staticPins = new String[]{"CKEN1", design.getSeries() == Series.Versal ? "RST" : "SRST1"}; for (EDIFCellInst inst : design.getTopEDIFCell().getCellInsts()) { if (inst.getName().endsWith(INLINE_SUFFIX)) { Cell flop = design.getCell(inst.getName()); @@ -193,7 +297,7 @@ public static void removeInlineFlops(Design design) { } DesignTools.batchRemoveSitePins(pinsToRemove, true); - String[] ctrlPins = new String[] { "C", "R", "CE" }; + String[] ctrlPins = new String[]{"C", "R", "CE"}; EDIFCell top = design.getTopEDIFCell(); for (EDIFCellInst c : cellsToRemove) { // Remove control set pins @@ -236,11 +340,11 @@ public static void removeInlineFlops(Design design) { public static void main(String[] args) { if (args.length < 3 || args.length > 4) { - System.out.println("USAGE (to add flops) : "+CLK_OPT+"= "+PBLOCK_OPT+"="); + System.out.println("USAGE (to add flops) : " + CLK_OPT + "= " + PBLOCK_OPT + "="); System.out.println("USAGE (to remove flops): " + REMOVE_FLOPS_OPT); return; } - + Design d = Design.readCheckpoint(args[0]); if (args[2].startsWith(CLK_OPT) || args[2].startsWith(PBLOCK_OPT)) { @@ -248,18 +352,17 @@ public static void main(String[] args) { String clkName = StringTools.getOptionValue(CLK_OPT, args); String pblockRange = StringTools.getOptionValue(PBLOCK_OPT, args); if (clkName == null || pblockRange == null) { - throw new RuntimeException("ERROR: Missing value(s) for option(s): " + throw new RuntimeException("ERROR: Missing value(s) for option(s): " + CLK_OPT + "=" + clkName + ", " + PBLOCK_OPT + "=" + pblockRange); } PBlock pblock = new PBlock(d.getDevice(), pblockRange); - createAndPlaceFlopsInlineOnTopPorts(d, clkName, pblock); + createAndPlaceFlopsInlineOnTopPortsArbitrarily(d, clkName, pblock); } else if (args[2].equals(REMOVE_FLOPS_OPT)) { removeInlineFlops(d); } else { - System.err.println("ERROR: Unrecognized option '" + args[2] +"'"); + System.err.println("ERROR: Unrecognized option '" + args[2] + "'"); } - - + d.writeCheckpoint(args[1]); } } diff --git a/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java b/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java new file mode 100644 index 000000000..178517db0 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/ClockConstraint.java @@ -0,0 +1,75 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.util.Locale; + +/** + * A constraint representing a clock + */ +public class ClockConstraint implements Constraint{ + private String clockName; + private double period; + + private String portName; + + public ClockConstraint(String clockName, double period, String portName) { + this.clockName = clockName; + this.period = period; + this.portName = portName; + } + + @Override + public ClockConstraint clone() { + return new ClockConstraint(clockName, period, portName); + } + + public String getClockName() { + return clockName; + } + + public void setClockName(String clockName) { + this.clockName = clockName; + } + + public double getPeriod() { + return period; + } + + public void setPeriod(double period) { + this.period = period; + } + + public String asXdc() { + String periodString = String.format(Locale.US, "%.3f", period); + return "create_clock -period "+periodString+" -name "+clockName+" [get_ports "+portName+"]"; + } + + public String getPortName() { + return portName; + } + + public void setPortName(String portName) { + this.portName = portName; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/Constraint.java b/src/com/xilinx/rapidwright/design/xdc/Constraint.java new file mode 100644 index 000000000..5831ea303 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/Constraint.java @@ -0,0 +1,26 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; +public interface Constraint> { + T clone(); +} diff --git a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java index 8a80a14ac..a57dfef0b 100644 --- a/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java +++ b/src/com/xilinx/rapidwright/design/xdc/ConstraintTools.java @@ -24,14 +24,11 @@ import java.util.HashMap; import java.util.Map; -import java.util.EnumSet; -import java.util.regex.Matcher; -import java.util.regex.Pattern; +import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.blocks.PBlock; -import com.xilinx.rapidwright.design.blocks.PblockProperty; -import com.xilinx.rapidwright.design.ConstraintGroup; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; /** * A collection of methods to access design constraints. @@ -39,63 +36,17 @@ * Created on: Oct 31, 2025 */ public class ConstraintTools { - private static final Pattern PBLOCK_NAME_PATTERN = Pattern.compile("\\[get_pblocks\\s+(\\S+)]"); - private static final Pattern RANGE_PATTERN = Pattern.compile("\\{([^}]+)}"); - public static Map getPBlockFromXDCConstraints(Design d) { + public static Map getPBlocksFromXDC(Design d) { Map pblockMap = new HashMap<>(); - Map> pblockProperties = new HashMap<>(); for (ConstraintGroup cg : ConstraintGroup.values()) { - for (String tclLine : d.getXDCConstraints(cg)) { - String name = extractPBlockName(tclLine); - if (name == null) continue; - - EnumSet props = - pblockProperties.computeIfAbsent(name, k -> EnumSet.noneOf(PblockProperty.class)); - - if (tclLine.contains("resize_pblock")) { - String range = extractRange(tclLine); - if (range != null) { - pblockMap.put(name, new PBlock(d.getDevice(), range)); - } - } else if (tclLine.contains(PblockProperty.CONTAIN_ROUTING + " 1")) { - props.add(PblockProperty.CONTAIN_ROUTING); - } else if (tclLine.contains(PblockProperty.IS_SOFT + " 1")) { - props.add(PblockProperty.IS_SOFT); - } else if (tclLine.contains(PblockProperty.EXCLUDE_PLACEMENT + " 1")) { - props.add(PblockProperty.EXCLUDE_PLACEMENT); - } - } + XDCConstraints xdcConstraints = XDCParser.parseXDC(d.getDevice(), d.getXDCConstraints(cg), new RegularEdifCellLookup(d.getNetlist())); + xdcConstraints.getPBlockConstraints().forEach((k,v)->{ + pblockMap.put(k, v.getPblock()); + }); } - // set property - for (Map.Entry> entry : pblockProperties.entrySet()) { - String name = entry.getKey(); - EnumSet props = entry.getValue(); - PBlock pb = pblockMap.get(name); - if (pb == null) continue; - - if (props.contains(PblockProperty.CONTAIN_ROUTING)) { - pb.setContainRouting(true); - } - if (props.contains(PblockProperty.IS_SOFT)) { - pb.setIsSoft(true); - } - if (props.contains(PblockProperty.EXCLUDE_PLACEMENT)) { - pb.setExcludePlacement(true); - } - } return pblockMap; } - - private static String extractPBlockName(String line) { - Matcher m = PBLOCK_NAME_PATTERN.matcher(line); - return m.find() ? m.group(1) : null; - } - - private static String extractRange(String line) { - Matcher m = RANGE_PATTERN.matcher(line); - return m.find() ? m.group(1) : null; - } } diff --git a/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java b/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java new file mode 100644 index 000000000..9b563d468 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/PBlockConstraint.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.blocks.PBlock; + +public class PBlockConstraint implements Constraint { + private PBlock pblock = new PBlock(); + private List cells = new ArrayList<>(); + + @Override + public PBlockConstraint clone() { + PBlockConstraint res = new PBlockConstraint(); + res.pblock = pblock; + res.cells = new ArrayList<>(cells); + return res; + } + + public PBlock getPblock() { + return pblock; + } + + public List getCells() { + return cells; + } + + public Stream asXdc() { + List res = getPblock().getTclConstraints(); + if (cells.size()==1 && cells.get(0).isEmpty()) { + res.add("add_cells_to_pblock [get_pblocks "+pblock.getName()+"] -top"); + } else if (!cells.isEmpty()) { + res.add("add_cells_to_pblock [get_pblocks "+pblock.getName()+"] [get_cells {"+String.join(" ", cells)+"}]"); + } + return res.stream(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java b/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java new file mode 100644 index 000000000..e96066e36 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/PackagePinConstraint.java @@ -0,0 +1,108 @@ +/* + * + * Copyright (c) 2018-2022, Xilinx, Inc. + * Copyright (c) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, Xilinx Research Labs. + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +/** + * + */ +package com.xilinx.rapidwright.design.xdc; + +import java.util.stream.Stream; + +/** + * Annotates a package pin name with an IO standard + * Created on: Jan 25, 2018 + */ +public class PackagePinConstraint implements Constraint { + + private String portName; + + private String packagePin; + + private String ioStandard; + + public PackagePinConstraint() { + } + + @Override + public PackagePinConstraint clone() { + PackagePinConstraint res = new PackagePinConstraint(portName); + res.setPackagePin(packagePin); + res.setIOStandard(ioStandard); + return res; + } + + public PackagePinConstraint(String portName) { + setPortName(portName); + } + + /** + * @return the name + */ + public String getPackagePin() { + return packagePin; + } + + /** + * @param packagePin the name to set + */ + public void setPackagePin(String packagePin) { + this.packagePin = packagePin; + } + + /** + * @return the ioStandard + */ + public String getIoStandard() { + return ioStandard; + } + + /** + * @param ioStandard the ioStandard to set + */ + public void setIOStandard(String ioStandard) { + this.ioStandard = ioStandard; + } + + public String toString() { + return packagePin + ":" + ioStandard; + } + + public String getPortName() { + return portName; + } + + public Stream asXdc() { + Stream.Builder res = Stream.builder(); + if (packagePin!=null) { + res.add("set_property PACKAGE_PIN "+packagePin+" [get_ports "+portName+"]"); + } + if (ioStandard!=null) { + res.add("set_property IOSTANDARD "+ioStandard+" [get_ports "+portName+"]"); + } + return res.build(); + } + + public void setPortName(String portName) { + this.portName = portName; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java new file mode 100644 index 000000000..5e7aa1b2b --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/UnsupportedConstraintElement.java @@ -0,0 +1,331 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.parser.CellObject; +import com.xilinx.rapidwright.design.xdc.parser.DesignObject; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject; +import com.xilinx.rapidwright.design.xdc.parser.XDCTools; +import org.apache.commons.io.FilenameUtils; +import org.jetbrains.annotations.NotNull; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; +import tcl.lang.TclToken; + +/** + * Stringified representation of a (partial) constraint that our parser does not currently support. + * + * Subclasses specify what type of data a class instance contains + */ +public abstract class UnsupportedConstraintElement { + public static String toXdc(Stream unsupportedConstraintElements) { + return unsupportedConstraintElements.map(e->e.toXdc()).collect(Collectors.joining()); + } + + public static Stream wrapStream(Stream inner, Stream prefix, Stream suffix) { + if (prefix !=null) { + inner = Stream.concat( + prefix, + inner + ); + } + if (suffix != null) { + inner = Stream.concat( + inner, + suffix + ); + } + return inner; + + } + public static Stream wrapStream(Stream inner, String prefix, String suffix) { + if (prefix !=null) { + inner = Stream.concat( + Stream.of(new SyntaxConstraintElement(prefix)), + inner + ); + } + if (suffix != null) { + inner = Stream.concat( + inner, + Stream.of(new SyntaxConstraintElement(suffix)) + ); + } + return inner; + } + + + public static Function> addSpacesBetween(Function> innerFunc) { + final boolean[] first = {true}; + return e->{ + if (first[0]) { + first[0] =false; + return innerFunc.apply(e); + } + return Stream.concat(Stream.of(new SyntaxConstraintElement(" ")), innerFunc.apply(e)); + }; + } + + public static Function> addSpacesBetween() { + final boolean[] first = {true}; + return e->{ + if (first[0]) { + first[0] =false; + return Stream.of(e); + } + return Stream.of(new SyntaxConstraintElement(" "), e); + }; + } + + public static @NotNull List commandToUnsupportedConstraints(Interp interp, TclObject[] objv, EdifCellLookup cellLookup) { + return Arrays.stream(objv) + .flatMap(addSpacesBetween(obj -> objToUnsupportedConstraintElement(interp, obj, cellLookup, false, false))) + .collect(Collectors.toList()); + } + + public abstract String toXdc(); + + public abstract boolean referencesCell(String name); + + + /** + * Unsupported Constraint Element that contains any text or command name except cells + */ + public static class NameConstraintElement extends UnsupportedConstraintElement { + private final String text; + + public NameConstraintElement(String text) { + this.text = text; + } + + @Override + public String toXdc() { + return text; + } + + @Override + public boolean referencesCell(String name) { + return false; + } + + @Override + public String toString() { + return "N<"+toXdc()+">"; + } + } + + /** + * Unsupported Constraint Element that contains Tcl syntax + */ + public static class SyntaxConstraintElement extends UnsupportedConstraintElement { + private final String text; + + public SyntaxConstraintElement(String text) { + this.text = text; + } + + @Override + public String toXdc() { + return text; + } + + @Override + public boolean referencesCell(String name) { + return false; + } + + @Override + public String toString() { + return "S<"+toXdc()+">"; + } + } + + /** + * Unsupported Constraint Element that references a cell name + */ + public static class CellConstraintElement extends UnsupportedConstraintElement{ + private final String cellName; + + public CellConstraintElement(String cellName) { + this.cellName = cellName; + } + + @Override + public String toXdc() { + return cellName; + } + + @Override + public boolean referencesCell(String name) { + return cellName.equals(name); + } + + public String getCellName() { + return cellName; + } + + @Override + public String toString() { + return "C<"+toXdc()+">"; + } + } + + + /** + * Convert a TclObject into UnsupportedConstraintElements + */ + public static Stream objToUnsupportedConstraintElement(Interp interp, TclObject obj, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) { + try { + Optional> designObject = DesignObject.unwrapTclObject(interp, obj, lookup); + if (designObject.isPresent()) { + return designObject.get().toUnsupportedConstraintElement(); + } + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + + Stream inner = Arrays.stream(elements).flatMap(e -> objToUnsupportedConstraintElement(interp, e, lookup, false, applyWildcardsChooseAny)); + return wrapStream(inner, "{","}"); + } + + final boolean[] startsWithCell = {false}; + final T[] cell = (T[]) new Object[]{null}; + + String s = obj.toString(); + List res = new ArrayList<>(); + TclHashIdentifiedObject.unpack(interp, s, partS -> { + res.add(new UnsupportedConstraintElement.NameConstraintElement(partS)); + }, partObj -> { + DesignObject po = DesignObject.requireCastUnwrappedObject(partObj, lookup); + if (po instanceof CellObject && cell[0]==null) { + List cells = ((CellObject) po).getCells(); + if (cells.size()!=1) { + throw new RuntimeException("should have one cell??"); + } + startsWithCell[0] = true; + + T c = cells.iterator().next(); + cell[0] = c; + } else { + po.toUnsupportedConstraintElement().forEach(res::add); + } + }); + + if (replaceProbableCells) { + absorbIntoCells(lookup, res, (T) cell[0], applyWildcardsChooseAny); + } else if (cell[0]!=null) { + res.add(0, new UnsupportedConstraintElement.CellConstraintElement(lookup.getAbsoluteFinalName(cell[0]))); + } + + if (XDCTools.stringNeedsBraces(s)) { + return wrapStream(res.stream(), "{", "}"); + } + return res.stream(); + + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + /** + * If a cell name is followed by text that looks like a subcell reference, absorb that text into the cell name + */ + private static void absorbIntoCells(EdifCellLookup lookup, List res, T cell, boolean applyWildcardsChooseAny) { + if (res.size()!=1 || lookup==null) { + return; + } + String suffix = res.get(0).toXdc(); + if (cell != null) { + if (!suffix.startsWith("/")) { + //Suffix should always start with slash, something unsupported is happening + + //Currently unimplemented! The code is trying to access a sibling cell on the same hierarchy level. + //Need to strip last level off cell, prepend suffix with its name + throw new RuntimeException("Suffix should start with slash!"); + } + suffix = suffix.substring(1); + } else { + cell = lookup.getRoot(); + } + + while (suffix!=null) { + System.out.println("try to expand " + cell + " and " + suffix); + int slashPos = suffix.indexOf("/"); + String currLevel, remaining; + if (slashPos == -1) { + currLevel = suffix; + remaining = null; + } else { + currLevel = suffix.substring(0, slashPos); + remaining = suffix.substring(slashPos+1); + } + System.out.println("decomposed into "+currLevel+" and "+remaining); + T child = lookup.getChild(cell, currLevel); + if (child == null) { + if (applyWildcardsChooseAny) { + List matches = lookup.getChildrenOf(cell) + .filter(x-> FilenameUtils.wildcardMatch(lookup.getRelativeOriginalName(x), currLevel)) + .collect(Collectors.toList()); + if (matches.isEmpty()) { + break; + } + child = matches.get(0); + if (matches.size() > 1) { + System.out.println("chose arbitrary wildcard match for "+currLevel+": "+child); + } + } else { + break; + } + } + if (child == null) { + break; + } + cell = child; + suffix = remaining; + } + + if (cell==lookup.getRoot()) { + return; + } + System.out.println("applying absorption: "+cell+" and "+suffix); + + res.clear(); + res.add(new UnsupportedConstraintElement.CellConstraintElement(lookup.getAbsoluteFinalName(cell))); + if (suffix!=null) { + res.add(new NameConstraintElement("/"+suffix)); + } + + System.out.println(res); + + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java b/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java new file mode 100644 index 000000000..e5833037e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/XDCConstraints.java @@ -0,0 +1,181 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.io.IOException; +import java.io.PrintWriter; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.ArrayList; +import java.util.Comparator; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.parser.XDCTools; +import com.xilinx.rapidwright.util.Pair; + +public class XDCConstraints { + private Map pinConstraints = new HashMap<>(); + private Map clockConstraints = new HashMap<>(); + private Map> cellProperties = new HashMap<>(); + private List> unsupportedConstraints = new ArrayList<>(); + private Map pBlockConstraints = new HashMap<>(); + + + + public XDCConstraints(Map pinConstraints, + Map clockConstraints, + Map> cellProperties, + List> unsupportedConstraints) { + this.pinConstraints = pinConstraints; + this.clockConstraints = clockConstraints; + this.cellProperties = cellProperties; + this.unsupportedConstraints = unsupportedConstraints; + } + + private static > Map cloneMap(Map map) { + return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e->e.getValue().clone())); + } + private static Map> cloneStringMap(Map> map) { + return map.entrySet().stream().collect(Collectors.toMap(Map.Entry::getKey, e->new HashMap<>(e.getValue()))); + } + + private static List> cloneList(List> list) { + return list.stream().map(ArrayList::new).collect(Collectors.toList()); + } + public XDCConstraints clone() { + return new XDCConstraints( + cloneMap(pinConstraints), + cloneMap(clockConstraints), + cloneStringMap(cellProperties), + cloneList(unsupportedConstraints) + ); + } + + public XDCConstraints() { + } + + public Map getPinConstraints() { + return pinConstraints; + } + + public Map getClockConstraints() { + return clockConstraints; + } + + public Map> getCellProperties() { + return cellProperties; + } + + public List> getUnsupportedConstraints() { + return unsupportedConstraints; + } + + public Map getPBlockConstraints() { + return pBlockConstraints; + } + + private static Stream cellPropsToXdc(int counter, String cell, Map properties) { + if (properties.size()<2) { + return properties.entrySet().stream().map(propToValue-> + "set_property " + propToValue.getKey() + " " + XDCTools.braceEnclosedIfNeeded(propToValue.getValue()) + " [get_cells {" + cell + "}]" + ); + } + + String varName = "rw_getcell_"+counter; + String initVarLine = "set "+varName+ " [get_cells {" + cell + "}]"; + return Stream.concat( + Stream.of(initVarLine), + properties.entrySet().stream().sorted(Map.Entry.comparingByKey()).map(propToValue-> + "set_property " + propToValue.getKey() + " " + XDCTools.braceEnclosedIfNeeded(propToValue.getValue()) + " $"+varName + ) + ); + } + + public Stream getAllAsXdc() { + Stream clocks = clockConstraints.values().stream().sorted(Comparator.comparing(ClockConstraint::getPortName)).map(ClockConstraint::asXdc); + Stream unsupported = unsupportedConstraints.stream().map(e->UnsupportedConstraintElement.toXdc(e.stream())); + + AtomicInteger varCounter = new AtomicInteger(); + Stream cellProps = cellProperties.entrySet().stream().sorted(Map.Entry.comparingByKey()).flatMap( + cellToProps -> cellPropsToXdc(varCounter.getAndIncrement(), cellToProps.getKey(), cellToProps.getValue())); + Stream pinConstrs = pinConstraints.values().stream().sorted(Comparator.comparing(PackagePinConstraint::getPortName)).flatMap(PackagePinConstraint::asXdc); + Stream pblockConstrs = pBlockConstraints.values().stream().sorted(Comparator.comparing(pBlockConstraint -> pBlockConstraint.getPblock().getName())).flatMap(PBlockConstraint::asXdc); + + + return Stream.of(clocks, cellProps, pinConstrs, pblockConstrs, unsupported).flatMap(e->e); + } + + public void writeToFile(Path file) { + try (PrintWriter pw = new PrintWriter(Files.newBufferedWriter(file))) { + getAllAsXdc().forEach(pw::println); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + private List rewriteUnsupported(List list, UnaryOperator cellNameMapper) { + return list.stream().map(elem -> { + if (!(elem instanceof UnsupportedConstraintElement.CellConstraintElement)) { + return elem; + } + UnsupportedConstraintElement.CellConstraintElement cellElem = (UnsupportedConstraintElement.CellConstraintElement) elem; + return new UnsupportedConstraintElement.CellConstraintElement(cellNameMapper.apply(cellElem.getCellName())); + }).collect(Collectors.toList()); + } + + public XDCConstraints duplicateWithReplacedCellNames(UnaryOperator cellNameMapper) { + Map> rewrittenProperties = cellProperties.entrySet().stream() + .flatMap(e -> e.getValue().entrySet().stream().map(e2 -> new Pair<>(e.getKey(), e2))) + .collect(Collectors.groupingBy( + e -> cellNameMapper.apply(e.getFirst()), Collectors.toMap( + e->e.getSecond().getKey(), + e->e.getSecond().getValue(), + (a,b) -> { + if (!a.equals(b)) { + throw new IllegalStateException("Cannot merge values "+a+" and "+b); + } + return a; + } + ) + )); + List> rewrittenUnsupported = unsupportedConstraints.stream() + .map(l->rewriteUnsupported(l, cellNameMapper)).collect(Collectors.toList()); + return new XDCConstraints( + pinConstraints, + clockConstraints, + rewrittenProperties, + rewrittenUnsupported + ); + } + + public boolean isCellReferencedInConstraints(String name) { + return unsupportedConstraints.stream().anyMatch(elements -> elements.stream().anyMatch(elem -> elem.referencesCell(name))); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/XDCParser.java b/src/com/xilinx/rapidwright/design/xdc/XDCParser.java new file mode 100644 index 000000000..146b00dee --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/XDCParser.java @@ -0,0 +1,265 @@ +/* + * Copyright (c) 2017-2022, Xilinx, Inc. + * Copyright (c) 2022, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, Xilinx Research Labs. + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.io.IOException; +import java.io.OutputStream; +import java.io.UncheckedIOException; +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.xdc.parser.AddCellsToPblockCommand; +import com.xilinx.rapidwright.design.xdc.parser.CreateClockCommand; +import com.xilinx.rapidwright.design.xdc.parser.CreatePBlockCommand; +import com.xilinx.rapidwright.design.xdc.parser.DebugDumpCommand; +import com.xilinx.rapidwright.design.xdc.parser.DumpObjsCommand; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.GetCellsCommand; +import com.xilinx.rapidwright.design.xdc.parser.ObjType; +import com.xilinx.rapidwright.design.xdc.parser.ObjectGetterCommand; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.ResizePBlockCommand; +import com.xilinx.rapidwright.design.xdc.parser.SetPropertyCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedGetterCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedIfCommand; +import com.xilinx.rapidwright.design.xdc.parser.UnsupportedSetterCommand; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.util.FileTools; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.Namespace; +import tcl.lang.Resolver; +import tcl.lang.TCL; +import tcl.lang.TclException; +import tcl.lang.TclObject; +import tcl.lang.Var; +import tcl.lang.WrappedCommand; + + +/** + * Parses an XDC file for a limited subset of constraint types. It uses a full Tcl interpreter, so + * complex language constructs are possible. + *

+ * If the Parser encounters unsupported commands or command options, the parsed TCL code is converted back to strings + * and returned as UnsupportedConstraintElements. + *

+ * The parser can match cell references in constraints to modified designs. Users can supply their own + * {@link EdifCellLookup} to specify how to rewrite constraints. + *

+ * For a regular, non-rewritten netlist, users should use {@link RegularEdifCellLookup}. + *

+ * If no netlist is present, a null lookup will leave more complex get_cells calls unevaluated + * as unsupported constraints. + *

+ * Created on: Jul 27, 2015 + */ +public class XDCParser { + + + /** + * Create a tcl interpreter with XDC parsing commands + * + * Cell references are intentionally never disposed + * (see {@link com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject}), so lifetime of this interpreter + * should be limited. + * + * @param constraints Constraints object to output to + * @param dev Device + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return interpreter + * @param lookup's cell representation + */ + public static Interp makeTclInterp(XDCConstraints constraints, Device dev, EdifCellLookup cellLookup) { + Interp interp = new Interp(); + interp.createCommand("set_property", new SetPropertyCommand<>(constraints, dev, cellLookup)); + interp.createCommand("current_design", new ObjectGetterCommand(cellLookup, false,ObjType.Design)); + + if (cellLookup!=null) { + interp.createCommand("get_cells", new GetCellsCommand<>(cellLookup)); + } else { + interp.createCommand("get_cells", new ObjectGetterCommand(cellLookup, true, ObjType.Cell)); + } + interp.createCommand("get_ports", new ObjectGetterCommand(cellLookup, true, ObjType.Port)); + interp.createCommand("get_pins", new ObjectGetterCommand(cellLookup, true, ObjType.Pin)); + interp.createCommand("get_pblocks", new ObjectGetterCommand(cellLookup, true, ObjType.PBlock)); + interp.createCommand("create_clock", new CreateClockCommand(constraints, cellLookup)); + + interp.createCommand("create_pblock", new CreatePBlockCommand(constraints)); + interp.createCommand("add_cells_to_pblock", new AddCellsToPblockCommand<>(constraints, cellLookup)); + interp.createCommand("resize_pblock", new ResizePBlockCommand(constraints, dev)); + + interp.createCommand("dump_objs", new DumpObjsCommand(cellLookup)); + + UnsupportedSetterCommand unsupportedSetterCommand = new UnsupportedSetterCommand(constraints, cellLookup, null); + interp.createCommand("set_false_path", unsupportedSetterCommand); + interp.createCommand("set_input_delay", unsupportedSetterCommand); + interp.createCommand("set_output_delay", unsupportedSetterCommand); + interp.createCommand("set_max_delay", unsupportedSetterCommand); + interp.createCommand("set_bus_skew", unsupportedSetterCommand); + interp.createCommand("current_instance", unsupportedSetterCommand); + interp.createCommand("set_clock_groups", unsupportedSetterCommand); + interp.createCommand("set_input_jitter", unsupportedSetterCommand); + interp.createCommand("set_clock_uncertainty", unsupportedSetterCommand); + + UnsupportedGetterCommand unsupportedGetterCommand = new UnsupportedGetterCommand(cellLookup); + interp.createCommand("get_clocks", unsupportedGetterCommand); + interp.createCommand("get_property", unsupportedGetterCommand); + interp.createCommand("get_nets", unsupportedGetterCommand); + interp.createCommand("all_fanout", unsupportedGetterCommand); + interp.createCommand("filter", unsupportedGetterCommand); + interp.createCommand("get_bels", unsupportedGetterCommand); + interp.createCommand("get_wires", unsupportedGetterCommand); + interp.createCommand("get_nodes", unsupportedGetterCommand); + UnsupportedGetterCommand.replaceInInterp(interp, cellLookup, "llength"); + UnsupportedGetterCommand.replaceInInterp(interp, cellLookup, "expr"); + + UnsupportedIfCommand.replaceInInterp(interp, constraints, cellLookup); + UnsupportedSetterCommand.replaceInInterp(interp, constraints, cellLookup, "foreach"); + + interp.createCommand("debugDump", new DebugDumpCommand()); + + //We need to allow [*] and bracketed numbers (e.h. [1] ) as suffix on quoted strings, so we need to hook into the command lookup + //This actually mirrors Vivado's behaviour very closely! Just enter * on Vivado's tcl prompt to see + interp.createCommand("wrapInput", new Command() { + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + interp.setResult("["+objv[0]+"]"); + } + }); + try { + WrappedCommand wrapInputCmd = Objects.requireNonNull(Namespace.findCommand(interp, "wrapInput", null, 0)); + interp.addInterpResolver("", new Resolver() { + @Override + public WrappedCommand resolveCmd(Interp interp, String name, Namespace context, int flags) throws TclException { + if (name.matches("\\d+") || name.equals("*")) { + return wrapInputCmd; + } + return null; + } + + @Override + public Var resolveVar(Interp interp, String name, Namespace context, int flags) throws TclException { + return null; + } + }); + } catch (TclException e) { + throw new RuntimeException(e); + } + + return interp; + } + + /** + * Parse XDC + * @param dev the device + * @param lines XDC content + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return parsed constraints + */ + public static XDCConstraints parseXDC(Device dev, List lines, EdifCellLookup cellLookup) { + XDCConstraints constraints = new XDCConstraints(); + + + Interp interp = makeTclInterp(constraints, dev, cellLookup); + try { + String data = String.join("\n", lines); + try { + interp.eval(data); + } catch (TclException ex) { + int code = ex.getCompletionCode(); + switch (code) { + case TCL.ERROR: + throw new RuntimeException(interp.getResult().toString()+" in line "+interp.getErrorLine(), ex); + case TCL.BREAK: + throw new RuntimeException( + "invoked \"break\" outside of a loop", ex); + case TCL.CONTINUE: + throw new RuntimeException( + "invoked \"continue\" outside of a loop", ex); + default: + throw new RuntimeException( + "command returned bad error code: " + code, ex); + } + } + } finally { + interp.dispose(); + } + + return constraints; + } + + + + /** + * @param fileName Name of the XDC file to parse + * @param dev the design + * @param cellLookup the cell lookup (see {@link XDCParser class level documentation} for parameter details) + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev, EdifCellLookup cellLookup){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), cellLookup); + } + + /** + * @param fileName Name of the XDC file to parse + * @param dev the design + * @param netlist optional netlist to enable more advanced get_cells calls + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev, EDIFNetlist netlist){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), new RegularEdifCellLookup(netlist)); + } + + /** + * @param fileName Name of the XDC file to parse + * @param design The design + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Design design){ + return parseXDC(design.getDevice(), FileTools.getLinesFromTextFile(fileName), new RegularEdifCellLookup(design.getNetlist())); + } + + /** + * @param fileName Name of the XDC file to parse + * @param dev the device + * @return A map of port names to package pin information. + */ + public static XDCConstraints parseXDC(String fileName, Device dev){ + return parseXDC(dev, FileTools.getLinesFromTextFile(fileName), null); + } + + public static void writeXDC(List constraints, OutputStream out){ + if(constraints == null) return; + try { + for(String s : constraints){ + out.write(s.getBytes()); + out.write('\n'); + } + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java new file mode 100644 index 000000000..61919d43e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/AddCellsToPblockCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class AddCellsToPblockCommand implements Command { + + private final XDCConstraints constraints; + private final EdifCellLookup cellLookup; + + public AddCellsToPblockCommand(XDCConstraints constraints, EdifCellLookup cellLookup) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + + if (objv.length!=3) { + throw new RuntimeException("wrong argument count"); + } + DesignObject pblockDO = DesignObject.unwrapTclObject(interp, objv[1], cellLookup).orElseThrow(()->new RuntimeException("expected designObject")); + if (! (pblockDO instanceof NameDesignObject)) { + throw new RuntimeException("wrong argument type: "+pblockDO.getClass()); + } + NameDesignObject pblock = (NameDesignObject)pblockDO; + if (pblock.getType()!=ObjType.PBlock) { + throw new RuntimeException("wrong argument type: "+pblock.getType()); + } + + String pblockName = pblock.requireOneObject(); + PBlockConstraint pBlockConstraint = Objects.requireNonNull( + constraints.getPBlockConstraints().get(pblockName), + ()->"Did not find pblock "+pblockName + ); + + if (objv[2].toString().equals("-top")) { + pBlockConstraint.getCells().add(""); + return; + } + DesignObject cellsDO = DesignObject.requireUnwrapTclObject(interp, objv[2], cellLookup); + + if (cellsDO instanceof UnsupportedCmdResult) { + + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + return; + } else if (cellsDO instanceof CellObject) { + for (T cell : ((CellObject) cellsDO).getCells()) { + String finalCell = cellLookup.getAbsoluteFinalName(cell); + pBlockConstraint.getCells().add(finalCell); + } + return; + } + + if (!(cellsDO instanceof NameDesignObject)) { + throw new RuntimeException("expected NameDesignObject but got "+cellsDO.getClass()+": "+cellsDO); + } + NameDesignObject cellsNDO = (NameDesignObject) cellsDO; + if (cellsNDO.getType()!=ObjType.Cell) { + throw new RuntimeException("expected CellObject but got "+cellsNDO.getType()); + } + for (String object : cellsNDO.getObjects()) { + pBlockConstraint.getCells().add(object); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java new file mode 100644 index 000000000..16e38774e --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CellObject.java @@ -0,0 +1,103 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; + +/** + * Tcl representation of a list of cells + * @param the cell representation + */ +public class CellObject extends DesignObject { + private final List cells; + private final EdifCellLookup cellLookup; + + public CellObject(List cells, EdifCellLookup cellLookup) { + this.cells = cells; + this.cellLookup = cellLookup; + } + + public String toXdc() { + if (cells.size()==1) { + String onlyCell = cellLookup.getAbsoluteFinalName(cells.get(0)); + if (cells.size() == 1 && !onlyCell.contains("[")) { + return "[get_cells " + onlyCell + "]"; + } + } + + return "[get_cells {" + cells.stream().map(cellLookup::getAbsoluteFinalName).collect(Collectors.joining(" ")) + "}]"; + } + + @Override + public Stream toUnsupportedConstraintElement() { + if (cells.isEmpty()) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ["), + new UnsupportedConstraintElement.NameConstraintElement("list"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]]") + ); + } + if (cells.size()==1) { + String onlyCell = cellLookup.getAbsoluteFinalName(cells.get(0)); + if (!onlyCell.contains("[")) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.CellConstraintElement(onlyCell), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + ); + } + } + + Stream cellStream = cells.stream() + .map(cellLookup::getAbsoluteFinalName) + .map(UnsupportedConstraintElement.CellConstraintElement::new) + .flatMap(UnsupportedConstraintElement.addSpacesBetween()); + return UnsupportedConstraintElement.wrapStream(cellStream, Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement("get_cells"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.SyntaxConstraintElement("{") + ), Stream.of( + + new UnsupportedConstraintElement.SyntaxConstraintElement("}"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + )); + } + + public List getCells() { + return cells; + } + + @Override + public String toString() { + return cells.toString(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java new file mode 100644 index 000000000..c2fd9d581 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CreateClockCommand.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.List; + +import com.xilinx.rapidwright.design.xdc.ClockConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command: create_clock + */ +public class CreateClockCommand implements Command { + + private final XDCConstraints constraints; + private final EdifCellLookup cellLookup; + + public CreateClockCommand(XDCConstraints constraints, EdifCellLookup cellLookup) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + List ports = null; + String period = null; + String clockName = null; + for (int i = 1; i < objv.length; i++) { + if (objv[i].toString().startsWith("-")) { + switch (objv[i].toString()) { + case "-period": + period = objv[++i].toString(); + break; + case "-name": + clockName = objv[++i].toString(); + + break; + case "-waveform": + //Just skip the waveform specification + ++i; + break; + default: + throw new RuntimeException("expected -name | -period | -waveform but got " + objv[i]); + } + } else { + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, objv[i], cellLookup); + if (obj instanceof UnsupportedCmdResult) { + + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + return; + } + if (!(obj instanceof NameDesignObject) || (((NameDesignObject) obj).getType() != ObjType.Port && ((NameDesignObject) obj).getType() != ObjType.Pin)) { + throw new RuntimeException("expected port or pin but got " + obj.toXdc()+" (a "+obj.getClass()); + } + ports = ((NameDesignObject) obj).getObjects(); + + if (i + 1 != objv.length) { + throw new RuntimeException("Extra elements after port name"); + } + } + } + if (ports == null) { + throw new RuntimeException("did not have ports!"); + } + for (String port : ports) { + constraints.getClockConstraints().put(port, new ClockConstraint(clockName, Double.parseDouble(period), port)); + } + + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java new file mode 100644 index 000000000..d40b371eb --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/CreatePBlockCommand.java @@ -0,0 +1,55 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Collections; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class CreatePBlockCommand implements Command { + private final XDCConstraints constraints; + + public CreatePBlockCommand(XDCConstraints constraints) { + this.constraints = constraints; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (objv.length!=2) { + throw new RuntimeException("wrong argument count"); + } + String pBlockName = objv[1].toString(); + if (constraints.getPBlockConstraints().containsKey(pBlockName)) { + throw new RuntimeException("duplicate pblock name: "+pBlockName); + } + PBlockConstraint constraint = new PBlockConstraint(); + constraint.getPblock().setName(pBlockName); + constraints.getPBlockConstraints().put(pBlockName, constraint); + NameDesignObject res = new NameDesignObject<>(ObjType.PBlock, Collections.singletonList(pBlockName)); + interp.setResult(TclHashIdentifiedObject.createReflectObject(interp, NameDesignObject.class, res)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java new file mode 100644 index 000000000..2ed865249 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DebugDumpCommand.java @@ -0,0 +1,71 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Tcl command to dump the arguments' internal representation + */ +public class DebugDumpCommand implements Command { + @Override + public void cmdProc(Interp interp, TclObject[] tclObjects) throws TclException { + debugDump(interp, tclObjects); + } + + public static void debugDump(Interp interp, TclObject[] tclObjects) { + for (int i = 0; i < tclObjects.length; i++) { + System.out.print("index "+i+": "); + debugDump(tclObjects[i], interp, " "); + } + } + + public static void debugDump(TclObject tclObject, Interp interp, String indent) { + if (tclObject.getInternalRep() instanceof TclList) { + System.out.println("list"); + try { + TclObject[] items = TclList.getElements(interp, tclObject); + for (int i = 0; i < items.length; i++) { + System.out.print(indent+" index "+i+": "); + debugDump(items[i], interp,indent+" "); + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } else if (tclObject.getInternalRep() instanceof ReflectObject) { + try { + Object refl = ReflectObject.get(interp, tclObject); + System.out.println("reflect object "+tclObject+" "+refl); + } catch (TclException e) { + throw new RuntimeException(e); + } + } else { + System.out.println(tclObject.getInternalRep().getClass().getName()+": "+tclObject); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java new file mode 100644 index 000000000..833ff4717 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DesignObject.java @@ -0,0 +1,136 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collector; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Base class for any object that will be referenced from Tcl + * @param Representation of Cells + */ +public abstract class DesignObject { + /** + * Casting helper: + *

    + *
  • If the object is an instance of the cell class (T), casts and wraps it into a DesignObject o
  • + *
  • Casts DesignObjects
  • + *
the lookup's cell representation + */ + public static DesignObject requireCastUnwrappedObject(Object obj, EdifCellLookup lookup) { + if (lookup != null && lookup.getCellClass().isInstance(obj)) { + return new CellObject(Collections.singletonList(lookup.castCellInst(obj)), lookup); + } + return (DesignObject) obj; + } + + /** + * Try to convert a TclObject into the DesignObject it represents + * @param interp the interpreter + * @param obj the object to unwrap + * @param lookup the cell lookup + * @return the design object or an empty optional if it isn't one + * @param the lookup's cell representation + * @throws TclException + */ + public static Optional> unwrapTclObject(Interp interp, TclObject obj, EdifCellLookup lookup) throws TclException { + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + if (!Arrays.stream(elements).allMatch(e-> { + try { + return e.getInternalRep() instanceof ReflectObject &&lookup.getCellClass().isInstance(ReflectObject.get(interp,e)); + } catch (TclException ex) { + throw new RuntimeException(ex); + } + })) { + return Optional.empty(); + } + List cells = Arrays.stream(elements).map(x -> { + try { + return lookup.castCellInst(ReflectObject.get(interp, x)); + } catch (TclException e) { + throw new RuntimeException(e); + } + }).collect(Collectors.toList()); + return Optional.of(new CellObject<>(cells, lookup)); + } + if (obj.getInternalRep() instanceof ReflectObject) { + return Optional.of(requireCastUnwrappedObject(ReflectObject.get(interp, obj), lookup)); + } + return Optional.empty(); + } + + private static String objDebugInfo(Interp interp, TclObject obj) { + try { + if (obj.getInternalRep() instanceof ReflectObject) { + Object ref = ReflectObject.get(interp, obj); + return " is reflect object type " + ref.getClass() + ": " + ref; + } else if (obj.getInternalRep() instanceof TclList) { + return " is list: ["+ Arrays.stream(TclList.getElements(interp, obj)).map(o->objDebugInfo(interp,o)).collect(Collectors.joining(", "))+"]"; + } else { + return " internal rep: "+obj.getInternalRep().getClass(); + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + /** + * Convert a TclObject into the DesignObject it represents or throw if it isn't one + * @param interp the interpreter + * @param obj the object to unwrap + * @param lookup the cell lookup + * @return the design object + * @param the lookup's cell representation + * @throws TclException + */ + public static DesignObject requireUnwrapTclObject(Interp interp, TclObject obj, EdifCellLookup lookup) throws TclException { + return unwrapTclObject(interp, obj, lookup) + .orElseThrow(()-> { + String moreInfo = objDebugInfo(interp, obj); + return new IllegalArgumentException("expected DesignObject but got " + obj + moreInfo); + }); + + } + + public abstract String toXdc(); + + public abstract Stream toUnsupportedConstraintElement(); +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java new file mode 100644 index 000000000..cb99515d6 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/DumpObjsCommand.java @@ -0,0 +1,53 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Optional; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command to dump the arguments' as unwrapped DesignObjects + */ +public class DumpObjsCommand implements Command { + private final EdifCellLookup cellLookup; + public DumpObjsCommand(EdifCellLookup cellLookup) { + this.cellLookup = cellLookup; + } + + + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + TclObject tclObject = objv[1]; + Optional obj = DesignObject.unwrapTclObject(interp, tclObject, cellLookup); + if (obj.isPresent()) { + System.out.println(obj.get()); + } else { + System.out.println("no java obj: "+tclObject); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java b/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java new file mode 100644 index 000000000..47690418c --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/EdifCellLookup.java @@ -0,0 +1,166 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.Set; +import java.util.function.BiPredicate; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.IntStream; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFTools; +import org.apache.commons.io.FilenameUtils; +import org.jetbrains.annotations.NotNull; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Lookup Cells for use in the XDC Parser. + *

+ * Cells can have different names in the source XDC versus the actual objects in the design. This allows rewriting + * constraints to match a possibly restructured design. Cells have an original and a final name to + * support this. + *

+ * A derived class can specify how to do this mapping. {@link RegularEdifCellLookup} operates on EDIF netlists without + * rewriting. + * + * @param Representation of Cells + */ +public abstract class EdifCellLookup { + + + /** + * Convert a cell to a Tcl Object + * @param interp the interpreter + * @param cell the cell + * @return Tcl object representing the cell + * @throws TclException + */ + @NotNull + public TclObject toReflectObj(Interp interp, T cell) throws TclException { + return TclHashIdentifiedObject.createReflectObject(interp, getCellClass(), cell); + } + + /** + * Stream of the tree of cells rooted at the current cell + * @param ci base of the tree + * @return stream of all children + */ + public Stream allCellInsts(T ci) { + Stream self = Stream.of(ci); + Stream directChildren = getChildrenOf(ci);; + Stream allChildren = directChildren.flatMap(this::allCellInsts); + return Stream.concat(self, allChildren); + } + + public Stream getHierCellInstsFromWildcardName(String cellName) { + if (!cellName.contains("*")) { + T res = getInstFromOriginalName(cellName); + if (res == null) { + return Stream.empty(); + } + return Stream.of(res); + } + return getChildBySomeAbsoluteName(cellName, (s, item) -> FilenameUtils.wildcardMatch(getRelativeOriginalName(item), s)); + } + + public Stream getHierCellInstsFromRegexpName(String cellName) { + return getChildBySomeAbsoluteName(cellName, (s, item) -> getRelativeOriginalName(item).matches(s)); + } + + public abstract T getInstFromOriginalName(String cellName); + + public abstract Stream getChildrenOf(T f); + + public abstract T getChild(T cell, String name); + + private Stream getChildBySomeAbsoluteNameWorker(String[] parts, int level, T current, BiPredicate filter) { + if (level==parts.length) { + return Stream.of(current); + } + return getChildrenOf(current) + .filter(child -> filter.test(parts[level], child)) + .flatMap(c->getChildBySomeAbsoluteNameWorker(parts, level+1, c, filter)); + } + + + public Stream getChildBySomeAbsoluteName(String name, BiPredicate filter) { + + if (name.isEmpty()) return Stream.of(getRoot()); + + String[] parts = name.split(EDIFTools.EDIF_HIER_SEP); + + // Sadly, cells can be named 'fred/' instead of 'fred', this code handles this situation + if (name.charAt(name.length()-1) == '/') { + parts[parts.length-1] = parts[parts.length-1] + EDIFTools.EDIF_HIER_SEP; + } + + return getChildBySomeAbsoluteNameWorker(parts, 0, getRoot(), filter); + } + + + + + public Predicate getAbsoluteRegexFilter(String cellNames) { + return eci -> getAbsoluteOriginalName(eci).matches(cellNames); + } + + public Predicate getAbsoluteWildcardFilter(String cellNames) { + return eci -> FilenameUtils.wildcardMatch(getAbsoluteOriginalName(eci), cellNames); + } + + public Stream getAllCellInsts() { + return allCellInsts(getRoot()); + } + + public abstract EDIFHierCellInst toEdifHierCellInst(T cell); + + public Predicate getCellTypeFilter(Set values) { + return ci -> values.contains(getCellType(ci)); + } + + public abstract T getRoot(); + + public abstract String getAbsoluteFinalName(T current); + public abstract String getRelativeFinalName(T current); + public abstract String getAbsoluteOriginalName(T current); + public abstract String getRelativeOriginalName(T current); + + public abstract String getCellType(T current); + + public abstract Class getCellClass(); + + public T castCellInst(Object obj) { + return (T)getCellClass().cast(obj); + } + + public abstract T getInstFromFinalName(String s); + + public String getOriginalName(String s) { + return getAbsoluteOriginalName(getInstFromFinalName(s)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java new file mode 100644 index 000000000..2cfe93d03 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/GetCellsCommand.java @@ -0,0 +1,310 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.io.IOException; +import java.io.PrintStream; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Objects; +import java.util.Set; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.util.Pair; +import tcl.lang.Command; +import tcl.lang.ExprValue; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; + +/** + * Tcl command: get_cells + */ +public class GetCellsCommand implements Command { + + private final EdifCellLookup cellLookup; + + public GetCellsCommand(EdifCellLookup cellLookup) { + this.cellLookup = cellLookup; + } + + @Override + public void cmdProc(Interp interp, TclObject[] argv) throws TclException { + boolean hierFlag = false; + boolean regexpFlag = false; + TclObject filter = null; + TclObject cellName = null; + + for (int i = 1; i < argv.length; i++) { + String item = argv[i].toString(); + switch (item) { + case "-hier": + case "-hierarchical": + hierFlag = true; + break; + case "-filter": + filter = argv[++i]; + break; + case "-regexp": + regexpFlag = true; + break; + case "-quiet": + //Ignore + break; + case "-of_objects": + case "-of": + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, cellLookup, false, false)); + return; + default: + if (cellName != null) { + String s = new UnsupportedCmdResult<>(interp, argv, cellLookup, false, false).toString(); + throw new RuntimeException("Duplicate cell-name string or unsupported flag: "+s); + } + cellName = argv[i]; + break; + } + + } + + if (cellName != null && !regexpFlag && filter == null && !hierFlag) { + simpleGetCells(interp, cellName); + } else { + String cellNameStr = cellName != null ? TclHashIdentifiedObject.unpackAsString(interp, cellName.toString(), cellLookup) : null; + if (!complexGetCells(interp, hierFlag, regexpFlag, cellNameStr, filter, argv)) { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, cellLookup, false, false)); + } + } + } + + /** + * This only supports OR-in together different == clauses. + * + * @param interp + * @param expr + * @return + */ + private static Map> parseFilterExpression(Interp interp, String expr) { + //Remove unneeded parens + if (expr.matches("\\([^)]*\\)")) { + expr = expr.substring(1, expr.length() - 1); + } + if (expr.contains("&&") || expr.contains("(")) { + try { + ExprValue exprValue = interp.evalExpression(expr.toString()); + } catch (TclException e) { + //Ignore + } + return null; //Give up + } + String[] split = expr.split("\\s*\\|\\|\\s*"); + + Map> oredClauses = new HashMap<>(); + for (String s : split) { + String[] clause = s.split("\\s*==\\s*"); + if (clause.length != 2) { + return null; //Give up + } + oredClauses.computeIfAbsent(clause[0], x -> new HashSet<>()).add(clause[1]); + } + + Set refname = oredClauses.get("REF_NAME"); + Set origrefname = oredClauses.get("ORIG_REF_NAME"); + if (!Objects.equals(refname, origrefname)) { + throw new RuntimeException("expression too complex: " + expr); + } + oredClauses.remove("ORIG_REF_NAME"); + + return oredClauses; + } + + private static int cellDebugCallCout = 0; + + private boolean complexGetCells(Interp interp, boolean hierFlag, boolean regexFlag, String cellNames, TclObject filterArg, TclObject[] argv) throws TclException { + Stream candidateSupplier = null; + List, Function>> filter = new ArrayList<>(); + + if (!hierFlag) { + return false; + } + + if (filterArg != null) { + Map> s = parseFilterExpression(interp, filterArg.toString()); + if (s==null) { + return false; + } + + if (s.size() > 1) { + return false; + } + String entryType = s.keySet().iterator().next(); + Set values = s.get(entryType); + if (Objects.equals(entryType, "REF_NAME")) { + filter.add(new Pair<>(cellLookup.getCellTypeFilter(values), e -> { + return "checked if " + cellLookup.getCellType(e) + " is in " + values; + })); + } else if (Objects.equals(entryType, "PARENT")) { + List roots = values.stream() + .map(r-> TclHashIdentifiedObject.unpackAsString(interp, r, cellLookup)) + .flatMap(cellLookup::getHierCellInstsFromWildcardName) + .collect(Collectors.toList()); + candidateSupplier = roots.stream().flatMap(cellLookup::getChildrenOf); + System.out.println("Supplying candidates from direct children of " + roots + ". raw roots: " + values); + } + } + + if (cellNames != null) { + if (regexFlag) { + if (candidateSupplier == null) { + candidateSupplier = cellLookup.getHierCellInstsFromRegexpName(cellNames); + } else { + filter.add(new Pair<>(cellLookup.getAbsoluteRegexFilter(cellNames), eci -> "Checking if " + eci + " matches regex " + cellNames)); + } + } else { + if (candidateSupplier == null) { + candidateSupplier = cellLookup.getHierCellInstsFromWildcardName(cellNames); + } else { + filter.add(new Pair<>(cellLookup.getAbsoluteWildcardFilter(cellNames), eci -> "Checking if " + eci + " matches wildcard " + cellNames)); + } + } + } + + if (candidateSupplier == null) { + System.out.println("Supplying candidates from all cells"); + candidateSupplier = cellLookup.getAllCellInsts(); + } + boolean debugFiltering = false; + List cells; + if (!debugFiltering) { + Stream resultStream = candidateSupplier; + for (Pair,?> filterStage : filter) { + resultStream = resultStream.filter(filterStage.getFirst()); + } + cells = resultStream.collect(Collectors.toList()); + } else{ + Path debugFile = Paths.get("./debugCellFiltering/"+(cellDebugCallCout++)+".txt"); + try { + Files.createDirectories(debugFile.getParent()); + } catch (IOException e) { + throw new RuntimeException(e); + } + System.out.println("Writing debug info to "+debugFile); + try (PrintStream ps = new PrintStream(Files.newOutputStream(debugFile))) { + ps.println("Debugging for "+ Arrays.toString(argv)); + + ps.println("hierFlag = " + hierFlag); + ps.println("regexFlag = " + regexFlag); + ps.println("cellNames = " + cellNames); + ps.println("filterArg = " + filterArg); + + cells = new ArrayList<>(); + Iterable iterable = candidateSupplier::iterator; + for (T eci : iterable) { + ps.println("checking eci: " + eci); + boolean in = true; + for (int i = 0; i < filter.size(); i++) { + Predicate f = filter.get(i).getFirst(); + boolean test = f.test(eci); + if (!test) { + ps.println("\tFiltered out by filter " + i + ": " + filter.get(i).getSecond().apply(eci)); + in = false; + break; + } else { + ps.println("\taccepted by filter " + i + ": " + filter.get(i).getSecond().apply(eci)); + } + } + if (in) { + ps.println("\tadding!"); + cells.add(eci); + } + } + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + TclObject list = TclList.newInstance(); + if (cells.isEmpty()) { + System.out.println("did not find cell for call "+Arrays.toString(argv)); + } else { + cells.stream().sorted(Comparator.comparing(cellLookup::getAbsoluteOriginalName)) + .forEach(cell-> { + try { + TclList.append(interp, list, cellLookup.toReflectObj(interp, cell)); + } catch (TclException e) { + throw new RuntimeException(e); + } + }); + } + interp.setResult(list); + return true; + } + + + private void simpleGetCells(Interp interp, TclObject cellName) throws TclException { + if (cellName.getInternalRep() instanceof TclList) { + System.out.println("started get cells list"); + TclObject[] elements = TclList.getElements(interp, cellName); + String[] cellNames = new String[elements.length]; + for (int i=0; i cells = cellLookup.getHierCellInstsFromWildcardName(cellName).collect(Collectors.toList()); + if (cells.isEmpty()) { + System.out.println("did not find cell for " + cellName); + } else { + cells.sort(Comparator.comparing(cellLookup::getAbsoluteOriginalName)); + for (T cell : cells) { + TclList.append(interp, list, cellLookup.toReflectObj(interp, cell)); + } + } + } + interp.setResult(list); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java new file mode 100644 index 000000000..fa219ff99 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/NameDesignObject.java @@ -0,0 +1,111 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; + +public class NameDesignObject extends DesignObject { + private final ObjType type; + private final List objects; + + public NameDesignObject(ObjType type, List objects) { + this.type = type; + this.objects = objects; + } + + @Override + public String toString() { + return "DesignObjects{" + + "type='" + type + '\'' + + ", objects=" + objects + + '}'; + } + + public String toXdc() { + return UnsupportedConstraintElement.toXdc(toUnsupportedConstraintElement()); + } + + @Override + public Stream toUnsupportedConstraintElement() { + if (objects == null) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + ); + } + + if (objects.isEmpty()) { + return Stream.of( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ["), + new UnsupportedConstraintElement.NameConstraintElement("list"), + new UnsupportedConstraintElement.SyntaxConstraintElement("]]") + ); + + } + + boolean braces = objects.size()!=1 || objects.stream().anyMatch(XDCTools::stringNeedsBraces); + + List before = new ArrayList<>(Arrays.asList( + new UnsupportedConstraintElement.SyntaxConstraintElement("["), + new UnsupportedConstraintElement.NameConstraintElement(type.getXdcCommand()), + new UnsupportedConstraintElement.SyntaxConstraintElement(" ") + )); + List after = new ArrayList<>(Collections.singleton( + + new UnsupportedConstraintElement.SyntaxConstraintElement("]") + )); + if (braces) { + before.add(new UnsupportedConstraintElement.SyntaxConstraintElement("{")); + after.add(0, new UnsupportedConstraintElement.SyntaxConstraintElement("}")); + } + + Function uceConstructor = type == ObjType.Cell ? UnsupportedConstraintElement.CellConstraintElement::new : UnsupportedConstraintElement.NameConstraintElement::new; + Stream objs = objects.stream().map(uceConstructor).flatMap(UnsupportedConstraintElement.addSpacesBetween()); + return UnsupportedConstraintElement.wrapStream(objs, before.stream(), after.stream()); + } + + public ObjType getType() { + return type; + } + + public List getObjects() { + return objects; + } + + public String requireOneObject() { + if (objects.size()!=1) { + throw new RuntimeException("requiring one object but got: "+objects); + } + return objects.get(0); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java b/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java new file mode 100644 index 000000000..bceed43af --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ObjType.java @@ -0,0 +1,41 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +public enum ObjType { + Design("current_design"), + Cell("get_cells"), + Port("get_ports"), + Pin("get_pins"), + PBlock("get_pblocks"); + + private final String xdcCommand; + + ObjType(String xdcCommand) { + this.xdcCommand = xdcCommand; + } + + public String getXdcCommand() { + return xdcCommand; + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java new file mode 100644 index 000000000..66e223178 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ObjectGetterCommand.java @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.stream.Collectors; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclNumArgsException; +import tcl.lang.TclObject; + +/** + * Tcl command to get something that will probably be passed onto a set_property call later + */ +public class ObjectGetterCommand implements Command { + + private final EdifCellLookup lookup; + private final boolean takesObjects; + private final ObjType objType; + + public ObjectGetterCommand(EdifCellLookup lookup, boolean takesObjects, ObjType objType) { + this.lookup = lookup; + this.takesObjects = takesObjects; + + this.objType = objType; + } + + @Override + public void cmdProc(Interp interp, TclObject[] argv) throws TclException { + DesignObject res; + if (!takesObjects) { + if (argv.length != 1) { + throw new TclNumArgsException(interp, 1, argv, ""); + } + res = new NameDesignObject(objType, null); + } else { + boolean argCountOk = argv.length == 2 || (argv.length==3 && argv[1].toString().equals("-quiet")); + if (!argCountOk || TclHashIdentifiedObject.containsStringifiedObject(argv)) { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, argv, lookup, false, false)); + return; + } + + TclObject objs = argv[argv.length-1]; + if (objs.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, objs); + List strings = Arrays.stream(elements).map(Object::toString).collect(Collectors.toList()); + res = new NameDesignObject(objType, strings); + } else { + res = new NameDesignObject(objType, Arrays.asList(argv[1].toString().split(" "))); + } + } + interp.setResult(TclHashIdentifiedObject.createReflectObject(interp, res.getClass(), res)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java b/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java new file mode 100644 index 000000000..91aba9352 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/RegularEdifCellLookup.java @@ -0,0 +1,114 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.stream.Stream; + +import com.xilinx.rapidwright.edif.EDIFCellInst; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFPropertyValue; + +/** + * Default cell lookup for normal designs. + *

+ * This assumes that the EDIFNetlist matches what a regular synthesis run would produce. No rewriting of cell names + * is performed. + */ +public class RegularEdifCellLookup extends EdifCellLookup { + + private final EDIFNetlist netlist; + + public RegularEdifCellLookup(EDIFNetlist netlist) { + this.netlist = netlist; + } + + @Override + public Stream getChildrenOf(EDIFHierCellInst f) { + return f.getCellType().getCellInsts().stream().map(f::getChild); + } + + @Override + public EDIFHierCellInst getChild(EDIFHierCellInst cell, String name) { + EDIFCellInst child = cell.getCellType().getCellInst(name); + if (child==null) { + return null; + } + return cell.getChild(child); + } + + @Override + public EDIFHierCellInst toEdifHierCellInst(EDIFHierCellInst cell) { + return cell; + } + + + @Override + public EDIFHierCellInst getRoot() { + return netlist.getTopHierCellInst(); + } + + @Override + public String getAbsoluteFinalName(EDIFHierCellInst current) { + return current.getFullHierarchicalInstName(); + } + + @Override + public String getRelativeFinalName(EDIFHierCellInst current) { + return current.getInst().getName(); + } + + @Override + public String getAbsoluteOriginalName(EDIFHierCellInst current) { + return current.getFullHierarchicalInstName(); + } + + @Override + public String getRelativeOriginalName(EDIFHierCellInst current) { + return current.getInst().getName(); + } + + @Override + public String getCellType(EDIFHierCellInst current) { + EDIFPropertyValue prop = current.getCellType().getProperty("ORIG_REF_NAME"); + if (prop != null) { + return prop.getValue(); + } + return current.getCellType().getName(); + } + + @Override + public Class getCellClass() { + return EDIFHierCellInst.class; + } + + @Override + public EDIFHierCellInst getInstFromOriginalName(String cellName) { + return netlist.getHierCellInstFromName(cellName); + } + + @Override + public EDIFHierCellInst getInstFromFinalName(String s) { + return netlist.getHierCellInstFromName(s); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java new file mode 100644 index 000000000..9dbc2a64b --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/ResizePBlockCommand.java @@ -0,0 +1,78 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Objects; + +import com.xilinx.rapidwright.design.blocks.PBlockRange; +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import com.xilinx.rapidwright.device.Device; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class ResizePBlockCommand implements Command { + private final XDCConstraints constraints; + private Device dev; + + public ResizePBlockCommand(XDCConstraints constraints, Device dev) { + this.constraints = constraints; + this.dev = dev; + } + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + String pblockName = null; + String toAdd = null; + for (int i = 1; i < objv.length; i++) { + if (objv[i].toString().startsWith("-")) { + switch (objv[i].toString()) { + case "-quiet": + case "-verbose": + break; + case "-add": + if (toAdd != null) { + throw new RuntimeException("multiple -add calls"); + } + toAdd = objv[++i].toString(); + break; + default: + throw new RuntimeException("unsupported resize_block mode " + objv[i]); + } + } else { + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, objv[i], null); + if (!(obj instanceof NameDesignObject) || ((NameDesignObject) obj).getType() != ObjType.PBlock) { + throw new RuntimeException("expected pblock but got " + obj.toXdc()); + } + pblockName = ((NameDesignObject) obj).requireOneObject(); + } + } + + if (pblockName == null) { + throw new RuntimeException("pblock name is null"); + } + PBlockConstraint constraint = Objects.requireNonNull(constraints.getPBlockConstraints().get(pblockName)); + constraint.getPblock().add(new PBlockRange(dev, toAdd)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java new file mode 100644 index 000000000..230f11e29 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/SetPropertyCommand.java @@ -0,0 +1,182 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Objects; +import java.util.function.BiConsumer; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.PBlockConstraint; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.design.xdc.PackagePinConstraint; +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Tcl command: set_property + * @param + */ +public class SetPropertyCommand implements Command { + private final XDCConstraints constraints; + private final Device dev; + private final EdifCellLookup cellLookup; + + public SetPropertyCommand(XDCConstraints constraints, Device dev, EdifCellLookup cellLookup) { + this.constraints = constraints; + this.dev = dev; + this.cellLookup = cellLookup; + } + + public void cmdProc(Interp interp, TclObject[] argv) + throws TclException { + String k = argv[1].toString(); + String v = argv[2].toString(); + DesignObject obj = DesignObject.requireUnwrapTclObject(interp, argv[argv.length - 1], cellLookup); + + if (k.equals("-dict")) { + String[] items = v.split(" "); + if (items.length % 2 != 0) { + throw new TclException(interp, "found odd number of arguments in kv-dict"); + } + for (int i = 0; i < items.length; i += 2) { + storeKV(items[i], items[i + 1], obj); + } + } else { + storeKV(k, v, obj); + } + + interp.setResult(0); + } + + private void eachPin(NameDesignObject pins, BiConsumer setter, String value) { + for (String object : pins.getObjects()) { + PackagePinConstraint ppc = constraints.getPinConstraints().computeIfAbsent(object, PackagePinConstraint::new); + setter.accept(ppc, value); + } + } + + private void addUnsupportedKV(String k, String v, DesignObject someObj) { + boolean needsBraces = XDCTools.stringNeedsBraces(v); + Stream line = Stream.concat( + Stream.of( + new UnsupportedConstraintElement.NameConstraintElement("set_property"), + new UnsupportedConstraintElement.SyntaxConstraintElement(" "), + new UnsupportedConstraintElement.NameConstraintElement(k), + new UnsupportedConstraintElement.SyntaxConstraintElement(needsBraces ? " {" : " "), + new UnsupportedConstraintElement.NameConstraintElement(v), + new UnsupportedConstraintElement.SyntaxConstraintElement(needsBraces ? "} " : " ") + ), + someObj.toUnsupportedConstraintElement() + ); + List l = line.collect(Collectors.toList()); + constraints.getUnsupportedConstraints().add(l); + } + + private void storeKV(String k, String v, DesignObject someObj) { + if (someObj instanceof CellObject) { + for (T cell : ((CellObject) someObj).getCells()) { + constraints.getCellProperties().computeIfAbsent(cellLookup.getAbsoluteFinalName(cell), x -> new HashMap<>()).put(k, v); + } + return; + } + if (someObj instanceof UnsupportedCmdResult) { + addUnsupportedKV(k, v, someObj); + return; + } + if (!(someObj instanceof NameDesignObject)) { + throw new RuntimeException("expected NameDesignObject but got "+someObj.getClass()+": "+someObj); + } + NameDesignObject obj = (NameDesignObject) someObj; + switch (obj.getType()) { + case Design: + addUnsupportedKV(k, v, obj); + break; + case Cell: + if (k.equals("ASYNC_REG")) { + addUnsupportedKV(k,v,obj); + } else { + for (String object : obj.getObjects()) { + constraints.getCellProperties().computeIfAbsent(object, x -> new HashMap<>()).put(k, v); + } + } + break; + case Port: + switch (k) { + case "IOSTANDARD": + eachPin(obj, PackagePinConstraint::setIOStandard, v); + break; + case "PACKAGE_PIN": + case "LOC": + if (dev !=null && !dev.getActivePackage().getPackagePinMap().containsKey(v)) { + throw new RuntimeException("Invalid pin for " + obj + ": " + v); + } + eachPin(obj, PackagePinConstraint::setPackagePin, v); + break; + default: + addUnsupportedKV(k, v, obj); + + } + break; + case PBlock: + PBlockConstraint pBlockConstraint = Objects.requireNonNull(constraints.getPBlockConstraints().get(obj.requireOneObject())); + switch (k) { + case "CONTAIN_ROUTING": + pBlockConstraint.getPblock().setContainRouting(parseBool(v)); + break; + case "IS_SOFT": + pBlockConstraint.getPblock().setIsSoft(parseBool(v)); + break; + case "EXCLUDE_PLACEMENT": + pBlockConstraint.getPblock().setExcludePlacement(parseBool(v)); + break; + default: + throw new RuntimeException("Trying to set unknown property "+k+" on pblock "+obj.requireOneObject()); + } + break; + default: + throw new RuntimeException("Unexpected obj type "+obj.getType()); + } + } + + private boolean parseBool(String v) { + switch (v.toLowerCase()) { + case "true": + case "1": + return true; + case "false": + case "0": + return false; + default: + throw new RuntimeException("invalid bool: "+v); + } + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java b/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java new file mode 100644 index 000000000..c3d457e11 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/TclHashIdentifiedObject.java @@ -0,0 +1,90 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.function.Consumer; + +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * Helper methods for registering java objects via their identity hash code and looking them up. + */ +public class TclHashIdentifiedObject { + private static final String NAME_START = "+%%javahashstart%%+"; + private static final String NAME_END = "+%%javahashend%%+"; + + public static TclObject createReflectObject(Interp interp, Class clazz, Object obj) throws TclException { + String customRefID = NAME_START + ReflectObject.getHashString(clazz, obj) + NAME_END; + TclObject tclObject = ReflectObject.newInstance(interp, clazz, obj, customRefID); + // Normally, Tcl objects are removed from internal table once no variable refers to them. + // We want to keep them around till the end of life of the interpreter (since a stringified reference may still exist) + // Therefore, increase reference count by 1 + tclObject.preserve(); + return tclObject; + } + + public static void unpack(Interp interp, String s, Consumer outputString, Consumer outputObject) { + int offset = 0; + while (offset < s.length()) { + int objStart = s.indexOf(NAME_START, offset); + if (objStart==-1) { + outputString.accept(s.substring(offset, s.length())); + break; + } + if (objStart>offset) { + outputString.accept(s.substring(offset, objStart)); + } + int objEnd = s.indexOf(NAME_END, objStart); + + String objName = s.substring(objStart+NAME_START.length(), objEnd); + Object obj = ReflectObject.findObjectByHash(interp, objName); + if (obj==null) { + throw new RuntimeException("Did not find hash identified object "+objName+". Was this mistakenly freed?"); + } + outputObject.accept(obj); + + offset = objEnd+NAME_END.length(); + } + } + + public static boolean containsStringifiedObject(String s) { + return s.contains(NAME_START); + } + + public static boolean containsStringifiedObject(TclObject[] args) { + return Arrays.stream(args).anyMatch(o->containsStringifiedObject(o.toString())); + } + + public static String unpackAsString(Interp interp, String s, EdifCellLookup lookup) { + StringBuilder sb = new StringBuilder(); + unpack(interp, s, sb::append, obj -> { + T t = lookup.castCellInst(obj); + sb.append(lookup.getAbsoluteOriginalName(t)); + }); + return sb.toString(); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java new file mode 100644 index 000000000..fa3bdb72c --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedCmdResult.java @@ -0,0 +1,141 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * This wraps an unsupported command's call to support passing it on + */ +public class UnsupportedCmdResult extends DesignObject{ + private final List cmd; + + private final EdifCellLookup lookup; + + public UnsupportedCmdResult(List cmd, EdifCellLookup lookup) { + this.cmd = cmd; + this.lookup = lookup; + } + + private static Function> wrapDollarSigns() { + final boolean[] inBraces = {false}; + return uce -> { + if (uce instanceof UnsupportedConstraintElement.CellConstraintElement || uce instanceof UnsupportedConstraintElement.NameConstraintElement) { + if (!inBraces[0] && uce.toXdc().contains("$")) { + inBraces[0] = true; + return Stream.of(new UnsupportedConstraintElement.SyntaxConstraintElement("{"), uce); + } + return Stream.of(uce); + } else { + String s = uce.toXdc(); + StringBuilder sb = new StringBuilder(); + for (char c : s.toCharArray()) { + switch (c) { + case '{': + inBraces[0] = true; + sb.append(c); + break; + case '}': + inBraces[0] = false; + sb.append(c); + break; + case ']': + if (inBraces[0]) { + sb.append('}'); + inBraces[0] = false; + } + sb.append(c); + break; + default: + sb.append(c); + break; + } + } + return Stream.of(new UnsupportedConstraintElement.SyntaxConstraintElement(sb.toString())); + } + }; + } + + public UnsupportedCmdResult(Interp interp, TclObject[] argv, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) { + this.lookup = lookup; + Stream objs = Arrays.stream(argv) + .flatMap(UnsupportedConstraintElement.addSpacesBetween( + obj->UnsupportedConstraintElement.objToUnsupportedConstraintElement(interp, obj, lookup, replaceProbableCells, applyWildcardsChooseAny) + )); + this.cmd = UnsupportedConstraintElement.wrapStream(objs, "[", "]") + .flatMap(wrapDollarSigns()) + .collect(Collectors.toList()); + } + + public static TclObject makeTclObj(Interp interp, TclObject[] objv, EdifCellLookup lookup, boolean replaceProbableCells, boolean applyWildcardsChooseAny) throws TclException { + return new UnsupportedCmdResult<>(interp, objv, lookup, replaceProbableCells, applyWildcardsChooseAny).toReflectObj(interp); + } + + @Override + public String toString() { + return toXdc(); + } + + @Override + public String toXdc() { + return UnsupportedConstraintElement.toXdc(cmd.stream()); + } + + @Override + public Stream toUnsupportedConstraintElement() { + return cmd.stream(); + } + + public TclObject toReflectObj(Interp interp) throws TclException { + return TclHashIdentifiedObject.createReflectObject(interp, DesignObject.class, this); + } + + public List getCmd() { + return cmd; + } + + private static boolean isSyntaxStr(UnsupportedConstraintElement elem, String s) { + if (!(elem instanceof UnsupportedConstraintElement.SyntaxConstraintElement)) { + return false; + } + return elem.toXdc().equals(s); + } + + public UnsupportedCmdResult withoutOutsideBrackets() { + if (isSyntaxStr(cmd.get(0), "[") && isSyntaxStr(cmd.get(cmd.size()-1), "]")) { + List newList = cmd.stream().skip(1).limit(cmd.size() - 2).collect(Collectors.toList()); + return new UnsupportedCmdResult<>(newList, lookup); + } + return new UnsupportedCmdResult<>(new ArrayList<>(cmd), lookup); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java new file mode 100644 index 000000000..4c4aad0fe --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedGetterCommand.java @@ -0,0 +1,87 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.Objects; +import java.util.Optional; + +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.ReflectObject; +import tcl.lang.TclException; +import tcl.lang.TclList; +import tcl.lang.TclObject; +import tcl.lang.TclString; + +/** + * A setter command that is not supported in detail + */ +public class UnsupportedGetterCommand implements Command { + protected final EdifCellLookup lookup; + protected final Command replacedCommand; + + public UnsupportedGetterCommand(EdifCellLookup lookup, Command replacedCommand) { + this.lookup = lookup; + this.replacedCommand = replacedCommand; + } + public UnsupportedGetterCommand(EdifCellLookup lookup) { + this.lookup = lookup; + this.replacedCommand = null; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (replacedCommand!=null && Arrays.stream(objv).noneMatch(obj -> containsUnsupportedCmdResults(lookup, interp, obj, false))) { + replacedCommand.cmdProc(interp, objv); + } else { + interp.setResult(UnsupportedCmdResult.makeTclObj(interp, objv, lookup, true, true)); + } + } + + public static boolean containsUnsupportedCmdResults(EdifCellLookup lookup, Interp interp, TclObject obj, boolean isInList) { + try { + if (obj.getInternalRep() instanceof TclList) { + TclObject[] elements = TclList.getElements(interp, obj); + return Arrays.stream(elements).anyMatch(elem -> containsUnsupportedCmdResults(lookup, interp, elem, true)); + } else if (obj.getInternalRep() instanceof ReflectObject) { + Optional designObject = DesignObject.unwrapTclObject(interp, obj, lookup); + if (!designObject.isPresent()) { + return true; + } + return !isInList || !(designObject.get() instanceof CellObject); + } else if (obj.getInternalRep() instanceof TclString) { + return TclHashIdentifiedObject.containsStringifiedObject(obj.toString()); + } else { + return false; + } + } catch (TclException e) { + throw new RuntimeException(e); + } + } + + public static void replaceInInterp(Interp interp, EdifCellLookup lookup, String name) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand(name)); + interp.createCommand(name, new UnsupportedGetterCommand(lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java new file mode 100644 index 000000000..632a32362 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedIfCommand.java @@ -0,0 +1,249 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.CharPointer; +import tcl.lang.Command; +import tcl.lang.ExprValue; +import tcl.lang.Interp; +import tcl.lang.Parser; +import tcl.lang.TCL; +import tcl.lang.TclDouble; +import tcl.lang.TclException; +import tcl.lang.TclInteger; +import tcl.lang.TclObject; +import tcl.lang.TclParse; +import tcl.lang.TclRuntimeError; +import tcl.lang.TclString; +import tcl.lang.TclToken; + +/** + * Replacement for if that supports the condition returning an UnsupportedConstraintElement + */ +public class UnsupportedIfCommand extends UnsupportedSetterCommand { + public UnsupportedIfCommand(XDCConstraints constraints, EdifCellLookup lookup, Command replacedCommand) { + super(constraints, lookup, replacedCommand); + } + + private TclObject exprValueToObj(ExprValue exprValue) { + switch (exprValue.getType()) { + case 0: + return TclInteger.newInstance(exprValue.getIntValue()); + case 1: + return TclDouble.newInstance(exprValue.getDoubleValue()); + case 2: + return TclString.newInstance("{"+exprValue.getStringValue()+"}"); + default: + throw new RuntimeException("invalid expr type"); + } + } + + //Copied from Parser.java + static final int TCL_TOKEN_WORD = 1; + static final int TCL_TOKEN_SIMPLE_WORD = 2; + static final int TCL_TOKEN_TEXT = 4; + static final int TCL_TOKEN_BS = 8; + static final int TCL_TOKEN_COMMAND = 16; + static final int TCL_TOKEN_VARIABLE = 32; + static final int TCL_TOKEN_SUB_EXPR = 64; + static final int TCL_TOKEN_OPERATOR = 128; + + String tokensToStr(TclParse tclParse, Interp interp) { + try { + StringBuilder res = new StringBuilder(); + boolean needsSpace = false; + for (int i = 0; i < tclParse.numTokens; i++) { + TclToken token = tclParse.tokenList[i]; + switch (token.type) { + case TCL_TOKEN_VARIABLE: + + String varName = tclParse.tokenList[++i].getTokenString(); + if (token.numComponents != 1) { + throw new RuntimeException("we don't currently support arrays here"); + } + TclObject value = interp.getVar(varName, null, 0); + if (needsSpace) { + res.append(' '); + } + res.append(value.toString()); + needsSpace = true; + break; + case TCL_TOKEN_SIMPLE_WORD: + case TCL_TOKEN_COMMAND: + if (needsSpace) { + res.append(' '); + } + res.append(token.getTokenString()); + needsSpace = true; + break; + case TCL_TOKEN_TEXT: + case TCL_TOKEN_WORD: + //Ignore + break; + default: + throw new RuntimeException("unsupported token type: "+token.type); + } + } + return res.toString(); + } catch (TclException e) { + throw new RuntimeException(e); + } + } + void makeUnsupportedResult(Interp interp, TclObject[] objv, int i) throws TclException { + + for (++i;i= objv.length) { + throw new TclException(interp, + "wrong # args: no expression after \"" + + objv[i - 1] + "\" argument"); + } + try { + ExprValue exprValue = interp.evalExpression(objv[i].toString()); + try { + value = exprValue.getBooleanValue(interp); + } catch (TclException | TclRuntimeError e) { + //Not evaluatable, forward if to Vivado + objv[i] = exprValueToObj(exprValue); + + makeUnsupportedResult(interp, objv, i); + return; + } finally { + interp.releaseExprValue(exprValue); + } + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" test expression)"); + break; + } + throw e; + } + + i++; + if ((i < objv.length) && (objv[i].toString().equals("then"))) { + i++; + } + if (i >= objv.length) { + throw new TclException(interp, + "wrong # args: no script following \"" + + objv[i - 1] + "\" argument"); + } + if (value) { + try { + interp.eval(objv[i], 0); + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" then script line " + + interp.getErrorLine() + ")"); + break; + } + throw e; + } + return; + } + + // The expression evaluated to false. Skip the command, then + // see if there is an "else" or "elseif" clause. + + i++; + if (i >= objv.length) { + interp.resetResult(); + return; + } + if (objv[i].toString().equals("elseif")) { + i++; + continue; + } + break; + } + + // Couldn't find a "then" or "elseif" clause to execute. + // Check now for an "else" clause. We know that there's at + // least one more argument when we get here. + + if (objv[i].toString().equals("else")) { + i++; + if (i >= objv.length) { + throw new TclException(interp, + "wrong # args: no script following \"else\" argument"); + } else if (i != (objv.length - 1)) { + throw new TclException(interp, + "wrong # args: extra words after \"else\" clause in " + + "\"if\" command"); + } + } else { + // Not else, if there is more than 1 more argument + // then generate an error. + + if (i != (objv.length - 1)) { + throw new TclException(interp, + "wrong # args: extra words after \"else\" clause in \"if\" command"); + } + } + try { + interp.eval(objv[i], 0); + } catch (TclException e) { + switch (e.getCompletionCode()) { + case TCL.ERROR: + interp.addErrorInfo("\n (\"if\" else script line " + + interp.getErrorLine() + ")"); + break; + } + throw e; + } + } + + public static void replaceInInterp(Interp interp, XDCConstraints xdcConstraints, EdifCellLookup lookup) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand("if")); + interp.createCommand("if", new UnsupportedIfCommand(xdcConstraints, lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java new file mode 100644 index 000000000..3c5cdecd1 --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/UnsupportedSetterCommand.java @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +import java.util.Arrays; +import java.util.List; +import java.util.Objects; + +import com.xilinx.rapidwright.design.xdc.UnsupportedConstraintElement; +import com.xilinx.rapidwright.design.xdc.XDCConstraints; +import tcl.lang.Command; +import tcl.lang.Interp; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +/** + * A setter command that is not supported in detail + */ +public class UnsupportedSetterCommand implements Command { + + protected final XDCConstraints constraints; + protected final EdifCellLookup cellLookup; + protected final Command replacedCommand; + + public UnsupportedSetterCommand(XDCConstraints constraints, EdifCellLookup cellLookup, Command replacedCommand) { + + this.constraints = constraints; + this.cellLookup = cellLookup; + this.replacedCommand = replacedCommand; + } + + @Override + public void cmdProc(Interp interp, TclObject[] objv) throws TclException { + if (replacedCommand!=null && Arrays.stream(objv).noneMatch(obj -> UnsupportedGetterCommand.containsUnsupportedCmdResults(cellLookup, interp, obj, false))) { + replacedCommand.cmdProc(interp, objv); + } else { + List constraint = UnsupportedConstraintElement.commandToUnsupportedConstraints(interp, objv, cellLookup); + constraints.getUnsupportedConstraints().add(constraint); + + interp.resetResult(); + } + } + + public static void replaceInInterp(Interp interp, XDCConstraints constraints, EdifCellLookup lookup, String name) { + Command replacedCommand = Objects.requireNonNull(interp.getCommand(name)); + interp.createCommand(name, new UnsupportedSetterCommand(constraints, lookup, replacedCommand)); + } +} diff --git a/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java b/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java new file mode 100644 index 000000000..b3b5bb19a --- /dev/null +++ b/src/com/xilinx/rapidwright/design/xdc/parser/XDCTools.java @@ -0,0 +1,35 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc.parser; + +public class XDCTools { + public static boolean stringNeedsBraces(String s) { + return s.contains(" ") || s.contains("*") || s.contains("$"); + } + public static String braceEnclosedIfNeeded(String s) { + if (stringNeedsBraces(s)) { + return '{'+s+'}'; + } + return s; + } +} diff --git a/src/com/xilinx/rapidwright/device/Series.java b/src/com/xilinx/rapidwright/device/Series.java index aa6215f25..e1122c070 100644 --- a/src/com/xilinx/rapidwright/device/Series.java +++ b/src/com/xilinx/rapidwright/device/Series.java @@ -37,7 +37,7 @@ public enum Series { Series7 ("\\-7", 23, 3, 22, 17, 5, 7, 10, 0, 7, 50), UltraScale ("UltraScale$", 23, 3, -1, 17, 6, 7, 10, 0, 7, 60), UltraScalePlus("UltraScale\\+", 24, 3, -1, 18, 6, 8, 10, 0, 8, 60), - Versal ("Versal", -1, -1, -1, -1, -1, -1, -1, -1, -1, -1); + Versal ("Versal", -1, -1, -1, -1, -1, -1, -1, -1, -1, 96); private Pattern archRegex; diff --git a/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java b/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java index e2e2bd2ec..eb99ebbff 100644 --- a/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java +++ b/src/com/xilinx/rapidwright/device/helper/TileColumnPattern.java @@ -86,7 +86,7 @@ public static TileColumnPattern createTileColumnPattern(List types /** * Creates a TileColumnPattern from an existing list of tile types and uses the start and end - * as indicies to get a sublist of filteredTypes. + * as indices to get a sublist of filteredTypes. * @param filteredTypes The list of tile types to turn into a pattern * @param start The start index to use to build a subList of filteredTypes * @param end The end index to use to build a subList of filteredTypes diff --git a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java index c978637df..8f125e016 100644 --- a/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java +++ b/src/com/xilinx/rapidwright/eco/ECOPlacementHelper.java @@ -272,7 +272,7 @@ public static Site getCentroidOfPoints(Device device, List points, Set targetSiteTypes) for (SitePinInst i : net.getPins()) { points.add(new Point(i.getTile().getColumn(), i.getTile().getRow())); } - return ECOPlacementHelper.getCentroidOfPoints(net.getSource().getTile().getDevice(), points, targetSiteTypes); + return ECOPlacementHelper.getCentroidOfPoints(net.getDesign().getDevice(), points, targetSiteTypes); } /** diff --git a/src/com/xilinx/rapidwright/eco/ECOTools.java b/src/com/xilinx/rapidwright/eco/ECOTools.java index c8c1ca55f..a5a0b600a 100644 --- a/src/com/xilinx/rapidwright/eco/ECOTools.java +++ b/src/com/xilinx/rapidwright/eco/ECOTools.java @@ -60,6 +60,7 @@ import com.xilinx.rapidwright.edif.EDIFTools; import com.xilinx.rapidwright.rwroute.RouterHelper; import com.xilinx.rapidwright.util.Pair; +import com.xilinx.rapidwright.util.Params; /** * A collection of methods for performing ECO operations. @@ -117,10 +118,16 @@ public static void disconnectNet(Design design, * if this method is called many times as the process is expensive * without batching. This map can also allow SitePinInst objects to be * reused by {@link #connectNet(Design, Map, Map)}. + * By default, this method will unroute any intra-site routing associated with the disconnected + * pin. The Java property "rapidwright.ecotools.disconnectNet.skipUnrouteIntraSite" disables this + * behaviour which can be helpful for when disconnectNet() is followed by connectNet() that + * will re-use this intra-site routing. */ public static void disconnectNet(Design design, List pins, Map> deferredRemovals) { + final boolean unrouteIntraSite = !Params.isParamSet("rapidwright.ecotools.disconnectNet.skipUnrouteIntraSite"); + for (EDIFHierPortInst ehpi : pins) { EDIFHierNet ehn = ehpi.getHierarchicalNet(); List leafPortInsts; @@ -165,7 +172,7 @@ public static void disconnectNet(Design design, } if (sourcePresent) { - // Downstream does contains a source, remove everything upstream instead + // Downstream does contain a source, remove everything upstream instead visitedNets.clear(); visitedNets.add(internalEhn); leafPortInsts = ehn.getLeafHierPortInsts(false, visitedNets); @@ -196,11 +203,14 @@ public static void disconnectNet(Design design, // SPI also services a different logical port inst; skip continue; } - BELPin otherPin = cell.getBELPin(leafEhpi); - BELPin src = otherPin.isOutput() ? otherPin : spi.getBELPin(); - BELPin snk = otherPin.isOutput() ? spi.getBELPin() : otherPin; - cell.getSiteInst().unrouteIntraSiteNet(src, snk); + if (unrouteIntraSite) { + BELPin otherPin = cell.getBELPin(leafEhpi); + BELPin src = otherPin.isOutput() ? otherPin : spi.getBELPin(); + BELPin snk = otherPin.isOutput() ? spi.getBELPin() : otherPin; + cell.getSiteInst().unrouteIntraSiteNet(src, snk); + } + DesignTools.handlePinRemovals(spi, deferredRemovals); } } @@ -465,7 +475,7 @@ public static void connectNet(Design design, String message = "Site pin " + spi.getSitePinName() + " cannot be used " + "to connect to logical pin '" + ehpi + "' since it is also connected to pin '" + otherEhpi + "'."; - String warnIfCellInstStartsWith = System.getProperty("rapidwright.ecotools.connectNet.warnIfCellInstStartsWith"); + String warnIfCellInstStartsWith = Params.getParamValue("rapidwright.ecotools.connectNet.warnIfCellInstStartsWith"); String cellInstName = (warnIfCellInstStartsWith != null) ? otherEhpi.getPortInst().getCellInst().getName() : null; if (cellInstName != null && cellInstName.startsWith(warnIfCellInstStartsWith)) { System.err.println("WARNING: " + message); diff --git a/src/com/xilinx/rapidwright/edif/EDIFCell.java b/src/com/xilinx/rapidwright/edif/EDIFCell.java index 75fee845e..e361e8ae6 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFCell.java +++ b/src/com/xilinx/rapidwright/edif/EDIFCell.java @@ -313,7 +313,7 @@ public EDIFPort getPort(String name) { /** * Given a port instance name (not including the name of the cell instance), * gets the associated port. - * + * * @param portInstName * @return */ @@ -346,9 +346,8 @@ public void renamePort(String currName, String newName) { EDIFPort newPort = new EDIFPort(newName, port.getDirection(), port.getWidth()); addPort(newPort); - int[] indices = port.isBus() ? port.getBitBlastedIndicies() : new int[]{0}; - for (int i : indices) { + for (int i : port.getBitBlastedIndices()) { EDIFPortInst portInst = port.getInternalPortInstFromIndex(i); if (portInst == null) { continue; @@ -807,5 +806,33 @@ public int getNonHierInstantiationCount() { public boolean isUniquified() { return getNonHierInstantiationCount() <= 1; } + + /** + * Checks if this cell and the provided cell have the same set of ports. + * Port names that are the same except for starting with EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE ("[]") are + * considered equivalent for the purpose of cells having a matching set of ports. + * + * @param other The other cell to match against. + * @return True if the set of ports on both this cell and the other cell match + * exactly. False otherwise. + */ + public boolean matchesInterface(EDIFCell other) { + if (getPorts().size() != other.getPorts().size()) { + return false; + } + Map otherPorts = other.getPortMap(); + for (EDIFPort port : getPorts()) { + EDIFPort otherPort = otherPorts.get(port.getBusName(true)); + if (otherPort == null) { + otherPort = otherPorts.get(EDIFTools.VIVADO_PRESERVE_PORT_INTERFACE + + port.getBusName(true)); + } + if (otherPort == null || port.getWidth() != otherPort.getWidth() + || port.getDirection() != otherPort.getDirection()) { + return false; + } + } + return true; + } } diff --git a/src/com/xilinx/rapidwright/edif/EDIFPort.java b/src/com/xilinx/rapidwright/edif/EDIFPort.java index 1efe0b4a5..e753d4828 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFPort.java +++ b/src/com/xilinx/rapidwright/edif/EDIFPort.java @@ -108,7 +108,7 @@ else if (c == '[') { } } if (colonIdx == -1 || leftBracket == -1) { - throw new RuntimeException("ERROR: Interpreting port " + getName() + ", couldn't identify indicies."); + throw new RuntimeException("ERROR: Interpreting port " + getName() + ", couldn't identify indices."); } int left = Integer.parseInt(name.substring(leftBracket+1, colonIdx)); @@ -256,7 +256,7 @@ public EDIFPortInst getInternalPortInstFromIndex(int index) { public EDIFPortInst getInternalPortInst() { assert (!isBus()); EDIFNet net = getInternalNet(); - return net.getPortInst(null, getBusName()); + return net == null ? null : net.getPortInst(null, getBusName()); } /** @@ -356,13 +356,33 @@ public boolean isBus() { return width > 1 || !getName().equals(busName); } + private static final int[] SINGLE_BIT_INDICES = new int[] { 0 }; + + /** + * @see #getBitBlastedIndicies() + * @deprecated Misspelling in name, to be removed in 2026.1.0 + */ public int[] getBitBlastedIndicies() { - int lastLeftBracket = getName().lastIndexOf('['); - if (getName().contains(":")) - return EDIFTools.bitBlastBus(getName().substring(lastLeftBracket)); - if (getName().contains("[")) - return new int[] {Integer.parseInt(getName().substring(lastLeftBracket,getName().length()-1))}; - return null; + return getBitBlastedIndices(); + } + + /** + * Returns an array of all the integer indices of this port. If the port is a + * single bit it returns an array with a single entry of '0'. This is useful + * when needing to iterate over a port's PortInst objects. + * + * @return The integer list of indices of this port, or {0} for a single bit + * port. + */ + public int[] getBitBlastedIndices() { + if (isBus()) { + int lastLeftBracket = getName().lastIndexOf('['); + assert(lastLeftBracket != -1); + return getName().indexOf(':', lastLeftBracket) != -1 ? + EDIFTools.bitBlastBus(getName().substring(lastLeftBracket)) : + new int[] { Integer.parseInt(getName().substring(lastLeftBracket, getName().length() - 1)) }; + } + return SINGLE_BIT_INDICES; } public boolean isBusRangeEqual(EDIFPort otherPort) { diff --git a/src/com/xilinx/rapidwright/edif/EDIFTools.java b/src/com/xilinx/rapidwright/edif/EDIFTools.java index 3ffba6c37..9158c2a00 100644 --- a/src/com/xilinx/rapidwright/edif/EDIFTools.java +++ b/src/com/xilinx/rapidwright/edif/EDIFTools.java @@ -475,7 +475,7 @@ else if (c == '[') { } } if (colonIdx == -1 || leftBracket == -1) { - throw new RuntimeException("ERROR: Interpreting port " + name + ", couldn't identify indicies."); + throw new RuntimeException("ERROR: Interpreting port " + name + ", couldn't identify indices."); } int left = Integer.parseInt(name.substring(leftBracket+1, colonIdx)); @@ -1736,7 +1736,7 @@ public static EDIFNetlist createFlatNetlist(EDIFNetlist netlist, String partName for (EDIFPort topPort : netlist.getTopCell().getPorts()) { EDIFPort flatPort = flatTop.createPort(topPort); if (flatPort.isBus()) { - int[] indicies = flatPort.getBitBlastedIndicies(); + int[] indices = flatPort.getBitBlastedIndices(); int i = 0; for (EDIFNet net : topPort.getInternalNets()) { if (net == null) continue; @@ -1744,7 +1744,7 @@ public static EDIFNetlist createFlatNetlist(EDIFNetlist netlist, String partName if (flatNet == null) { flatNet = flatTop.createNet(net.getName()); } - flatNet.createPortInst(flatPort, indicies[i++]); + flatNet.createPortInst(flatPort, indices[i++]); } } else { EDIFNet net = topPort.getInternalNet(); @@ -1783,18 +1783,25 @@ public static void ensurePreservedInterfaceVivado(EDIFNetlist netlist) { } } + /** + * Removes vivado bus prevention annotations from top-level ports. Vivado sometimes adds these annotations + * to prevent multiple single bit ports with similar names from getting merged into a single bus. The method + * used here will only work on the top-level cell as we do not traverse the netlist to ensure the new names + * are consistent. + * + * @param netlist The netlist to remove bus prevention annotations from. + * + */ public static void removeVivadoBusPreventionAnnotations(EDIFNetlist netlist) { EDIFCell top = netlist.getTopCell(); - for (EDIFCell cell : netlist.getLibrary(top.getLibrary().getName()).getCells()) { - List portsToRename = new ArrayList<>(); - for (EDIFPort p : cell.getPorts()) { - if (p.getName().startsWith(VIVADO_PRESERVE_PORT_INTERFACE)) { - portsToRename.add(p.getName()); - } - } - for (String p : portsToRename) { - cell.renamePort(p, p.substring(VIVADO_PRESERVE_PORT_INTERFACE.length())); + List portsToRename = new ArrayList<>(); + for (EDIFPort p : top.getPorts()) { + if (p.getName().startsWith(VIVADO_PRESERVE_PORT_INTERFACE)) { + portsToRename.add(p.getName()); } } + for (String p : portsToRename) { + top.renamePort(p, p.substring(VIVADO_PRESERVE_PORT_INTERFACE.length())); + } } } diff --git a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java index a187b2f42..0048c5180 100644 --- a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java +++ b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java @@ -29,7 +29,7 @@ import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; -import com.xilinx.rapidwright.ipi.XDCParser; +import com.xilinx.rapidwright.design.xdc.XDCParser; import com.xilinx.rapidwright.util.FileTools; public class DcpToInterchange { diff --git a/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java b/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java index 79af2a005..44ad316a6 100644 --- a/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java +++ b/src/com/xilinx/rapidwright/interchange/DeviceResourcesVerifier.java @@ -189,7 +189,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device Reader stReaders = dReader.getSiteTypeList(); for (SiteType.Reader stReader : stReaders) { - Set belPinIndicies = new HashSet(); + Set belPinIndices = new HashSet(); SiteTypeEnum siteTypeEnum = SiteTypeEnum.valueOf(allStrings.get(stReader.getName())); Site site = siteTypes.get(siteTypeEnum); @@ -212,7 +212,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device for (int j=0; j < belPins.length; j++) { BELPin pin = belPins[j]; int belPinIndex = pinsReader.get(j); - belPinIndicies.add(belPinIndex); + belPinIndices.add(belPinIndex); verifyBelPin(belPinsReader, pin, belPinIndex); } @@ -223,11 +223,11 @@ public static boolean verifyDeviceResources(String devResFileName, String device BELInverter.Reader belInverter = belReader.getInverting(); BELPin nonInverting = bel.getNonInvertingPin(); - belPinIndicies.add(belInverter.getNonInvertingPin()); + belPinIndices.add(belInverter.getNonInvertingPin()); verifyBelPin(belPinsReader, nonInverting, belInverter.getNonInvertingPin()); BELPin inverting = bel.getInvertingPin(); - belPinIndicies.add(belInverter.getInvertingPin()); + belPinIndices.add(belInverter.getInvertingPin()); verifyBelPin(belPinsReader, inverting, belInverter.getInvertingPin()); } else { expect(false, belReader.hasInverting()); @@ -274,7 +274,7 @@ public static boolean verifyDeviceResources(String devResFileName, String device } BELPin belPin = belPins[0]; - belPinIndicies.add(pinReader.getBelpin()); + belPinIndices.add(pinReader.getBelpin()); verifyBelPin(belPinsReader, belPin, pinReader.getBelpin()); } @@ -297,12 +297,12 @@ public static boolean verifyDeviceResources(String devResFileName, String device expect(belPins.length, wiresReader.size()); for (int i=0; i < belPins.length; i++) { - belPinIndicies.add(wiresReader.get(i)); + belPinIndices.add(wiresReader.get(i)); verifyBelPin(belPinsReader, belPins[i], wiresReader.get(i)); } } - expect(belPinIndicies.size(), belPinsReader.size()); + expect(belPinIndices.size(), belPinsReader.size()); StructList.Reader sitePipsReader = stReader.getSitePIPs(); SitePIP[] sitePIPs = siteInst.getSitePIPs(); diff --git a/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java b/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java index 01a19e554..ac5c233d9 100644 --- a/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java +++ b/src/com/xilinx/rapidwright/interchange/DeviceResourcesWriter.java @@ -288,14 +288,14 @@ public static void writeDeviceResourcesFile(String part, Device device, CodePerf writeAllSiteTypesToBuilder(design, device, devBuilder); t.stop().start("TileTypes"); - Map tileTypeIndicies = writeAllTileTypesToBuilder(design, device, devBuilder); + Map tileTypeIndices = writeAllTileTypesToBuilder(design, device, devBuilder); Map tileTypesObj = new HashMap(); - for (Map.Entry tileType : tileTypeIndicies.entrySet()) { + for (Map.Entry tileType : tileTypeIndices.entrySet()) { tileTypesObj.put(tileType.getKey(), devBuilder.getTileTypeList().get(tileType.getValue())); } t.stop().start("Tiles"); - writeAllTilesToBuilder(device, devBuilder, tileTypeIndicies); + writeAllTilesToBuilder(device, devBuilder, tileTypeIndices); t.stop().start("Wires&Nodes"); writeAllWiresAndNodesToBuilder(device, devBuilder, skipRouteResources); @@ -696,7 +696,7 @@ private static void populateAltSitePins( public static Map writeAllTileTypesToBuilder(Design design, Device device, DeviceResources.Device.Builder devBuilder) { StructList.Builder tileTypesList = devBuilder.initTileTypeList(tileTypes.size()); - Map tileTypeIndicies = new HashMap(); + Map tileTypeIndices = new HashMap(); // Order tile types by their TILE_TYPE_IDX (may not be contiguous) Map tileTypeIndexMap = new TreeMap<>(); @@ -709,7 +709,7 @@ public static Map writeAllTileTypesToBuilder(Design desig TileTypeEnum type = e.getValue(); Tile tile = tileTypes.get(type); TileType.Builder tileType = tileTypesList.get(i); - tileTypeIndicies.put(type, i); + tileTypeIndices.put(type, i); // name tileType.setName(allStrings.getIndex(type.name())); @@ -792,10 +792,10 @@ public static Map writeAllTileTypesToBuilder(Design desig i++; } - return tileTypeIndicies; + return tileTypeIndices; } - public static void writeAllTilesToBuilder(Device device, DeviceResources.Device.Builder devBuilder, Map tileTypeIndicies) { + public static void writeAllTilesToBuilder(Device device, DeviceResources.Device.Builder devBuilder, Map tileTypeIndices) { StructList.Builder tileBuilders = devBuilder.initTileList(device.getColumns() * device.getRows()); @@ -804,7 +804,7 @@ public static void writeAllTilesToBuilder(Device device, DeviceResources.Device. for (Tile tile : tiles) { DeviceResources.Device.Tile.Builder tileBuilder = tileBuilders.get(i); tileBuilder.setName(allStrings.getIndex(tile.getName())); - tileBuilder.setType(tileTypeIndicies.get(tile.getTileTypeEnum())); + tileBuilder.setType(tileTypeIndices.get(tile.getTileTypeEnum())); Site[] sites = tile.getSites(); StructList.Builder siteBuilders = tileBuilder .initSites(sites.length); diff --git a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java index 0b7de6d52..e3a4616e4 100644 --- a/src/com/xilinx/rapidwright/ipi/BlockStitcher.java +++ b/src/com/xilinx/rapidwright/ipi/BlockStitcher.java @@ -30,6 +30,7 @@ import java.util.HashMap; import java.util.HashSet; import java.util.LinkedList; +import java.util.Map; import java.util.Map.Entry; import java.util.Queue; import java.util.Set; @@ -50,6 +51,8 @@ import com.xilinx.rapidwright.design.blocks.BlockInst; import com.xilinx.rapidwright.design.blocks.ImplGuide; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.xdc.PackagePinConstraint; +import com.xilinx.rapidwright.design.xdc.XDCParser; import com.xilinx.rapidwright.device.PartNameTools; import com.xilinx.rapidwright.device.Site; import com.xilinx.rapidwright.edif.EDIFCell; @@ -137,7 +140,7 @@ private Net validateNet(Net curr, Net found) { * @param design * @param constraints */ - public void stitchDesign(Design design, HashMap constraints) { + public void stitchDesign(Design design, Map constraints) { boolean debug = false; EDIFNetlist n = design.getNetlist(); // Create a reverse parent net map (Parent Net -> Children Nets: all logical nets that are physically equivalent) @@ -271,7 +274,7 @@ else if (net.getType() == NetType.VCC) { Net portNet = null; portNet = e.getValue(); - Site site = design.getDevice().getSiteFromPackagePin(constraints.get(portName).getName()); + Site site = design.getDevice().getSiteFromPackagePin(constraints.get(portName).getPackagePin()); if (site == null) { MessageGenerator.briefMessage("WARNING: It appears that the I/O called " + portName + " is not assigned to a package pin!"); continue nextPort; @@ -294,7 +297,7 @@ else if (net.getType() == NetType.VCC) { } String ioStandard = constraints.get(portName).getIoStandard(); - String pkgPin = constraints.get(portName).getName(); + String pkgPin = constraints.get(portName).getPackagePin(); EDIFNet logNet = e.getKey().getPortInst().getNet(); design.createAndPlaceIOB(portName, isPortOutput ? PinType.OUT : PinType.IN, pkgPin, ioStandard, portNet, logNet); } @@ -562,9 +565,9 @@ public static void main(String[] args) { runtimes[2] = System.currentTimeMillis(); System.out.println("Total Blocks : " + totalBlocks); String xdcFileName = args[1].replace(".edf", ".xdc"); - HashMap constraints = null; + Map constraints = null; if (new File(xdcFileName).exists()) { - constraints = XDCParser.parseXDC(xdcFileName,stitched.getDevice()); + constraints = XDCParser.parseXDC(xdcFileName,stitched.getDevice()).getPinConstraints(); } else { MessageGenerator.briefError("WARNING: Could not find XDC file " + xdcFileName); } diff --git a/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java b/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java deleted file mode 100644 index 49bb2a681..000000000 --- a/src/com/xilinx/rapidwright/ipi/PackagePinConstraint.java +++ /dev/null @@ -1,70 +0,0 @@ -/* - * - * Copyright (c) 2018-2022, Xilinx, Inc. - * Copyright (c) 2022, Advanced Micro Devices, Inc. - * All rights reserved. - * - * Author: Chris Lavin, Xilinx Research Labs. - * - * This file is part of RapidWright. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -/** - * - */ -package com.xilinx.rapidwright.ipi; - -/** - * Annotates a package pin name with an IO standard - * Created on: Jan 25, 2018 - */ -public class PackagePinConstraint { - - private String name; - - private String ioStandard; - - /** - * @return the name - */ - public String getName() { - return name; - } - - /** - * @param name the name to set - */ - public void setName(String name) { - this.name = name; - } - - /** - * @return the ioStandard - */ - public String getIoStandard() { - return ioStandard; - } - - /** - * @param ioStandard the ioStandard to set - */ - public void setIOStandard(String ioStandard) { - this.ioStandard = ioStandard; - } - - public String toString() { - return name + ":" + ioStandard; - } -} diff --git a/src/com/xilinx/rapidwright/ipi/XDCParser.java b/src/com/xilinx/rapidwright/ipi/XDCParser.java deleted file mode 100644 index 626ca3b9b..000000000 --- a/src/com/xilinx/rapidwright/ipi/XDCParser.java +++ /dev/null @@ -1,118 +0,0 @@ -/* - * Copyright (c) 2017-2022, Xilinx, Inc. - * Copyright (c) 2022, Advanced Micro Devices, Inc. - * All rights reserved. - * - * Author: Chris Lavin, Xilinx Research Labs. - * - * This file is part of RapidWright. - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - * - */ -/** - * - */ -package com.xilinx.rapidwright.ipi; - -import java.io.IOException; -import java.io.OutputStream; -import java.util.HashMap; -import java.util.List; - -import com.xilinx.rapidwright.device.Device; -import com.xilinx.rapidwright.util.FileTools; - - -/** - * Parses an XDC file for package constraints only. Does not - * perform full XDC parsing. - * - * Created on: Jul 27, 2015 - */ -public class XDCParser { - - private static boolean expect(String expected, String found, int lineNum, String line) { - if (!expected.equals(found)) { - throw new RuntimeException("\nERROR: While parsing line:\n '" + - line + "' (line number " + lineNum + ")\n" + " Expected: '" + - expected + "'\n Found: '" + found + "'\nStack Trace:"); - } - return true; - } - - /** - * Very rudimentary parsing to extract IO placements and IO standards. This - * does not support many Tcl constructs. - * @param fileName Name of the XDC file to parse - * @param dev The device associated with the design. - * @return A map of port names to package pin information. - */ - public static HashMap parseXDC(String fileName, Device dev) { - HashMap constraints = new HashMap<>(); - int lineNum = 1; - for (String line : FileTools.getLinesFromTextFile(fileName)) { - if (line.trim().startsWith("#")) continue; - if (line.contains("set_property") && line.contains("PACKAGE_PIN")) { - String[] parts = line.split("\\s+"); - expect("set_property", parts[0], lineNum, line); - expect("PACKAGE_PIN", parts[1], lineNum, line); - String pinLoc = parts[2]; - if (!dev.getActivePackage().getPackagePinMap().containsKey(pinLoc)) { - expect("", pinLoc,lineNum,line); - } - expect("[get_ports", parts[3], lineNum, line); - String key = parts[4].substring(0, parts[4].lastIndexOf(']')); - key = key.replace("}", ""); - key = key.replace("{", ""); - PackagePinConstraint pkgPin = constraints.get(key); - if (pkgPin == null) { - pkgPin = new PackagePinConstraint(); - constraints.put(key, pkgPin); - } - pkgPin.setName(pinLoc); - } else if (line.contains("set_property") && line.contains("IOSTANDARD")) { - String[] parts = line.split("\\s+"); - expect("set_property", parts[0], lineNum, line); - expect("IOSTANDARD", parts[1], lineNum, line); - String ioStandard = parts[2]; - expect("[get_ports", parts[3], lineNum, line); - String key = parts[4].substring(0, parts[4].lastIndexOf(']')); - key = key.replace("}", ""); - key = key.replace("{", ""); - PackagePinConstraint pkgPin = constraints.get(key); - if (pkgPin == null) { - pkgPin = new PackagePinConstraint(); - constraints.put(key, pkgPin); - } - pkgPin.setIOStandard(ioStandard); - } - lineNum++; - } - - - return constraints; - } - - public static void writeXDC(List constraints, OutputStream out) { - if (constraints == null) return; - try { - for (String s : constraints) { - out.write(s.getBytes()); - out.write('\n'); - } - } catch (IOException e) { - e.printStackTrace(); - } - } -} diff --git a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java index 35cfccfb4..89f53ba5b 100644 --- a/src/com/xilinx/rapidwright/util/PerformanceExplorer.java +++ b/src/com/xilinx/rapidwright/util/PerformanceExplorer.java @@ -28,19 +28,18 @@ import java.io.File; import java.io.IOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; import java.text.DecimalFormat; -import java.util.ArrayList; -import java.util.Arrays; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; import com.xilinx.rapidwright.design.ConstraintGroup; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.blocks.PBlock; +import com.xilinx.rapidwright.design.tools.InlineFlopTools; import joptsimple.OptionParser; import joptsimple.OptionSet; @@ -52,8 +51,11 @@ public class PerformanceExplorer { private static final String INITIAL_DCP_NAME = "initial.dcp"; + private static final String INITIAL_ENCRYPTED_TCL_NAME = "initial_load.tcl"; private static final String PLACED_TIMING_RESULT = "place_timing.twr"; private static final String ROUTED_TIMING_RESULT = "route_timing.twr"; + private static final String ROUTED_TIMING_SUMMARY = "route_timing_summary.rpt"; + private static final String ROUTE_STATUS_RESULT = "route_status.rpt"; private static final String RUN_TCL_NAME = "run.tcl"; private static final double DEFAULT_MIN_CLK_UNCERT = -0.100; private static final double DEFAULT_MAX_CLK_UNCERT = 0.250; @@ -77,10 +79,14 @@ public class PerformanceExplorer { /** Maps a pblock to optional named cell(s) to be constrained by the pblock */ private Map pblocks; + private PBlock[] pblockLookup; + private boolean containRouting; private boolean addEDIFAndMetadata; + private boolean getBestPerPBlock; + private ArrayList placerDirectives; private ArrayList routerDirectives; @@ -95,6 +101,10 @@ public class PerformanceExplorer { private String vivadoPath = DEFAULT_VIVADO; + private boolean reusePreviousResults; + + private boolean ensureExternalRoutability; + public PerformanceExplorer(Design d, String testDir, String clkName, double targetPeriod) { init(d, testDir, clkName, targetPeriod, null); } @@ -262,23 +272,63 @@ public void setAddEDIFAndMetadata(boolean addEDIFAndMetadata) { this.addEDIFAndMetadata = addEDIFAndMetadata; } + public boolean getBestPerPBlock() { + return getBestPerPBlock; + } + + public void setGetBestPerPBlock(boolean getBestPerPBlock) { + this.getBestPerPBlock = getBestPerPBlock; + } + + public boolean ensureExternalRoutability() { + return ensureExternalRoutability; + } + + public void setEnsureExternalRoutability(boolean ensureExternalRoutability) { + this.ensureExternalRoutability = ensureExternalRoutability; + } + + public PBlock getPBlock(int i) { + return pblockLookup[i]; + } + + public void setReusePreviousResults(boolean value) { + this.reusePreviousResults = value; + } + + public boolean reusePreviousResults() { + return reusePreviousResults; + } + public ArrayList createTclScript(String initialDcp, String instDirectory, - PlacerDirective p, RouterDirective r, String clockUncertainty, Entry pblockEntry) { + PlacerDirective p, RouterDirective r, String clockUncertainty, + Entry pblockEntry, String encryptedTcl) { PBlock pblock = pblockEntry.getKey(); String pblockCells = pblockEntry.getValue(); ArrayList lines = new ArrayList<>(); - lines.add("open_checkpoint " + initialDcp); + if (encryptedTcl == null) { + lines.add("open_checkpoint " + initialDcp); + } else { + lines.add("source " + encryptedTcl); + } lines.add("set_clock_uncertainty -setup "+clockUncertainty+" [get_clocks "+clkName+"]"); if (pblock != null) { String pblockName = pblock.getName() == null ? "pe_pblock_1" : pblock.getName(); lines.add("create_pblock " + pblockName); lines.add("resize_pblock "+pblockName+" -add {"+pblock.toString()+"}"); lines.add("add_cells_to_pblock "+pblockName+" " + (pblockCells == null ? "-top" : "[get_cells {"+ pblockCells +"}]" )); - if (containRouting) { + lines.add("set_property IS_SOFT 0 [get_pblocks " + pblockName + "]"); + if (isContainRouting()) { lines.add("set_property CONTAIN_ROUTING 1 [get_pblocks "+ pblockName+"]"); } } - lines.add("place_design -unplace"); + if (ensureExternalRoutability()) { + lines.add("lock_design -level placement"); + } + lines.add("opt_design"); + if (!ensureExternalRoutability()) { + lines.add("place_design -unplace"); + } lines.add("place_design -directive " + p.name()); lines.add("set_clock_uncertainty -setup 0.0 [get_clocks "+clkName+"]"); lines.add("report_timing -file "+instDirectory + File.separator+PLACED_TIMING_RESULT); @@ -296,6 +346,25 @@ public ArrayList createTclScript(String initialDcp, String instDirectory return lines; } + public static void updateClockPeriodConstraint(Design design, String clkName, double period) { + // Update clock period constraint + boolean foundExistingConstraint = false; + for (ConstraintGroup g : ConstraintGroup.values()) { + List xdcList = design.getXDCConstraints(g); + for (int i = 0; i < xdcList.size(); i++) { + String xdc = xdcList.get(i); + if (xdc.contains("create_clock") + && (xdc.contains("-name " + clkName) || xdc.contains("[get_ports " + clkName + "]"))) { + // TODO This may overwrite other existing options + xdcList.set(i, "create_clock -period " + period + " [get_ports " + clkName + "]"); + foundExistingConstraint = true; + } + } + } + if (!foundExistingConstraint) { + design.addXDCConstraint("create_clock -period " + period + " [get_ports " + clkName + "]"); + } + } public void explorePerformance() { if (vivadoPath.equals(DEFAULT_VIVADO) && !FileTools.isVivadoOnPath()) { @@ -306,16 +375,8 @@ public void explorePerformance() { FileTools.makeDirs(runDirectory); runDirectory = new File(runDirectory).getAbsolutePath(); String dcpName = runDirectory + File.separator + INITIAL_DCP_NAME; - // Update clock period constraint - for (ConstraintGroup g : ConstraintGroup.values()) { - List xdcList = design.getXDCConstraints(g); - for (int i=0; i < xdcList.size(); i++) { - String xdc = xdcList.get(i); - if (xdc.contains("create_clock") && xdc.contains("-name " + clkName)) { - // TODO - For now, user will need to update DCP beforehand - } - } - } + + updateClockPeriodConstraint(design, getClkName(), getTargetPeriod()); design.writeCheckpoint(dcpName); JobQueue jobs = new JobQueue(); @@ -328,37 +389,54 @@ public void explorePerformance() { int pb = 0; int jobsStarted = 0; int maxConcurrentJobs = JobQueue.isLSFAvailable() ? JobQueue.MAX_LSF_CONCURRENT_JOBS : JobQueue.MAX_LOCAL_CONCURRENT_JOBS; + pblockLookup = new PBlock[pblocks.size()]; for (Entry e : pblocks.entrySet()) { PBlock pblock = e.getKey(); + + String pblockDcpName = dcpName; + if (ensureExternalRoutability()) { + InlineFlopTools.createAndPlaceFlopsInlineOnTopPortsArbitrarily(design, clkName, pblock); + pblockDcpName = runDirectory + File.separator + "pblock" + pb + "_" + INITIAL_DCP_NAME; + design.writeCheckpoint(pblockDcpName); + } + for (PlacerDirective p : getPlacerDirectives()) { for (RouterDirective r : getRouterDirectives()) { for (double c : getClockUncertaintyValues()) { String roundedC = printNS(c); String uniqueID = p.name() + "_" + r.name() + "_" + roundedC; if (pblock != null) { - uniqueID = uniqueID + "_pblock" + pb + "_" + pblock.get(0).getLowerLeftSite() +"-"; + uniqueID = uniqueID + "_pblock" + pb + "_" + pblock.get(0).getLowerLeftSite() + "-"; } System.out.println(uniqueID); String instDir = runDirectory + File.separator + uniqueID; FileTools.makeDir(instDir); - ArrayList tcl = createTclScript(dcpName, instDir, p, r, roundedC, e); + String encryptedTcl = null; + boolean encrypted = !design.getNetlist().getEncryptedCells().isEmpty(); + if (encrypted) { + encryptedTcl = runDirectory + File.separator + "pblock" + pb + + "_" + INITIAL_ENCRYPTED_TCL_NAME; + } + ArrayList tcl = createTclScript(pblockDcpName, instDir, p, r, roundedC, e, encryptedTcl); String scriptName = instDir + File.separator + RUN_TCL_NAME; - FileTools.writeLinesToTextFile(tcl, scriptName); - - Job j = JobQueue.createJob(); - j.setRunDir(instDir); - j.setCommand(getVivadoPath() + " -mode batch -source " + scriptName); - if (jobsStarted < maxConcurrentJobs) { - j.launchJob(); - jobs.addRunningJob(j); - jobsStarted++; - } else { - jobs.addJob(j); + + if (!reusePreviousResults) { + FileTools.writeLinesToTextFile(tcl, scriptName); + Job j = JobQueue.createJob(); + j.setRunDir(instDir); + j.setCommand(getVivadoPath() + " -mode batch -source " + scriptName); + if (jobsStarted < maxConcurrentJobs) { + j.launchJob(); + jobs.addRunningJob(j); + jobsStarted++; + } else { + jobs.addJob(j); + } } } } } - pb++; + pblockLookup[pb++] = pblock; } boolean success = jobs.runAllToCompletion(maxConcurrentJobs); @@ -366,6 +444,77 @@ public void explorePerformance() { System.out.println("Performance Explorer " + (success ? "Finished Successfully." : "Failed!")); } + private Float parseWNSFromTimingReport(Path timingReport) { + for (String line : FileTools.getLinesFromTextFile(timingReport.toString())) { + if (line.contains("Slack ")) { + String[] values = line.split("\\s+"); + assert (values.length >= 4); + String value = values[3].replace("ns", ""); + return Float.parseFloat(value); + } + } + return null; + } + + private static final String PBLOCK_SUFFIX = "_pblock"; + + public List> getBestResultsPerPBlock() { + List> results = new ArrayList<>(); + for (int i = 0; i < pblocks.size(); i++) { + results.add(new Pair(null, -1 * Float.MAX_VALUE)); + } + Path runDir = Paths.get(runDirectory); + String[] runDirFiles = runDir.toFile().list(); + for (String dirName : runDirFiles) { + Path dir = runDir.resolve(dirName); + int pblockIdx = 0; + int i = dirName.indexOf(PBLOCK_SUFFIX); + if (i >= 0) { + int start = i + PBLOCK_SUFFIX.length(); + int end = dirName.indexOf('_', start); + String tmp = dirName.substring(start, end); + pblockIdx = Integer.parseInt(tmp); + } + if (Files.isDirectory(dir)) { + Path routeTiming = dir.resolve("route_timing.twr"); + if (Files.exists(routeTiming)) { + Float value = parseWNSFromTimingReport(routeTiming); + Pair bestFoundSoFar = results.get(pblockIdx); + if (value > bestFoundSoFar.getSecond()) { + bestFoundSoFar.setFirst(dir); + bestFoundSoFar.setSecond(value); + } + Pair curr = results.get(pblockIdx); + curr.setFirst(dir); + curr.setSecond(value); + } + } + } + return results; + } + + public void getBestDesignPerPBlock() { + List> bestResultsPerPBlock = getBestResultsPerPBlock(); + Path runDir = Paths.get(runDirectory); + + int pblockNum = 0; + for (Pair best : bestResultsPerPBlock) { + String pblock = "pblock" + pblockNum; + Path bestPath = best.getFirst(); + Float bestSlack = best.getSecond(); + if (bestSlack < 0.0) { + System.out.println("No implementation passes timing for " + pblock); + } + + Design d = Design.readCheckpoint(bestPath + File.separator + "routed.dcp"); + + if (ensureExternalRoutability()) { + InlineFlopTools.removeInlineFlops(d); + } + + d.writeCheckpoint(runDirectory + File.separator + pblock + "_best.dcp"); + } + } private static final String INPUT_DCP_OPT = "i"; private static final String PBLOCK_FILE_OPT = "b"; @@ -383,6 +532,9 @@ public void explorePerformance() { private static final String RUN_DIR_OPT = "d"; private static final String VIVADO_PATH_OPT = "y"; private static final String MAX_CONCURRENT_JOBS_OPT = "z"; + private static final String ENSURE_EXT_ROUTABILITY = "e"; + private static final String COLLECT_RESULTS_OPT = "o"; + private static final String REUSE_PREVIOUS_RESULTS = "reuse-previous"; private static OptionParser createOptionParser() { // Defaults @@ -404,6 +556,9 @@ private static OptionParser createOptionParser() { accepts(VIVADO_PATH_OPT).withOptionalArg().defaultsTo(DEFAULT_VIVADO).describedAs("Specifies vivado path"); accepts(CONTAIN_ROUTING_OPT).withOptionalArg().ofType(Boolean.class).defaultsTo(DEFAULT_CONTAIN_ROUTING).describedAs("Sets attribute on pblock to contain routing"); accepts(MAX_CONCURRENT_JOBS_OPT).withOptionalArg().ofType(Integer.class).defaultsTo(JobQueue.MAX_LOCAL_CONCURRENT_JOBS).describedAs("Max number of concurrent job when run locally"); + accepts(ENSURE_EXT_ROUTABILITY, "Ensure all I/O are routable outside the pblock"); + accepts(COLLECT_RESULTS_OPT,"Collect results into output csv"); + accepts(REUSE_PREVIOUS_RESULTS, "Reuse previous results if they exist"); acceptsAll( Arrays.asList(HELP_OPT, "?"), "Print Help" ).forHelp(); }}; @@ -467,7 +622,9 @@ public static void main(String[] args) { pe.setVivadoPath((String)opts.valueOf(VIVADO_PATH_OPT)); pe.setContainRouting((boolean)opts.valueOf(CONTAIN_ROUTING_OPT)); pe.setAddEDIFAndMetadata((boolean)opts.valueOf(ADD_EDIF_METADATA_OPT)); - + pe.setGetBestPerPBlock(opts.has(COLLECT_RESULTS_OPT)); + pe.setReusePreviousResults(opts.has(REUSE_PREVIOUS_RESULTS)); + pe.setEnsureExternalRoutability(opts.has(ENSURE_EXT_ROUTABILITY)); if (opts.hasArgument(PBLOCK_FILE_OPT)) { String fileName = (String) opts.valueOf(PBLOCK_FILE_OPT); @@ -491,5 +648,9 @@ public static void main(String[] args) { } pe.explorePerformance(); + + if (pe.getBestPerPBlock) { + pe.getBestDesignPerPBlock(); + } } } diff --git a/src/com/xilinx/rapidwright/util/Utils.java b/src/com/xilinx/rapidwright/util/Utils.java index 6f9029f8d..545563cb8 100644 --- a/src/com/xilinx/rapidwright/util/Utils.java +++ b/src/com/xilinx/rapidwright/util/Utils.java @@ -374,7 +374,8 @@ public static boolean isPS(SiteTypeEnum s) { lockedSiteTypes = EnumSet.of( SiteTypeEnum.CONFIG_SITE, - SiteTypeEnum.BUFG + SiteTypeEnum.BUFG, + SiteTypeEnum.BUFGCE ); sliceTypes = EnumSet.of( diff --git a/src/com/xilinx/rapidwright/util/VivadoTools.java b/src/com/xilinx/rapidwright/util/VivadoTools.java index b5cbce750..08ecb3ae2 100644 --- a/src/com/xilinx/rapidwright/util/VivadoTools.java +++ b/src/com/xilinx/rapidwright/util/VivadoTools.java @@ -161,15 +161,25 @@ public static String reportRouteStatus(Design design, String netName) { } private static Path writeCheckpoint(Design design) { - final Path workdir = FileSystems.getDefault() - .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); - File workdirHandle = new File(workdir.toString()); - workdirHandle.mkdirs(); + final Path workdir = createTempVivadoToolsWorkDir(); final Path dcp = workdir.resolve("checkpoint.dcp"); design.writeCheckpoint(dcp); return dcp; } + /** + * Creates a unique, temporary work directory for Vivado interaction. + * + * @return + */ + public static Path createTempVivadoToolsWorkDir() { + Path workDir = FileSystems.getDefault().getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); + ; + File workDirHandle = new File(workDir.toString()); + workDirHandle.mkdirs(); + return workDir; + } + /** * Run Vivado's `write_bitstream` on the provided DCP file to generate a bit * file at the specified location. @@ -209,7 +219,6 @@ public static List writeBitstream(Path dcp, Path bitFile, boolean hasEnc .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); File workdirHandle = new File(workdir.toString()); workdirHandle.mkdirs(); - final Path outputLog = workdir.resolve("outputLog.log"); StringBuilder sb = new StringBuilder(); sb.append(createTclDCPLoadCommand(dcp, hasEncryptedIP)); @@ -282,15 +291,9 @@ public static ReportRouteStatusResult reportRouteStatus(Path dcp) { * @return ReportRouteStatusResult object. */ public static ReportRouteStatusResult reportRouteStatus(Path dcp, boolean encrypted) { - final Path workdir = FileSystems.getDefault() - .getPath("vivadoToolsWorkdir" + FileTools.getUniqueProcessAndHostID()); - File workdirHandle = new File(workdir.toString()); - workdirHandle.mkdirs(); - + final Path workdir = createTempVivadoToolsWorkDir(); ReportRouteStatusResult rrs = reportRouteStatus(dcp, workdir, encrypted); - FileTools.deleteFolder(workdir.toString()); - return rrs; } @@ -474,6 +477,31 @@ public static float getWorstSetupSlack(Path dcp, Path workdir, boolean encrypted return Float.NaN; } + /** + * Creates a utilization report and shapes report for a given synthesized DCP + * file. + * + * @param design The input DCP to measure and extract reports from. + * @param utilReport The desired path to a utilization report from Vivado + * (report_utilization -file ). + * @param shapesReport The desired shapes report. + */ + public static void getUtilizationAndShapesReport(Path design, Path utilReport, Path shapesReport) { + final Path workDir = createTempVivadoToolsWorkDir(); + final Path outputLog = workDir.resolve("outputLog.log"); + + StringBuilder sb = new StringBuilder(); + sb.append("open_checkpoint " + design.toString() + "\n"); + sb.append("report_utilization -file " + utilReport.toString() + "\n"); + sb.append("set_param place.debugShape " + shapesReport.toString() + "\n"); + sb.append("place_design -directive Quick \n"); + sb.append("set_param place.debugShape \"\"\n"); + + VivadoTools.runTcl(outputLog, sb.toString(), true); + + FileTools.deleteFolder(workDir.toString()); + } + /** * Open a DCP in Vivado and write a new DCP. * diff --git a/test/RapidWrightDCP b/test/RapidWrightDCP index 72829b123..fce08f70c 160000 --- a/test/RapidWrightDCP +++ b/test/RapidWrightDCP @@ -1 +1 @@ -Subproject commit 72829b1235814d8ea3145f6c99a9eb531028b3a5 +Subproject commit fce08f70c35ed42d75408ba4f14fb502b47dbd49 diff --git a/test/src/com/xilinx/rapidwright/design/TestCell.java b/test/src/com/xilinx/rapidwright/design/TestCell.java index 52fa4cf89..1319be876 100644 --- a/test/src/com/xilinx/rapidwright/design/TestCell.java +++ b/test/src/com/xilinx/rapidwright/design/TestCell.java @@ -66,6 +66,29 @@ public void testGetAllCorrespondingSitePinNames(String deviceName, Assertions.assertEquals(expectedSitePins, sitePinNames.toString()); } + @ParameterizedTest + @CsvSource({ + "false,[G5]", + "true,'[G5, G1, G2, G3, G4, G6]'", + }) + public void testGetAllCorrespondingSitePinNamesLUTRouteThru(boolean considerLutRoutethru, String expectedSitePins) { + Design d = new Design("testGetAllCorrespondingSitePinNamesLUTRouteThru", Device.KCU105); + SiteInst si = d.createSiteInst(d.getDevice().getSite("SLICE_X32Y73")); + Cell cell = d.createAndPlaceCell("f7mux", Unisim.MUXF7, si.getSiteName() + "/F7MUX_GH"); + + Net netS = d.createNet("netS"); + Assertions.assertTrue(si.routeIntraSiteNet(netS, si.getBELPin("GX", "GX"), + si.getBELPin("F7MUX_GH", "S0"))); + List sitePinNames = cell.getAllCorrespondingSitePinNames("S", considerLutRoutethru); + Assertions.assertEquals("[GX]", sitePinNames.toString()); + + Net net1 = d.createNet("net1"); + Assertions.assertTrue(si.routeIntraSiteNet(net1, si.getBELPin("G5", "G5"), + si.getBELPin("F7MUX_GH", "1"))); + sitePinNames = cell.getAllCorrespondingSitePinNames("I1", considerLutRoutethru); + Assertions.assertEquals(expectedSitePins, sitePinNames.toString()); + } + @ParameterizedTest @CsvSource({ // Versal input pins diff --git a/test/src/com/xilinx/rapidwright/design/TestNetTools.java b/test/src/com/xilinx/rapidwright/design/TestNetTools.java index 80c122ef1..a1469be9c 100644 --- a/test/src/com/xilinx/rapidwright/design/TestNetTools.java +++ b/test/src/com/xilinx/rapidwright/design/TestNetTools.java @@ -41,7 +41,7 @@ public class TestNetTools { /** * Tests the method NetTools.isGlobalClock(Net net). - * For each DCP file, Vivado uses the TCL command + * For each DCP file, Vivado uses the Tcl command * get_nets -hier -parent_net -filter { TYPE == "GLOBAL_CLOCK" } * to retrieve all nets of type GLOBAL_CLOCK. * The method NetTools.isGlobalClock(Net net) returns true for these nets and false for others. diff --git a/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java b/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java index a5485464b..16d9381b1 100644 --- a/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java +++ b/test/src/com/xilinx/rapidwright/design/TestPartitionPin.java @@ -81,17 +81,10 @@ public void testPartitionPins() { int wireIdx = 10; int count = 0; for (EDIFPort port : design.getTopEDIFCell().getPorts()) { - if (port.isBus()) { - for (int i : port.getBitBlastedIndicies()) { - Node node = Node.getNode(t, wireIdx++); - PartitionPin ppin = design.createPartitionPin(port, i, node); - testPortPartitionPin(design, ppin, port, i, node); - count++; - } - } else { + for (int i : port.getBitBlastedIndices()) { Node node = Node.getNode(t, wireIdx++); - PartitionPin ppin = design.createPartitionPin(port, node); - testPortPartitionPin(design, ppin, port, -1, node); + PartitionPin ppin = design.createPartitionPin(port, i, node); + testPortPartitionPin(design, ppin, port, i, node); count++; } } diff --git a/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java new file mode 100644 index 000000000..ac163d0ea --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/blocks/TestUtilizationType.java @@ -0,0 +1,53 @@ +/* + * + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, AMD Advanced Research and Development. + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.blocks; + +import java.util.Map; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Test; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.support.RapidWrightDCP; + +public class TestUtilizationType { + + @Test + public void testComputeUtilization() { + Design design = RapidWrightDCP.loadDCP("optical-flow.dcp"); + + Map utilization = UtilizationType.computeUtilization(design); + System.out.println(utilization); + + Assertions.assertEquals(2891, utilization.get(UtilizationType.CARRY8S)); + Assertions.assertEquals(25740, utilization.get(UtilizationType.CLB_REGS)); + Assertions.assertEquals(124, utilization.get(UtilizationType.DSPS)); + Assertions.assertEquals(59, utilization.get(UtilizationType.RAMB36S_FIFOS)); + Assertions.assertEquals(10, utilization.get(UtilizationType.RAMB18S)); + Assertions.assertEquals(287, utilization.get(UtilizationType.LUTS_AS_MEMORY)); + Assertions.assertEquals(64, utilization.get(UtilizationType.BRAMS)); + Assertions.assertEquals(30239, utilization.get(UtilizationType.CLB_LUTS)); + + } +} diff --git a/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java b/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java index 61c50208b..be2cb09d1 100644 --- a/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java +++ b/test/src/com/xilinx/rapidwright/design/compare/TestDesignComparator.java @@ -109,12 +109,24 @@ public void testDesignComparator() { String oldType = removedCell.getType(); removedCell.setType("MISMATCHEDTYPE"); - compareDesign(4, 1, DesignDiffType.PLACED_CELL_TYPE, dc, gold, test2); + compareDesign(6, 1, DesignDiffType.PLACED_CELL_TYPE, dc, gold, test2); removedCell.setType(oldType); removedCell.updateName("NEWNAME"); - compareDesign(4, 1, DesignDiffType.PLACED_CELL_NAME, dc, gold, test2); + compareDesign(6, 1, DesignDiffType.PLACED_CELL_NAME, dc, gold, test2); + + Cell compareCell = siteInst.getCell("F6LUT"); + + Assertions.assertFalse(compareCell.isBELFixed()); + compareCell.setBELFixed(true); + + compareDesign(7, 2, DesignDiffType.PLACED_CELL_IS_BEL_FIXED, dc, gold, test2); + + Assertions.assertFalse(compareCell.isSiteFixed()); + compareCell.setSiteFixed(true); + + compareDesign(8, 2, DesignDiffType.PLACED_CELL_IS_SITE_FIXED, dc, gold, test2); SitePIP sitePIP = siteInst.getUsedSitePIP("CLKINV"); Net clk = siteInst.getNetFromSiteWire(sitePIP.getInputPin().getSiteWireName()); @@ -122,11 +134,11 @@ public void testDesignComparator() { siteInst.routeIntraSiteNet(clk, sitePIP.getInputPin(), sitePIP.getInputPin()); siteInst.routeIntraSiteNet(clk, sitePIP.getOutputPin(), sitePIP.getOutputPin()); - compareDesign(5, 1, DesignDiffType.SITEPIP_MISSING, dc, gold, test2); + compareDesign(9, 1, DesignDiffType.SITEPIP_MISSING, dc, gold, test2); siteInst.addSitePIP("FFMUXC1", "D6"); - compareDesign(6, 1, DesignDiffType.SITEPIP_EXTRA, dc, gold, test2); + compareDesign(10, 1, DesignDiffType.SITEPIP_EXTRA, dc, gold, test2); sitePIP = siteInst.getUsedSitePIP("FFMUXA1"); Net net = siteInst.getNetFromSiteWire(sitePIP.getInputPin().getSiteWireName()); @@ -135,21 +147,21 @@ public void testDesignComparator() { siteInst.routeIntraSiteNet(net, sitePIP.getOutputPin(), sitePIP.getOutputPin()); siteInst.addSitePIP("FFMUXA1", "D6"); - compareDesign(8, 1, DesignDiffType.SITEPIP_INPIN_NAME, dc, gold, test2); + compareDesign(12, 1, DesignDiffType.SITEPIP_INPIN_NAME, dc, gold, test2); Assertions.assertEquals(1, dc.getDiffList(DesignDiffType.SITEWIRE_NET_EXTRA).size()); siteInst.unrouteIntraSiteNet(sitePIP.getInputPin(), sitePIP.getInputPin()); - compareDesign(9, 1, DesignDiffType.SITEWIRE_NET_MISSING, dc, gold, test2); + compareDesign(13, 1, DesignDiffType.SITEWIRE_NET_MISSING, dc, gold, test2); siteInst.routeIntraSiteNet(new Net("mismatch"), sitePIP.getInputPin(), sitePIP.getInputPin()); - compareDesign(9, 1, DesignDiffType.SITEWIRE_NET_NAME, dc, gold, test2); + compareDesign(13, 1, DesignDiffType.SITEWIRE_NET_NAME, dc, gold, test2); Assertions.assertTrue(net.hasPIPs()); test2.removeNet(net); - compareDesign(28, 1, DesignDiffType.NET_MISSING, dc, gold, test2); + compareDesign(32, 1, DesignDiffType.NET_MISSING, dc, gold, test2); Net extra = new Net("extraNet"); for (PIP p : net.getPIPs()) { @@ -157,17 +169,17 @@ public void testDesignComparator() { } test2.addNet(extra); - compareDesign(29, 1, DesignDiffType.NET_EXTRA, dc, gold, test2); + compareDesign(33, 1, DesignDiffType.NET_EXTRA, dc, gold, test2); Assertions.assertTrue(extra.hasPIPs()); clk.addPIP(extra.getPIPs().get(0)); - compareDesign(30, 1, DesignDiffType.PIP_EXTRA, dc, gold, test2); + compareDesign(34, 1, DesignDiffType.PIP_EXTRA, dc, gold, test2); clk.getPIPs().remove(clk.getPIPs().size() - 1); clk.getPIPs().remove(clk.getPIPs().size() - 1); - compareDesign(30, 1, DesignDiffType.PIP_MISSING, dc, gold, test2); + compareDesign(34, 1, DesignDiffType.PIP_MISSING, dc, gold, test2); if (dc.comparePIPFlags()) { clk.getPIPs().get(clk.getPIPs().size() - 1).setIsStub(true); diff --git a/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java b/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java new file mode 100644 index 000000000..460d495aa --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/xdc/NicerStringifyList.java @@ -0,0 +1,184 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ +package com.xilinx.rapidwright.design.xdc; + +import java.util.Collection; +import java.util.Comparator; +import java.util.Iterator; +import java.util.List; +import java.util.ListIterator; +import java.util.Objects; +import java.util.Spliterator; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import org.jetbrains.annotations.NotNull; + +public class NicerStringifyList implements List { + + @Override + public String toString() { + return list.stream().map(Object::toString).map(s -> s.replaceAll("] ", "] \" + \n \"")).collect(Collectors.joining("\",\n\"", "\"", "\"")); + } + + public int size() { + return list.size(); + } + + public boolean isEmpty() { + return list.isEmpty(); + } + + public boolean contains(Object o) { + return list.contains(o); + } + + public Iterator iterator() { + return list.iterator(); + } + + public Object[] toArray() { + return list.toArray(); + } + + public T1[] toArray(@NotNull T1[] a) { + return list.toArray(a); + } + + public boolean add(T t) { + return list.add(t); + } + + public boolean remove(Object o) { + return list.remove(o); + } + + public boolean containsAll(@NotNull Collection c) { + return list.containsAll(c); + } + + public boolean addAll(@NotNull Collection c) { + return list.addAll(c); + } + + public boolean addAll(int index, @NotNull Collection c) { + return list.addAll(index, c); + } + + public boolean removeAll(@NotNull Collection c) { + return list.removeAll(c); + } + + public boolean retainAll(@NotNull Collection c) { + return list.retainAll(c); + } + + public void replaceAll(UnaryOperator operator) { + list.replaceAll(operator); + } + + public void sort(Comparator c) { + list.sort(c); + } + + public void clear() { + list.clear(); + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + NicerStringifyList that = (NicerStringifyList) o; + return Objects.equals(list, that.list); + } + + @Override + public int hashCode() { + return list.hashCode(); + } + + public T get(int index) { + return list.get(index); + } + + public T set(int index, T element) { + return list.set(index, element); + } + + public void add(int index, T element) { + list.add(index, element); + } + + public T remove(int index) { + return list.remove(index); + } + + public int indexOf(Object o) { + return list.indexOf(o); + } + + public int lastIndexOf(Object o) { + return list.lastIndexOf(o); + } + + public ListIterator listIterator() { + return list.listIterator(); + } + + public ListIterator listIterator(int index) { + return list.listIterator(index); + } + + public List subList(int fromIndex, int toIndex) { + return list.subList(fromIndex, toIndex); + } + + public Spliterator spliterator() { + return list.spliterator(); + } + + public boolean removeIf(Predicate filter) { + return list.removeIf(filter); + } + + public Stream stream() { + return list.stream(); + } + + public Stream parallelStream() { + return list.parallelStream(); + } + + public void forEach(Consumer action) { + list.forEach(action); + } + + private final List list; + + public NicerStringifyList(List list) { + this.list = list; + } +} diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java index 02545947c..898a5bda0 100644 --- a/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestConstraintTools.java @@ -33,15 +33,21 @@ import com.xilinx.rapidwright.design.blocks.PBlock; import com.xilinx.rapidwright.design.blocks.PblockProperty; import com.xilinx.rapidwright.support.RapidWrightDCP; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; public class TestConstraintTools { - @Test - public void testGetPBlockFromXDCConstraints() { + @ParameterizedTest + @EnumSource(TestXDCParser.RoundtripMode.class) + public void testGetPBlockFromXDCConstraints(TestXDCParser.RoundtripMode roundtripMode) { Design d = RapidWrightDCP.loadDCP("microblazeAndILA_3pblocks.dcp"); d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.IS_SOFT + " 1 [get_pblocks pblock_dbg_hub]"); d.getXDCConstraints(ConstraintGroup.LATE).add("set_property " + PblockProperty.EXCLUDE_PLACEMENT + " 1 [get_pblocks pblock_u_ila_0]"); - Map pblockMap = ConstraintTools.getPBlockFromXDCConstraints(d); + roundtripMode.doRoundtrip(d); + + Map pblockMap = ConstraintTools.getPBlocksFromXDC(d); Assertions.assertEquals(3, pblockMap.size()); Assertions.assertTrue(pblockMap.containsKey("pblock_dbg_hub")); Assertions.assertTrue(pblockMap.containsKey("pblock_base_mb_i")); @@ -50,7 +56,6 @@ public void testGetPBlockFromXDCConstraints() { // Check for the property and cooresponding TclConstraints String TclConstraints; PBlock dbgHub = pblockMap.get("pblock_dbg_hub"); - dbgHub.setName("pblock_dbg_hub"); Assertions.assertTrue(dbgHub.containRouting()); Assertions.assertTrue(dbgHub.isSoft()); Assertions.assertFalse(dbgHub.excludePlacement()); @@ -62,7 +67,6 @@ public void testGetPBlockFromXDCConstraints() { ); PBlock baseMb = pblockMap.get("pblock_base_mb_i"); - baseMb.setName("pblock_base_mb_i"); Assertions.assertTrue(baseMb.containRouting()); Assertions.assertFalse(baseMb.isSoft()); Assertions.assertFalse(baseMb.excludePlacement()); @@ -74,7 +78,6 @@ public void testGetPBlockFromXDCConstraints() { ); PBlock uila0 = pblockMap.get("pblock_u_ila_0"); - uila0.setName("pblock_u_ila_0"); Assertions.assertTrue(uila0.containRouting()); Assertions.assertFalse(uila0.isSoft()); Assertions.assertTrue(uila0.excludePlacement()); diff --git a/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java b/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java new file mode 100644 index 000000000..82fe4daa9 --- /dev/null +++ b/test/src/com/xilinx/rapidwright/design/xdc/TestXDCParser.java @@ -0,0 +1,1071 @@ +/* + * Copyright (c) 2025, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Jakob Wenzel, Technical University of Darmstadt + * + * This file is part of RapidWright. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package com.xilinx.rapidwright.design.xdc; + +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.file.Files; +import java.nio.file.Path; +import java.util.Arrays; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Function; +import java.util.function.Predicate; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import com.xilinx.rapidwright.design.ConstraintGroup; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.design.xdc.parser.CellObject; +import com.xilinx.rapidwright.design.xdc.parser.DesignObject; +import com.xilinx.rapidwright.design.xdc.parser.EdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.RegularEdifCellLookup; +import com.xilinx.rapidwright.design.xdc.parser.TclHashIdentifiedObject; +import com.xilinx.rapidwright.edif.EDIFHierCellInst; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.support.RapidWrightDCP; +import com.xilinx.rapidwright.util.FileTools; +import org.jetbrains.annotations.NotNull; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.EnumSource; +import org.junit.jupiter.params.provider.MethodSource; +import tcl.lang.Interp; +import tcl.lang.TCL; +import tcl.lang.TclException; +import tcl.lang.TclObject; + +public class TestXDCParser { + + //Shared between all test class instances, but weak reference + private static WeakReference ethernetDesignShared = new WeakReference<>(null); + //Strong reference in our test class instance + private Design ethernetDesign; + + private Design getEthernetDesign() { + if (ethernetDesign == null) { + Design fromShared = ethernetDesignShared.get(); + if (fromShared != null) { + ethernetDesign = fromShared; + } else { + ethernetDesign = RapidWrightDCP.loadDCP("verilog_ethernet.dcp", true); + //ethernetDesignShared = new WeakReference<>(ethernetDesign); + } + } + return ethernetDesign; + } + + + public static Stream getSimpleArgs() { + Stream list = Stream.of( + Arguments.of( + (Function>) RegularEdifCellLookup::new, + Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_0}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_1}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_2}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_3}]", + "set_property PACKAGE_PIN AA14 [get_ports phy_tx_clk]", + "set_property IOSTANDARD LVCMOS25 [get_ports phy_tx_clk]", + "set_property SLEW FAST [get_ports phy_tx_clk]", + "set_property DRIVE 16 [get_ports phy_tx_clk]", + "set_property DUMMY 0 [get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]}]", + "set_input_delay 0 [get_ports uart_rxd]", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports phy_reset_n]" + ), + 5, + 6, + 1, + (Predicate) rm -> true + ), + Arguments.of( + (Function>) netlist -> null, + Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay}]", + "set_property IDELAY_VALUE 0 [get_cells {phy_rxd_idelay_*}]", + "set_property PACKAGE_PIN AA14 [get_ports phy_tx_clk]", + "set_property IOSTANDARD LVCMOS25 [get_ports phy_tx_clk]", + "set_property SLEW FAST [get_ports phy_tx_clk]", + "set_property DRIVE 16 [get_ports phy_tx_clk]", + "set_property DUMMY 0 [get_cells [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]/rd_ptr_reg_reg[0]]", + "set_input_delay 0 [get_ports uart_rxd]", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports phy_reset_n]" + ), + 6, + 2, + 1, + (Predicate) rm -> rm!=RoundtripMode.Roundtrip_with_netlist + ) + ); + return withRoundtripMode(list); + + } + + @MethodSource("getSimpleArgs") + @ParameterizedTest + public void testSimple(Function> lookupFactory, List expectedOutput, int unsupportedConstraints, int cellProperties, int pinConstraints, Predicate canHandleRoundtrip, RoundtripMode roundtripMode) { + Assumptions.assumeTrue(canHandleRoundtrip.test(roundtripMode)); + List orig = Arrays.asList( + "set_property IDELAY_VALUE 0 [get_cells {phy_rx_ctl_idelay phy_rxd_idelay_*}]", + "set_property -dict {LOC AA14 IOSTANDARD LVCMOS25 SLEW FAST DRIVE 16} [get_ports phy_tx_clk]", + "set_input_delay 0 [get_ports {uart_rxd}]", + "#comment", + "set_false_path -to [get_ports {phy_reset_n test_asdf}]", + "set_output_delay 0 [get_ports {phy_reset_n}]", + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]", + "set_property DUMMY 0 [get_cells \"$fifo_inst/rd_ptr_reg_reg[0]\"]" + ); + List roundtripped = roundtripMode.doRoundtrip(getEthernetDesign().getNetlist(), orig); + XDCConstraints xdcConstraints = XDCParser.parseXDC(getEthernetDesign().getDevice(), roundtripped, lookupFactory.apply(getEthernetDesign().getNetlist())); + List actual = xdcConstraints.getAllAsXdc().sorted().collect(Collectors.toList()); + Collections.sort(expectedOutput); + Assertions.assertEquals(new NicerStringifyList<>(expectedOutput), new NicerStringifyList<>(actual)); + Assertions.assertEquals(unsupportedConstraints, xdcConstraints.getUnsupportedConstraints().size()); + Assertions.assertEquals(cellProperties, xdcConstraints.getCellProperties().size()); + Assertions.assertEquals(pinConstraints, xdcConstraints.getPinConstraints().size()); + } + + + @FunctionalInterface + interface TclObjConsumer { + void accept(Interp interp, TclObject obj, EDIFNetlist netlist, EdifCellLookup lookup) throws TclException; + } + + private void eval(RoundtripMode roundtripMode, String tcl, XDCConstraints constraints, TclObjConsumer downstream) { + String roundtripped = roundtripMode.doRoundtrip(getEthernetDesign().getNetlist(), Arrays.asList(tcl)).stream().collect(Collectors.joining("\n")); + eval(roundtripped, constraints, downstream); + } + + private void eval(String tcl, XDCConstraints constraints, TclObjConsumer downstream) { + EDIFNetlist netlist = getEthernetDesign().getNetlist(); + RegularEdifCellLookup cellLookup = new RegularEdifCellLookup(netlist); + eval(tcl, constraints, downstream, netlist, cellLookup); + } + + + private static void eval(String tcl, XDCConstraints constraints, TclObjConsumer downstream, EDIFNetlist netlist, EdifCellLookup cellLookup) { + Interp interp = XDCParser.makeTclInterp(constraints, netlist.getDevice(), cellLookup); + try { + interp.eval(tcl); + downstream.accept(interp, interp.getResult(), netlist, cellLookup); + } catch (TclException ex) { + int code = ex.getCompletionCode(); + switch (code) { + case TCL.ERROR: + throw new RuntimeException(interp.getResult().toString()+" in line "+interp.getErrorLine(), ex); + case TCL.BREAK: + throw new RuntimeException( + "invoked \"break\" outside of a loop", ex); + case TCL.CONTINUE: + throw new RuntimeException( + "invoked \"continue\" outside of a loop", ex); + default: + throw new RuntimeException( + "command returned bad error code: " + code, ex); + } + } finally { + interp.dispose(); + } + } + + private static Stream getComplexGetterArgs() { + return Stream.of( + Arguments.of( + "get_cells -hier -filter {(ORIG_REF_NAME == eth_mac_1g_rgmii || REF_NAME == eth_mac_1g_rgmii)}", + Arrays.asList("core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst") + ), + Arguments.of( + "set refname eth_mac_1g_rgmii\n get_cells -hier -filter \"(ORIG_REF_NAME == $refname || REF_NAME == $refname)\"", + Arrays.asList("core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst") + ), + Arguments.of( + "get_cells -hier -filter {(ORIG_REF_NAME == axis_async_fifo || REF_NAME == axis_async_fifo)}", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst","core_inst/eth_mac_inst/tx_fifo/fifo_inst") + ), + Arguments.of( + "get_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == core_inst/eth_mac_inst/rx_fifo/fifo_inst\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "set parent core_inst/eth_mac_inst/rx_fifo/fifo_inst\nget_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == $parent\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "set parent [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\nget_cells -quiet -hier -regexp \".*/s_rst_sync\\[23\\]_reg_reg\" -filter \"PARENT == $parent\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/s_rst_sync3_reg_reg") + ), + Arguments.of( + "get_cells -quiet -hier -regexp \".*/wr_ptr_update(_ack)?_sync\\[123\\]_reg_reg\" -filter \"PARENT == core_inst/eth_mac_inst/rx_fifo/fifo_inst\"", + Arrays.asList("core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_ack_sync1_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_ack_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync1_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync2_reg_reg", "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_update_sync3_reg_reg") + ), + Arguments.of( + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\n" + + "get_cells \"$fifo_inst/rd_ptr_reg_reg[*] $fifo_inst/rd_ptr_gray_reg_reg[*]\"", + Arrays.asList( + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9]" + ) + ), + Arguments.of( + "set fifo_inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\n" + + "get_cells \"$fifo_inst/rd_ptr_reg_reg[2] $fifo_inst/rd_ptr_gray_reg_reg[3]\"", + Arrays.asList( + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3]", + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2]" + ) + ), + Arguments.of( + "get_cells [list]", + Collections.emptyList() + ), + Arguments.of( + "get_cells [list clk90_bufg_inst clk_200_bufg_inst]", + Arrays.asList( + "clk90_bufg_inst", + "clk_200_bufg_inst" + ) + ) + ); + } + + @ParameterizedTest + @MethodSource("getComplexGetterArgs") + public void testComplexGetters(String tcl, List expected) { + eval(tcl, null, (interp, res, netlist, cellLookup) -> { + DesignObject designObject = DesignObject.unwrapTclObject(interp, interp.getResult(), cellLookup).orElseThrow(()->new RuntimeException("expected design object")); + List actual = ((CellObject) designObject).getCells(); + List actualStr = actual.stream().map(EDIFHierCellInst::toString).sorted().collect(Collectors.toList()); + Assertions.assertEquals(expected,actualStr); + }); + } + + private static Stream withRoundtripMode(Stream args) { + return args.flatMap(arg-> Arrays.stream(RoundtripMode.values()).map(mode-> new Arguments() { + @Override + public Object[] get() { + Object[] objects = arg.get(); + Object[] res = new Object[objects.length+1]; + System.arraycopy(objects, 0, res, 0, objects.length); + res[objects.length] = mode; + return res; + } + })); + } + + + private static Stream getComplexSetterArgs() { + Stream args = Stream.of( + Arguments.of( + "set if_inst core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst\n" + + "set src_clk [get_clocks -of_objects [get_pins $if_inst/rgmii_tx_clk_1_reg/C]]\n" + + "set_max_delay -from [get_cells $if_inst/rgmii_tx_clk_1_reg] -to " + + "[get_cells $if_inst/clk_oddr_inst/oddr[0].oddr_inst] -datapath_only " + + "[expr [get_property -min PERIOD $src_clk]/4]", + Arrays.asList( + "set_max_delay -from [get_cells core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg]" + + " -to [get_cells {core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/clk_oddr_inst/oddr[0].oddr_inst}]" + + " -datapath_only [expr [get_property -min PERIOD " + + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg/C]]]/4]" + ) + ), + Arguments.of( + "set_max_delay [expr [get_property asdf]/[get_property asdf]]", + Arrays.asList("set_max_delay [expr [get_property asdf]/[get_property asdf]]") + ), + Arguments.of( + "set inst [get_cells core_inst/eth_mac_inst/rx_fifo/fifo_inst]\nset_max_delay [get_pins $inst/x/y]", + Arrays.asList("set_max_delay [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/x/y]") + ), + Arguments.of( + "foreach fifo_inst [get_cells -hier -filter {(ORIG_REF_NAME == axis_async_fifo || REF_NAME == axis_async_fifo)}] {\n" + + "set pin [get_pins $fifo_inst/rd_ptr_reg_reg[0]/C]\n" + + "set_max_delay $pin\n" + + "}", + Arrays.asList( + "set_max_delay [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]", + "set_max_delay [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]" + ) + ), + Arguments.of( + "set_max_delay -datapath_only -from [get_cells [list]] " + + "-to [get_cells [list]] " + + "8.000", + Arrays.asList( + "set_max_delay -datapath_only -from [get_cells [list]] " + + "-to [get_cells [list]] " + + "8.000") + ) + ); + return withRoundtripMode(args); + + } + + @ParameterizedTest + @MethodSource("getComplexSetterArgs") + public void testComplexSetters(String tcl, List expected, RoundtripMode roundtripMode) { + XDCConstraints constraints = new XDCConstraints(); + eval(roundtripMode, tcl, constraints, (interp, res, netlist, cellLookup) -> { + List actual = constraints.getAllAsXdc().collect(Collectors.toList()); + Assertions.assertEquals(new NicerStringifyList<>(expected), new NicerStringifyList<>(actual)); + }); + } + + + public enum RoundtripMode { + /** + * Use constraints directly + */ + Normal(null), + /** + * Parse constraints with netlist, stringify, then run testcase + */ + Roundtrip_with_netlist(RegularEdifCellLookup::new), + /** + * Parse constraints without netlist, stringify, then run testcase + */ + Roundtrip_without_netlist(n->null); + + private final Function> lookupFactory; + + + public EdifCellLookup getLookup(EDIFNetlist netlist) { + return lookupFactory.apply(netlist); + } + + RoundtripMode(Function> lookupFactory) { + this.lookupFactory = lookupFactory; + } + + public List doRoundtrip(EDIFNetlist netlist, List lines) { + if (lookupFactory == null) { + return lines; + } + XDCConstraints xdcConstraints = XDCParser.parseXDC(netlist.getDevice(), lines, lookupFactory.apply(netlist)); + List res = xdcConstraints.getAllAsXdc().collect(Collectors.toList()); + for (String line : res) { + Assertions.assertFalse(TclHashIdentifiedObject.containsStringifiedObject(line), ()->"Output line contains hash object: "+line); + } + return res; + } + + public void doRoundtrip(Design design) { + for (ConstraintGroup value : ConstraintGroup.values()) { + List constraints = design.getXDCConstraints(value); + if (constraints == null) { + continue; + } + List roundtripped = doRoundtrip(design.getNetlist(), constraints); + if (roundtripped != constraints) { + constraints.clear(); + constraints.addAll(roundtripped); + } + } + } + } + + + @ParameterizedTest + @EnumSource(RoundtripMode.class) + void compareToVivadoConstraints(RoundtripMode roundtripMode) throws IOException { + EDIFNetlist netlist = getEthernetDesign().getNetlist(); + String xdcFilename = RapidWrightDCP.getString("verilog_ethernet_source_constraints.xdc"); + List origConstraints = FileTools.getLinesFromTextFile(xdcFilename); + List afterRoundtrip = roundtripMode.doRoundtrip(netlist, origConstraints); + XDCConstraints xdcConstraints = XDCParser.parseXDC(netlist.getDevice(), afterRoundtrip, new RegularEdifCellLookup(netlist)); + + + Map actualReplacements = getReplacementsForActual(); + + List actual = expandReplacements( + xdcConstraints.getAllAsXdc() + .filter(l->!l.contains("PACKAGE_PIN") && !l.contains("IOSTANDARD") &&!l.startsWith("set_property")), + actualReplacements); + List reference = expandReferenceConstraints(); + Assertions.assertEquals(new NicerStringifyList<>(reference), new NicerStringifyList<>(actual)); + + } + + /** + * This returns string replacements to clean up some formatting differences and replace the output of unsupported + * commands by hardcoding their result as returned by running them manually in Vivado's Tcl console + * @return replacers + */ + private static @NotNull Map getReplacementsForActual() { + Map actualReplacements = new HashMap<>(); + actualReplacements.put( + "set_input_delay 0 ", + "set_input_delay 0.0 " + ); + actualReplacements.put( + "set_output_delay 0 ", + "set_output_delay 0.0 " + ); + actualReplacements.put( + "set_max_delay -from", + "set_max_delay -datapath_only -from" + ); + actualReplacements.put( + "[get_property -min PERIOD clk_mmcm_out]", + "8.0" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + + + actualReplacements.put( + "[get_property -min PERIOD phy_rx_clk]", + "8.0" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0]/C]]", + "phy_rx_clk" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0]/C]]", + "clk_mmcm_out" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/mii_select_reg_reg/C]]", + "clk_mmcm_out" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rx_prescale_reg[2]/C]]", + "phy_rx_clk" + ); + actualReplacements.put( + "[get_clocks -of_objects [get_pins core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rgmii_tx_clk_1_reg/C]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {" + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}]]", + "phy_rx_clk" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[get_clocks -of_objects [get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}]]", + "clk_mmcm_out" + ); + + actualReplacements.put( + "[if {[llength clk_mmcm_out]} {get_property -min PERIOD clk_mmcm_out} {expr 1.0}]", + "8.0" + ); + actualReplacements.put( + "[if {[llength phy_rx_clk]} {get_property -min PERIOD phy_rx_clk} {expr 1.0}]", + "8.0" + ); + + + actualReplacements.put( + "[expr {8.0/4}]", + "2.0" + ); + actualReplacements.put( + "[expr 8.0/4]", + "2.0" + ); + + + actualReplacements.put( + "] -datapath_only", + "]" + ); + + actualReplacements.put(".000", ".0"); + return actualReplacements; + } + + + String streamExpand(String line, String from, String to, boolean[] changed) { + int i = line.indexOf(from); + if (i==-1) { + return line; + } + String before = line.substring(0, i); + String after = line.substring(i+from.length()); + if (to==null) { + if (!before.isEmpty() || !after.isEmpty()) { + throw new RuntimeException("tried to delete partial line "+from+" in "+line); + } + return null; + } + + changed[0] = true; + + return before+to+after; + } + + List expandReferenceConstraints() throws IOException { + + //Vivado leaves some wildcards in the stored constraints. Replace them with the list of matches + Map expands = new HashMap<>(); + expands.put("get_cells -hier -regexp {.*/(rx|tx)_rst_reg_reg\\[\\d\\]} -filter {PARENT == core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst}", + "get_cells {core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[0] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[1] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[2] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/rx_rst_reg_reg[3] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[0] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[1] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[2] " + + "core_inst/eth_mac_inst/eth_mac_1g_rgmii_inst/rgmii_phy_if_inst/tx_rst_reg_reg[3]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {{core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[*]} {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells {{core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/rd_ptr_gray_reg_reg[9]}" + ); + expands.put( + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[*]}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_sync1_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet {{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[*]} " + + "{core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/rx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet {{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[*]} " + + "{core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[*]}}", + "get_cells {core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_gray_reg_reg[9] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[0] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[10] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[11] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[12] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[1] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[2] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[3] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[4] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[5] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[6] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[7] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[8] " + + "core_inst/eth_mac_inst/tx_fifo/fifo_inst/wr_ptr_sync_gray_reg_reg[9]}" + ); + expands.put( + "get_cells -quiet -hier -regexp {.*/sync_reg_reg\\[\\d+\\]} -filter {PARENT == sync_reset_inst}", + "get_cells {sync_reset_inst/sync_reg_reg[0] sync_reset_inst/sync_reg_reg[1] sync_reset_inst/sync_reg_reg[2] sync_reset_inst/sync_reg_reg[3]}" + ); + expands.put(".000", ".0"); + + List lines = getEthernetDesign().getXDCConstraints(ConstraintGroup.NORMAL); + return expandReplacements(lines.stream().filter(l->{ + String trimmed = l.trim(); + return !trimmed.isEmpty() && !trimmed.startsWith("#") && !l.contains("PACKAGE_PIN") && !l.contains("IOSTANDARD") &&!l.startsWith("set_property"); + }), expands); + } + + /** + * Repeatedly apply a list of String replacements on all lines until a stable point is reached + * @param lines the lines to modify + * @param expands replacements as map of from-to-pairs + * @return lines with replacements applied + */ + @NotNull + private List expandReplacements(Stream lines, Map expands) { + + return lines.flatMap(line -> { + + boolean[] changes = new boolean[]{false}; + int iterations = 0; + do { + iterations++; + if (iterations>10000) { + Assertions.fail("Too many iterations"); + } + changes[0] = false; + for (Map.Entry entry : expands.entrySet()) { + line = streamExpand(line, entry.getKey(), entry.getValue(), changes); + if (line == null) { + return Stream.empty(); + } + } + + } while (changes[0]); + return Stream.of(line); + }) + .sorted() + .collect(Collectors.toList()); + } + + private static List findAllDcp(Path dir) { + + try (Stream list = Files.list(dir)) { + return list.flatMap(path -> { + if (Files.isDirectory(path)) { + return findAllDcp(path).stream(); + } + if (path.toString().endsWith(".dcp")) { + return Stream.of(path); + } + return Stream.empty(); + }).collect(Collectors.toList()); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + public static class DesignReference { + public final Path path; + private WeakReference design; + + public DesignReference(Path path) { + this.path = path; + } + + public Design getDesign() { + Design d = design == null ? null : design.get(); + if (d == null) { + d = Design.readCheckpoint(path, true); + this.design = new WeakReference<>(d); + } + return d; + } + + public String toString() { + return path.toString(); + } + + + } + + public static Stream getAllTheDesigns() { + Set skippedDcps = new HashSet<>(); + skippedDcps.add("picoblaze_ooc_X10Y235_unreadable_edif.dcp"); //Needs Vivado + + List modes = Arrays.asList( + RoundtripMode.Roundtrip_without_netlist, + RoundtripMode.Roundtrip_with_netlist + ); + + return findAllDcp(RapidWrightDCP.dirPath).stream() + .filter(p->!skippedDcps.contains(p.getFileName().toString())) + .flatMap(path -> { + DesignReference r = new DesignReference(path); + return modes.stream().map(factory -> Arguments.of(r, factory)); + }); + } + + HashMap> designCache = new HashMap<>(); + + @ParameterizedTest + @MethodSource("getAllTheDesigns") + void checkAllTheDesigns(DesignReference p, RoundtripMode mode) { + Design design = p.getDesign(); + EDIFNetlist netlist = design.getNetlist(); + + for (ConstraintGroup cg : ConstraintGroup.values()) { + List sourceText = design.getXDCConstraints(cg); + EdifCellLookup lookup = mode.getLookup(netlist); + XDCConstraints xdcConstraints = XDCParser.parseXDC(design.getDevice(), sourceText, lookup); + List stringified = xdcConstraints.getAllAsXdc().collect(Collectors.toList()); + XDCConstraints reparse = XDCParser.parseXDC(design.getDevice(), stringified, lookup); + Assertions.assertEquals(new NicerStringifyList<>(stringified), new NicerStringifyList<>(reparse.getAllAsXdc().collect(Collectors.toList()))); + } + + } +} diff --git a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java index 24515cfcd..dbc11cb3e 100644 --- a/test/src/com/xilinx/rapidwright/eco/TestECOTools.java +++ b/test/src/com/xilinx/rapidwright/eco/TestECOTools.java @@ -1185,5 +1185,4 @@ public void testConnectNetWithoutSource() { VivadoToolsHelper.assertFullyRouted(design); } - } diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java index 066ae7c14..531c995ff 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFCell.java @@ -149,7 +149,7 @@ public void testRenamePort() { EDIFPort myNewSignal = topCell.getPort("my_new_signal["); Assertions.assertNotNull(myNewSignal); - for (int i : myNewSignal.getBitBlastedIndicies()) { + for (int i : myNewSignal.getBitBlastedIndices()) { EDIFPortInst portInst = myNewSignal.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } @@ -164,7 +164,7 @@ public void testRenamePort() { EDIFPort newTestZero = topCell.getPort("new_test_zero["); Assertions.assertNotNull(newTestZero); - for (int i : newTestZero.getBitBlastedIndicies()) { + for (int i : newTestZero.getBitBlastedIndices()) { EDIFPortInst portInst = newTestZero.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } @@ -179,7 +179,7 @@ public void testRenamePort() { EDIFPort newTestFive = topCell.getPort("new_test_five["); Assertions.assertNotNull(newTestFive); - for (int i : newTestFive.getBitBlastedIndicies()) { + for (int i : newTestFive.getBitBlastedIndices()) { EDIFPortInst portInst = newTestFive.getInternalPortInstFromIndex(i); Assertions.assertNotNull(portInst); } diff --git a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java index 525359017..728cdc8f9 100644 --- a/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java +++ b/test/src/com/xilinx/rapidwright/edif/TestEDIFPort.java @@ -96,7 +96,7 @@ public void testCreatePort() { EDIFPort copy = cell.getPort("bus_output[" + outer + "]["); Assertions.assertEquals(busOutput, copy); - int[] portIndices = busOutput.getBitBlastedIndicies(); + int[] portIndices = busOutput.getBitBlastedIndices(); Assertions.assertEquals(width, portIndices.length); Assertions.assertEquals(left, portIndices[0]); Assertions.assertEquals(right, portIndices[portIndices.length - 1]);