// SPDX-License-Identifier: BSD-3-Clause
// Copyright (c) 2022-2025, The OpenROAD Authors

%{
#include "pdn/PdnGen.hh"
#include "odb/db.h"
#include "utl/timer.h"
#include <array>
#include <regex>
#include <memory>
#include <vector>

namespace ord {
// Defined in OpenRoad.i
odb::dbDatabase* getDb();
pdn::PdnGen* getPdnGen();
utl::Logger* getLogger();
} // namespace ord

using std::regex;
using utl::PDN;

#if TCL_MAJOR_VERSION < 9 && !defined(Tcl_Size)
 typedef int Tcl_Size;
#endif
%}

%import <std_vector.i>
%import "dbtypes.i"
%include "../../Exception.i"

%template(split_cuts_pitch_map) std::vector<int>;

%typemap(in) pdn::ExtensionMode {
  char *str = Tcl_GetStringFromObj($input, 0);
  if (strcasecmp(str, "Core") == 0) {
    $1 = pdn::ExtensionMode::kCore;
  } else if (strcasecmp(str, "Rings") == 0) {
    $1 = pdn::ExtensionMode::kRings;
  } else if (strcasecmp(str, "Boundary") == 0) {
    $1 = pdn::ExtensionMode::kBoundary;
  } else {
    $1 = pdn::ExtensionMode::kCore;
  }
}

%inline %{

namespace pdn {

void run_pdngen(bool trim, bool add_pins, const char* report_file)
{
  utl::Timer timer;
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->checkSetup();
  pdngen->buildGrids(trim);
  pdngen->writeToDb(add_pins, report_file);
  pdngen->resetShapes();
  ord::getLogger()->info(utl::PDN, 500, "Runtime: {:.2f}s", timer.elapsed());
}

void set_core_domain(odb::dbNet* power, odb::dbNet* switched_power, odb::dbNet* ground, const std::vector<odb::dbNet*>& secondary_nets)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->setCoreDomain(power, switched_power, ground, secondary_nets);
}

void make_region_domain(const char* name, odb::dbNet* power, odb::dbNet* switched_power, odb::dbNet* ground, const std::vector<odb::dbNet*>& secondary_nets, odb::dbRegion* region)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->makeRegionVoltageDomain(name, power, switched_power, ground, secondary_nets, region);
}

void reset()
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->reset();
}

void reset_shapes()
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->resetShapes();
}

void build_grids(bool trim = true)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->buildGrids(trim);
}

void make_core_grid(pdn::VoltageDomain* domain, 
                    const char* name, 
                    bool starts_with_power, 
                    const std::vector<odb::dbTechLayer*>& pin_layers, 
                    const std::vector<odb::dbTechLayer*>& generate_obstructions,
                    pdn::PowerCell* powercell,
                    odb::dbNet* powercontrol,
                    const char* powercontrolnetwork,
                    const std::vector<odb::dbTechLayer*>& pad_pin_layers)
{
  PdnGen* pdngen = ord::getPdnGen();
  StartsWith starts_with = kPower;
  if (!starts_with_power) {
    starts_with = kGround;
  }
  pdngen->makeCoreGrid(domain, 
                       name, 
                       starts_with, 
                       pin_layers,
                       generate_obstructions, 
                       powercell, 
                       powercontrol, 
                       powercontrolnetwork,
                       pad_pin_layers);
}

void make_instance_grid(pdn::VoltageDomain* domain,
                        const char* name,
                        bool starts_with_power,
                        odb::dbInst* inst,
                        int x0,
                        int y0,
                        int x1,
                        int y1,
                        bool pg_pins_to_boundary,
                        bool default_grid, 
                        const std::vector<odb::dbTechLayer*>& generate_obstructions,
                        bool is_bump)
{
  PdnGen* pdngen = ord::getPdnGen();
  StartsWith starts_with = kPower;
  if (!starts_with_power) {
    starts_with = kGround;
  }
  
  std::array<int, 4> halo{x0, y0, x1, y1};
  pdngen->makeInstanceGrid(domain, name, starts_with, inst, halo, pg_pins_to_boundary, default_grid, generate_obstructions, is_bump);
}

