Skip to content

Commit 4c0358b

Browse files
committed
add latch-based RAM
Signed-off-by: Thinh Nguyen <nguyenthinh19011@gmail.com>
1 parent fbcbb1c commit 4c0358b

15 files changed

Lines changed: 2917 additions & 8621 deletions

src/ram/include/ram/ram.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@ class RamGen
9292
int num_words,
9393
int column_mux_ratio,
9494
int read_ports,
95+
bool use_latch,
9596
odb::dbMaster* storage_cell,
9697
odb::dbMaster* tristate_cell,
9798
odb::dbMaster* inv_cell,
@@ -197,6 +198,7 @@ class RamGen
197198
odb::dbMaster* clock_gate_cell_{nullptr};
198199
odb::dbMaster* buffer_cell_{nullptr};
199200
odb::dbMaster* aoi22_cell_{nullptr};
201+
odb::dbMaster* latch_cell_{nullptr};
200202
odb::dbMaster* tapcell_{nullptr};
201203

202204
std::map<PortRole, std::string> storage_ports_;
@@ -205,6 +207,7 @@ class RamGen
205207
std::map<PortRole, std::string> and2_ports_;
206208
std::map<PortRole, std::string> clock_gate_ports_;
207209
std::map<PortRole, std::string> buffer_ports_;
210+
std::map<PortRole, std::string> latch_ports_;
208211

209212
std::vector<odb::dbBTerm*> addr_inputs_;
210213
std::vector<odb::dbBTerm*> data_inputs_;
@@ -213,6 +216,7 @@ class RamGen
213216
std::string aoi22_in_a1_, aoi22_in_a2_, aoi22_in_b1_, aoi22_in_b2_,
214217
aoi22_out_;
215218
Grid ram_grid_;
219+
bool use_latch_{false};
216220
};
217221

218222
} // namespace ram

src/ram/src/ram.cpp

Lines changed: 148 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -120,13 +120,23 @@ std::unique_ptr<Cell> RamGen::makeBit(const std::string& prefix,
120120

121121
auto storage_net = makeNet(prefix, "storage");
122122

123-
makeInst(bit_cell.get(),
124-
prefix,
125-
"bit",
126-
storage_cell_,
127-
{{storage_ports_[{PortRoleType::Clock, 0}], clock},
128-
{storage_ports_[{PortRoleType::DataIn, 0}], data_input},
129-
{storage_ports_[{PortRoleType::DataOut, 0}], storage_net}});
123+
if (!use_latch_) {
124+
makeInst(bit_cell.get(),
125+
prefix,
126+
"bit",
127+
storage_cell_,
128+
{{storage_ports_[{PortRoleType::Clock, 0}], clock},
129+
{storage_ports_[{PortRoleType::DataIn, 0}], data_input},
130+
{storage_ports_[{PortRoleType::DataOut, 0}], storage_net}});
131+
} else {
132+
makeInst(bit_cell.get(),
133+
prefix,
134+
"bit",
135+
latch_cell_,
136+
{{latch_ports_[{PortRoleType::Clock, 0}], clock},
137+
{latch_ports_[{PortRoleType::DataIn, 0}], data_input},
138+
{latch_ports_[{PortRoleType::DataOut, 0}], storage_net}});
139+
}
130140

131141
for (int read_port = 0; read_port < read_ports; ++read_port) {
132142
makeInst(
@@ -225,19 +235,6 @@ void RamGen::makeSlice(const int slice_idx,
225235
{and2_ports_[{PortRoleType::DataOut, 0}], write_sel}});
226236
}
227237

228-
// Write path: this net is row_select AND with word_select so clock gate only
229-
// fires for the addressed word within the row. Read path handled by AOI mux.
230-
// word_select is nullptr when column_mux_ratio=1 (no mux needed).
231-
dbNet* write_sel = selects[0];
232-
if (word_select) {
233-
write_sel = makeNet(prefix, "write_sel");
234-
makeInst(sel_cell.get(),
235-
prefix,
236-
"word_and",
237-
and2_cell_,
238-
{{"A", selects[0]}, {"B", word_select}, {"X", write_sel}});
239-
}
240-
241238
// Make clock and
242239
// this AND gate needs to be fed a net created by a decoder
243240
// adding any net will automatically connect with any port
@@ -627,31 +624,33 @@ void RamGen::findMasters()
627624
}
628625
and2_ports_ = buildPortMap(and2_cell_);
629626

