diff options
| -rw-r--r-- | ecp5/arch.cc | 20 | ||||
| -rw-r--r-- | ecp5/bitstream.cc | 18 | ||||
| -rw-r--r-- | ecp5/cells.cc | 51 | ||||
| -rw-r--r-- | ecp5/constids.inc | 40 | ||||
| -rw-r--r-- | ecp5/globals.cc | 2 | ||||
| -rw-r--r-- | ecp5/pack.cc | 134 | 
6 files changed, 265 insertions, 0 deletions
| diff --git a/ecp5/arch.cc b/ecp5/arch.cc index 719426ab..380c0d7d 100644 --- a/ecp5/arch.cc +++ b/ecp5/arch.cc @@ -579,6 +579,8 @@ bool Arch::getCellDelay(const CellInfo *cell, IdString fromPort, IdString toPort          return false;      } else if (cell->type == id_DP16KD) {          return false; +    } else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) { +        return false;      } else {          return false;      } @@ -669,6 +671,16 @@ TimingPortClass Arch::getPortTimingClass(const CellInfo *cell, IdString port, in              return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT;          }          return TMG_IGNORE; +    } else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) { +        if (port == id_CLK || port == id_ECLK) { +            return TMG_CLOCK_INPUT; +        } else if (port == id_IOLDO || port == id_IOLDOI || port == id_IOLDOD || port == id_IOLTO || port == id_PADDI || +                   port == id_DQSR90 || port == id_DQSW || port == id_DQSW270) { +            return TMG_IGNORE; +        } else { +            clockInfoCount = 1; +            return (cell->ports.at(port).type == PORT_OUT) ? TMG_REGISTER_OUTPUT : TMG_REGISTER_INPUT; +        }      } else {          log_error("cell type '%s' is unsupported (instantiated as '%s')\n", cell->type.c_str(this),                    cell->name.c_str(this)); @@ -744,6 +756,14 @@ TimingClockingInfo Arch::getPortClockingInfo(const CellInfo *cell, IdString port              info.setup = getDelayFromNS(1);              info.hold = getDelayFromNS(0);          } +    } else if (cell->type == id_IOLOGIC || cell->type == id_SIOLOGIC) { +        info.clock_port = id_CLK; +        if (cell->ports.at(port).type == PORT_OUT) { +            info.clockToQ = getDelayFromNS(0.5); +        } else { +            info.setup = getDelayFromNS(0.1); +            info.hold = getDelayFromNS(0); +        }      }      return info;  } diff --git a/ecp5/bitstream.cc b/ecp5/bitstream.cc index df16946d..5031dab8 100644 --- a/ecp5/bitstream.cc +++ b/ecp5/bitstream.cc @@ -745,6 +745,12 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex              }              if (ci->attrs.count(ctx->id("SLEWRATE")))                  cc.tiles[pio_tile].add_enum(pio + ".SLEWRATE", str_or_default(ci->attrs, ctx->id("SLEWRATE"), "SLOW")); +            std::string datamux_oddr = str_or_default(ci->params, ctx->id("DATAMUX_ODDR"), "PADDO"); +            if (datamux_oddr != "PADDO") +                cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_ODDR", datamux_oddr); +            std::string datamux_mddr = str_or_default(ci->params, ctx->id("DATAMUX_MDDR"), "PADDO"); +            if (datamux_mddr != "PADDO") +                cc.tiles[pic_tile].add_enum(pio + ".DATAMUX_MDDR", datamux_mddr);          } else if (ci->type == ctx->id("DCCA")) {              // Nothing to do          } else if (ci->type == ctx->id("DP16KD")) { @@ -1078,6 +1084,18 @@ void write_bitstream(Context *ctx, std::string base_config_file, std::string tex                                 int_to_bitvector(int_or_default(ci->attrs, ctx->id("MFG_ENABLE_FILTEROPAMP"), 0), 1));              cc.tilegroups.push_back(tg); +        } else if (ci->type == id_IOLOGIC || ci->type == id_SIOLOGIC) { +            Loc pio_loc = ctx->getBelLocation(ci->bel); +            pio_loc.z -= ci->type == id_SIOLOGIC ? 2 : 4; +            std::string pic_tile = get_pic_tile(ctx, ctx->getBelByLocation(pio_loc)); +            std::string prim = std::string("IOLOGIC") + "ABCD"[pio_loc.z]; +            for (auto ¶m : ci->params) { +                if (param.first == ctx->id("DELAY.DEL_VALUE")) +                    cc.tiles[pic_tile].add_word(prim + "." + param.first.str(ctx), +                                                int_to_bitvector(std::stoi(param.second), 7)); +                else +                    cc.tiles[pic_tile].add_enum(prim + "." + param.first.str(ctx), param.second); +            }          } else if (ci->type == id_DCUA) {              TileGroup tg;              tg.tiles = get_dcu_tiles(ctx, ci->bel); diff --git a/ecp5/cells.cc b/ecp5/cells.cc index 31839ee4..58d4797c 100644 --- a/ecp5/cells.cc +++ b/ecp5/cells.cc @@ -41,6 +41,22 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str          new_cell->name = ctx->id(name);      }      new_cell->type = type; + +    auto copy_bel_ports = [&]() { +        // First find a Bel of the target type +        BelId tgt; +        for (auto bel : ctx->getBels()) { +            if (ctx->getBelType(bel) == type) { +                tgt = bel; +                break; +            } +        } +        NPNR_ASSERT(tgt != BelId()); +        for (auto port : ctx->getBelPins(tgt)) { +            add_port(ctx, new_cell.get(), port.str(ctx), ctx->getBelPinType(tgt, port)); +        } +    }; +      if (type == ctx->id("TRELLIS_SLICE")) {          new_cell->params[ctx->id("MODE")] = "LOGIC";          new_cell->params[ctx->id("GSR")] = "DISABLED"; @@ -111,11 +127,17 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str      } else if (type == ctx->id("TRELLIS_IO")) {          new_cell->params[ctx->id("DIR")] = "INPUT";          new_cell->attrs[ctx->id("IO_TYPE")] = "LVCMOS33"; +        new_cell->params[ctx->id("DATAMUX_ODDR")] = "PADDO"; +        new_cell->params[ctx->id("DATAMUX_MDDR")] = "PADDO";          add_port(ctx, new_cell.get(), "B", PORT_INOUT);          add_port(ctx, new_cell.get(), "I", PORT_IN);          add_port(ctx, new_cell.get(), "T", PORT_IN);          add_port(ctx, new_cell.get(), "O", PORT_OUT); + +        add_port(ctx, new_cell.get(), "IOLDO", PORT_IN); +        add_port(ctx, new_cell.get(), "IOLTO", PORT_IN); +      } else if (type == ctx->id("LUT4")) {          new_cell->params[ctx->id("INIT")] = "0"; @@ -150,6 +172,35 @@ std::unique_ptr<CellInfo> create_ecp5_cell(Context *ctx, IdString type, std::str          add_port(ctx, new_cell.get(), "CLKI", PORT_IN);          add_port(ctx, new_cell.get(), "CLKO", PORT_OUT);          add_port(ctx, new_cell.get(), "CE", PORT_IN); +    } else if (type == id_IOLOGIC || type == id_SIOLOGIC) { +        new_cell->params[ctx->id("MODE")] = "NONE"; +        new_cell->params[ctx->id("GSR")] = "DISABLED"; +        new_cell->params[ctx->id("CLKIMUX")] = "CLK"; +        new_cell->params[ctx->id("CLKOMUX")] = "CLK"; +        new_cell->params[ctx->id("LSRIMUX")] = "0"; +        new_cell->params[ctx->id("LSROMUX")] = "0"; +        new_cell->params[ctx->id("LSRMUX")] = "LSR"; + +        new_cell->params[ctx->id("DELAY.OUTDEL")] = "DISABLED"; +        new_cell->params[ctx->id("DELAY.DEL_VALUE")] = "0"; +        new_cell->params[ctx->id("DELAY.WAIT_FOR_EDGE")] = "DISABLED"; + +        if (type == id_IOLOGIC) { +            new_cell->params[ctx->id("IDDRXN.MODE")] = "NONE"; +            new_cell->params[ctx->id("ODDRXN.MODE")] = "NONE"; + +            new_cell->params[ctx->id("MIDDRX.MODE")] = "NONE"; +            new_cell->params[ctx->id("MODDRX.MODE")] = "NONE"; +            new_cell->params[ctx->id("MTDDRX.MODE")] = "NONE"; + +            new_cell->params[ctx->id("IOLTOMUX")] = "NONE"; +            new_cell->params[ctx->id("MTDDRX.DQSW_INVERT")] = "DISABLED"; +            new_cell->params[ctx->id("MTDDRX.REGSET")] = "RESET"; + +            new_cell->params[ctx->id("MIDDRX_MODDRX.WRCLKMUX")] = "NONE"; +        } +        // Just copy ports from the Bel +        copy_bel_ports();      } else {          log_error("unable to create ECP5 cell of type %s", type.c_str(ctx));      } diff --git a/ecp5/constids.inc b/ecp5/constids.inc index 250bc3fc..202a7eb2 100644 --- a/ecp5/constids.inc +++ b/ecp5/constids.inc @@ -1142,3 +1142,43 @@ X(PAD)  X(PADDI)  X(PADDO)  X(PADDT) + +X(IOLOGIC) +X(SIOLOGIC) +X(DI) +X(IOLDO) +X(IOLDOD) +X(IOLDOI) +X(IOLTO) +X(INDD) +X(LOADN) +X(MOVE) +X(DIRECTION) +X(TSDATA0) +X(TXDATA0) +X(TXDATA1) +X(RXDATA0) +X(RXDATA1) +X(INFF) +X(CFLAG) +X(ECLK) +X(TSDATA1) +X(TXDATA2) +X(TXDATA3) +X(RXDATA2) +X(RXDATA3) +X(TXDATA4) +X(TXDATA5) +X(TXDATA6) +X(RXDATA4) +X(RXDATA5) +X(RXDATA6) +X(DQSR90) +X(DQSW270) +X(DQSW) +X(RDPNTR0) +X(RDPNTR1) +X(RDPNTR2) +X(WRPNTR0) +X(WRPNTR1) +X(WRPNTR2) diff --git a/ecp5/globals.cc b/ecp5/globals.cc index ddaae5e5..49947b20 100644 --- a/ecp5/globals.cc +++ b/ecp5/globals.cc @@ -58,6 +58,8 @@ class Ecp5GlobalRouter          if (user.cell->type == id_DCUA && (user.port == id_CH0_FF_RXI_CLK || user.port == id_CH1_FF_RXI_CLK ||                                             user.port == id_CH0_FF_TXI_CLK || user.port == id_CH1_FF_TXI_CLK))              return true; +        if ((user.cell->type == id_IOLOGIC || user.cell->type == id_SIOLOGIC) && user.port == id_CLK) +            return true;          return false;      } diff --git a/ecp5/pack.cc b/ecp5/pack.cc index ca329530..6ae169c9 100644 --- a/ecp5/pack.cc +++ b/ecp5/pack.cc @@ -1377,10 +1377,144 @@ class Ecp5Packer          }      } +    // Check if two nets have identical constant drivers +    bool equal_constant(NetInfo *a, NetInfo *b) +    { +        if (a->driver.cell == nullptr || b->driver.cell == nullptr) +            return (a->driver.cell == nullptr && b->driver.cell == nullptr); +        if (a->driver.cell->type != ctx->id("GND") && a->driver.cell->type != ctx->id("VCC")) +            return false; +        return a->driver.cell->type == b->driver.cell->type; +    } + +    // Pack IOLOGIC +    void pack_iologic() +    { +        std::unordered_map<IdString, CellInfo *> pio_iologic; + +        auto set_iologic_sclk = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) { +            NetInfo *sclk = nullptr; +            if (prim->ports.count(port)) +                sclk = prim->ports[port].net; +            if (sclk == nullptr) { +                iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "0"; +            } else { +                iol->params[input ? ctx->id("CLKIMUX") : ctx->id("CLKOMUX")] = "CLK"; +                if (iol->ports[id_CLK].net != nullptr) { +                    if (iol->ports[id_CLK].net != sclk && !equal_constant(iol->ports[id_CLK].net, sclk)) +                        log_error("IOLOGIC '%s' has conflicting clocks '%s' and '%s'\n", iol->name.c_str(ctx), +                                  iol->ports[id_CLK].net->name.c_str(ctx), sclk->name.c_str(ctx)); +                } else { +                    connect_port(ctx, sclk, iol, id_CLK); +                } +            } +            if (prim->ports.count(port)) +                disconnect_port(ctx, prim, port); +        }; + +        auto set_iologic_lsr = [&](CellInfo *iol, CellInfo *prim, IdString port, bool input) { +            NetInfo *lsr = nullptr; +            if (prim->ports.count(port)) +                lsr = prim->ports[port].net; +            if (lsr == nullptr) { +                iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "0"; +            } else { +                iol->params[input ? ctx->id("LSRIMUX") : ctx->id("LSROMUX")] = "LSRMUX"; +                if (iol->ports[id_LSR].net != nullptr && !equal_constant(iol->ports[id_LSR].net, lsr)) { +                    if (iol->ports[id_LSR].net != lsr) +                        log_error("IOLOGIC '%s' has conflicting LSR signals '%s' and '%s'\n", iol->name.c_str(ctx), +                                  iol->ports[id_LSR].net->name.c_str(ctx), lsr->name.c_str(ctx)); +                } else { +                    connect_port(ctx, lsr, iol, id_LSR); +                } +            } +            if (prim->ports.count(port)) +                disconnect_port(ctx, prim, port); +        }; + +        auto set_iologic_mode = [&](CellInfo *iol, std::string mode) { +            auto &curr_mode = iol->params[ctx->id("MODE")]; +            if (curr_mode != "NONE" && curr_mode != mode) +                log_error("IOLOGIC '%s' has conflicting modes '%s' and '%s'\n", iol->name.c_str(ctx), curr_mode.c_str(), +                          mode.c_str()); +            curr_mode = mode; +        }; + +        auto create_pio_iologic = [&](CellInfo *pio, CellInfo *curr) { +            if (!pio->attrs.count(ctx->id("BEL"))) +                log_error("IOLOGIC functionality (DDR, DELAY, DQS, etc) can only be used with pin-constrained PIO " +                          "(while processing '%s').\n", +                          curr->name.c_str(ctx)); +            BelId bel = ctx->getBelByName(ctx->id(pio->attrs.at(ctx->id("BEL")))); +            NPNR_ASSERT(bel != BelId()); +            log_info("IOLOGIC component %s connected to PIO Bel %s\n", curr->name.c_str(ctx), +                     ctx->getBelName(bel).c_str(ctx)); +            Loc loc = ctx->getBelLocation(bel); +            bool s = false; +            if (loc.y == 0 || loc.y == (ctx->chip_info->height - 1)) +                s = true; +            std::unique_ptr<CellInfo> iol = +                    create_ecp5_cell(ctx, s ? id_SIOLOGIC : id_IOLOGIC, pio->name.str(ctx) + "$IOL"); + +            loc.z += s ? 2 : 4; +            iol->attrs[ctx->id("BEL")] = ctx->getBelName(ctx->getBelByLocation(loc)).str(ctx); + +            CellInfo *iol_ptr = iol.get(); +            pio_iologic[pio->name] = iol_ptr; +            new_cells.push_back(std::move(iol)); +            return iol_ptr; +        }; + +        for (auto cell : sorted(ctx->cells)) { +            CellInfo *ci = cell.second; +            if (ci->type == ctx->id("IDDRX1F")) { +                CellInfo *pio = net_driven_by(ctx, ci->ports.at(ctx->id("D")).net, is_trellis_io, id_O); +                if (pio == nullptr || ci->ports.at(ctx->id("D")).net->users.size() > 1) +                    log_error("IDDRX1F '%s' D input must be connected only to a top level input\n", +                              ci->name.c_str(ctx)); +                CellInfo *iol; +                if (pio_iologic.count(pio->name)) +                    iol = pio_iologic.at(pio->name); +                else +                    iol = create_pio_iologic(pio, ci); +                set_iologic_mode(iol, "IDDRX1_ODDRX1"); +                replace_port(ci, ctx->id("D"), iol, id_PADDI); +                set_iologic_sclk(iol, ci, ctx->id("SCLK"), true); +                set_iologic_lsr(iol, ci, ctx->id("RST"), true); +                replace_port(ci, ctx->id("Q0"), iol, id_RXDATA0); +                replace_port(ci, ctx->id("Q1"), iol, id_RXDATA1); +                iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED"); +                packed_cells.insert(cell.first); +            } else if (ci->type == ctx->id("ODDRX1F")) { +                CellInfo *pio = net_only_drives(ctx, ci->ports.at(ctx->id("Q")).net, is_trellis_io, id_I, true); +                if (pio == nullptr) +                    log_error("ODDRX1F '%s' Q output must be connected only to a top level output\n", +                              ci->name.c_str(ctx)); +                CellInfo *iol; +                if (pio_iologic.count(pio->name)) +                    iol = pio_iologic.at(pio->name); +                else +                    iol = create_pio_iologic(pio, ci); +                set_iologic_mode(iol, "IDDRX1_ODDRX1"); +                replace_port(ci, ctx->id("Q"), iol, id_IOLDO); +                replace_port(pio, id_I, pio, id_IOLDO); +                pio->params[ctx->id("DATAMUX_ODDR")] = "IOLDO"; +                set_iologic_sclk(iol, ci, ctx->id("SCLK"), false); +                set_iologic_lsr(iol, ci, ctx->id("RST"), false); +                replace_port(ci, ctx->id("D0"), iol, id_TXDATA0); +                replace_port(ci, ctx->id("D1"), iol, id_TXDATA1); +                iol->params[ctx->id("GSR")] = str_or_default(ci->params, ctx->id("GSR"), "DISABLED"); +                packed_cells.insert(cell.first); +            } +        } +        flush_cells(); +    }; +    public:      void pack()      {          pack_io(); +        pack_iologic();          pack_ebr();          pack_dsps();          pack_dcus(); | 