void make_dummy_inst_grid(pdn::VoltageDomain* domain,
                        const char* name)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->makeDummyInstanceGrid(domain, name);
}

void make_existing_grid(const char* name, 
                        const std::vector<odb::dbTechLayer*>& generate_obstructions)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->makeExistingGrid(name, generate_obstructions);
}

void make_ring(const char* grid_name, 
               odb::dbTechLayer* l0,
               int width0,
               int spacing0,
               odb::dbTechLayer* l1,
               int width1,
               int spacing1,
               bool use_grid_power_order,
               bool starts_with_power,
               int core_offset_x0,
               int core_offset_y0,
               int core_offset_x1,
               int core_offset_y1, 
               int pad_offset_x0,
               int pad_offset_y0,
               int pad_offset_x1,
               int pad_offset_y1, 
               bool extend,
               const std::vector<odb::dbTechLayer*>& pad_pin_layers,
               const std::vector<odb::dbNet*>& nets,
               bool allow_outside_of_die)
{
  PdnGen* pdngen = ord::getPdnGen();
  StartsWith starts_with = kGrid;
  if (!use_grid_power_order) {
    if (starts_with_power) {
      starts_with = kPower;
    } else {
      starts_with = kGround;
    }
  }
  for (auto* grid : pdngen->findGrid(grid_name, true)) {
    pdngen->makeRing(grid,
                     l0, width0, spacing0,
                     l1, width1, spacing1,
                     starts_with,
                     {core_offset_x0, core_offset_y0, core_offset_x1, core_offset_y1},
                     {pad_offset_x0, pad_offset_y0, pad_offset_x1, pad_offset_y1},
                     extend,
                     pad_pin_layers,
                     nets,
                     allow_outside_of_die);
  }
}

void createSrouteWires(
    const char* net,
    const char* outerNet,
    odb::dbTechLayer* layer0,
    odb::dbTechLayer* layer1,
    int cut_pitch_x,
    int cut_pitch_y,
    const std::vector<odb::dbTechViaGenerateRule*>& vias,
    const std::vector<odb::dbTechVia*>& techvias,
    int max_rows,
    int max_columns,
    const std::vector<odb::dbTechLayer*>& ongrid,
    std::vector<int> metalwidths,
    std::vector<int> metalspaces,
    const std::vector<odb::dbInst*>& insts)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->createSrouteWires(net,
                            outerNet,
                            layer0,
                            layer1,
                            cut_pitch_x,
                            cut_pitch_y,
                            vias,
                            techvias,
                            max_rows,
                            max_columns,
                            ongrid,
                            metalwidths,
                            metalspaces,
                            insts);
}


void make_followpin(const char* grid_name, 
                    odb::dbTechLayer* layer, 
                    int width, 
                    pdn::ExtensionMode extend)
{
  PdnGen* pdngen = ord::getPdnGen();
  for (auto* grid : pdngen->findGrid(grid_name, true)) {
    pdngen->makeFollowpin(grid, layer, width, extend);
  }
}

void make_strap(const char* grid_name, 
                odb::dbTechLayer* layer, 
                int width, 
                int spacing, 
                int pitch, 
                int offset, 
                int number_of_straps, 
                bool snap,
                bool use_grid_power_order,
                bool starts_with_power,
                pdn::ExtensionMode extend,
                const std::vector<odb::dbNet*>& nets,
                bool allow_out_of_core)
{
  PdnGen* pdngen = ord::getPdnGen();
  StartsWith starts_with = kGrid;
  if (!use_grid_power_order) {
    if (starts_with_power) {
      starts_with = kPower;
    } else {
      starts_with = kGround;
    }
  }
  for (auto* grid : pdngen->findGrid(grid_name, true)) {
    pdngen->makeStrap(grid,
                      layer,
                      width,
                      spacing,
                      pitch,
                      offset,
                      number_of_straps,
                      snap,
                      starts_with,
                      extend,
                      nets,
                      allow_out_of_core);
  }
}

