1 /*
2 BSD 3-Clause License
3 
4 Copyright (c) 2020, The Regents of the University of Minnesota
5 
6 All rights reserved.
7 
8 Redistribution and use in source and binary forms, with or without
9 modification, are permitted provided that the following conditions are met:
10 
11 * Redistributions of source code must retain the above copyright notice, this
12   list of conditions and the following disclaimer.
13 
14 * Redistributions in binary form must reproduce the above copyright notice,
15   this list of conditions and the following disclaimer in the documentation
16   and/or other materials provided with the distribution.
17 
18 * Neither the name of the copyright holder nor the names of its
19   contributors may be used to endorse or promote products derived from
20   this software without specific prior written permission.
21 
22 THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
23 AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
24 IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
25 DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
26 FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
27 DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
28 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
29 CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
30 OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
31 OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
32 */
33 #include <vector>
34 #include <queue>
35 #include <math.h>
36 #include <cmath>
37 #include <iostream>
38 #include <stdlib.h>
39 #include <map>
40 #include <fstream>
41 #include <iomanip>
42 #include <iostream>
43 #include <time.h>
44 #include <sstream>
45 #include <iterator>
46 #include <string>
47 
48 #include <Eigen/Sparse>
49 #include <Eigen/SparseLU>
50 #include "opendb/db.h"
51 #include "get_voltage.h"
52 #include "ir_solver.h"
53 #include "node.h"
54 #include "gmat.h"
55 #include "get_power.h"
56 
57 namespace psm {
58 using odb::dbBlock;
59 using odb::dbBox;
60 using odb::dbChip;
61 using odb::dbDatabase;
62 using odb::dbInst;
63 using odb::dbNet;
64 using odb::dbSBox;
65 using odb::dbSet;
66 using odb::dbSigType;
67 using odb::dbSWire;
68 using odb::dbTech;
69 using odb::dbTechLayer;
70 using odb::dbTechLayerDir;
71 using odb::dbVia;
72 using odb::dbViaParams;
73 
74 using std::endl;
75 using std::get;
76 using std::ifstream;
77 using std::make_pair;
78 using std::make_tuple;
79 using std::map;
80 using std::ofstream;
81 using std::pair;
82 using std::queue;
83 using std::setprecision;
84 using std::stod;
85 using std::string;
86 using std::stringstream;
87 using std::to_string;
88 using std::tuple;
89 using std::vector;
90 
91 using Eigen::Map;
92 using Eigen::SparseLU;
93 using Eigen::SparseMatrix;
94 using Eigen::Success;
95 using Eigen::VectorXd;
96 
97 //! Returns the created G matrix for the design
98 /*
99  * \return G Matrix
100  */
GetGMat()101 GMat* IRSolver::GetGMat()
102 {
103   return m_Gmat;
104 }
105 
106 //! Returns current map represented as a 1D vector
107 /*
108  * \return J vector
109  */
GetJ()110 vector<double> IRSolver::GetJ()
111 {
112   return m_J;
113 }
114 
115 //! Function to solve for voltage using SparseLU
SolveIR()116 void IRSolver::SolveIR()
117 {
118   if (!m_connection) {
119     m_logger->warn(utl::PSM,
120                    8,
121                    "Powergrid is not connected to all instances, therefore the "
122                    "IR Solver may not be accurate. LVS may also fail.");
123   }
124   int        unit_micron = (m_db->getTech())->getDbUnitsPerMicron();
125   clock_t    t1, t2;
126   CscMatrix* Gmat = m_Gmat->GetGMat();
127   // fill A
128   int                       nnz     = Gmat->nnz;
129   int                       m       = Gmat->num_rows;
130   int                       n       = Gmat->num_cols;
131   double*                   values  = &(Gmat->values[0]);
132   int*                      row_idx = &(Gmat->row_idx[0]);
133   int*                      col_ptr = &(Gmat->col_ptr[0]);
134   Map<SparseMatrix<double>> A(Gmat->num_rows,
135                               Gmat->num_cols,
136                               Gmat->nnz,
137                               col_ptr,  // read-write
138                               row_idx,
139                               values);
140 
141   vector<double>                 J = GetJ();
142   Map<VectorXd>                  b(J.data(), J.size());
143   VectorXd                       x;
144   SparseLU<SparseMatrix<double>> solver;
145   debugPrint(m_logger, utl::PSM, "IR Solver", 1, "Factorizing the G matrix");
146   solver.compute(A);
147   if (solver.info() != Success) {
148     // decomposition failed
149     m_logger->error(utl::PSM, 10, "LU factorization of the G Matrix failed.");
150   }
151   debugPrint(m_logger, utl::PSM, "IR Solver", 1, "Solving system of equations GV=J");
152   x = solver.solve(b);
153   if (solver.info() != Success) {
154     // solving failed
155     m_logger->error(utl::PSM, 12, "Solving V = inv(G)*J failed.");
156   } else {
157     debugPrint(m_logger, utl::PSM, "IR Solver", 1, "Solving system of equations GV=J complete");
158   }
159   ofstream ir_report;
160   ir_report.open(m_out_file);
161   ir_report << "Instance name, "
162             << " X location, "
163             << " Y location, "
164             << " Voltage "
165             << "\n";
166   int    num_nodes = m_Gmat->GetNumNodes();
167   int    node_num  = 0;
168   double sum_volt  = 0;
169   wc_voltage       = supply_voltage_src;
170   while (node_num < num_nodes) {
171     Node*  node = m_Gmat->GetNode(node_num);
172     double volt = x(node_num);
173     sum_volt    = sum_volt + volt;
174     if (m_power_net_type == dbSigType::POWER) {
175       if (volt < wc_voltage) {
176         wc_voltage = volt;
177       }
178     } else {
179       if (volt > wc_voltage) {
180         wc_voltage = volt;
181       }
182     }
183     node->SetVoltage(volt);
184     node_num++;
185     if (node->HasInstances()) {
186       NodeLoc         node_loc = node->GetLoc();
187       float           loc_x = ((float) node_loc.first) / ((float) unit_micron);
188       float           loc_y = ((float) node_loc.second) / ((float) unit_micron);
189       vector<dbInst*> insts = node->GetInstances();
190       vector<dbInst*>::iterator inst_it;
191       if (m_out_file != "") {
192         for (inst_it = insts.begin(); inst_it != insts.end(); inst_it++) {
193           ir_report << (*inst_it)->getName() << ", " << loc_x << ", " << loc_y
194                     << ", " << setprecision(6) << volt << "\n";
195         }
196       }
197     }
198   }
199   ir_report << endl;
200   ir_report.close();
201   avg_voltage = sum_volt / num_nodes;
202   if (m_em_flag == 1) {
203     map<GMatLoc, double>::iterator it;
204     DokMatrix*                     Gmat_dok          = m_Gmat->GetGMatDOK();
205     int                            resistance_number = 0;
206     max_cur                                          = 0;
207     double   sum_cur                                 = 0;
208     ofstream em_report;
209     if (m_em_out_file != "") {
210       em_report.open(m_em_out_file);
211       em_report << "Segment name, "
212                 << " Current, "
213                 << " Node 1, "
214                 << " Node 2 "
215                 << "\n";
216     }
217     NodeLoc node_loc;
218     for (it = Gmat_dok->values.begin(); it != Gmat_dok->values.end(); it++) {
219       NodeIdx col = (it->first).first;
220       NodeIdx row = (it->first).second;
221       if (col <= row) {
222         continue;  // ignore lower half and diagonal as matrix is symmetric
223       }
224       double cond = it->second;  // get cond value
225       if (abs(cond) < 1e-15) {   // ignore if an empty cell
226         continue;
227       }
228       string net_name = m_power_net;
229       if (col < num_nodes) {  // resistances
230         double resistance = -1 / cond;
231 
232         Node* node1       = m_Gmat->GetNode(col);
233         Node* node2       = m_Gmat->GetNode(row);
234         node_loc          = node1->GetLoc();
235         int    x1         = node_loc.first;
236         int    y1         = node_loc.second;
237         int    l1         = node1->GetLayerNum();
238         string node1_name = net_name + "_" + to_string(x1) + "_" + to_string(y1)
239                             + "_" + to_string(l1);
240 
241         node_loc          = node2->GetLoc();
242         int    x2         = node_loc.first;
243         int    y2         = node_loc.second;
244         int    l2         = node2->GetLayerNum();
245         string node2_name = net_name + "_" + to_string(x2) + "_" + to_string(y2)
246                             + "_" + to_string(l2);
247 
248         string segment_name = "seg_" + to_string(resistance_number);
249 
250         double v1 = node1->GetVoltage();
251         double v2 = node2->GetVoltage();
252         double seg_cur;
253         seg_cur = (v1 - v2) / resistance;
254         sum_cur += abs(seg_cur);
255         if (m_em_out_file != "") {
256           em_report << segment_name << ", " << setprecision(3) << seg_cur
257                     << ", " << node1_name << ", " << node2_name << endl;
258         }
259         seg_cur = abs(seg_cur);
260         if (seg_cur > max_cur) {
261           max_cur = seg_cur;
262         }
263         resistance_number++;
264       }
265     }  // for gmat values
266     avg_cur = sum_cur / resistance_number;
267     num_res = resistance_number;
268 
269   }  // enable em
270 }
271 
272 //! Function to add C4 bumps to the G matrix
AddC4Bump()273 bool IRSolver::AddC4Bump()
274 {
275   if (m_C4Bumps.size() == 0) {
276     m_logger->error(utl::PSM, 14, "Number of voltage sources cannot be 0.");
277   }
278   m_logger->info(utl::PSM, 64, "Number of voltage sources = {}",m_C4Bumps.size());
279   for (size_t it = 0; it < m_C4Nodes.size(); ++it) {
280     NodeIdx node_loc      = m_C4Nodes[it].first;
281     double  voltage_value = m_C4Nodes[it].second;
282     Node*        c4_node = m_Gmat->GetNode(node_loc);
283     m_Gmat->AddC4Bump(node_loc, it);  // add the 0th bump
284     m_J.push_back(voltage_value);     // push back first vdd
285     NodeLoc c4_node_loc = c4_node->GetLoc();
286   }
287   return true;
288 }
289 
290 //! Function that parses the Vsrc file
ReadC4Data()291 void IRSolver::ReadC4Data()
292 {
293   int unit_micron = (m_db->getTech())->getDbUnitsPerMicron();
294   if (m_vsrc_file != "") {
295     m_logger->info(utl::PSM,
296                    15,
297                    "Reading location of VDD and VSS sources from {}.",
298                    m_vsrc_file);
299     ifstream file(m_vsrc_file);
300     string   line = "";
301     // Iterate through each line and split the content using delimiter
302     while (getline(file, line)) {
303       tuple<int, int, int, double> c4_bump;
304       int                          first, second, size;
305       stringstream                 X(line);
306       string                       val;
307       for (int i = 0; i < 4; ++i) {
308         getline(X, val, ',');
309         if (i == 0) {
310           first = (int) (unit_micron * stod(val));
311         } else if (i == 1) {
312           second = (int) (unit_micron * stod(val));
313         } else if (i == 2) {
314           size = (int) (unit_micron * stod(val));
315         } else {
316           supply_voltage_src = stod(val);
317         }
318       }
319       m_C4Bumps.push_back(make_tuple(first, second, size, supply_voltage_src));
320     }
321     file.close();
322   } else {
323     m_logger->warn(utl::PSM,
324                    16,
325                    "Voltage pad location (vsrc) file not specified, defaulting "
326                    "pad location to checkerboard pattern on core area.");
327     dbChip*   chip  = m_db->getChip();
328     dbBlock*  block = chip->getBlock();
329     odb::Rect coreRect;
330     block->getCoreArea(coreRect);
331     int       coreW = coreRect.xMax() - coreRect.xMin();
332     int       coreL = coreRect.yMax() - coreRect.yMin();
333     odb::Rect dieRect;
334     block->getDieArea(dieRect);
335     int dieW     = dieRect.xMax() - dieRect.xMin();
336     int dieL     = dieRect.xMax() - dieRect.xMin();
337     int offset_x = coreRect.xMin() - dieRect.xMin();
338     int offset_y = coreRect.yMin() - dieRect.yMin();
339     if (m_bump_pitch_x == 0) {
340       m_bump_pitch_x = m_bump_pitch_default * unit_micron;
341       m_logger->warn(
342           utl::PSM,
343           17,
344           "X direction bump pitch is not specified, defaulting to {}um.",
345           m_bump_pitch_default);
346     }
347     if (m_bump_pitch_y == 0) {
348       m_bump_pitch_y = m_bump_pitch_default * unit_micron;
349       m_logger->warn(
350           utl::PSM,
351           18,
352           "Y direction bump pitch is not specified, defaulting to {}um.",
353           m_bump_pitch_default);
354     }
355     if (!m_net_voltage_map.empty()
356         && m_net_voltage_map.count(m_power_net) > 0) {
357       supply_voltage_src = m_net_voltage_map.at(m_power_net);
358     } else {
359       m_logger->warn(utl::PSM,
360                      19,
361                      "Voltage on net {} is not explicitly set.",
362                      m_power_net);
363       pair<double, double> supply_voltages = GetSupplyVoltage();
364       dbNet*               power_net       = block->findNet(m_power_net.data());
365       if (power_net == NULL) {
366         m_logger->error(utl::PSM,
367                         20,
368                         "Cannot find net {} in the design. Please provide a "
369                         "valid VDD/VSS net.",
370                         m_power_net);
371       }
372       m_power_net_type = power_net->getSigType();
373       if (m_power_net_type == dbSigType::GROUND) {
374         supply_voltage_src = supply_voltages.second;
375         m_logger->warn(utl::PSM,
376                        21,
377                        "Using voltage {:4.3f}V for ground network.",
378                        supply_voltage_src);
379       } else {
380         supply_voltage_src = supply_voltages.first;
381         m_logger->warn(utl::PSM,
382                        22,
383                        "Using voltage {:4.3f}V for VDD network.",
384                        supply_voltage_src);
385       }
386     }
387     int x_cor, y_cor;
388     if (coreW < m_bump_pitch_x || coreL < m_bump_pitch_y) {
389       float to_micron = 1.0f/unit_micron;
390       m_logger->warn(utl::PSM, 63, "Specified bump pitches of {:4.3f} and {:4.3f} are less than core width of {:4.3f} or core height of "
391            "{:4.3f}. Changing bump location to the center of the die at ({:4.3f}, {:4.3f})",
392            m_bump_pitch_x * to_micron, m_bump_pitch_y * to_micron,
393            coreW * to_micron, coreL * to_micron, (coreW * to_micron)/2, (coreL * to_micron)/2);
394       x_cor = coreW/2;
395       y_cor = coreL/2;
396       m_C4Bumps.push_back(make_tuple(x_cor, y_cor, m_bump_size * unit_micron, supply_voltage_src));
397     }
398     int num_b_x = coreW / m_bump_pitch_x;
399     int num_b_y = coreL / m_bump_pitch_y;
400     m_logger->warn(utl::PSM,
401                    65,
402                    "VSRC location not specified using default checkerboard pattern with one VDD every"
403                    "size bumps in x-direction and one in two bumps in the y-direction");
404     for (int i = 0; i < num_b_y; i++) {
405       for (int j = 0; j < num_b_x; j=j+6) {
406         x_cor = (m_bump_pitch_x * j) + (((2 * i) % 6) * m_bump_pitch_x)
407                 + offset_x;
408         y_cor = (m_bump_pitch_y * i) + offset_y;
409         if (x_cor <= coreW && y_cor <= coreL) {
410           m_C4Bumps.push_back(make_tuple(
411               x_cor, y_cor, m_bump_size * unit_micron, supply_voltage_src));
412         }
413       }
414     }
415   }
416 }
417 
418 //! Function to create a J vector from the current map
CreateJ()419 bool IRSolver::CreateJ()
420 {  // take current_map as an input?
421   int num_nodes = m_Gmat->GetNumNodes();
422   m_J.resize(num_nodes, 0);
423 
424   vector<pair<string, double>> power_report = GetPower();
425   dbChip*                      chip         = m_db->getChip();
426   dbBlock*                     block        = chip->getBlock();
427   for (vector<pair<string, double>>::iterator it = power_report.begin();
428        it != power_report.end();
429        ++it) {
430     dbInst* inst = block->findInst(it->first.c_str());
431     if (inst == NULL) {
432       m_logger->warn(
433           utl::PSM, 23, "Instance {} not found within database.", it->first);
434       continue;
435     }
436     int x, y;
437     inst->getLocation(x, y);
438     // cout << "Got location" <<endl;
439     int     l      = m_bottom_layer;  // atach to the bottom most routing layer
440     Node*   node_J = m_Gmat->GetNode(x, y, l, true);
441     NodeLoc node_loc = node_J->GetLoc();
442     if (abs(node_loc.first - x) > m_node_density
443         || abs(node_loc.second - y) > m_node_density) {
444       m_logger->warn(utl::PSM,
445                      24,
446                      "Instance {} curent node at ({}, {}) at layer {} have "
447                      "been moved from ({}, {}).",
448                      it->first,
449                      node_loc.first,
450                      node_loc.second,
451                      l,
452                      x,
453                      y);
454     }
455     // Both these lines will change in the future for multiple power domains
456     node_J->AddCurrentSrc(it->second);
457     node_J->AddInstance(inst);
458   }
459   for (int i = 0; i < num_nodes; ++i) {
460     Node* node_J = m_Gmat->GetNode(i);
461     if (m_power_net_type == dbSigType::GROUND) {
462       m_J[i] = (node_J->GetCurrent());
463     } else {
464       m_J[i] = -1 * (node_J->GetCurrent());
465     }
466   }
467   debugPrint(m_logger, utl::PSM, "IR Solver", 1, "Created J vector");
468   //m_logger->info(utl::PSM, 25, "Created J vector");
469   return true;
470 }
471 
472 //! Function to create a G matrix using the nodes
CreateGmat(bool connection_only)473 bool IRSolver::CreateGmat(bool connection_only)
474 {
475   debugPrint(m_logger, utl::PSM, "G Matrix", 1, "Creating G matrix");
476   vector<Node*> node_vector;
477   dbTech*       tech = m_db->getTech();
478   // dbSet<dbTechLayer>           layers = tech->getLayers();
479   dbSet<dbTechLayer>::iterator litr;
480   int                          unit_micron = tech->getDbUnitsPerMicron();
481   int num_routing_layers                   = tech->getRoutingLayerCount();
482 
483   m_Gmat                 = new GMat(num_routing_layers, m_logger);
484   dbChip*      chip      = m_db->getChip();
485   dbBlock*     block     = chip->getBlock();
486   dbSet<dbNet> nets      = block->getNets();
487   dbNet*       power_net = block->findNet(m_power_net.data());
488   if (power_net == NULL) {
489     m_logger->error(utl::PSM,
490                     27,
491                     " Cannot find net {} in the design. Please provide a valid "
492                     "VDD/VSS net.",
493                     m_power_net);
494   }
495   m_power_net_type = power_net->getSigType();
496   vector<dbNet*> power_nets;
497   int            num_wires = 0;
498   debugPrint(m_logger, utl::PSM, "G Matrix", 1, "Extracting power stripes on net {}", power_net->getName());
499   power_nets.push_back(power_net);
500 
501   if (power_nets.size() == 0) {
502     m_logger->error(
503         utl::PSM,
504         29,
505         "No power stripes found in design. Power grid checker will not run.");
506   }
507   vector<dbNet*>::iterator vIter;
508   for (vIter = power_nets.begin(); vIter != power_nets.end(); ++vIter) {
509     dbNet*                   curDnet = *vIter;
510     dbSet<dbSWire>           swires  = curDnet->getSWires();
511     dbSet<dbSWire>::iterator sIter;
512     for (sIter = swires.begin(); sIter != swires.end(); ++sIter) {
513       dbSWire*                curSWire = *sIter;
514       dbSet<dbSBox>           wires    = curSWire->getWires();
515       dbSet<dbSBox>::iterator wIter;
516       for (wIter = wires.begin(); wIter != wires.end(); ++wIter) {
517         num_wires++;
518         dbSBox*               curWire = *wIter;
519         int                   l;
520         dbTechLayerDir::Value layer_dir;
521         if (curWire->isVia()) {
522           dbVia*       via       = curWire->getBlockVia();
523           dbTechLayer* via_layer = via->getTopLayer();
524           l                      = via_layer->getRoutingLevel();
525           layer_dir              = via_layer->getDirection();
526         } else {
527           dbTechLayer* wire_layer = curWire->getTechLayer();
528           l                       = wire_layer->getRoutingLevel();
529           layer_dir               = wire_layer->getDirection();
530           if (l < m_bottom_layer) {
531             m_bottom_layer     = l;
532             m_bottom_layer_dir = layer_dir;
533           }
534         }
535         if (l > m_top_layer) {
536           m_top_layer     = l;
537           m_top_layer_dir = layer_dir;
538         }
539       }
540     }
541   }
542   for (vIter = power_nets.begin(); vIter != power_nets.end(); ++vIter) {
543     dbNet*                   curDnet = *vIter;
544     dbSet<dbSWire>           swires  = curDnet->getSWires();
545     dbSet<dbSWire>::iterator sIter;
546     for (sIter = swires.begin(); sIter != swires.end(); ++sIter) {
547       dbSWire*                curSWire = *sIter;
548       dbSet<dbSBox>           wires    = curSWire->getWires();
549       dbSet<dbSBox>::iterator wIter;
550       for (wIter = wires.begin(); wIter != wires.end(); ++wIter) {
551         dbSBox* curWire = *wIter;
552         if (curWire->isVia()) {
553           dbVia* via                = curWire->getBlockVia();
554           dbBox* via_bBox           = via->getBBox();
555           int    check_params       = via->hasParams();
556           int    x_cut_size         = 0;
557           int    y_cut_size         = 0;
558           int    x_bottom_enclosure = 0;
559           int    y_bottom_enclosure = 0;
560           int    x_top_enclosure    = 0;
561           int    y_top_enclosure    = 0;
562           if (check_params == 1) {
563             dbViaParams params;
564             via->getViaParams(params);
565             x_cut_size         = params.getXCutSize();
566             y_cut_size         = params.getYCutSize();
567             x_bottom_enclosure = params.getXBottomEnclosure();
568             y_bottom_enclosure = params.getYBottomEnclosure();
569             x_top_enclosure    = params.getXTopEnclosure();
570             y_top_enclosure    = params.getYTopEnclosure();
571           }
572           BBox bBox
573               = make_pair((via_bBox->getDX()) / 2, (via_bBox->getDY()) / 2);
574           int x, y;
575           curWire->getViaXY(x, y);
576           dbTechLayer*          via_layer = via->getBottomLayer();
577           dbTechLayerDir::Value layer_dir = via_layer->getDirection();
578           int                   l         = via_layer->getRoutingLevel();
579           int                   x_loc1, x_loc2, y_loc1, y_loc2;
580           if (m_bottom_layer != l
581               && l != m_top_layer) {  // do not set for top and bottom layers
582             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
583               y_loc1 = y;
584               y_loc2 = y;
585               x_loc1 = x - (x_bottom_enclosure + x_cut_size / 2);
586               x_loc2 = x + (x_bottom_enclosure + x_cut_size / 2);
587             } else {
588               y_loc1 = y - (y_bottom_enclosure + y_cut_size / 2);
589               y_loc2 = y + (y_bottom_enclosure + y_cut_size / 2);
590               x_loc1 = x;
591               x_loc2 = x;
592             }
593             m_Gmat->SetNode(x_loc1, y_loc1, l, make_pair(0, 0));
594             m_Gmat->SetNode(x_loc2, y_loc2, l, make_pair(0, 0));
595             m_Gmat->SetNode(x, y, l, bBox);
596           }
597           via_layer = via->getTopLayer();
598           l         = via_layer->getRoutingLevel();
599 
600           // TODO this may count the stripe conductance twice but is needed to
601           // fix a staggered stacked via
602           layer_dir = via_layer->getDirection();
603           if (m_bottom_layer != l
604               && l != m_top_layer) {  // do not set for top and bottom layers
605             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
606               y_loc1 = y;
607               y_loc2 = y;
608               x_loc1 = x - (x_top_enclosure + x_cut_size / 2);
609               x_loc2 = x + (x_top_enclosure + x_cut_size / 2);
610             } else {
611               y_loc1 = y - (y_top_enclosure + y_cut_size / 2);
612               y_loc2 = y + (y_top_enclosure + y_cut_size / 2);
613               x_loc1 = x;
614               x_loc2 = x;
615             }
616             m_Gmat->SetNode(x_loc1, y_loc1, l, make_pair(0, 0));
617             m_Gmat->SetNode(x_loc2, y_loc2, l, make_pair(0, 0));
618             m_Gmat->SetNode(x, y, l, bBox);
619           }
620         } else {
621           int                   x_loc1, x_loc2, y_loc1, y_loc2;
622           dbTechLayer*          wire_layer = curWire->getTechLayer();
623           int                   l          = wire_layer->getRoutingLevel();
624           dbTechLayerDir::Value layer_dir  = wire_layer->getDirection();
625           if (l == m_bottom_layer) {
626             layer_dir = dbTechLayerDir::Value::HORIZONTAL;
627           }
628           if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
629             y_loc1 = (curWire->yMin() + curWire->yMax()) / 2;
630             y_loc2 = (curWire->yMin() + curWire->yMax()) / 2;
631             x_loc1 = curWire->xMin();
632             x_loc2 = curWire->xMax();
633           } else {
634             x_loc1 = (curWire->xMin() + curWire->xMax()) / 2;
635             x_loc2 = (curWire->xMin() + curWire->xMax()) / 2;
636             y_loc1 = curWire->yMin();
637             y_loc2 = curWire->yMax();
638           }
639           if (l == m_bottom_layer
640               || l == m_top_layer) {  // special case for bottom and top layers
641                                       // we design a dense grid
642             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
643               int x_i;
644               x_loc1 = (x_loc1 / m_node_density)
645                        * m_node_density;  // quantize the horizontal direction
646               x_loc2 = (x_loc2 / m_node_density)
647                        * m_node_density;  // quantize the horizontal direction
648               for (x_i = x_loc1; x_i <= x_loc2; x_i = x_i + m_node_density) {
649                 m_Gmat->SetNode(x_i, y_loc1, l, make_pair(0, 0));
650               }
651             } else {
652               y_loc1 = (y_loc1 / m_node_density)
653                        * m_node_density;  // quantize the vertical direction
654               y_loc2 = (y_loc2 / m_node_density)
655                        * m_node_density;  // quantize the vertical direction
656               int y_i;
657               for (y_i = y_loc1; y_i <= y_loc2; y_i = y_i + m_node_density) {
658                 m_Gmat->SetNode(x_loc1, y_i, l, make_pair(0, 0));
659               }
660             }
661           } else {  // add end nodes
662             m_Gmat->SetNode(x_loc1, y_loc1, l, make_pair(0, 0));
663             m_Gmat->SetNode(x_loc2, y_loc2, l, make_pair(0, 0));
664           }
665         }
666       }
667     }
668   }
669   // insert c4 bumps as nodes
670   int num_C4 = 0;
671   for (size_t it = 0; it < m_C4Bumps.size(); ++it) {
672     int           x    = get<0>(m_C4Bumps[it]);
673     int           y    = get<1>(m_C4Bumps[it]);
674     int           size = get<2>(m_C4Bumps[it]);
675     double        v    = get<3>(m_C4Bumps[it]);
676     vector<Node*> RDL_nodes;
677     RDL_nodes = m_Gmat->GetRDLNodes(m_top_layer,
678                                     m_top_layer_dir,
679                                     x - size / 2,
680                                     x + size / 2,
681                                     y - size / 2,
682                                     y + size / 2);
683     if (RDL_nodes.empty() == true) {
684       Node*   node     = m_Gmat->GetNode(x, y, m_top_layer, true);
685       NodeLoc node_loc = node->GetLoc();
686       double  new_loc1 = ((double) node_loc.first) / ((double) unit_micron);
687       double  new_loc2 = ((double) node_loc.second) / ((double) unit_micron);
688       double  old_loc1 = ((double) x) / ((double) unit_micron);
689       double  old_loc2 = ((double) y) / ((double) unit_micron);
690       double  old_size = ((double) size) / ((double) unit_micron);
691       m_logger->warn(utl::PSM,
692                      30,
693                      "Vsrc location at ({:4.3f}um, {:4.3f}um) and size ={:4.3f}um, is not located on a power stripe. Moving to closest stripe at ({:4.3f}um, {:4.3f}um).",
694                      old_loc1,
695                      old_loc2,
696                      old_size,
697                      new_loc1,
698                      new_loc2);
699       RDL_nodes = m_Gmat->GetRDLNodes(m_top_layer,
700                                       m_top_layer_dir,
701                                       node_loc.first - size / 2,
702                                       node_loc.first + size / 2,
703                                       node_loc.second - size / 2,
704                                       node_loc.second + size / 2);
705     }
706     vector<Node*>::iterator node_it;
707     for (node_it = RDL_nodes.begin(); node_it != RDL_nodes.end(); ++node_it) {
708       Node* node = *node_it;
709       m_C4Nodes.push_back(make_pair(node->GetGLoc(), v));
710       num_C4++;
711     }
712   }
713   // All new nodes must be inserted by this point
714   // initialize G Matrix
715   m_logger->info(utl::PSM,
716                  31,
717                  "Number of PDN nodes on net {} = {}.",
718                  m_power_net,
719                  m_Gmat->GetNumNodes());
720   m_Gmat->InitializeGmatDok(num_C4);
721   for (vIter = power_nets.begin(); vIter != power_nets.end();
722        ++vIter) {  // only 1 is expected?
723     dbNet*                   curDnet = *vIter;
724     dbSet<dbSWire>           swires  = curDnet->getSWires();
725     dbSet<dbSWire>::iterator sIter;
726     for (sIter = swires.begin(); sIter != swires.end();
727          ++sIter) {  // only 1 is expected?
728       dbSWire*                curSWire = *sIter;
729       dbSet<dbSBox>           wires    = curSWire->getWires();
730       dbSet<dbSBox>::iterator wIter;
731       for (wIter = wires.begin(); wIter != wires.end(); ++wIter) {
732         dbSBox* curWire = *wIter;
733         if (curWire->isVia()) {
734           dbVia* via                = curWire->getBlockVia();
735           int    num_via_rows       = 1;
736           int    num_via_cols       = 1;
737           int    check_params       = via->hasParams();
738           int    x_cut_size         = 0;
739           int    y_cut_size         = 0;
740           int    x_bottom_enclosure = 0;
741           int    y_bottom_enclosure = 0;
742           int    x_top_enclosure    = 0;
743           int    y_top_enclosure    = 0;
744           if (check_params == 1) {
745             dbViaParams params;
746             via->getViaParams(params);
747             num_via_rows       = params.getNumCutRows();
748             num_via_cols       = params.getNumCutCols();
749             x_cut_size         = params.getXCutSize();
750             y_cut_size         = params.getYCutSize();
751             x_bottom_enclosure = params.getXBottomEnclosure();
752             y_bottom_enclosure = params.getYBottomEnclosure();
753             x_top_enclosure    = params.getXTopEnclosure();
754             y_top_enclosure    = params.getYTopEnclosure();
755           }
756           dbBox* via_bBox = via->getBBox();
757           BBox   bBox
758               = make_pair((via_bBox->getDX()) / 2, (via_bBox->getDY()) / 2);
759           int x, y;
760           curWire->getViaXY(x, y);
761           dbTechLayer* via_layer = via->getBottomLayer();
762           int          l         = via_layer->getRoutingLevel();
763 
764           double R = via_layer->getUpperLayer()->getResistance();
765           R        = R / (num_via_rows * num_via_cols);
766           if (!CheckValidR(R) && !connection_only) {
767             m_logger->error(utl::PSM,
768                 35, "{} resistance not found in DB. Check the LEF or set it with set_layer_rc command.", via_layer->getName());
769           }
770           bool    top_or_bottom = ((l == m_bottom_layer) || (l == m_top_layer));
771           Node*   node_bot      = m_Gmat->GetNode(x, y, l, top_or_bottom);
772           NodeLoc node_loc      = node_bot->GetLoc();
773           if (abs(node_loc.first - x) > m_node_density
774               || abs(node_loc.second - y) > m_node_density) {
775             m_logger->warn(utl::PSM,
776                            32,
777                            "Node at ({}, {}) and layer {} moved from ({}, {}).",
778                            node_loc.first,
779                            node_loc.second,
780                            l,
781                            x,
782                            y);
783           }
784 
785           via_layer      = via->getTopLayer();
786           l              = via_layer->getRoutingLevel();
787           top_or_bottom  = ((l == m_bottom_layer) || (l == m_top_layer));
788           Node* node_top = m_Gmat->GetNode(x, y, l, top_or_bottom);
789           node_loc       = node_top->GetLoc();
790           if (abs(node_loc.first - x) > m_node_density
791               || abs(node_loc.second - y) > m_node_density) {
792             m_logger->warn(utl::PSM,
793                            33,
794                            "Node at ({}, {}) and layer {} moved from ({}, {}).",
795                            node_loc.first,
796                            node_loc.second,
797                            l,
798                            x,
799                            y);
800           }
801 
802           if (node_bot == nullptr || node_top == nullptr) {
803             m_logger->error(
804                 utl::PSM,
805                 34,
806                 "Unxpected condition. Null pointer received for node.");
807           } else {
808             if (R <= 1e-12) {  // if the resitance was not set.
809               m_Gmat->SetConductance(node_bot, node_top, 0);
810             } else {
811               m_Gmat->SetConductance(node_bot, node_top, 1 / R);
812             }
813           }
814 
815           via_layer                       = via->getBottomLayer();
816           dbTechLayerDir::Value layer_dir = via_layer->getDirection();
817           l                               = via_layer->getRoutingLevel();
818           if (l != m_bottom_layer) {
819             double rho = via_layer->getResistance();
820             if (!CheckValidR(rho) && !connection_only) {
821               m_logger->error( utl::PSM,
822                     36, "Layer {} per-unit resistance not found in DB. Check the LEF or set it with a set_layer_rc -layer command.",via_layer->getName());
823             }
824             int x_loc1, x_loc2, y_loc1, y_loc2;
825             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
826               y_loc1 = y - y_cut_size / 2;
827               y_loc2 = y + y_cut_size / 2;
828               x_loc1 = x - (x_bottom_enclosure + x_cut_size / 2);
829               x_loc2 = x + (x_bottom_enclosure + x_cut_size / 2);
830             } else {
831               y_loc1 = y - (y_bottom_enclosure + y_cut_size / 2);
832               y_loc2 = y + (y_bottom_enclosure + y_cut_size / 2);
833               x_loc1 = x - x_cut_size / 2;
834               x_loc2 = x + x_cut_size / 2;
835             }
836             m_Gmat->GenerateStripeConductance(via_layer->getRoutingLevel(),
837                                               layer_dir,
838                                               x_loc1,
839                                               x_loc2,
840                                               y_loc1,
841                                               y_loc2,
842                                               rho);
843           }
844           via_layer = via->getTopLayer();
845           layer_dir = via_layer->getDirection();
846           l         = via_layer->getRoutingLevel();
847           if (l != m_top_layer) {
848             double rho = via_layer->getResistance();
849             if (!CheckValidR(rho) && !connection_only) {
850               m_logger->error( utl::PSM,
851                     37, "Layer {} per-unit resistance not found in DB. Check the LEF or set it with a set_layer_rc -layer command.",via_layer->getName());
852             }
853             int x_loc1, x_loc2, y_loc1, y_loc2;
854             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
855               y_loc1 = y - y_cut_size / 2;
856               y_loc2 = y + y_cut_size / 2;
857               x_loc1 = x - (x_top_enclosure + x_cut_size / 2);
858               x_loc2 = x + (x_top_enclosure + x_cut_size / 2);
859             } else {
860               y_loc1 = y - (y_top_enclosure + y_cut_size / 2);
861               y_loc2 = y + (y_top_enclosure + y_cut_size / 2);
862               x_loc1 = x - x_cut_size / 2;
863               x_loc2 = x + x_cut_size / 2;
864             }
865             m_Gmat->GenerateStripeConductance(via_layer->getRoutingLevel(),
866                                               layer_dir,
867                                               x_loc1,
868                                               x_loc2,
869                                               y_loc1,
870                                               y_loc2,
871                                               rho);
872           }
873 
874         } else {
875           dbTechLayer* wire_layer = curWire->getTechLayer();
876           int          l          = wire_layer->getRoutingLevel();
877           double       rho        = wire_layer->getResistance();
878           if (!CheckValidR(rho) && !connection_only) {
879             m_logger->error( utl::PSM,
880                   66, "Layer {} per-unit resistance not found in DB. Check the LEF or set it with a set_layer_rc -layer command.",wire_layer->getName());
881           }
882           dbTechLayerDir::Value layer_dir = wire_layer->getDirection();
883           if (l == m_bottom_layer) {  // ensure that the bootom layer(rail) is
884                                       // horizontal
885             layer_dir = dbTechLayerDir::Value::HORIZONTAL;
886           }
887           int x_loc1 = curWire->xMin();
888           int x_loc2 = curWire->xMax();
889           int y_loc1 = curWire->yMin();
890           int y_loc2 = curWire->yMax();
891           if (l == m_bottom_layer
892               || l == m_top_layer) {  // special case for bottom and top layers
893                                       // we design a dense grid
894             if (layer_dir == dbTechLayerDir::Value::HORIZONTAL) {
895               x_loc1 = (x_loc1 / m_node_density)
896                        * m_node_density;  // quantize the horizontal direction
897               x_loc2 = (x_loc2 / m_node_density)
898                        * m_node_density;  // quantize the horizontal direction
899             } else {
900               y_loc1 = (y_loc1 / m_node_density)
901                        * m_node_density;  // quantize the vertical direction
902               y_loc2 = (y_loc2 / m_node_density)
903                        * m_node_density;  // quantize the vertical direction
904             }
905           }
906           m_Gmat->GenerateStripeConductance(wire_layer->getRoutingLevel(),
907                                             layer_dir,
908                                             x_loc1,
909                                             x_loc2,
910                                             y_loc1,
911                                             y_loc2,
912                                             rho);
913         }
914       }
915     }
916   }
917   debugPrint(m_logger, utl::PSM, "G Matrix", 1, "G matrix created sucessfully.");
918   return true;
919 }
920 
921 
CheckValidR(double R)922 bool IRSolver::CheckValidR(double R) {
923   return R>=1e-12;
924 }
925 
CheckConnectivity()926 bool IRSolver::CheckConnectivity()
927 {
928   vector<pair<NodeIdx, double>>::iterator c4_node_it;
929   int                                     x, y;
930   CscMatrix*                              Amat      = m_Gmat->GetAMat();
931   int                                     num_nodes = m_Gmat->GetNumNodes();
932 
933   dbTech* tech        = m_db->getTech();
934   int     unit_micron = tech->getDbUnitsPerMicron();
935 
936   for (c4_node_it = m_C4Nodes.begin(); c4_node_it != m_C4Nodes.end();
937        c4_node_it++) {
938     Node*        c4_node = m_Gmat->GetNode((*c4_node_it).first);
939     queue<Node*> node_q;
940     node_q.push(c4_node);
941     while (!node_q.empty()) {
942       NodeIdx col_loc, n_col_loc;
943       Node*   node = node_q.front();
944       node_q.pop();
945       node->SetConnected();
946       NodeIdx col_num = node->GetGLoc();
947       col_loc         = Amat->col_ptr[col_num];
948       if (col_num < Amat->col_ptr.size() - 1) {
949         n_col_loc = Amat->col_ptr[col_num + 1];
950       } else {
951         n_col_loc = Amat->row_idx.size();
952       }
953       vector<NodeIdx> col_vec(Amat->row_idx.begin() + col_loc,
954                               Amat->row_idx.begin() + n_col_loc);
955 
956       vector<NodeIdx>::iterator col_vec_it;
957       for (col_vec_it = col_vec.begin(); col_vec_it != col_vec.end();
958            col_vec_it++) {
959         if (*col_vec_it < num_nodes) {
960           Node* node_next = m_Gmat->GetNode(*col_vec_it);
961           if (!(node_next->GetConnected())) {
962             node_q.push(node_next);
963           }
964         }
965       }
966     }
967   }
968   int                     uncon_err_cnt   = 0;
969   int                     uncon_err_flag  = 0;
970   int                     uncon_inst_cnt  = 0;
971   int                     uncon_inst_flag = 0;
972   vector<Node*>           node_list       = m_Gmat->GetAllNodes();
973   vector<Node*>::iterator node_list_it;
974   bool                    unconnected_node = false;
975   for (node_list_it = node_list.begin(); node_list_it != node_list.end();
976        node_list_it++) {
977     if (!(*node_list_it)->GetConnected()) {
978       uncon_err_cnt++;
979       NodeLoc node_loc = (*node_list_it)->GetLoc();
980       float   loc_x    = ((float) node_loc.first) / ((float) unit_micron);
981       float   loc_y    = ((float) node_loc.second) / ((float) unit_micron);
982       unconnected_node = true;
983       m_logger->warn(utl::PSM, 38, "Unconnected PDN node on net {} at location ({:4.3f}um, {:4.3f}um), layer: {}.",
984                      m_power_net, loc_x, loc_y,(*node_list_it)->GetLayerNum());
985       if ((*node_list_it)->HasInstances()) {
986         vector<dbInst*>           insts = (*node_list_it)->GetInstances();
987         vector<dbInst*>::iterator inst_it;
988         for (inst_it = insts.begin(); inst_it != insts.end(); inst_it++) {
989           uncon_inst_cnt++;
990           m_logger->warn(utl::PSM, 39,"Unconnected Instance {} at location ({:4.3f}um, {:4.3f}um) layer: {}.",
991             (*inst_it)->getName(), loc_x, loc_y, (*node_list_it)->GetLayerNum());
992         }
993       }
994     }
995   }
996   if (unconnected_node == false) {
997     m_logger->info(utl::PSM, 40, "All PDN stripes on net {} are connected.", m_power_net);
998   }
999   return !unconnected_node;
1000 }
1001 
GetConnectionTest()1002 int IRSolver::GetConnectionTest()
1003 {
1004   if (m_connection) {
1005     return 1;
1006   } else {
1007     return 0;
1008   }
1009 }
1010 
1011 //! Function to get the power value from OpenSTA
1012 /*
1013  *\return vector of pairs of instance name
1014  and its corresponding power value
1015 */
GetPower()1016 vector<pair<string, double>> IRSolver::GetPower()
1017 {
1018   PowerInst power_inst;
1019 
1020   debugPrint(m_logger, utl::PSM, "IR Solver", 1, "Executing STA for power calculation");
1021   return power_inst.executePowerPerInst(m_sta, m_logger);
1022 }
1023 
GetSupplyVoltage()1024 pair<double, double> IRSolver::GetSupplyVoltage()
1025 {
1026   SupplyVoltage supply_volt;
1027   return supply_volt.getSupplyVoltage(m_sta);
1028 }
1029 
GetResult()1030 bool IRSolver::GetResult()
1031 {
1032   return m_result;
1033 }
1034 
PrintSpice()1035 int IRSolver::PrintSpice()
1036 {
1037   DokMatrix*                     Gmat = m_Gmat->GetGMatDOK();
1038   map<GMatLoc, double>::iterator it;
1039 
1040   ofstream pdnsim_spice_file;
1041   pdnsim_spice_file.open(m_spice_out_file);
1042   if (!pdnsim_spice_file.is_open()) {
1043     m_logger->error(
1044         utl::PSM,
1045         41,
1046         "Spice file {} did not open. Please check if it is a valid path.",
1047         m_spice_out_file);
1048   }
1049   vector<double> J                 = GetJ();
1050   int            num_nodes         = m_Gmat->GetNumNodes();
1051   int            resistance_number = 0;
1052   int            voltage_number    = 0;
1053   int            current_number    = 0;
1054 
1055   NodeLoc node_loc;
1056   for (it = Gmat->values.begin(); it != Gmat->values.end(); it++) {
1057     NodeIdx col = (it->first).first;
1058     NodeIdx row = (it->first).second;
1059     if (col <= row) {
1060       continue;  // ignore lower half and diagonal as matrix is symmetric
1061     }
1062     double cond = it->second;  // get cond value
1063     if (abs(cond) < 1e-15) {   // ignore if an empty cell
1064       continue;
1065     }
1066 
1067     string net_name = m_power_net;
1068     if (col < num_nodes) {  // resistances
1069       double resistance = -1 / cond;
1070 
1071       Node* node1       = m_Gmat->GetNode(col);
1072       Node* node2       = m_Gmat->GetNode(row);
1073       node_loc          = node1->GetLoc();
1074       int    x1         = node_loc.first;
1075       int    y1         = node_loc.second;
1076       int    l1         = node1->GetLayerNum();
1077       string node1_name = net_name + "_" + to_string(x1) + "_" + to_string(y1)
1078                           + "_" + to_string(l1);
1079 
1080       node_loc          = node2->GetLoc();
1081       int    x2         = node_loc.first;
1082       int    y2         = node_loc.second;
1083       int    l2         = node2->GetLayerNum();
1084       string node2_name = net_name + "_" + to_string(x2) + "_" + to_string(y2)
1085                           + "_" + to_string(l2);
1086 
1087       string resistance_name = "R" + to_string(resistance_number);
1088       resistance_number++;
1089 
1090       pdnsim_spice_file << resistance_name << " " << node1_name << " "
1091                         << node2_name << " " << to_string(resistance) << endl;
1092 
1093       double current      = node1->GetCurrent();
1094       string current_name = "I" + to_string(current_number);
1095       if (abs(current) > 1e-18) {
1096         pdnsim_spice_file << current_name << " " << node1_name << " " << 0
1097                           << " " << current << endl;
1098         current_number++;
1099       }
1100 
1101     } else {                                        // voltage
1102       Node* node1          = m_Gmat->GetNode(row);  // VDD location
1103       node_loc             = node1->GetLoc();
1104       double voltage_value = J[col];
1105       int    x1            = node_loc.first;
1106       int    y1            = node_loc.second;
1107       int    l1            = node1->GetLayerNum();
1108       string node1_name = net_name + "_" + to_string(x1) + "_" + to_string(y1)
1109                           + "_" + to_string(l1);
1110       string voltage_name = "V" + to_string(voltage_number);
1111       voltage_number++;
1112       pdnsim_spice_file << voltage_name << " " << node1_name << " 0 "
1113                         << to_string(voltage_value) << endl;
1114     }
1115   }
1116 
1117   pdnsim_spice_file << ".OPTION NUMDGT=6" << endl;
1118   pdnsim_spice_file << ".OP" << endl;
1119   pdnsim_spice_file << ".END" << endl;
1120   pdnsim_spice_file << endl;
1121   pdnsim_spice_file.close();
1122   return 1;
1123 }
1124 
Build()1125 bool IRSolver::Build()
1126 {
1127   bool res = true;
1128   ReadC4Data();
1129   if (res) {
1130     res = CreateGmat();
1131   }
1132   if (res) {
1133     res = CreateJ();
1134   }
1135   if (res) {
1136     res = AddC4Bump();
1137   }
1138   if (res) {
1139     res = m_Gmat->GenerateCSCMatrix();
1140     res = m_Gmat->GenerateACSCMatrix();
1141   }
1142   if (res) {
1143     m_connection = CheckConnectivity();
1144     res          = m_connection;
1145   }
1146   m_result = res;
1147   return m_result;
1148 }
1149 
BuildConnection()1150 bool IRSolver::BuildConnection()
1151 {
1152   bool res = true;
1153   ReadC4Data();
1154   if (res) {
1155     res = CreateGmat(true);
1156   }
1157   if (res) {
1158     res = AddC4Bump();
1159   }
1160   if (res) {
1161     res = m_Gmat->GenerateACSCMatrix();
1162   }
1163   if (res) {
1164     m_connection = CheckConnectivity();
1165     res          = m_connection;
1166   }
1167   m_result = res;
1168   return m_result;
1169 }
1170 }  // namespace psm
1171