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 extends T> directChildren = getChildrenOf(ci);;
+ Stream extends T> 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 extends T> 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