diff --git a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java index f03de20ad..a187b2f42 100644 --- a/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java +++ b/src/com/xilinx/rapidwright/interchange/DcpToInterchange.java @@ -24,7 +24,6 @@ import java.io.FileOutputStream; import java.io.IOException; -import java.nio.file.Path; import java.nio.file.Paths; import java.util.List; @@ -43,8 +42,8 @@ public static void main(String[] args) throws IOException { Design design = Design.readCheckpoint(args[0]); String baseName = Paths.get(args[0]).getFileName().toString(); baseName = FileTools.removeFileExtension(baseName); - String logNetlistName = baseName + ".netlist"; - String physNetlistName = baseName + ".phys"; + String logNetlistName = baseName + Interchange.LOG_NETLIST_EXT; + String physNetlistName = baseName + Interchange.PHYS_NETLIST_EXT; String xdcName = baseName + ".xdc"; LogNetlistWriter.writeLogNetlist(design.getNetlist(), logNetlistName); diff --git a/src/com/xilinx/rapidwright/interchange/Interchange.java b/src/com/xilinx/rapidwright/interchange/Interchange.java index a3350f87c..76d33e631 100644 --- a/src/com/xilinx/rapidwright/interchange/Interchange.java +++ b/src/com/xilinx/rapidwright/interchange/Interchange.java @@ -23,33 +23,205 @@ package com.xilinx.rapidwright.interchange; -import com.xilinx.rapidwright.design.Design; -import com.xilinx.rapidwright.edif.EDIFNetlist; -import com.xilinx.rapidwright.tests.CodePerfTracker; -import com.xilinx.rapidwright.util.FileTools; -import org.capnproto.MessageBuilder; -import org.capnproto.MessageReader; -import org.capnproto.ReaderOptions; -import org.capnproto.Serialize; -import org.capnproto.SerializePacked; - +import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; +import java.io.UncheckedIOException; import java.nio.channels.Channels; import java.nio.channels.ReadableByteChannel; import java.nio.channels.WritableByteChannel; +import java.nio.charset.Charset; +import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; +import java.util.List; import java.util.zip.GZIPInputStream; import java.util.zip.GZIPOutputStream; +import org.capnproto.MessageBuilder; +import org.capnproto.MessageReader; +import org.capnproto.ReaderOptions; +import org.capnproto.Serialize; +import org.capnproto.SerializePacked; + +import com.xilinx.rapidwright.design.ConstraintGroup; +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.tests.CodePerfTracker; +import com.xilinx.rapidwright.util.FileTools; + public class Interchange { /** Flag indicating use of Packed Cap'n Proto Serialization */ public static boolean IS_PACKED = false; /** Flag indicating that files are gzipped on output */ public static boolean IS_GZIPPED = true; + /** Standard file extension for a logical netlist in the FPGA Interchange Format */ + public static final String LOG_NETLIST_EXT = ".netlist"; + /** Standard file extension for a physical netlist in the FPGA Interchange Format */ + public static final String PHYS_NETLIST_EXT = ".phys"; + + /** + * Reads an FPGA Interchange Format design from a specified file. The file could + * be a logical netlist or physical netlist and this method will assume the + * corresponding file with the same root name to load as a companion. If the + * provided name is a logical netlist and no physical netlist is found, it will + * only load the logical netlist. It will also load any XDC file with the same + * root name. + * + * @param fileName The name of the logical or physical netlist file. + * @return The loaded design. + */ + public static Design readInterchangeDesign(String fileName) { + return readInterchangeDesign(Paths.get(fileName)); + } + + /** + * Reads an FPGA Interchange Format design from a specified file. The file could + * be a logical netlist or physical netlist and this method will assume the + * corresponding file with the same root name to load as a companion. If the + * provided name is a logical netlist and no physical netlist is found, it will + * only load the logical netlist. It will also load any XDC file with the same + * root name. + * + * @param filePath The path to the logical or physical netlist file. + * @return The loaded design. + */ + public static Design readInterchangeDesign(Path filePath) { + String lowerName = filePath.toString().toLowerCase(); + Path logFileName = lowerName.endsWith(LOG_NETLIST_EXT) ? filePath : getExistingCompanionFile(filePath); + Path physFileName = lowerName.endsWith(PHYS_NETLIST_EXT) ? filePath : getExistingCompanionFile(filePath); + + if (logFileName == null) { + throw new RuntimeException("ERROR: Could not find logical netlist file: " + logFileName); + } + + String xdcFileName = FileTools.replaceExtension(logFileName, ".xdc").toString(); + + return readInterchangeDesign(logFileName.toString(), physFileName.toString(), xdcFileName, false, null); + } + + /** + * Gets the existing Interchange companion file name based on the provided + * filename. For example, if the logical netlist file name is provided, it will + * return the physical netlist filename if it exists. If the physical netlist is + * provided, it returns the logical netlist filename if the file exists. + * + * @param filePath Path of an existing FPGA Interchange file (logical netlist + * with the {@link #LOG_NETLIST_EXT} extension or physical + * netlist with the {@link #PHYS_NETLIST_EXT}) + * @return The companion file if it exists or null if it could not be found. + */ + private static Path getExistingCompanionFile(Path filePath) { + String lowerFileName = filePath.toString().toLowerCase(); + if (lowerFileName.endsWith(LOG_NETLIST_EXT)) { + Path physFileName = FileTools.replaceExtension(filePath, PHYS_NETLIST_EXT); + if (Files.exists(physFileName)) { + return physFileName; + } + } else if (lowerFileName.endsWith(PHYS_NETLIST_EXT)) { + Path logFileName = FileTools.replaceExtension(filePath, LOG_NETLIST_EXT); + if (Files.exists(logFileName)) { + return logFileName; + } + } + return null; + } + + /** + * Reads a set of existing FPGA Interchange files and returns a new design. + * + * @param logFileName The logical netlist file to be loaded. + * @param physFileName The physical netlist file to be loaded, this can be + * null for no placement or routing information. + * @param xdcFileName The constraints to associate with the design, this can + * be null for no constraints. + * @param isOutOfContext A flag indicating if the design should be marked out of + * context. + * @param t If using an existing CodePerfTracker, this allows + * continuity otherwise the default is null (an instance + * will be created each time) and will track runtime of + * each loading step. To silence this measurement, provide + * {@link CodePerfTracker#SILENT}). + * @return The newly created design based on the provided files. + */ + public static Design readInterchangeDesign(String logFileName, String physFileName, String xdcFileName, + boolean isOutOfContext, CodePerfTracker t) { + String msg = "Reading Interchange: " + logFileName; + CodePerfTracker tt = t == null ? new CodePerfTracker(msg, true) : t; + Design design = null; + try { + tt.start("Read Logical Netlist"); + EDIFNetlist n = LogNetlistReader.readLogNetlist(logFileName); + if (physFileName != null) { + tt.stop().start("Read Physical Netlist"); + design = PhysNetlistReader.readPhysNetlist(physFileName, n); + } else { + // No physical netlist information, let's attach the logical netlist to a new + // design + design = new Design(n); + } + if (xdcFileName != null) { + File xdcFile = new File(xdcFileName); + if (xdcFile.exists()) { + // Add XDC constraints + tt.stop().start("Read Constraints"); + List lines = Files.readAllLines(xdcFile.toPath(), Charset.defaultCharset()); + design.setXDCConstraints(lines, ConstraintGroup.NORMAL); + } + } + if (isOutOfContext) { + design.setAutoIOBuffers(false); + design.setDesignOutOfContext(true); + } + tt.stop().printSummary(); + return design; + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Writes out a set of Interchange files for the given design. It will write up + * to 3 files with the logical netlist always being written out. The two + * optional files are the physical netlist and XDC (constraints) file. + * + * @param design The design in memory to write out. + * @param rootFileName The root or common name among the output files. + */ + public static void writeDesignToInterchange(Design design, String rootFileName) { + String logFileName = rootFileName + LOG_NETLIST_EXT; + try { + LogNetlistWriter.writeLogNetlist(design.getNetlist(), logFileName); + + if (design.getSiteInsts().size() > 0 || design.getNets().size() > 0) { + String physFileName = rootFileName + PHYS_NETLIST_EXT; + PhysNetlistWriter.writePhysNetlist(design, physFileName); + } + + if (!design.getXDCConstraints(ConstraintGroup.NORMAL).isEmpty()) { + String xdcFileName = rootFileName + ".xdc"; + FileTools.writeLinesToTextFile(design.getXDCConstraints(ConstraintGroup.NORMAL), xdcFileName); + } + + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Checks if the provided file name is a logical or physical FPGA Interchange + * file. + * + * @param fileName The file name in question. + * @return True if the file name matches the logical or physical netlist file + * name type. + */ + public static boolean isInterchangeFile(String fileName) { + String lowerFileName = fileName.toLowerCase(); + return lowerFileName.endsWith(LOG_NETLIST_EXT) || lowerFileName.endsWith(PHYS_NETLIST_EXT); + } /** * Common method to write out Interchange files diff --git a/src/com/xilinx/rapidwright/rwroute/RWRoute.java b/src/com/xilinx/rapidwright/rwroute/RWRoute.java index bd6a781b5..3d4d2bcbe 100644 --- a/src/com/xilinx/rapidwright/rwroute/RWRoute.java +++ b/src/com/xilinx/rapidwright/rwroute/RWRoute.java @@ -53,6 +53,7 @@ import com.xilinx.rapidwright.device.Tile; import com.xilinx.rapidwright.device.TileTypeEnum; import com.xilinx.rapidwright.edif.EDIFHierNet; +import com.xilinx.rapidwright.interchange.Interchange; import com.xilinx.rapidwright.router.RouteThruHelper; import com.xilinx.rapidwright.tests.CodePerfTracker; import com.xilinx.rapidwright.timing.ClkRouteTiming; @@ -1886,13 +1887,16 @@ protected static Design routeDesign(Design design, RWRoute router) { } /** - * The main interface of {@link RWRoute} that reads in a {@link Design} checkpoint, - * and parses the arguments for the {@link RWRouteConfig} object of the router. - * @param args An array of strings that are used to create a {@link RWRouteConfig} object for the router. + * The main interface of {@link RWRoute} that reads in a {@link Design} design + * (DCP or FPGA Interchange), and parses the arguments for the + * {@link RWRouteConfig} object of the router. + * + * @param args An array of strings that are used to create a + * {@link RWRouteConfig} object for the router. */ public static void main(String[] args) { if (args.length < 2) { - System.out.println("USAGE: "); + System.out.println("USAGE: "); return; } // Reads the output directory and set the output design checkpoint file name @@ -1900,9 +1904,15 @@ public static void main(String[] args) { CodePerfTracker t = new CodePerfTracker("RWRoute", true); - // Reads in a design checkpoint and routes it + // Reads in a design and routes it String[] rwrouteArgs = Arrays.copyOfRange(args, 2, args.length); - Design routed = routeDesignWithUserDefinedArguments(Design.readCheckpoint(args[0]), rwrouteArgs); + Design input = null; + if (Interchange.isInterchangeFile(args[0])) { + input = Interchange.readInterchangeDesign(args[0]); + } else { + input = Design.readCheckpoint(args[0]); + } + Design routed = routeDesignWithUserDefinedArguments(input, rwrouteArgs); // Writes out the routed design checkpoint routed.writeCheckpoint(routedDCPfileName,t); diff --git a/src/com/xilinx/rapidwright/util/FileTools.java b/src/com/xilinx/rapidwright/util/FileTools.java index 190c90918..06786b02b 100644 --- a/src/com/xilinx/rapidwright/util/FileTools.java +++ b/src/com/xilinx/rapidwright/util/FileTools.java @@ -1997,7 +1997,15 @@ public static Path appendExtension(Path path, String extension) { return path.resolveSibling(path.getFileName().toString()+extension); } - + /** + * Replaces the file extension of the provided file name. The current extension + * includes the final period '.' and all characters following it. If the file + * has no extension, it will add it as a suffix to the filename. + * + * @param path Name of the file to receive the updated extension. + * @param newExtension The new extension (should include starting period '.') + * @return The newly updated file path. + */ public static Path replaceExtension(Path path, String newExtension) { String fn = path.getFileName().toString(); int idx = fn.lastIndexOf('.'); diff --git a/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java b/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java index ec00cc863..389892527 100644 --- a/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java +++ b/test/src/com/xilinx/rapidwright/rwroute/TestRWRoute.java @@ -23,6 +23,8 @@ package com.xilinx.rapidwright.rwroute; +import java.nio.file.Files; +import java.nio.file.Path; import java.util.ArrayList; import java.util.HashMap; import java.util.List; @@ -32,21 +34,26 @@ import org.junit.jupiter.api.Assumptions; import org.junit.jupiter.api.Disabled; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; import org.junit.jupiter.params.ParameterizedTest; import org.junit.jupiter.params.provider.CsvSource; import org.junit.jupiter.params.provider.EnumSource; +import com.xilinx.rapidwright.design.Cell; import com.xilinx.rapidwright.design.Design; import com.xilinx.rapidwright.design.DesignTools; import com.xilinx.rapidwright.design.Net; import com.xilinx.rapidwright.design.SiteInst; import com.xilinx.rapidwright.design.SitePinInst; +import com.xilinx.rapidwright.design.Unisim; import com.xilinx.rapidwright.device.Device; import com.xilinx.rapidwright.device.Node; import com.xilinx.rapidwright.device.PIP; import com.xilinx.rapidwright.device.Part; import com.xilinx.rapidwright.device.PartNameTools; import com.xilinx.rapidwright.device.Series; +import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.interchange.Interchange; import com.xilinx.rapidwright.support.LargeTest; import com.xilinx.rapidwright.support.RapidWrightDCP; import com.xilinx.rapidwright.util.FileTools; @@ -316,4 +323,32 @@ public void testBug701() { Assertions.assertEquals(0, rrs.unroutedNets); } } + + private Design generateSmallPlacedDesign() { + Design d = new Design("HelloWorld", Device.KCU105); + Cell and2 = d.createAndPlaceCell("and2", Unisim.AND2, "SLICE_X100Y100/A6LUT"); + Net net0 = d.createNet("button0_IBUF"); + net0.connect(and2, "O"); + net0.connect(and2, "I0"); + net0.connect(and2, "I1"); + + // Route site internal nets + d.routeSites(); + + EDIFTools.ensureCorrectPartInEDIF(d.getNetlist(), d.getPartName()); + return d; + } + + @Test + public void testRWRouteInterchange(@TempDir Path dir) { + Path rootFile = dir.resolve("interchange-design"); + Interchange.writeDesignToInterchange(generateSmallPlacedDesign(), rootFile.toString()); + Path outputFile = dir.resolve("output.dcp"); + RWRoute.main(new String[] { + rootFile.toString() + Interchange.PHYS_NETLIST_EXT, + outputFile.toString(), + "--nonTimingDriven" + }); + Assertions.assertTrue(Files.exists(outputFile)); + } }