Skip to content

Commit f5df008

Browse files
authored
Merge pull request #10794 from The-OpenROAD-Project-staging/tap_overlap
tap: prevent overlapping endcap edges in single-height rows between macros
2 parents e823004 + f643672 commit f5df008

12 files changed

Lines changed: 3917 additions & 39 deletions

src/tap/include/tap/tapcell.h

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,12 @@ class Tapcell
213213
int placeEndcapEdgeHorizontal(const Edge& edge,
214214
const CornerMap& corners,
215215
const EndcapCellOptions& options);
216+
int fillEndcapEdge(odb::dbRow* row,
217+
int x_start,
218+
int x_end,
219+
const std::vector<odb::dbMaster*>& masters,
220+
EdgeType edge_type,
221+
const std::string& prefix);
216222
int placeEndcapEdgeVertical(const Edge& edge,
217223
const CornerMap& corners,
218224
const EndcapCellOptions& options);
@@ -234,6 +240,11 @@ class Tapcell
234240
std::string tap_prefix_;
235241
std::string endcap_prefix_;
236242
std::vector<Edge> filled_edges_;
243+
// x-ranges already filled by horizontal endcap edges, per row.
244+
odb::PtrMap<odb::dbRow, std::vector<std::pair<int, int>>>
245+
filled_horizontal_edges_;
246+
// corner cells placed so far, per row, persisted across areas/holes.
247+
CornerMap placed_corners_;
237248
};
238249

239250
} // namespace tap

src/tap/src/tapcell.cpp

Lines changed: 132 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -238,6 +238,36 @@ static void findStartEnd(int x,
238238
}
239239
}
240240

241+
// Subtract the blocker intervals from [start, end), returning the open gaps.
242+
static std::vector<std::pair<int, int>> computeOpenSpans(
243+
const int start,
244+
const int end,
245+
std::vector<std::pair<int, int>> blockers)
246+
{
247+
std::ranges::sort(blockers);
248+
249+
std::vector<std::pair<int, int>> open_spans;
250+
int cursor = start;
251+
for (const auto& [b_start, b_end] : blockers) {
252+
// blockers are sorted by start, so none of the rest can open a span
253+
if (b_start >= end) {
254+
break;
255+
}
256+
if (b_end <= cursor) {
257+
continue;
258+
}
259+
if (b_start > cursor) {
260+
open_spans.emplace_back(cursor, b_start);
261+
}
262+
cursor = b_end;
263+
}
264+
if (cursor < end) {
265+
open_spans.emplace_back(cursor, end);
266+
}
267+
268+
return open_spans;
269+
}
270+
241271
std::optional<int> Tapcell::findValidLocation(
242272
int x,
243273
int width,
@@ -524,6 +554,8 @@ void Tapcell::placeEndcaps(const EndcapCellOptions& options)
524554
}
525555

526556
filled_edges_.clear();
557+
filled_horizontal_edges_.clear();
558+
placed_corners_.clear();
527559
}
528560

