diff --git a/src/com/xilinx/rapidwright/interchange/PhysicalNetlistToDcp.java b/src/com/xilinx/rapidwright/interchange/PhysicalNetlistToDcp.java index dd4851b94..e327cacb3 100644 --- a/src/com/xilinx/rapidwright/interchange/PhysicalNetlistToDcp.java +++ b/src/com/xilinx/rapidwright/interchange/PhysicalNetlistToDcp.java @@ -36,7 +36,7 @@ public class PhysicalNetlistToDcp { - private static final String MAKE_DCP_OUT_OF_CONTEXT = "--out_of_context"; + public static final String MAKE_DCP_OUT_OF_CONTEXT = "--out_of_context"; public static void main(String[] args) throws IOException { if (args.length != 4 && args.length != 5) { diff --git a/src/com/xilinx/rapidwright/placer/dreamplacefpga/DREAMPlaceFPGA.java b/src/com/xilinx/rapidwright/placer/dreamplacefpga/DREAMPlaceFPGA.java new file mode 100644 index 000000000..1c4980a55 --- /dev/null +++ b/src/com/xilinx/rapidwright/placer/dreamplacefpga/DREAMPlaceFPGA.java @@ -0,0 +1,284 @@ +/* + * Copyright (c) 2024, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Chris Lavin, AMD Research and Advanced 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.placer.dreamplacefpga; + +import java.io.BufferedWriter; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.UncheckedIOException; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.device.Device; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.edif.EDIFTools; +import com.xilinx.rapidwright.interchange.DeviceResourcesWriter; +import com.xilinx.rapidwright.interchange.Interchange; +import com.xilinx.rapidwright.interchange.LogNetlistWriter; +import com.xilinx.rapidwright.interchange.PhysNetlistReader; +import com.xilinx.rapidwright.interchange.PhysicalNetlistToDcp; +import com.xilinx.rapidwright.tests.CodePerfTracker; +import com.xilinx.rapidwright.util.FileTools; + +/** + * Utility wrapper class to facilitate using DREAMPlaceFPGA as an external + * placer. + */ +public class DREAMPlaceFPGA { + + public static final String INTERCHANGE_NETLIST = "interchange_netlist"; + public static final String INTERCHANGE_DEVICE = "interchange_device"; + public static final String RESULT_DIR = "result_dir"; + public static final String IO_PL = "io_pl"; + public static final String GPU = "gpu"; + public static final String NUM_BINS_X = "num_bins_x"; + public static final String NUM_BINS_Y = "num_bins_y"; + public static final String GLOBAL_PLACE_STAGES = "global_place_stages"; + public static final String TARGET_DENSITY = "target_density"; + public static final String DENSITY_WEIGHT = "density_weight"; + public static final String RANDOM_SEED = "random_seed"; + public static final String SCALE_FACTOR = "scale_factor"; + public static final String GLOBAL_PLACE_FLAG = "global_place_flag"; + public static final String ROUTABILITY_OPT_FLAG = "routability_opt_flag"; + public static final String LEGALIZE_FLAG = "legalize_flag"; + public static final String DETAILED_PLACE_FLAG = "detailed_place_flag"; + public static final String DTYPE = "dtype"; + public static final String PLOT_FLAG = "plot_flag"; + public static final String NUM_THREADS = "num_threads"; + public static final String DETERMINISTIC_FLAG = "deterministic_flag"; + public static final String ENABLE_IF = "enable_if"; + public static final String ENABLE_SITE_ROUTING = "enable_site_routing"; + + public static final String IO_PL_DEFAULT = ""; + public static final boolean GPU_DEFAULT = false; + public static final int NUM_BINS_X_DEFAULT = 512; + public static final int NUM_BINS_Y_DEFAULT = 512; + public static final String GLOBAL_PLACE_STAGES_DEFAULT = + "[\n{\"num_bins_x\" : 512," + + " \"num_bins_y\" : 512," + + " \"iteration\" : 2000," + + " \"learning_rate\" : 0.01," + + " \"wirelength\" : \"weighted_average\"," + + " \"optimizer\" : \"nesterov\"}\n]"; + public static final double TARGET_DENSITY_DEFAULT = 1.0; + public static final double DENSITY_WEIGHT_DEFAULT = 8e-5; + public static final int RANDOM_SEED_DEFAULT = 1000; + public static final double SCALE_FACTOR_DEFAULT = 1.0; + public static final boolean GLOBAL_PLACE_FLAG_DEFAULT = true; + public static final boolean ROUTABILITY_OPT_FLAG_DEFAULT = false; + public static final boolean LEGALIZE_FLAG_DEFAULT = true; + public static final boolean DETAILED_PLACE_FLAG_DEFAULT = false; + public static final String DTYPE_DEFAULT = "float32"; + public static final boolean PLOT_FLAG_DEFAULT = false; + public static final int NUM_THREADS_DEFAULT = 8; + public static final boolean DETERMINISTIC_FLAG_DEFAULT = true; + public static final boolean ENABLE_IF_DEFAULT = true; + public static final boolean ENABLE_SITE_ROUTING_DEFAULT = false; + + // public static final String dreamPlaceFPGAExec = "DREAMPlaceFPGA"; + public static final String dreamPlaceFPGAExec = "dreamplacefpga"; + + public static final String MAKE_DCP_OUT_OF_CONTEXT = PhysicalNetlistToDcp.MAKE_DCP_OUT_OF_CONTEXT; + + public static Map getSettingsMap() { + Map map = new HashMap<>(); + + map.put(IO_PL, IO_PL_DEFAULT); + map.put(GPU, GPU_DEFAULT); + map.put(NUM_BINS_X, NUM_BINS_X_DEFAULT); + map.put(NUM_BINS_Y, NUM_BINS_Y_DEFAULT); + map.put(GLOBAL_PLACE_STAGES, GLOBAL_PLACE_STAGES_DEFAULT); + map.put(TARGET_DENSITY, TARGET_DENSITY_DEFAULT); + map.put(DENSITY_WEIGHT, DENSITY_WEIGHT_DEFAULT); + map.put(RANDOM_SEED, RANDOM_SEED_DEFAULT); + map.put(SCALE_FACTOR, SCALE_FACTOR_DEFAULT); + map.put(GLOBAL_PLACE_FLAG, GLOBAL_PLACE_FLAG_DEFAULT); + map.put(ROUTABILITY_OPT_FLAG, ROUTABILITY_OPT_FLAG_DEFAULT); + map.put(LEGALIZE_FLAG, LEGALIZE_FLAG_DEFAULT); + map.put(DETAILED_PLACE_FLAG, DETAILED_PLACE_FLAG_DEFAULT); + map.put(DTYPE, DTYPE_DEFAULT); + map.put(PLOT_FLAG, PLOT_FLAG_DEFAULT); + map.put(NUM_THREADS, NUM_THREADS_DEFAULT); + map.put(DETERMINISTIC_FLAG, DETERMINISTIC_FLAG_DEFAULT); + map.put(ENABLE_IF, ENABLE_IF_DEFAULT); + map.put(ENABLE_SITE_ROUTING, ENABLE_SITE_ROUTING_DEFAULT); + + return map; + } + + public static void writeJSONForDREAMPlaceFPGA(Path jsonPath, Map attributes) { + try (BufferedWriter bw = new BufferedWriter(new FileWriter(jsonPath.toFile()))) { + bw.write("{\n"); + boolean first = true; + for (Entry e : attributes.entrySet()) { + if (first) { + first = false; + } else { + bw.write(",\n"); + } + bw.write(" \"" + e.getKey() + "\""); + bw.write(" : "); + if (e.getValue() instanceof String && !e.getKey().equals(GLOBAL_PLACE_STAGES)) { + bw.write("\"" + e.getValue().toString() + "\""); + } else if (e.getValue() instanceof Boolean) { + bw.write((boolean) e.getValue() ? "1" : "0"); + } else { + bw.write(e.getValue().toString() + ""); + } + + } + bw.write("\n}\n"); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + /** + * Given a EDIFNetlist object, place it using DREAMPlaceFPGA. + * @param netlist EDIFNetlist object to be placed. + * @return Placed Design object. + * @throws IOException + */ + public static Design placeDesign(EDIFNetlist netlist) throws IOException { + return placeDesign(netlist, null, false); + } + + /** + * Given a EDIFNetlist object, place it using DREAMPlaceFPGA. + * @param netlist EDIFNetlist object to be placed. + * @param workDir Path to working directory (null to use a temporary directory which gets deleted on return) + * @return Placed Design object. + * @throws IOException + */ + public static Design placeDesign(EDIFNetlist netlist, Path workDir, boolean makeOutOfContext) throws IOException { + boolean removeWorkDir = false; + if (workDir == null) { + workDir = Paths.get("DREAMPlaceFPGAWorkdir" + FileTools.getUniqueProcessAndHostID()); + FileTools.makeDirs(workDir.toString()); + removeWorkDir = true; + } + + // Create interchange netlist file + String inputLogNetlistName = "input" + Interchange.LOG_NETLIST_EXT; + String inputLogNetlistPath = workDir.resolve(inputLogNetlistName).toString(); + LogNetlistWriter.writeLogNetlist(netlist, inputLogNetlistPath); + + // Create device file if it doesn't already exist + String partName = EDIFTools.getPartName(netlist); + if (partName == null) { + throw new RuntimeException("ERROR: Netlist has no part name"); + } + Device device = netlist.getDevice(); + Path deviceDir = FileTools.getUserSpecificRapidWrightDataPath(); + Path deviceFile = deviceDir.resolve(device.getName() + ".device"); + if (!Files.exists(deviceFile)) { + try { + DeviceResourcesWriter.writeDeviceResourcesFile(partName, device, + new CodePerfTracker("Create IF Device"), deviceFile.toString(), true); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + } + + // Create JSON file for DREAMPlaceFPGA + Path jsonFile = workDir.resolve("design.json"); + Map settings = getSettingsMap(); + settings.put(INTERCHANGE_DEVICE, deviceFile.toString()); + settings.put(INTERCHANGE_NETLIST, inputLogNetlistName); + settings.put(RESULT_DIR, "."); + writeJSONForDREAMPlaceFPGA(jsonFile, settings); + + // Run DREAMPlaceFPGA + List exec = new ArrayList<>(); + exec.add(dreamPlaceFPGAExec); + exec.add("-json"); + exec.add(jsonFile.toString()); + + boolean verbose = true; + String[] environ = null; + Integer exitCode = FileTools.runCommand(exec.toArray(new String[0]), verbose, environ, workDir.toFile()); + if (exitCode != 0) { + throw new RuntimeException("DREAMPlaceFPGA with code: " + exitCode); + } + + // Load placed result + Design placedDesign; + String outputPhysNetlistPath = workDir.resolve("design/design.phys").toString(); + try { + placedDesign = PhysNetlistReader.readPhysNetlist(outputPhysNetlistPath, + netlist); + } catch (IOException e) { + throw new UncheckedIOException(e); + } + + if (makeOutOfContext) { + placedDesign.setAutoIOBuffers(false); + placedDesign.setDesignOutOfContext(true); + } + + placedDesign.routeSites(); + + if (removeWorkDir) { + FileTools.deleteFolder(workDir.toString()); + } + return placedDesign; + } + + /** + * Checks if dreamplacefpga is available on current PATH (uses unix 'which' or windows 'where'). + * @return true if yosys is on current PATH, false otherwise. + */ + public static boolean isDREAMPlaceFPGAOnPath() { + return FileTools.isExecutableOnPath(dreamPlaceFPGAExec); + } + + public static void main(String[] args) throws IOException { + // Usage: [--out_of_context] [work directory] + if (args.length < 2 || args.length > 4) { + System.out.println("USAGE: [" + + MAKE_DCP_OUT_OF_CONTEXT + "] [work directory]"); + return; + } + Design input = Design.readCheckpoint(args[0]); + + boolean makeOutOfContext = false; + if (args.length >= 3) { + if (args[2].equals(MAKE_DCP_OUT_OF_CONTEXT)) { + makeOutOfContext = true; + } + } + + Path workDir = args.length == 4 ? Paths.get(args[3]) : + args.length == 3 && !makeOutOfContext ? Paths.get(args[2]) : + null; + Design placed = placeDesign(input.getNetlist(), workDir, makeOutOfContext); + placed.writeCheckpoint(args[1]); + } +} diff --git a/src/com/xilinx/rapidwright/util/FileTools.java b/src/com/xilinx/rapidwright/util/FileTools.java index 534c71042..c08e54bb8 100644 --- a/src/com/xilinx/rapidwright/util/FileTools.java +++ b/src/com/xilinx/rapidwright/util/FileTools.java @@ -1008,22 +1008,35 @@ public static String getRapidWrightPath() { return path; } - public static String getExecJarStoragePath() { - String rootPath = ""; + /** + * Gets and returns the path for storing user-specific RapidWright data. + * On Linux, this is overridden by the $XDG_DATA_HOME environment variable, defaulting to ~/.local/share + * On Windows, this is overridden by %APPDATA% environment variable + * @return A string containing this user-specific data path plus the "RapidWright" subdirectory (created + * if it does not exist). + */ + public static Path getUserSpecificRapidWrightDataPath() { + Path rootPath; if (isWindows()) { - rootPath = System.getenv("APPDATA"); + rootPath = Paths.get(System.getenv("APPDATA")); } else { - rootPath = System.getenv("XDG_DATA_HOME"); - if (rootPath == null || rootPath.length() == 0) { - rootPath = System.getenv("HOME") + File.separator + ".local" + File.separator + "share"; + String env = System.getenv("XDG_DATA_HOME"); + if (env == null || env.length() == 0) { + rootPath = Paths.get(System.getenv("HOME"), ".local", "share"); + } else { + rootPath = Paths.get(env); } // TODO for Mac OS, the default for XDG_DATA_HOME would be '~/Library/My App/' } - rootPath += File.separator + "RapidWright"; - makeDirs(rootPath); + rootPath = rootPath.resolve("RapidWright"); + makeDirs(rootPath.toString()); return rootPath; } + public static String getExecJarStoragePath() { + return getUserSpecificRapidWrightDataPath().toString(); + } + public static void updateAllDataFiles() { System.out.println("Updating all RapidWright data files (this may take several minutes)..."); for (String fileName : DataVersions.dataVersionMap.keySet()) { diff --git a/test/src/com/xilinx/rapidwright/placer/TestDREAMPlaceFPGA.java b/test/src/com/xilinx/rapidwright/placer/TestDREAMPlaceFPGA.java new file mode 100644 index 000000000..c9be74111 --- /dev/null +++ b/test/src/com/xilinx/rapidwright/placer/TestDREAMPlaceFPGA.java @@ -0,0 +1,85 @@ +/* + * Copyright (c) 2024, Advanced Micro Devices, Inc. + * All rights reserved. + * + * Author: Eddie Hung, Advanced Micro Devices, Inc. + * + * 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.placer; + +import com.xilinx.rapidwright.design.Design; +import com.xilinx.rapidwright.edif.EDIFNetlist; +import com.xilinx.rapidwright.placer.dreamplacefpga.DREAMPlaceFPGA; +import com.xilinx.rapidwright.support.RapidWrightDCP; +import com.xilinx.rapidwright.util.ReportRouteStatusResult; +import com.xilinx.rapidwright.util.VivadoTools; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.Assumptions; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.io.TempDir; + +import java.io.IOException; +import java.nio.file.Path; +import java.nio.file.Paths; + +public class TestDREAMPlaceFPGA { + + @Test + public void testDREAMPlaceFPGAMain(@TempDir Path tempDir) throws IOException { + // Skip test if dreamplacefpga is not on PATH + Assumptions.assumeTrue(DREAMPlaceFPGA.isDREAMPlaceFPGAOnPath()); + + String inputDcp = RapidWrightDCP.getString("gnl_2_4_3_1.3_gnl_3000_07_3_80_80_placed.dcp"); + String outputDcp = tempDir.resolve("output.dcp").toString(); + DREAMPlaceFPGA.main(new String[]{inputDcp, outputDcp, DREAMPlaceFPGA.MAKE_DCP_OUT_OF_CONTEXT, tempDir.toString()}); + + ReportRouteStatusResult rrs = VivadoTools.reportRouteStatus(Paths.get(outputDcp)); + Assertions.assertEquals(0, rrs.netsWithNoPlacedPins); + // DREAMPlaceFPGA does not appear to be deterministic across machines + // Assertions.assertEquals(3465, rrs.routableNets); + Assertions.assertTrue(rrs.routableNets > 3000); + Assertions.assertEquals(rrs.routableNets, rrs.unroutedNets); + Assertions.assertEquals(0, rrs.netsWithRoutingErrors); + } + + @Test + public void testDREAMPlaceFPGA(@TempDir Path tempDir) throws IOException { + // Skip test if dreamplacefpga is not on PATH + Assumptions.assumeTrue(DREAMPlaceFPGA.isDREAMPlaceFPGAOnPath()); + + boolean skipXdef = true; + Design design = RapidWrightDCP.loadDCP("gnl_2_4_3_1.3_gnl_3000_07_3_80_80_placed.dcp", skipXdef); + Assertions.assertTrue(design.getSiteInsts().isEmpty()); + EDIFNetlist netlist = design.getNetlist(); + design = null; + + boolean makeOutOfContext = true; + design = DREAMPlaceFPGA.placeDesign(netlist, tempDir, makeOutOfContext); + + Path outputDcp = tempDir.resolve("output.dcp"); + design.writeCheckpoint(outputDcp); + boolean encrypted = !netlist.getEncryptedCells().isEmpty(); + ReportRouteStatusResult rrs = VivadoTools.reportRouteStatus(outputDcp, tempDir, encrypted); + Assertions.assertEquals(0, rrs.netsWithNoPlacedPins); + // DREAMPlaceFPGA does not appear to be deterministic across machines + // Assertions.assertEquals(3465, rrs.routableNets); + Assertions.assertTrue(rrs.routableNets > 3000); + Assertions.assertEquals(rrs.routableNets, rrs.unroutedNets); + Assertions.assertEquals(0, rrs.netsWithRoutingErrors); + } +}