630-
if (!storage_cell_) {
631-
// FIXME
632-
// Still needs changes to get right type of flip-flop
633-
storage_cell_ = findMaster(
634-
[](sta::LibertyPort* port) {
635-
if (!port->isRegOutput()) {
636-
return false;
637-
}
638-
// looking for DFF specifically
639-
auto cell = port->libertyCell();
640-
auto port_iter = cell->portIterator();
641-
while (port_iter->hasNext()) {
642-
auto p = port_iter->next()->libertyPort();
643-
// check to filter out latches
644-
if (p && p->isLatchData()) {
645-
delete port_iter;
627+
if (!use_latch_) {
628+
if (!storage_cell_) {
629+
// FIXME
630+
// Still needs changes to get right type of flip-flop
631+
storage_cell_ = findMaster(
632+
[](sta::LibertyPort* port) {
633+
if (!port->isRegOutput()) {
646634
return false;
647635
}
648-
}
649-
delete port_iter;
650-
return true;
651-
},
652-
"storage");
636+
// looking for DFF specifically
637+
auto cell = port->libertyCell();
638+
auto port_iter = cell->portIterator();
639+
while (port_iter->hasNext()) {
640+
auto p = port_iter->next()->libertyPort();
641+
// check to filter out latches
642+
if (p && p->isLatchData()) {
643+
delete port_iter;
644+
return false;
645+
}
646+
}
647+
delete port_iter;
648+
return true;
649+
},
650+
"storage");
651+
}
652+
storage_ports_ = buildPortMap(storage_cell_);
653653
}
654-
storage_ports_ = buildPortMap(storage_cell_);
655654

656655
if (!clock_gate_cell_) {
657656
clock_gate_cell_ = findMaster(
@@ -670,6 +669,39 @@ void RamGen::findMasters()
670669
}
671670
buffer_ports_ = buildPortMap(buffer_cell_);
672671

672+
// latch cells for latch-based ram: find neg/pos latch cell
673+
if (use_latch_ && !latch_cell_) {
674+
latch_cell_ = findMaster(
675+
[](sta::LibertyPort* port) {
676+
if (!port->direction()->isOutput()) {
677+
return false;
678+
}
679+
auto cell = port->libertyCell();
680+
if (!cell->hasSequentials()) {
681+
return false;
682+
}
683+
bool has_latch_data = false;
684+
bool has_gate = false;
685+
auto port_iter = cell->portIterator();
686+
while (port_iter->hasNext()) {
687+
auto p = static_cast<sta::ConcretePort*>(port_iter->next());
688+
auto lp = p->libertyPort();
689+
if (lp && lp->isLatchData()) {
690+
has_latch_data = true;
691+
}
692+
if (std::string(p->name()) == "GATE"
693+
|| std::string(p->name()) == "G") {
694+
has_gate = true;
695+
}
696+
}
697+
delete port_iter;
698+
return has_latch_data && has_gate;
699+
},
700+
"latch");
701+
}
702+
703+
latch_ports_ = buildPortMap(latch_cell_);
704+
673705
// aoi cells used for column mux functionality when column_mux_ratio > 1
674706
// uses truth table simulation to identify AOI22 and discover port names
675707
// to work for all PDKs, avoiding hardcoding PDK-specific expression parsing
@@ -1021,6 +1053,7 @@ void RamGen::generate(const int mask_size,
10211053
const int num_words,
10221054
const int column_mux_ratio,
10231055
const int read_ports,
1056+
const bool use_latch,
10241057
dbMaster* storage_cell,
10251058
dbMaster* tristate_cell,
10261059
dbMaster* inv_cell,
@@ -1078,6 +1111,7 @@ void RamGen::generate(const int mask_size,
10781111

10791112
logger_->info(RAM, 3, "Generating {}", ram_name);
10801113

1114+
use_latch_ = use_latch;
10811115
storage_cell_ = storage_cell;
10821116
tristate_cell_ = tristate_cell;
10831117
inv_cell_ = inv_cell;
@@ -1086,6 +1120,7 @@ void RamGen::generate(const int mask_size,
10861120
clock_gate_cell_ = nullptr;
10871121
buffer_cell_ = nullptr;
10881122
aoi22_cell_ = nullptr;
1123+
latch_cell_ = nullptr;
10891124
findMasters();
10901125

10911126
auto chip = db_->getChip();
@@ -1181,19 +1216,6 @@ void RamGen::generate(const int mask_size,
11811216
ram_grid_.addCell(std::move(decoder_and_cell), col_cell_count);
11821217
}
11831218
}
1184-
} else {
1185-
word_decoder_nets[row] = selectNets(decoder_name, read_ports);
1186-
auto decoder_and_cell = makeDecoder(decoder_name,
1187-
num_rows,
1188-
read_ports,
1189-
word_decoder_nets[row],
1190-
decoder_input_nets[row]);
1191-
ram_grid_.addCell(std::move(decoder_and_cell), col_cell_count);
1192-
}
1193-
}
1194-
1195-
std::unique_ptr<Cell> inv_sel_cell;
1196-
std::unique_ptr<Cell> word_sel_cell;
11971219

11981220
std::unique_ptr<Cell> inv_sel_cell;
11991221
std::unique_ptr<Cell> word_sel_cell;
@@ -1301,6 +1323,17 @@ void RamGen::generate(const int mask_size,
13011323
}
13021324
}
13031325

1326+
// For latch-based ram: mid_nets carry data from negative latch down to all
1327+
// pos latches per bit in the same column
1328+
vector<dbNet*> mid_nets(word_size);
1329+
dbNet* clk_b_net = nullptr;
1330+
if (use_latch_) {
1331+
clk_b_net = makeNet("global", "clk_b");
1332+
for (int bit = 0; bit < word_size; ++bit) {
1333+
mid_nets[bit] = makeNet("mid", fmt::format("b{}", bit));
1334+
}
1335+
}
1336+
13041337
// For column_mux_ratio > 1, create one shared select_b net per row before
13051338
// iterating over word_idx. All words in the same row share this net as the
13061339
// tristate TE_B enable signal. Only word_idx=0 instantiates the select_inv
@@ -1337,7 +1370,7 @@ void RamGen::generate(const int mask_size,
13371370
word_decoder_nets[row],
13381371
shared_select_b_nets,
13391372
(word_idx == 0 || column_mux_ratio == 1),
1340-
D_nets,
1373+
use_latch_ ? mid_nets : D_nets,
13411374
word_output_nets);
13421375
}
13431376
}
@@ -1437,6 +1470,54 @@ void RamGen::generate(const int mask_size,
14371470
}
14381471
}
14391472

