diff options
| -rw-r--r-- | common/command.cc | 10 | ||||
| -rw-r--r-- | common/nextpnr.h | 5 | ||||
| -rw-r--r-- | common/sdf.cc | 161 | 
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 | 
