diff --git a/common/kernel/nextpnr_types.h b/common/kernel/nextpnr_types.h index d3269cec73..4c9dba600a 100644 --- a/common/kernel/nextpnr_types.h +++ b/common/kernel/nextpnr_types.h @@ -132,7 +132,8 @@ struct NetInfo : ArchNetInfo indexed_store users; dict attrs; - // If this is set to a non-empty ID, then the driver is ignored and it will be routed from any wire with a matching getWireConstantValue + // If this is set to a non-empty ID, then the driver is ignored and it will be routed from any wire with a matching + // getWireConstantValue IdString constant_value; // wire -> uphill_pip diff --git a/common/route/router1.cc b/common/route/router1.cc index e832177d2c..a3ebdb60c1 100644 --- a/common/route/router1.cc +++ b/common/route/router1.cc @@ -133,8 +133,10 @@ struct Router1 if (queued_arcs.count(arc)) return; - delay_t pri = ctx->estimateDelay(src_wire, dst_wire) * - (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx)))); + delay_t pri = (arc.net_info->constant_value == IdString()) + ? (ctx->estimateDelay(src_wire, dst_wire) * + (100 * tmg.get_criticality(CellPortKey(arc.net_info->users.at(arc.user_idx))))) + : 0; arc_entry entry; entry.arc = arc; @@ -293,7 +295,7 @@ struct Router1 if (net_info->is_global) return true; #endif - if (net_info->driver.cell == nullptr) + if (net_info->driver.cell == nullptr && net_info->constant_value == IdString()) return true; return false; @@ -380,7 +382,7 @@ struct Router1 auto src_wire = ctx->getNetinfoSourceWire(net_info); - if (src_wire == WireId()) + if (src_wire == WireId() && net_info->constant_value == IdString()) log_error("No wire found for port %s on source cell %s.\n", ctx->nameOf(net_info->driver.port), ctx->nameOf(net_info->driver.cell)); @@ -430,7 +432,8 @@ struct Router1 wire_to_arcs[cursor].insert(arc); arc_to_wires[arc].insert(cursor); - while (src_wire != cursor) { + while (src_wire != cursor && (net_info->constant_value == IdString() || + ctx->getWireConstantValue(cursor) != net_info->constant_value)) { auto it = net_info->wires.find(cursor); if (it == net_info->wires.end()) { arc_queue_insert(arc, src_wire, dst_wire); @@ -797,6 +800,298 @@ struct Router1 return true; } + bool route_const_arc(const arc_key &arc, bool ripup) + { + + NetInfo *net_info = arc.net_info; + auto user_idx = arc.user_idx; + + auto dst_wire = ctx->getNetinfoSinkWire(net_info, net_info->users[user_idx], arc.phys_idx); + ripup_flag = false; + + if (ctx->debug) { + log("Routing constant arc %d on net %s (%d arcs total):\n", user_idx.idx(), ctx->nameOf(net_info), + int(net_info->users.capacity())); + log(" value ... %s\n", ctx->nameOf(net_info->constant_value)); + log(" sink ..... %s\n", ctx->nameOfWire(dst_wire)); + } + + // unbind wires that are currently used exclusively by this arc + + pool old_arc_wires; + old_arc_wires.swap(arc_to_wires[arc]); + + for (WireId wire : old_arc_wires) { + auto &arc_wires = wire_to_arcs.at(wire); + NPNR_ASSERT(arc_wires.count(arc)); + arc_wires.erase(arc); + if (arc_wires.empty()) { + if (ctx->debug) + log(" unbind %s\n", ctx->nameOfWire(wire)); + ctx->unbindWire(wire); + } + } + + // special case + + if (ctx->getWireConstantValue(dst_wire) == net_info->constant_value) { + NetInfo *bound = ctx->getBoundWireNet(dst_wire); + if (bound != nullptr) + NPNR_ASSERT(bound == net_info); + else { + ctx->bindWire(dst_wire, net_info, STRENGTH_WEAK); + } + arc_to_wires[arc].insert(dst_wire); + wire_to_arcs[dst_wire].insert(arc); + return true; + } + + // reset wire queue + + if (!queue.empty()) { + std::priority_queue, QueuedWire::Greater> new_queue; + queue.swap(new_queue); + } + dict visited; + + // A* main loop + + int visitCnt = 0; + int maxVisitCnt = INT_MAX; + delay_t best_score = -1; + WireId best_src; + + { + QueuedWire qw; + qw.wire = dst_wire; + qw.pip = PipId(); + qw.delay = ctx->getWireDelay(qw.wire).maxDelay(); + qw.penalty = 0; + qw.bonus = 0; + qw.randtag = ctx->rng(); + + queue.push(qw); + visited[qw.wire] = qw; + } + + while (visitCnt++ < maxVisitCnt && !queue.empty()) { + QueuedWire qw = queue.top(); + queue.pop(); + + for (auto pip : ctx->getPipsUphill(qw.wire)) { + delay_t next_delay = qw.delay + ctx->getPipDelay(pip).maxDelay(); + delay_t next_penalty = qw.penalty; + delay_t next_bonus = qw.bonus; + delay_t penalty_delta = 0; + + WireId next_wire = ctx->getPipSrcWire(pip); + next_delay += ctx->getWireDelay(next_wire).maxDelay(); + + WireId conflictWireWire = WireId(), conflictPipWire = WireId(); + NetInfo *conflictWireNet = nullptr, *conflictPipNet = nullptr; + + if (net_info->wires.count(qw.wire)) { + if (net_info->wires.at(qw.wire).pip != pip) + continue; + next_bonus += cfg.reuseBonus; + } else { + if (!ctx->checkWireAvail(next_wire)) { + if (!ripup) + continue; + conflictWireWire = ctx->getConflictingWireWire(next_wire); + if (conflictWireWire == WireId()) { + conflictWireNet = ctx->getConflictingWireNet(next_wire); + if (conflictWireNet == nullptr) + continue; + else { + if (conflictWireNet->wires.count(next_wire) && + conflictWireNet->wires.at(next_wire).strength > STRENGTH_STRONG) + continue; + } + } else { + NetInfo *conflicting = ctx->getBoundWireNet(conflictWireWire); + if (conflicting != nullptr) { + if (conflicting->wires.count(conflictWireWire) && + conflicting->wires.at(conflictWireWire).strength > STRENGTH_STRONG) + continue; + } + } + } + + if (!ctx->checkPipAvail(pip)) { + if (!ripup) + continue; + conflictPipWire = ctx->getConflictingPipWire(pip); + if (conflictPipWire == WireId()) { + conflictPipNet = ctx->getConflictingPipNet(pip); + if (conflictPipNet == nullptr) + continue; + else { + if (conflictPipNet->wires.count(next_wire) && + conflictPipNet->wires.at(next_wire).strength > STRENGTH_STRONG) + continue; + } + } else { + NetInfo *conflicting = ctx->getBoundWireNet(conflictPipWire); + if (conflicting != nullptr) { + if (conflicting->wires.count(conflictPipWire) && + conflicting->wires.at(conflictPipWire).strength > STRENGTH_STRONG) + continue; + } + } + } + + if (conflictWireNet != nullptr && conflictPipWire != WireId() && + conflictWireNet->wires.count(conflictPipWire)) + conflictPipWire = WireId(); + + if (conflictPipNet != nullptr && conflictWireWire != WireId() && + conflictPipNet->wires.count(conflictWireWire)) + conflictWireWire = WireId(); + + if (conflictWireWire == conflictPipWire) + conflictWireWire = WireId(); + + if (conflictWireNet == conflictPipNet) + conflictWireNet = nullptr; + + if (conflictWireWire != WireId()) { + auto scores_it = wireScores.find(conflictWireWire); + if (scores_it != wireScores.end()) + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; + } + + if (conflictPipWire != WireId()) { + auto scores_it = wireScores.find(conflictPipWire); + if (scores_it != wireScores.end()) + penalty_delta += scores_it->second * cfg.wireRipupPenalty; + penalty_delta += cfg.wireRipupPenalty; + } + + if (conflictWireNet != nullptr) { + auto scores_it = netScores.find(conflictWireNet); + if (scores_it != netScores.end()) + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictWireNet->wires.size() * cfg.wireRipupPenalty; + } + + if (conflictPipNet != nullptr) { + auto scores_it = netScores.find(conflictPipNet); + if (scores_it != netScores.end()) + penalty_delta += scores_it->second * cfg.netRipupPenalty; + penalty_delta += cfg.netRipupPenalty; + penalty_delta += conflictPipNet->wires.size() * cfg.wireRipupPenalty; + } + } + + next_penalty += penalty_delta; + + delay_t next_score = next_delay + next_penalty; + NPNR_ASSERT(next_score >= 0); + + if ((best_score >= 0) && (next_score - next_bonus - cfg.estimatePrecision > best_score)) + continue; + + auto old_visited_it = visited.find(next_wire); + if (old_visited_it != visited.end()) { + continue; + } + + QueuedWire next_qw; + next_qw.wire = next_wire; + next_qw.pip = pip; + next_qw.delay = next_delay; + next_qw.penalty = next_penalty; + next_qw.bonus = next_bonus; + next_qw.randtag = ctx->rng(); + + visited[next_qw.wire] = next_qw; + queue.push(next_qw); + + if (ctx->getWireConstantValue(next_wire) == net_info->constant_value) { + maxVisitCnt = std::min(maxVisitCnt, 2 * visitCnt + (next_qw.penalty > 0 ? 100 : 0)); + if (best_src == WireId() || next_score < best_score) { + best_src = next_wire; + } + best_score = next_score - next_bonus; + } + } + } + + if (ctx->debug) + log(" total number of visited nodes: %d\n", visitCnt); + + if (best_src == WireId()) { + if (ctx->debug) + log(" no route found for this arc\n"); + return false; + } + + if (ctx->debug) { + log(" final route delay: %8.2f\n", ctx->getDelayNS(visited[dst_wire].delay)); + log(" final route penalty: %8.2f\n", ctx->getDelayNS(visited[dst_wire].penalty)); + log(" final route bonus: %8.2f\n", ctx->getDelayNS(visited[dst_wire].bonus)); + } + + // bind resulting route (and maybe unroute other nets) + + pool unassign_wires = arc_to_wires[arc]; + + WireId cursor = best_src; + + if (!net_info->wires.count(cursor)) { + if (!ctx->checkWireAvail(cursor)) { + ripup_wire(cursor); + NPNR_ASSERT(ctx->checkWireAvail(cursor)); + } + ctx->bindWire(cursor, net_info, STRENGTH_WEAK); + } + + wire_to_arcs[cursor].insert(arc); + arc_to_wires[arc].insert(cursor); + + while (1) { + auto pip = visited[cursor].pip; + + if (pip == PipId()) { + NPNR_ASSERT(cursor == dst_wire); + break; + } + + WireId next = ctx->getPipDstWire(pip); + + if (!net_info->wires.count(next) || (net_info->wires.count(next) && net_info->wires.at(next).pip != pip)) { + if (!ctx->checkWireAvail(next)) { + ripup_wire(next); + NPNR_ASSERT(ctx->checkWireAvail(next)); + } + + if (!ctx->checkPipAvail(pip)) { + ripup_pip(pip); + NPNR_ASSERT(ctx->checkPipAvail(pip)); + } + + if (ctx->debug) + log(" bind pip %s\n", ctx->nameOfPip(pip)); + ctx->bindPip(pip, net_info, STRENGTH_WEAK); + } + + wire_to_arcs[next].insert(arc); + arc_to_wires[arc].insert(next); + + cursor = next; + } + + if (ripup_flag) + arcs_with_ripup++; + else + arcs_without_ripup++; + + return true; + } + delay_t find_slack_thresh() { // If more than 5% of arcs have negative slack; use the 5% threshold as a ripup criteria @@ -911,15 +1206,26 @@ bool router1(Context *ctx, const Router1Cfg &cfg) log("-- %d --\n", iter_cnt); arc_key arc = router.arc_queue_pop(); - - if (!router.route_arc(arc, true)) { - log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(), - ctx->nameOf(arc.net_info)); + if (arc.net_info->constant_value != IdString()) { + if (!router.route_const_arc(arc, true)) { + log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(), + ctx->nameOf(arc.net_info)); #ifndef NDEBUG - router.check(); - ctx->check(); + router.check(); + ctx->check(); #endif - return false; + return false; + } + } else { + if (!router.route_arc(arc, true)) { + log_warning("Failed to find a route for arc %d of net %s.\n", arc.user_idx.idx(), + ctx->nameOf(arc.net_info)); +#ifndef NDEBUG + router.check(); + ctx->check(); +#endif + return false; + } } // Timing driven ripup if (timing_ripup && router.arc_queue.empty() && timing_fail_count < 50) { @@ -1039,17 +1345,19 @@ bool Context::checkRoutedDesign() const } auto src_wire = ctx->getNetinfoSourceWire(net_info); - if (src_wire == WireId()) { - log_assert(net_info->driver.cell == nullptr); - if (ctx->debug) - log(" undriven and unrouted\n"); - continue; - } + if (net_info->constant_value == IdString()) { + if (src_wire == WireId()) { + log_assert(net_info->driver.cell == nullptr); + if (ctx->debug) + log(" undriven and unrouted\n"); + continue; + } - if (net_info->wires.count(src_wire) == 0) { - if (ctx->debug) - log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire)); - found_unrouted = true; + if (net_info->wires.count(src_wire) == 0) { + if (ctx->debug) + log(" source (%s) not bound to net\n", ctx->nameOfWire(src_wire)); + found_unrouted = true; + } } dict> dest_wires; @@ -1100,7 +1408,14 @@ bool Context::checkRoutedDesign() const log(" driver: %s\n", ctx->nameOfWire(src_wire)); logged_wires.insert(src_wire); } - setOrderNum(src_wire, 1); + if (net_info->constant_value != IdString()) { + for (const auto &wire : net_info->wires) { + if (wire.second.pip == PipId() && ctx->getWireConstantValue(wire.first) == net_info->constant_value) + setOrderNum(wire.first, 1); + } + } else { + setOrderNum(src_wire, 1); + } pool dangling_wires; diff --git a/common/route/router2.cc b/common/route/router2.cc index db8885d47f..49fe327a06 100644 --- a/common/route/router2.cc +++ b/common/route/router2.cc @@ -346,7 +346,8 @@ struct Router2 return; WireId src = nets.at(net->udata).src_wire; WireId cursor = ad.sink_wire; - while (cursor != src) { + while (cursor != src && + (net->constant_value == IdString() || ctx->getWireConstantValue(cursor) == net->constant_value)) { PipId pip = nd.wires.at(cursor).first; unbind_pip_internal(nd, user, cursor); cursor = ctx->getPipSrcWire(pip); @@ -533,18 +534,11 @@ struct Router2 // These nets have very-high-fanout pips and special rules must be followed (only working backwards) to avoid // crippling perf - bool is_pseudo_const_net(const NetInfo *net) - { -#ifdef ARCH_NEXUS - if (net->driver.cell != nullptr && net->driver.cell->type == id_VCC_DRV) - return true; -#endif - return false; - } + bool is_dedi_const_net(const NetInfo *net) { return net->constant_value != IdString(); } void update_wire_by_loc(ThreadContext &t, NetInfo *net, store_index i, size_t phys_pin, bool is_mt) { - if (is_pseudo_const_net(net)) + if (is_dedi_const_net(net)) return; auto &nd = nets.at(net->udata); auto &ad = nd.arcs.at(i.idx()).at(phys_pin); @@ -613,16 +607,17 @@ struct Router2 auto &nd = nets[net->udata]; auto &ad = nd.arcs.at(i.idx()).at(phys_pin); auto &usr = net->users.at(i); + bool const_mode = is_dedi_const_net(net); ROUTE_LOG_DBG("Routing arc %d of net '%s' (%d, %d) -> (%d, %d)\n", i.idx(), ctx->nameOf(net), ad.bb.x0, ad.bb.y0, ad.bb.x1, ad.bb.y1); WireId src_wire = ctx->getNetinfoSourceWire(net), dst_wire = ctx->getNetinfoSinkWire(net, usr, phys_pin); - if (src_wire == WireId()) + if (src_wire == WireId() && !const_mode) ARC_LOG_ERR("No wire found for port %s on source cell %s.\n", ctx->nameOf(net->driver.port), ctx->nameOf(net->driver.cell)); if (dst_wire == WireId()) ARC_LOG_ERR("No wire found for port %s on destination cell %s.\n", ctx->nameOf(usr.port), ctx->nameOf(usr.cell)); - int src_wire_idx = wire_to_idx.at(src_wire); + int src_wire_idx = const_mode ? -1 : wire_to_idx.at(src_wire); int dst_wire_idx = wire_to_idx.at(dst_wire); // Calculate a timing weight based on criticality float crit = get_arc_crit(net, i); @@ -683,12 +678,13 @@ struct Router2 if (mode == 0 && t.fwd_queue.size() < 4) continue; - - if (mode == 1 && !is_pseudo_const_net(net)) { - // Seed forwards with the source wire, if less than 8 existing wires added - seed_queue_fwd(src_wire); - } else { - set_visited_fwd(t, src_wire_idx, PipId(), 0.0); + if (!const_mode) { + if (mode == 1) { + // Seed forwards with the source wire, if less than 8 existing wires added + seed_queue_fwd(src_wire); + } else { + set_visited_fwd(t, src_wire_idx, PipId(), 0.0); + } } auto seed_queue_bwd = [&](WireId wire) { WireScore base_score; @@ -711,7 +707,7 @@ struct Router2 : (!t.fwd_queue.empty() || !t.bwd_queue.empty())) && (!is_bb || iter < toexplore)) { ++iter; - if (!t.fwd_queue.empty()) { + if (!t.fwd_queue.empty() && !const_mode) { // Explore forwards auto curr = t.fwd_queue.top(); t.fwd_queue.pop(); @@ -760,12 +756,13 @@ struct Router2 auto curr = t.bwd_queue.top(); t.bwd_queue.pop(); ++explored; - if (was_visited_fwd(curr.wire, std::numeric_limits::max())) { + auto &curr_data = flat_wires.at(curr.wire); + if (was_visited_fwd(curr.wire, std::numeric_limits::max()) || + (const_mode && ctx->getWireConstantValue(curr_data.w) == net->constant_value)) { // Meet in the middle; done midpoint_wire = curr.wire; break; } - auto &curr_data = flat_wires.at(curr.wire); // Don't allow the same wire to be bound to the same net with a different driving pip PipId bound_pip; auto fnd_wire = nd.wires.find(curr_data.w); @@ -809,44 +806,48 @@ struct Router2 ArcRouteResult result = ARC_SUCCESS; if (midpoint_wire != -1) { ROUTE_LOG_DBG(" Routed (explored %d wires): ", explored); - int cursor_bwd = midpoint_wire; - while (was_visited_fwd(cursor_bwd, std::numeric_limits::max())) { - PipId pip = flat_wires.at(cursor_bwd).pip_fwd; - if (pip == PipId() && cursor_bwd != src_wire_idx) - break; - bind_pip_internal(nd, i, cursor_bwd, pip); - if (ctx->debug && !is_mt) { - auto &wd = flat_wires.at(cursor_bwd); - ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), - wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); - } - if (pip == PipId()) { - break; + if (const_mode) { + bind_pip_internal(nd, i, midpoint_wire, PipId()); + } else { + int cursor_bwd = midpoint_wire; + while (was_visited_fwd(cursor_bwd, std::numeric_limits::max())) { + PipId pip = flat_wires.at(cursor_bwd).pip_fwd; + if (pip == PipId() && cursor_bwd != src_wire_idx) + break; + bind_pip_internal(nd, i, cursor_bwd, pip); + if (ctx->debug && !is_mt) { + auto &wd = flat_wires.at(cursor_bwd); + ROUTE_LOG_DBG(" fwd wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), + wd.curr_cong - 1, wd.hist_cong_cost, nd.wires.at(wd.w).second); + } + if (pip == PipId()) { + break; + } + ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, + ctx->getPipLocation(pip).y); + cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); } - ROUTE_LOG_DBG(" fwd pip: %s (%d, %d)\n", ctx->nameOfPip(pip), ctx->getPipLocation(pip).x, - ctx->getPipLocation(pip).y); - cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); - } - while (cursor_bwd != src_wire_idx) { - // Tack onto existing routing - WireId bwd_w = flat_wires.at(cursor_bwd).w; - if (!nd.wires.count(bwd_w)) - break; - auto &bound = nd.wires.at(bwd_w); - PipId pip = bound.first; - if (ctx->debug && !is_mt) { - auto &wd = flat_wires.at(cursor_bwd); - ROUTE_LOG_DBG(" ext wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), - wd.curr_cong - 1, wd.hist_cong_cost, bound.second); + while (cursor_bwd != src_wire_idx) { + // Tack onto existing routing + WireId bwd_w = flat_wires.at(cursor_bwd).w; + if (!nd.wires.count(bwd_w)) + break; + auto &bound = nd.wires.at(bwd_w); + PipId pip = bound.first; + if (ctx->debug && !is_mt) { + auto &wd = flat_wires.at(cursor_bwd); + ROUTE_LOG_DBG(" ext wire: %s (curr %d hist %f share %d)\n", ctx->nameOfWire(wd.w), + wd.curr_cong - 1, wd.hist_cong_cost, bound.second); + } + bind_pip_internal(nd, i, cursor_bwd, pip); + if (pip == PipId()) + break; + cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); } - bind_pip_internal(nd, i, cursor_bwd, pip); - if (pip == PipId()) - break; - cursor_bwd = wire_to_idx.at(ctx->getPipSrcWire(pip)); - } - NPNR_ASSERT(cursor_bwd == src_wire_idx); + NPNR_ASSERT(cursor_bwd == src_wire_idx); + } int cursor_fwd = midpoint_wire; while (was_visited_bwd(cursor_fwd, std::numeric_limits::max())) { @@ -1029,7 +1030,7 @@ struct Router2 auto &usr = net->users.at(usr_idx); WireId src = ctx->getNetinfoSourceWire(net); // Skip routes with no source - if (src == WireId()) + if (src == WireId() && net->constant_value == IdString()) return true; WireId dst = ctx->getNetinfoSinkWire(net, usr, phys_pin); if (dst == WireId()) @@ -1086,6 +1087,10 @@ struct Router2 break; } cursor = ctx->getPipSrcWire(p); + if (net->constant_value != IdString() && ctx->getWireConstantValue(cursor) == net->constant_value) { + src = cursor; + break; + } } if (success) { @@ -1343,6 +1348,8 @@ struct Router2 delay_t get_route_delay(int net, store_index usr_idx, int phys_idx) { auto &nd = nets.at(net); + if (nets_by_udata.at(net)->constant_value != IdString()) + return 0; auto &ad = nd.arcs.at(usr_idx.idx()).at(phys_idx); WireId cursor = ad.sink_wire; if (cursor == WireId() || nd.src_wire == WireId()) diff --git a/nexus/arch.h b/nexus/arch.h index 0c1475f619..5d08c66191 100644 --- a/nexus/arch.h +++ b/nexus/arch.h @@ -1170,8 +1170,9 @@ struct Arch : BaseArch virtual bool checkWireAvail(WireId wire) const override { return getBoundWireNet(wire) == nullptr; } NetInfo *getBoundWireNet(WireId wire) const override { return tileStatus.at(wire.tile).boundwires.at(wire.index); } - IdString getWireConstantValue(WireId wire) const override { - if (chip_wire_data(db, chip, wire).name == ID_LOCAL_VCC) + IdString getWireConstantValue(WireId wire) const override + { + if (chip_wire_data(db, chip_info, wire).name == ID_LOCAL_VCC) return id_VCC_DRV; else return {};