void make_connect(const char* grid_name, 
                  odb::dbTechLayer* layer0, 
                  odb::dbTechLayer* layer1, 
                  int cut_pitch_x, 
                  int cut_pitch_y, 
                  const std::vector<odb::dbTechViaGenerateRule*>& vias, 
                  const std::vector<odb::dbTechVia*>& techvias,
                  int max_rows,
                  int max_columns,
                  const std::vector<odb::dbTechLayer*>& ongrid,
                  const std::vector<odb::dbTechLayer*>& min_width_layers,
                  const std::vector<odb::dbTechLayer*>& split_cuts_layers,
                  const std::vector<int>& split_cut_pitches,
                  const bool split_cut_stagger,
                  const char* dont_use_vias)
{
  PdnGen* pdngen = ord::getPdnGen();
  odb::PtrMap<odb::dbTechLayer, std::pair<int, bool>> split_cuts;
  for (size_t i = 0; i < split_cuts_layers.size(); i++) {
    split_cuts[split_cuts_layers[i]] = {split_cut_pitches[i], split_cut_stagger};
  }
  for (auto* grid : pdngen->findGrid(grid_name, true)) {
    pdngen->makeConnect(grid, layer0, layer1, cut_pitch_x, cut_pitch_y, vias, techvias, max_rows, max_columns, ongrid, min_width_layers, split_cuts, dont_use_vias);
  }
}

void debug_renderer(bool on)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->setDebugRenderer(on);
}

void debug_renderer_update()
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->rendererRedraw();
}

void write_to_db(bool add_pins, const char* report_file)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->writeToDb(add_pins, report_file);
}

void rip_up(odb::dbNet* net = nullptr)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->ripUp(net);
}

void report()
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->report();
}

pdn::VoltageDomain* find_domain(const char* name)
{
  PdnGen* pdngen = ord::getPdnGen();
  return pdngen->findDomain(name);
}

bool has_grid(const char* name)
{
  PdnGen* pdngen = ord::getPdnGen();
  return !pdngen->findGrid(name, false).empty();
}

void remove_dummy_grid(const char* name)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->removeDummyInstanceGrid(name);
}

void allow_repair_channels(bool allow)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->setAllowRepairChannels(allow);
}

void filter_vias(const char* filter)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->filterVias(filter);
}

void check_setup()
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->checkSetup();
}

void make_switched_power_cell(odb::dbMaster* master,
                              odb::dbMTerm* control,
                              odb::dbMTerm* acknowledge,
                              odb::dbMTerm* switched_power,
                              odb::dbMTerm* alwayson_power,
                              odb::dbMTerm* ground)
{
  PdnGen* pdngen = ord::getPdnGen();
  pdngen->makeSwitchedPowerCell(master, control, acknowledge, switched_power, alwayson_power, ground);
}

pdn::PowerCell* find_switched_power_cell(const char* name)
{
  PdnGen* pdngen = ord::getPdnGen();
  return pdngen->findSwitchedPowerCell(name);
}

void repair_pdn_vias(const std::vector<odb::dbNet*>& nets)
{
  PdnGen* pdngen = ord::getPdnGen();
  odb::PtrSet<odb::dbNet> net_set(nets.begin(), nets.end());
  pdngen->repairVias(net_set);
}

// used for building debugging grids and should not be used.
void add_debug_strap(odb::dbNet* net, odb::dbTechLayer* layer, int offset, int width, int start, int stop, const char* direction)
{
  auto* swire = odb::dbSWire::create(net, odb::dbWireType::ROUTED);
  odb::Rect strap(start, start, stop, stop);
  if (start == stop) {
    auto* block = swire->getBlock();
    strap = block->getCoreArea();
  }
  if (strcmp(direction, "HORIZONTAL") == 0) {
    strap.set_ylo(offset);
    strap.set_yhi(offset + width);
  } else if (strcmp(direction, "VERTICAL") == 0) {
    strap.set_xlo(offset);
    strap.set_xhi(offset + width);    
  } else {
    return;
  }
  odb::dbSBox::create(swire, layer, strap.xMin(), strap.yMin(), strap.xMax(), strap.yMax(), odb::dbWireShapeType::STRIPE);
}

} // namespace

%} // inline