529561
std::vector<Tapcell::Edge> Tapcell::getBoundaryEdges(const Polygon& area,
@@ -873,18 +905,19 @@ std::pair<int, int> Tapcell::placeEndcaps(const Tapcell::Polygon90& area,
873905
int corner_count = 0;
874906
int endcaps = 0;
875907

876-
CornerMap corners;
877-
// insert corners first
908+
// insert corners first. placed_corners_ persists across areas/holes so that
909+
// edges and corners of one macro's hole see the corners already placed by an
910+
// adjacent macro's hole in the same row.
878911
for (const auto& corner : getBoundaryCorners(area, outer)) {
879912
for (const auto& [row, insts] : placeEndcapCorner(corner, options)) {
880-
corners[row].insert(insts.begin(), insts.end());
913+
placed_corners_[row].insert(insts.begin(), insts.end());
881914
corner_count += insts.size();
882915
}
883916
}
884917

885918
for (const auto& edge : getBoundaryEdges(area, outer)) {
886919
if (std::ranges::find(filled_edges_, edge) == filled_edges_.end()) {
887-
endcaps += placeEndcapEdge(edge, corners, options);
920+
endcaps += placeEndcapEdge(edge, placed_corners_, options);
888921
filled_edges_.push_back(edge);
889922
}
890923
}
@@ -1048,6 +1081,32 @@ Tapcell::CornerMap Tapcell::placeEndcapCorner(const Tapcell::Corner& corner,
10481081
return {};
10491082
}
10501083

1084+
// Skip corners overlapping one already placed in this row, e.g. the inner
1085+
// top and bottom corners of a single-height row between macros.
1086+
auto placed = placed_corners_.find(row);
1087+
if (placed != placed_corners_.end()) {
1088+
const odb::Rect cell(
1089+
ll.getX(), ll.getY(), ll.getX() + width, ll.getY() + height);
1090+
for (auto* other : placed->second) {
1091+
if (cell.overlaps(other->getBBox()->getBox())) {
1092+
return {};
1093+
}
1094+
}
1095+
}
1096+
1097+
// Skip corners overlapping a horizontal edge already placed in this row: an
1098+
// adjacent macro's hole may have filled the row before this corner.
1099+
auto filled = filled_horizontal_edges_.find(row);
1100+
if (filled != filled_horizontal_edges_.end()) {
1101+
const int x_start = ll.getX();
1102+
const int x_end = ll.getX() + width;
1103+
for (const auto& [e_start, e_end] : filled->second) {
1104+
if (x_end > e_start && x_start < e_end) {
1105+
return {};
1106+
}
1107+
}
1108+
}
1109+
10511110
auto inst = makeInstance(db_->getChip()->getBlock(),
10521111
master,
10531112
orient,
@@ -1166,59 +1225,93 @@ int Tapcell::placeEndcapEdgeHorizontal(const Tapcell::Edge& edge,
11661225
}
11671226
}
11681227

1169-
odb::Point ll = row->getBBox().ll();
1170-
ll.setX(e0.getX());
1228+
// Fill only x-ranges not already covered by another horizontal edge in this
1229+
// row, so a single-height row between macros gets one edge, not overlaps.
1230+
std::vector<std::pair<int, int>>& occupied = filled_horizontal_edges_[row];
11711231

1232+
// Also skip the corner cells in this row that land within the span.
1233+
std::vector<std::pair<int, int>> blockers(occupied);
1234+
if (check_row != corners.end()) {
1235+
for (auto* inst : check_row->second) {
1236+
const auto bbox = inst->getBBox()->getBox();
1237+
blockers.emplace_back(bbox.xMin(), bbox.xMax());
1238+
}
1239+
}
1240+
1241+
for (const auto& [span_start, span_end] :
1242+
computeOpenSpans(e0.getX(), e1.getX(), std::move(blockers))) {
1243+
insts += fillEndcapEdge(
1244+
row, span_start, span_end, masters, edge.type, options.prefix);
1245+
occupied.emplace_back(span_start, span_end);
1246+
}
1247+
1248+
return insts;
1249+
}
1250+
1251+
int Tapcell::fillEndcapEdge(odb::dbRow* row,
1252+
const int x_start,
1253+
const int x_end,
1254+
const std::vector<odb::dbMaster*>& masters,
1255+
const EdgeType edge_type,
1256+
const std::string& prefix)
1257+
{
1258+
// Consider only masters that can be legally placed in this row's
1259+
// orientation. masters is sorted widest first, so the last valid one is the
1260+
// narrowest, used as a fallback when none divides the span evenly.
11721261
auto pick_next_master
1173-
= [&e1, &masters](const odb::Point& ll) -> odb::dbMaster* {
1174-
int remaining = e1.getX() - ll.getX();
1262+
= [this, x_end, &masters, row](int x) -> odb::dbMaster* {
1263+
const int remaining = x_end - x;
1264+
odb::dbMaster* fallback = nullptr;
11751265
for (auto* master : masters) {
1266+
if (!checkSymmetry(master, row->getOrient())) {
1267+
continue;
1268+
}
1269+
fallback = master;
11761270
if (remaining % master->getWidth() == 0) {
11771271
return master;
11781272
}
11791273
}
1180-
// pick smallest if none will divide evenly
1181-
return masters[masters.size() - 1];
1274+
return fallback;
11821275
};
11831276

1184-
while (ll.getX() < e1.getX()) {
1185-
auto* master = pick_next_master(ll);
1186-
1187-
debugPrint(logger_,
1188-
utl::TAP,
1189-
"Endcap",
1190-
3,
1191-
"From {} -> {}: picked {}",
1192-
ll.getX(),
1193-
e1.getX(),
1194-
master->getName());
1195-
1196-
if (!checkSymmetry(master, row->getOrient())) {
1197-
continue;
1198-
}
1277+
const int row_lly = row->getBBox().yMin();
1278+
int insts = 0;
1279+
int x = x_start;
1280+
while (x < x_end) {
1281+
auto* master = pick_next_master(x);
11991282

1200-
if (ll.getX() + master->getWidth() > e1.getX()) {
1283+
// No symmetric master fits the remaining space: the boundary cannot be
1284+
// filled without leaving a gap.
1285+
if (master == nullptr || x + master->getWidth() > x_end) {
12011286
const double dbus = row->getBlock()->getDbUnitsPerMicron();
12021287
logger_->error(
12031288
utl::TAP,
12041289
20,
12051290
"Unable to fill {} boundary in {} from {:.4f}um to {:.4f}um",
1206-
toString(edge.type),
1291+
toString(edge_type),
12071292
row->getName(),
1208-
ll.getX() / dbus,
1209-
e1.getX() / dbus);
1293+
x / dbus,
1294+
x_end / dbus);
12101295
}
12111296

1212-
makeInstance(db_->getChip()->getBlock(),
1213-
master,
1214-
row->getOrient(),
1215-
ll.getX(),
1216-
ll.getY(),
1217-
fmt::format("{}EDGE_{}_{}_",
1218-
options.prefix,
1219-
row->getName(),
1220-
toString(edge.type)));
1221-
ll.addX(master->getWidth());
1297+
debugPrint(logger_,
1298+
utl::TAP,
1299+
"Endcap",
1300+
3,
1301+
"From {} -> {}: picked {}",
1302+
x,
1303+
x_end,
1304+
master->getName());
1305+
1306+
makeInstance(
1307+
db_->getChip()->getBlock(),
1308+
master,
1309+
row->getOrient(),
1310+
x,
1311+
row_lly,
1312+
fmt::format(
1313+
"{}EDGE_{}_{}_", prefix, row->getName(), toString(edge_type)));
1314+
x += master->getWidth();
12221315
insts++;
12231316
}
12241317

src/tap/test/BUILD

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@ COMPULSORY_TESTS = [
4545
"no_endcap",
4646
"overlap_cover",
4747
"region1",
48+
"single_row_macros",
49+
"single_row_macros_offset",
4850
"symmetry",
4951
]
5052

@@ -159,6 +161,8 @@ filegroup(
159161
"gcd_sky130_separate": ["gcd_sky130.defok"],
160162
"multiple_calls": ["gcd_ripup.def"],
161163
"overlap_cover": ["sky130hs_data/cover.lef"],
164+
"single_row_macros": [],
165+
"single_row_macros_offset": [],
162166
}.get(
163167
test_name,
164168
["boundary_macros.def"],

src/tap/test/CMakeLists.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,8 @@ or_integration_tests(
3838
no_endcap
3939
overlap_cover
4040
region1
41+
single_row_macros
42+
single_row_macros_offset
4143
symmetry
4244
)
4345

src/tap/test/single_row_macros.def

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
VERSION 5.8 ;
2+
DIVIDERCHAR "/" ;
3+
BUSBITCHARS "[]" ;
4+
DESIGN single_row_macros ;
5+
UNITS DISTANCE MICRONS 2000 ;
6+
DIEAREA ( 0 0 ) ( 145540 226800 ) ;
7+
ROW ROW_0 FreePDK45_38x28_10R_NP_162NW_34O 0 0 N DO 383 BY 1 STEP 380 0 ;
8+
ROW ROW_1 FreePDK45_38x28_10R_NP_162NW_34O 0 2800 N DO 383 BY 1 STEP 380 0 ;
9+
ROW ROW_2 FreePDK45_38x28_10R_NP_162NW_34O 0 5600 N DO 383 BY 1 STEP 380 0 ;
10+
ROW ROW_3 FreePDK45_38x28_10R_NP_162NW_34O 0 8400 N DO 383 BY 1 STEP 380 0 ;
11+
ROW ROW_4 FreePDK45_38x28_10R_NP_162NW_34O 0 11200 N DO 383 BY 1 STEP 380 0 ;
12+
ROW ROW_5 FreePDK45_38x28_10R_NP_162NW_34O 0 14000 N DO 383 BY 1 STEP 380 0 ;
13+
ROW ROW_6 FreePDK45_38x28_10R_NP_162NW_34O 0 16800 N DO 383 BY 1 STEP 380 0 ;
14+
ROW ROW_7 FreePDK45_38x28_10R_NP_162NW_34O 0 19600 N DO 383 BY 1 STEP 380 0 ;
15+
ROW ROW_8 FreePDK45_38x28_10R_NP_162NW_34O 0 22400 N DO 383 BY 1 STEP 380 0 ;
16+
ROW ROW_9 FreePDK45_38x28_10R_NP_162NW_34O 0 25200 N DO 383 BY 1 STEP 380 0 ;
17+
ROW ROW_10 FreePDK45_38x28_10R_NP_162NW_34O 0 28000 N DO 383 BY 1 STEP 380 0 ;
18+
ROW ROW_11 FreePDK45_38x28_10R_NP_162NW_34O 0 30800 N DO 383 BY 1 STEP 380 0 ;
19+
ROW ROW_12 FreePDK45_38x28_10R_NP_162NW_34O 0 33600 N DO 383 BY 1 STEP 380 0 ;
20+
ROW ROW_13 FreePDK45_38x28_10R_NP_162NW_34O 0 36400 N DO 383 BY 1 STEP 380 0 ;
21+
ROW ROW_14 FreePDK45_38x28_10R_NP_162NW_34O 0 39200 N DO 383 BY 1 STEP 380 0 ;
22+
ROW ROW_15 FreePDK45_38x28_10R_NP_162NW_34O 0 42000 N DO 383 BY 1 STEP 380 0 ;
23+
ROW ROW_16 FreePDK45_38x28_10R_NP_162NW_34O 0 44800 N DO 383 BY 1 STEP 380 0 ;
24+
ROW ROW_17 FreePDK45_38x28_10R_NP_162NW_34O 0 47600 N DO 383 BY 1 STEP 380 0 ;
25+
ROW ROW_18 FreePDK45_38x28_10R_NP_162NW_34O 0 50400 N DO 383 BY 1 STEP 380 0 ;
26+
ROW ROW_19 FreePDK45_38x28_10R_NP_162NW_34O 0 53200 N DO 383 BY 1 STEP 380 0 ;
27+
ROW ROW_20 FreePDK45_38x28_10R_NP_162NW_34O 0 56000 N DO 383 BY 1 STEP 380 0 ;
28+
ROW ROW_21 FreePDK45_38x28_10R_NP_162NW_34O 0 58800 N DO 383 BY 1 STEP 380 0 ;
29+
ROW ROW_22 FreePDK45_38x28_10R_NP_162NW_34O 0 61600 N DO 383 BY 1 STEP 380 0 ;
30+
ROW ROW_23 FreePDK45_38x28_10R_NP_162NW_34O 0 64400 N DO 383 BY 1 STEP 380 0 ;
31+
ROW ROW_24 FreePDK45_38x28_10R_NP_162NW_34O 0 67200 N DO 383 BY 1 STEP 380 0 ;
32+
ROW ROW_25 FreePDK45_38x28_10R_NP_162NW_34O 0 70000 N DO 383 BY 1 STEP 380 0 ;
33+
ROW ROW_26 FreePDK45_38x28_10R_NP_162NW_34O 0 72800 N DO 383 BY 1 STEP 380 0 ;
34+
ROW ROW_27 FreePDK45_38x28_10R_NP_162NW_34O 0 75600 N DO 383 BY 1 STEP 380 0 ;
35+
ROW ROW_28 FreePDK45_38x28_10R_NP_162NW_34O 0 78400 N DO 383 BY 1 STEP 380 0 ;
36+
ROW ROW_29 FreePDK45_38x28_10R_NP_162NW_34O 0 81200 N DO 383 BY 1 STEP 380 0 ;
37+
ROW ROW_30 FreePDK45_38x28_10R_NP_162NW_34O 0 84000 N DO 383 BY 1 STEP 380 0 ;
38+
ROW ROW_31 FreePDK45_38x28_10R_NP_162NW_34O 0 86800 N DO 383 BY 1 STEP 380 0 ;
39+
ROW ROW_32 FreePDK45_38x28_10R_NP_162NW_34O 0 89600 N DO 383 BY 1 STEP 380 0 ;
40+
ROW ROW_33 FreePDK45_38x28_10R_NP_162NW_34O 0 92400 N DO 383 BY 1 STEP 380 0 ;
41+
ROW ROW_34 FreePDK45_38x28_10R_NP_162NW_34O 0 95200 N DO 383 BY 1 STEP 380 0 ;
42+
ROW ROW_35 FreePDK45_38x28_10R_NP_162NW_34O 0 98000 N DO 383 BY 1 STEP 380 0 ;
43+
ROW ROW_36 FreePDK45_38x28_10R_NP_162NW_34O 0 100800 N DO 383 BY 1 STEP 380 0 ;
44+
ROW ROW_37 FreePDK45_38x28_10R_NP_162NW_34O 0 103600 N DO 383 BY 1 STEP 380 0 ;
45+
ROW ROW_38 FreePDK45_38x28_10R_NP_162NW_34O 0 106400 N DO 383 BY 1 STEP 380 0 ;
46+
ROW ROW_39 FreePDK45_38x28_10R_NP_162NW_34O 0 109200 N DO 383 BY 1 STEP 380 0 ;
47+
ROW ROW_40 FreePDK45_38x28_10R_NP_162NW_34O 0 112000 N DO 383 BY 1 STEP 380 0 ;
48+
ROW ROW_41 FreePDK45_38x28_10R_NP_162NW_34O 0 114800 N DO 383 BY 1 STEP 380 0 ;
49+
ROW ROW_42 FreePDK45_38x28_10R_NP_162NW_34O 0 117600 N DO 383 BY 1 STEP 380 0 ;
50+
ROW ROW_43 FreePDK45_38x28_10R_NP_162NW_34O 0 120400 N DO 383 BY 1 STEP 380 0 ;
51+
ROW ROW_44 FreePDK45_38x28_10R_NP_162NW_34O 0 123200 N DO 383 BY 1 STEP 380 0 ;
52+
ROW ROW_45 FreePDK45_38x28_10R_NP_162NW_34O 0 126000 N DO 383 BY 1 STEP 380 0 ;
53+
ROW ROW_46 FreePDK45_38x28_10R_NP_162NW_34O 0 128800 N DO 383 BY 1 STEP 380 0 ;
54+
ROW ROW_47 FreePDK45_38x28_10R_NP_162NW_34O 0 131600 N DO 383 BY 1 STEP 380 0 ;
55+
ROW ROW_48 FreePDK45_38x28_10R_NP_162NW_34O 0 134400 N DO 383 BY 1 STEP 380 0 ;
56+
ROW ROW_49 FreePDK45_38x28_10R_NP_162NW_34O 0 137200 N DO 383 BY 1 STEP 380 0 ;
57+
ROW ROW_50 FreePDK45_38x28_10R_NP_162NW_34O 0 140000 N DO 383 BY 1 STEP 380 0 ;
58+
ROW ROW_51 FreePDK45_38x28_10R_NP_162NW_34O 0 142800 N DO 383 BY 1 STEP 380 0 ;
59+
ROW ROW_52 FreePDK45_38x28_10R_NP_162NW_34O 0 145600 N DO 383 BY 1 STEP 380 0 ;
60+
ROW ROW_53 FreePDK45_38x28_10R_NP_162NW_34O 0 148400 N DO 383 BY 1 STEP 380 0 ;
61+
ROW ROW_54 FreePDK45_38x28_10R_NP_162NW_34O 0 151200 N DO 383 BY 1 STEP 380 0 ;
62+
ROW ROW_55 FreePDK45_38x28_10R_NP_162NW_34O 0 154000 N DO 383 BY 1 STEP 380 0 ;
63+
ROW ROW_56 FreePDK45_38x28_10R_NP_162NW_34O 0 156800 N DO 383 BY 1 STEP 380 0 ;
64+
ROW ROW_57 FreePDK45_38x28_10R_NP_162NW_34O 0 159600 N DO 383 BY 1 STEP 380 0 ;
65+
ROW ROW_58 FreePDK45_38x28_10R_NP_162NW_34O 0 162400 N DO 383 BY 1 STEP 380 0 ;
66+
ROW ROW_59 FreePDK45_38x28_10R_NP_162NW_34O 0 165200 N DO 383 BY 1 STEP 380 0 ;
67+
ROW ROW_60 FreePDK45_38x28_10R_NP_162NW_34O 0 168000 N DO 383 BY 1 STEP 380 0 ;
68+
ROW ROW_61 FreePDK45_38x28_10R_NP_162NW_34O 0 170800 N DO 383 BY 1 STEP 380 0 ;
69+
ROW ROW_62 FreePDK45_38x28_10R_NP_162NW_34O 0 173600 N DO 383 BY 1 STEP 380 0 ;
70+
ROW ROW_63 FreePDK45_38x28_10R_NP_162NW_34O 0 176400 N DO 383 BY 1 STEP 380 0 ;
71+
ROW ROW_64 FreePDK45_38x28_10R_NP_162NW_34O 0 179200 N DO 383 BY 1 STEP 380 0 ;
72+
ROW ROW_65 FreePDK45_38x28_10R_NP_162NW_34O 0 182000 N DO 383 BY 1 STEP 380 0 ;
73+
ROW ROW_66 FreePDK45_38x28_10R_NP_162NW_34O 0 184800 N DO 383 BY 1 STEP 380 0 ;
74+
ROW ROW_67 FreePDK45_38x28_10R_NP_162NW_34O 0 187600 N DO 383 BY 1 STEP 380 0 ;
75+
ROW ROW_68 FreePDK45_38x28_10R_NP_162NW_34O 0 190400 N DO 383 BY 1 STEP 380 0 ;
76+
ROW ROW_69 FreePDK45_38x28_10R_NP_162NW_34O 0 193200 N DO 383 BY 1 STEP 380 0 ;
77+
ROW ROW_70 FreePDK45_38x28_10R_NP_162NW_34O 0 196000 N DO 383 BY 1 STEP 380 0 ;
78+
ROW ROW_71 FreePDK45_38x28_10R_NP_162NW_34O 0 198800 N DO 383 BY 1 STEP 380 0 ;
79+
ROW ROW_72 FreePDK45_38x28_10R_NP_162NW_34O 0 201600 N DO 383 BY 1 STEP 380 0 ;
80+
ROW ROW_73 FreePDK45_38x28_10R_NP_162NW_34O 0 204400 N DO 383 BY 1 STEP 380 0 ;
81+
ROW ROW_74 FreePDK45_38x28_10R_NP_162NW_34O 0 207200 N DO 383 BY 1 STEP 380 0 ;
82+
ROW ROW_75 FreePDK45_38x28_10R_NP_162NW_34O 0 210000 N DO 383 BY 1 STEP 380 0 ;
83+
ROW ROW_76 FreePDK45_38x28_10R_NP_162NW_34O 0 212800 N DO 383 BY 1 STEP 380 0 ;
84+
ROW ROW_77 FreePDK45_38x28_10R_NP_162NW_34O 0 215600 N DO 383 BY 1 STEP 380 0 ;
85+
ROW ROW_78 FreePDK45_38x28_10R_NP_162NW_34O 0 218400 N DO 383 BY 1 STEP 380 0 ;
86+
ROW ROW_79 FreePDK45_38x28_10R_NP_162NW_34O 0 221200 N DO 383 BY 1 STEP 380 0 ;
87+
ROW ROW_80 FreePDK45_38x28_10R_NP_162NW_34O 0 224000 N DO 383 BY 1 STEP 380 0 ;
88+
COMPONENTS 2 ;
89+
- macro_bot fakeram45_64x7 + FIXED ( 19000 0 ) N ;
90+
- macro_top fakeram45_64x7 + FIXED ( 19000 114800 ) N ;
91+
END COMPONENTS
92+
END DESIGN

0 commit comments

Comments
 (0)