1473+
if (use_latch_) {
1474+
auto clk_inv_cell = std::make_unique<Cell>();
1475+
makeInst(clk_inv_cell.get(),
1476+
"neg_row",
1477+
"clk_inv",
1478+
inv_cell_,
1479+
{{inv_ports_[{PortRoleType::DataIn, 0}], clock->getNet()},
1480+
{inv_ports_[{PortRoleType::DataOut, 0}], clk_b_net}});
1481+
ram_grid_.addCell(std::move(clk_inv_cell), col_cell_count);
1482+
1483+
const int nl_col_offset = (column_mux_ratio == 4) ? 1 : 0;
1484+
1485+
for (int slice = 0; slice < slices_per_word; ++slice) {
1486+
for (int bit = 0; bit < mask_size; ++bit) {
1487+
const int global_bit = slice * mask_size + bit;
1488+
const int group_start
1489+
= slice * (mask_size * column_mux_ratio + column_mux_ratio)
1490+
+ bit * column_mux_ratio;
1491+
const int nl_col = group_start + nl_col_offset;
1492+
for (int c = group_start; c < group_start + column_mux_ratio; ++c) {
1493+
if (c == nl_col) {
1494+
auto nl_cell = std::make_unique<Cell>();
1495+
makeInst(
1496+
nl_cell.get(),
1497+
fmt::format("neg_lat_b{}", global_bit),
1498+
"nlat",
1499+
latch_cell_,
1500+
{{latch_ports_[{PortRoleType::Clock, 0}], clk_b_net},
1501+
{latch_ports_[{PortRoleType::DataIn, 0}], D_nets[global_bit]},
1502+
{latch_ports_[{PortRoleType::DataOut, 0}],
1503+
mid_nets[global_bit]}});
1504+
ram_grid_.addCell(std::move(nl_cell), c);
1505+
} else {
1506+
ram_grid_.addCell(nullptr, c);
1507+
}
1508+
}
1509+
}
1510+
}
1511+
for (int slice = 0; slice < slices_per_word; ++slice) {
1512+
for (int w = 0; w < column_mux_ratio; ++w) {
1513+
int sel_col = slice * (mask_size * column_mux_ratio + column_mux_ratio)
1514+
+ mask_size * column_mux_ratio + w;
1515+
ram_grid_.addCell(nullptr, sel_col);
1516+
}
1517+
}
1518+
ram_grid_.addCell(nullptr, col_cell_count);
1519+
}
1520+
14401521
for (int slice = 0; slice < slices_per_word; ++slice) {
14411522
for (int bit = 0; bit < mask_size; ++bit) {
14421523
int bit_idx = bit + slice * mask_size;
@@ -1460,6 +1541,10 @@ void RamGen::generate(const int mask_size,
14601541
ram_grid_.addCell(std::move(word_sel_cell), col_cell_count - 1);
14611542
}
14621543

1544+
// One extra row at the top for placing input buffers, one extra row for
1545+
// negative latches if use latch-based ram
1546+
const int num_rows_grid = num_rows + 1 + (use_latch_ ? 1 : 0);
1547+
14631548
auto cell_inv_layout = std::make_unique<Layout>(odb::vertical);
14641549
// check for AND gate, specific case for 2 words
14651550
int inv_col_cells = 0;
@@ -1500,8 +1585,8 @@ void RamGen::generate(const int mask_size,
15001585
++inv_col_cells;
15011586
}
15021587
}
1503-
// Pad remaining slots so this column matches the grid height (num_rows + 1)
1504-
while (inv_col_cells < num_rows + 1) {
1588+
// Pad remaining slots so this column matches the grid height (num_rows_grid)
1589+
while (inv_col_cells < num_rows_grid) {
15051590
cell_inv_layout->addCell(nullptr);
15061591
++inv_col_cells;
15071592
}
@@ -1515,7 +1600,7 @@ void RamGen::generate(const int mask_size,
15151600
if (tapcell_) {
15161601
// max tap distance specified is greater than the length of ram
15171602
if (ram_grid_.getRowWidth() <= max_tap_dist) {
1518-
auto tapcell_layout = generateTapColumn(num_rows, 0);
1603+
auto tapcell_layout = generateTapColumn(num_rows_grid - 1, 0);
15191604
ram_grid_.insertLayout(std::move(tapcell_layout), 0);
15201605
} else {
15211606
// needed this calculation so first cells have right distance
@@ -1526,7 +1611,8 @@ void RamGen::generate(const int mask_size,
15261611
for (int col = 0; col < ram_grid_.numLayouts(); ++col) {
15271612
if (nearest_tap + ram_grid_.getLayoutWidth(col) >= max_tap_dist) {
15281613
// if the nearest_tap is too far, generate tap column
1529-
auto tapcell_layout = generateTapColumn(num_rows, tapcell_count);
1614+
auto tapcell_layout
1615+
= generateTapColumn(num_rows_grid - 1, tapcell_count);
15301616
ram_grid_.insertLayout(std::move(tapcell_layout), col);
15311617
++col; // col adjustment after insertion
15321618
nearest_tap = 0;
@@ -1536,7 +1622,8 @@ void RamGen::generate(const int mask_size,
15361622
}
15371623
// check for last column in the grid
15381624
if (nearest_tap >= max_tap_dist) {
1539-
auto tapcell_layout = generateTapColumn(num_rows, tapcell_count);
1625+
auto tapcell_layout
1626+
= generateTapColumn(num_rows_grid - 1, tapcell_count);
15401627
ram_grid_.addLayout(std::move(tapcell_layout));
15411628
}
15421629
}
@@ -1550,8 +1637,6 @@ void RamGen::generate(const int mask_size,
15501637

15511638
int num_sites = ram_grid_.getRowWidth() / db_sites->getWidth();
15521639

1553-
// One extra row at the top for placing input buffers
1554-
const int num_rows_grid = num_rows + 1;
15551640
for (int i = 0; i < num_rows_grid; ++i) {
15561641
auto row_name = fmt::format("RAM_ROW{}", i);
15571642
auto y_coord = i * ram_grid_.getHeight();

0 commit comments

Comments
 (0)