aboutsummaryrefslogtreecommitdiffstats
path: root/common
diff options
context:
space:
mode:
authorDavid Shah <dave@ds0.me>2019-10-19 16:52:47 +0100
committerDavid Shah <dave@ds0.me>2019-10-19 19:20:02 +0100
commit4775930e4943fef042b8a4c7e339fefeb1f251be (patch)
treec817bdbc7b6c267d9cd8b0caf3f79c37e84df5fd /common
parentc0484a317d1a25a23541b60dd3bfc800230a0536 (diff)
downloadnextpnr-4775930e4943fef042b8a4c7e339fefeb1f251be.tar.gz
nextpnr-4775930e4943fef042b8a4c7e339fefeb1f251be.tar.bz2
nextpnr-4775930e4943fef042b8a4c7e339fefeb1f251be.zip
sdf: Add basic support for writing SDF files
Signed-off-by: David Shah <dave@ds0.me>
Diffstat (limited to 'common')
-rw-r--r--common/command.cc10
-rw-r--r--common/nextpnr.h5
-rw-r--r--common/sdf.cc161
3 files changed, 155 insertions, 21 deletions
diff --git a/common/command.cc b/common/command.cc
index ad5b6c54..f7f804a0 100644
--- a/common/command.cc
+++ b/common/command.cc
@@ -149,6 +149,8 @@ po::options_description CommandHandler::getGeneralOptions()
general.add_options()("freq", po::value<double>(), "set target frequency for design in MHz");
general.add_options()("timing-allow-fail", "allow timing to fail in design");
general.add_options()("no-tmdriv", "disable timing-driven placement");
+ general.add_options()("sdf", po::value<std::string>(), "SDF delay back-annotation file to write");
+
return general;
}
@@ -336,6 +338,14 @@ int CommandHandler::executeMain(std::unique_ptr<Context> ctx)
log_error("Saving design failed.\n");
}
+ if (vm.count("sdf")) {
+ std::string filename = vm["sdf"].as<std::string>();
+ std::ofstream f(filename);
+ if (!f)
+ log_error("Failed to open SDF file '%s' for writing.\n", filename.c_str());
+ ctx->writeSDF(f);
+ }
+
#ifndef NO_PYTHON
deinit_python();
#endif
diff --git a/common/nextpnr.h b/common/nextpnr.h
index bae828f6..89e9c4f0 100644
--- a/common/nextpnr.h
+++ b/common/nextpnr.h
@@ -808,6 +808,11 @@ struct Context : Arch, DeterministicRNG
// --------------------------------------------------------------
+ // provided by sdf.cc
+ void writeSDF(std::ostream &out) const;
+
+ // --------------------------------------------------------------
+
uint32_t checksum() const;
void check() const;
diff --git a/common/sdf.cc b/common/sdf.cc
index 570b8aee..07773a6a 100644
--- a/common/sdf.cc
+++ b/common/sdf.cc
@@ -18,6 +18,7 @@
*/
#include "nextpnr.h"
+#include "util.h"
NEXTPNR_NAMESPACE_BEGIN
@@ -93,6 +94,17 @@ struct SDFWriter
return fmt;
}
+ std::string escape_name(const std::string &name)
+ {
+ std::string esc;
+ for (char c : name) {
+ if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':')
+ esc += '\\';
+ esc += c;
+ }
+ return esc;
+ }
+
std::string timing_check_name(TimingCheck::CheckType type)
{
switch (type) {
@@ -119,11 +131,11 @@ struct SDFWriter
out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")";
}
- void write_port(std::ostream &out, const CellPort &port) { out << format_name(port.cell + "/" + port.port); }
+ void write_port(std::ostream &out, const CellPort &port) { out << escape_name(port.cell + "/" + port.port); }
void write_portedge(std::ostream &out, const PortAndEdge &pe)
{
- out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << pe.port << ")";
+ out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")";
}
void write(std::ostream &out)
@@ -136,17 +148,35 @@ struct SDFWriter
out << " (PROGRAM " << format_name(program) << ")" << std::endl;
out << " (DIVIDER /)" << std::endl;
out << " (TIMESCALE 1ps)" << std::endl;
+ // Write interconnect delays, with the main design begin a "cell"
+ out << " (CELL" << std::endl;
+ out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
+ out << " (INSTANCE )" << std::endl;
+ out << " (DELAY" << std::endl;
+ out << " (ABSOLUTE" << std::endl;
+ for (auto &ic : conn) {
+ out << " (INTERCONNECT ";
+ write_port(out, ic.from);
+ out << " ";
+ write_port(out, ic.to);
+ out << " ";
+ write_delay(out, ic.delay);
+ out << ")" << std::endl;
+ }
+ out << " )" << std::endl;
+ out << " )" << std::endl;
+ out << " )" << std::endl;
// Write cells
for (auto &cell : cells) {
out << " (CELL" << std::endl;
out << " (CELLTYPE " << format_name(cell.celltype) << ")" << std::endl;
- out << " (INSTANCE " << format_name(cell.instance) << ")" << std::endl;
+ out << " (INSTANCE " << escape_name(cell.instance) << ")" << std::endl;
// IOPATHs (combinational delay and clock-to-q)
if (!cell.iopaths.empty()) {
out << " (DELAY" << std::endl;
out << " (ABSOLUTE" << std::endl;
for (auto &path : cell.iopaths) {
- out << " (IOPATH " << path.from << " " << path.to << " ";
+ out << " (IOPATH " << escape_name(path.from) << " " << escape_name(path.to) << " ";
write_delay(out, path.delay);
out << ")" << std::endl;
}
@@ -174,27 +204,116 @@ struct SDFWriter
}
out << " )" << std::endl;
}
- // Write interconnect delays, with the main design begin a "cell"
- out << " (CELL" << std::endl;
- out << " (CELLTYPE " << format_name(design) << ")" << std::endl;
- out << " (DELAY" << std::endl;
- out << " (ABSOLUTE" << std::endl;
- for (auto &ic : conn) {
- out << " (INTERCONNECT ";
- write_port(out, ic.from);
- out << " ";
- write_port(out, ic.to);
- out << " ";
- write_delay(out, ic.delay);
- out << std::endl;
- }
- out << " )" << std::endl;
- out << " )" << std::endl;
- out << " )" << std::endl;
out << ")" << std::endl;
}
};
} // namespace SDF
+void Context::writeSDF(std::ostream &out) const
+{
+ using namespace SDF;
+ SDFWriter wr;
+ wr.design = str_or_default(attrs, id("module"), "top");
+ wr.sdfversion = "3.0";
+ wr.vendor = "nextpnr";
+ wr.program = "nextpnr";
+
+ const double delay_scale = 1000;
+ // Convert from DelayInfo to SDF-friendly RiseFallDelay
+ auto convert_delay = [&](const DelayInfo &dly) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(dly.minRaiseDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((dly.minRaiseDelay() + dly.maxRaiseDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(dly.maxRaiseDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(dly.minFallDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((dly.minFallDelay() + dly.maxFallDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(dly.maxFallDelay()) * delay_scale;
+ return rf;
+ };
+
+ auto convert_setuphold = [&](const DelayInfo &setup, const DelayInfo &hold) {
+ RiseFallDelay rf;
+ rf.rise.min = getDelayNS(setup.minDelay()) * delay_scale;
+ rf.rise.typ = getDelayNS((setup.minDelay() + setup.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.rise.max = getDelayNS(setup.maxDelay()) * delay_scale;
+ rf.fall.min = getDelayNS(hold.minDelay()) * delay_scale;
+ rf.fall.typ = getDelayNS((hold.minDelay() + hold.maxDelay()) / 2) * delay_scale; // fixme: typ delays?
+ rf.fall.max = getDelayNS(hold.maxDelay()) * delay_scale;
+ return rf;
+ };
+
+ for (auto cell : sorted(cells)) {
+ Cell sc;
+ const CellInfo *ci = cell.second;
+ sc.instance = ci->name.str(this);
+ sc.celltype = ci->type.str(this);
+ for (auto port : ci->ports) {
+ int clockCount = 0;
+ TimingPortClass cls = getPortTimingClass(ci, port.first, clockCount);
+ if (cls == TMG_IGNORE)
+ continue;
+ if (port.second.type != PORT_IN) {
+ // Add combinational paths to this output (or inout)
+ for (auto other : ci->ports) {
+ if (other.second.type == PORT_OUT)
+ continue;
+ DelayInfo dly;
+ if (!getCellDelay(ci, other.first, port.first, dly))
+ continue;
+ IOPath iop;
+ iop.from = other.first.str(this);
+ iop.to = port.first.str(this);
+ iop.delay = convert_delay(dly);
+ sc.iopaths.push_back(iop);
+ }
+ // Add clock-to-output delays, also as IOPaths
+ if (cls == TMG_REGISTER_OUTPUT)
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ IOPath cqp;
+ cqp.from = clkInfo.clock_port.str(this);
+ cqp.to = port.first.str(this);
+ cqp.delay = convert_delay(clkInfo.clockToQ);
+ sc.iopaths.push_back(cqp);
+ }
+ }
+ if (port.second.type != PORT_OUT && cls == TMG_REGISTER_INPUT) {
+ // Add setup/hold checks
+ for (int i = 0; i < clockCount; i++) {
+ auto clkInfo = getPortClockingInfo(ci, port.first, i);
+ TimingCheck chk;
+ chk.from.edge = RISING_EDGE; // Add setup/hold checks equally for rising and falling edges
+ chk.from.port = port.first.str(this);
+ chk.to.edge = clkInfo.edge;
+ chk.to.port = clkInfo.clock_port.str(this);
+ chk.type = TimingCheck::SETUPHOLD;
+ chk.delay = convert_setuphold(clkInfo.setup, clkInfo.hold);
+ sc.checks.push_back(chk);
+ chk.from.edge = FALLING_EDGE;
+ sc.checks.push_back(chk);
+ }
+ }
+ }
+ wr.cells.push_back(sc);
+ }
+
+ for (auto net : sorted(nets)) {
+ NetInfo *ni = net.second;
+ if (ni->driver.cell == nullptr)
+ continue;
+ for (auto &usr : ni->users) {
+ Interconnect ic;
+ ic.from.cell = ni->driver.cell->name.str(this);
+ ic.from.port = ni->driver.port.str(this);
+ ic.to.cell = usr.cell->name.str(this);
+ ic.to.port = usr.port.str(this);
+ // FIXME: min/max routing delay - or at least constructing DelayInfo here
+ ic.delay = convert_delay(getDelayFromNS(getDelayNS(getNetinfoRouteDelay(ni, usr))));
+ wr.conn.push_back(ic);
+ }
+ }
+ wr.write(out);
+}
+
NEXTPNR_NAMESPACE_END \ No newline at end of file