| /* |
| * nextpnr -- Next Generation Place and Route |
| * |
| * Copyright (C) 2019 David Shah <dave@ds0.me> |
| * |
| * Permission to use, copy, modify, and/or distribute this software for any |
| * purpose with or without fee is hereby granted, provided that the above |
| * copyright notice and this permission notice appear in all copies. |
| * |
| * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES |
| * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR |
| * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| * |
| */ |
| |
| #include "nextpnr.h" |
| #include "util.h" |
| |
| NEXTPNR_NAMESPACE_BEGIN |
| |
| namespace SDF { |
| |
| struct MinMaxTyp |
| { |
| double min, typ, max; |
| }; |
| |
| struct RiseFallDelay |
| { |
| MinMaxTyp rise, fall; |
| }; |
| |
| struct PortAndEdge |
| { |
| std::string port; |
| ClockEdge edge; |
| }; |
| |
| struct IOPath |
| { |
| std::string from, to; |
| RiseFallDelay delay; |
| }; |
| |
| struct TimingCheck |
| { |
| enum CheckType |
| { |
| SETUPHOLD, |
| PERIOD, |
| WIDTH |
| } type; |
| PortAndEdge from, to; |
| RiseFallDelay delay; |
| }; |
| |
| struct Cell |
| { |
| std::string celltype, instance; |
| std::vector<IOPath> iopaths; |
| std::vector<TimingCheck> checks; |
| }; |
| |
| struct CellPort |
| { |
| std::string cell, port; |
| }; |
| |
| struct Interconnect |
| { |
| CellPort from, to; |
| RiseFallDelay delay; |
| }; |
| |
| struct SDFWriter |
| { |
| bool cvc_mode = false; |
| std::vector<Cell> cells; |
| std::vector<Interconnect> conn; |
| std::string sdfversion, design, vendor, program; |
| |
| std::string format_name(const std::string &name) |
| { |
| std::string fmt = "\""; |
| for (char c : name) { |
| if (c == '\\' || c == '\"') |
| fmt += "\""; |
| fmt += c; |
| } |
| fmt += "\""; |
| return fmt; |
| } |
| |
| std::string escape_name(const std::string &name) |
| { |
| std::string esc; |
| for (char c : name) { |
| if (c == '$' || c == '\\' || c == '[' || c == ']' || c == ':' || (cvc_mode && c == '.')) |
| esc += '\\'; |
| esc += c; |
| } |
| return esc; |
| } |
| |
| std::string timing_check_name(TimingCheck::CheckType type) |
| { |
| switch (type) { |
| case TimingCheck::SETUPHOLD: |
| return "SETUPHOLD"; |
| case TimingCheck::PERIOD: |
| return "PERIOD"; |
| case TimingCheck::WIDTH: |
| return "WIDTH"; |
| default: |
| NPNR_ASSERT_FALSE("unknown timing check type"); |
| } |
| } |
| |
| void write_delay(std::ostream &out, const RiseFallDelay &delay) |
| { |
| write_delay(out, delay.rise); |
| out << " "; |
| write_delay(out, delay.fall); |
| } |
| |
| void write_delay(std::ostream &out, const MinMaxTyp &delay) |
| { |
| if (cvc_mode) |
| out << "(" << int(delay.min) << ":" << int(delay.typ) << ":" << int(delay.max) << ")"; |
| else |
| out << "(" << delay.min << ":" << delay.typ << ":" << delay.max << ")"; |
| } |
| |
| void write_port(std::ostream &out, const CellPort &port) |
| { |
| if (cvc_mode) |
| out << escape_name(port.cell) + "." + escape_name(port.port); |
| else |
| out << escape_name(port.cell + "/" + port.port); |
| } |
| |
| void write_portedge(std::ostream &out, const PortAndEdge &pe) |
| { |
| out << "(" << (pe.edge == RISING_EDGE ? "posedge" : "negedge") << " " << escape_name(pe.port) << ")"; |
| } |
| |
| void write(std::ostream &out) |
| { |
| out << "(DELAYFILE" << std::endl; |
| // Headers and metadata |
| out << " (SDFVERSION " << format_name(sdfversion) << ")" << std::endl; |
| out << " (DESIGN " << format_name(design) << ")" << std::endl; |
| out << " (VENDOR " << format_name(vendor) << ")" << std::endl; |
| out << " (PROGRAM " << format_name(program) << ")" << std::endl; |
| out << " (DIVIDER " << (cvc_mode ? "." : "/") << ")" << 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 " << 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 " << escape_name(path.from) << " " << escape_name(path.to) << " "; |
| write_delay(out, path.delay); |
| out << ")" << std::endl; |
| } |
| out << " )" << std::endl; |
| out << " )" << std::endl; |
| } |
| // Timing Checks (setup/hold, period, width) |
| if (!cell.checks.empty()) { |
| out << " (TIMINGCHECK" << std::endl; |
| for (auto &check : cell.checks) { |
| out << " (" << timing_check_name(check.type) << " "; |
| write_portedge(out, check.from); |
| out << " "; |
| if (check.type == TimingCheck::SETUPHOLD) { |
| write_portedge(out, check.to); |
| out << " "; |
| } |
| if (check.type == TimingCheck::SETUPHOLD) |
| write_delay(out, check.delay); |
| else |
| write_delay(out, check.delay.rise); |
| out << ")" << std::endl; |
| } |
| out << " )" << std::endl; |
| } |
| out << " )" << std::endl; |
| } |
| out << ")" << std::endl; |
| } |
| }; |
| |
| } // namespace SDF |
| |
| void Context::writeSDF(std::ostream &out, bool cvc_mode) const |
| { |
| using namespace SDF; |
| SDFWriter wr; |
| wr.cvc_mode = cvc_mode; |
| 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.net == nullptr) |
| continue; // Ignore disconnected ports |
| if (port.second.type != PORT_IN) { |
| // Add combinational paths to this output (or inout) |
| for (auto other : ci->ports) { |
| if (other.second.net == nullptr) |
| continue; |
| 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 |