Skip to content

Commit 3a29dc8

Browse files
authored
Merge pull request #10290 from tnguy19/latch-ram
ram: latch-based memory support
2 parents 8ff372b + 7f08365 commit 3a29dc8

9 files changed

Lines changed: 2973 additions & 41 deletions

File tree

src/ram/README.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -70,6 +70,7 @@ generate_ram -mask_size bits
7070
| `-filler_cells` | A list of filler cells to use. Example: `{FILL_X1 FILL_X2 FILL_X4}`. |
7171
| `-tapcell` | The name of the tapcell master to insert into the grid to obey latchup requirements. Requires `-max_tap_dist`. If this argument is not provided, no tapcells will be inserted. |
7272
| `-max_tap_dist` | The distance (in microns) that tapcells should be placed apart. Requires `-tapcell`. |
73+
| `-use_latch` | If set to `1`, uses a two-phase latch-based design instead of flip-flops for the storage elements. Functionally equivalent to flip-flop design with smaller storage cell area. Default: `0`. |
7374
| `-write_behavioral_verilog` | Write a behavioral Verilog model of the RAM array to the specified file. |
7475

7576
## Example scripts

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: 175 additions & 37 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(
@@ -614,31 +624,33 @@ void RamGen::findMasters()
614624
}
615625
and2_ports_ = buildPortMap(and2_cell_);
616626

617-
if (!storage_cell_) {
618-
// FIXME
619-
// Still needs changes to get right type of flip-flop
620-
storage_cell_ = findMaster(
621-
[](sta::LibertyPort* port) {
622-
if (!port->isRegOutput()) {
623-
return false;
624-
}
625-
// looking for DFF specifically
626-
auto cell = port->libertyCell();
627-
auto port_iter = cell->portIterator();
628-
while (port_iter->hasNext()) {
629-
auto p = port_iter->next()->libertyPort();
630-
// check to filter out latches
631-
if (p && p->isLatchData()) {
632-
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()) {
633634
return false;
634635
}
635-
}
636-
delete port_iter;
637-
return true;
638-
},
639-
"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_);
640653
}
641-
storage_ports_ = buildPortMap(storage_cell_);
642654

643655
if (!clock_gate_cell_) {
644656
clock_gate_cell_ = findMaster(
@@ -657,6 +669,41 @@ void RamGen::findMasters()
657669
}
658670
buffer_ports_ = buildPortMap(buffer_cell_);
659671

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 lp = port_iter->next()->libertyPort();
688+
if (lp) {
689+
if (lp->isLatchData()) {
690+
has_latch_data = true;
691+
}
692+
if (lp->isClock()) {
693+
has_gate = true;
694+
}
695+
}
696+
}
697+
delete port_iter;
698+
return has_latch_data && has_gate;
699+
},
700+
"latch");
701+
}
702+
703+
if (use_latch_ && latch_cell_) {
704+
latch_ports_ = buildPortMap(latch_cell_);
705+
}
706+
660707
// aoi cells used for column mux functionality when column_mux_ratio > 1
661708
// uses truth table simulation to identify AOI22 and discover port names
662709
// to work for all PDKs, avoiding hardcoding PDK-specific expression parsing
@@ -1008,6 +1055,7 @@ void RamGen::generate(const int mask_size,
10081055
const int num_words,
10091056
const int column_mux_ratio,
10101057
const int read_ports,
1058+
const bool use_latch,
10111059
dbMaster* storage_cell,
10121060
dbMaster* tristate_cell,
10131061
dbMaster* inv_cell,
@@ -1065,6 +1113,7 @@ void RamGen::generate(const int mask_size,
10651113

10661114
logger_->info(RAM, 3, "Generating {}", ram_name);
10671115

1116+
use_latch_ = use_latch;
10681117
storage_cell_ = storage_cell;
10691118
tristate_cell_ = tristate_cell;
10701119
inv_cell_ = inv_cell;
@@ -1073,6 +1122,32 @@ void RamGen::generate(const int mask_size,
10731122
clock_gate_cell_ = nullptr;
10741123
buffer_cell_ = nullptr;
10751124
aoi22_cell_ = nullptr;
1125+
latch_cell_ = nullptr;
1126+
1127+
if (use_latch_ && storage_cell_) {
1128+
auto sta_cell = network_->dbToSta(storage_cell_);
1129+
auto liberty = network_->libertyCell(sta_cell);
1130+
bool is_latch = false;
1131+
auto port_iter = std::unique_ptr<sta::ConcreteCellPortIterator>(
1132+
liberty->portIterator());
1133+
while (port_iter->hasNext()) {
1134+
auto lp = port_iter->next()->libertyPort();
1135+
if (lp && lp->isLatchData()) {
1136+
is_latch = true;
1137+
break;
1138+
}
1139+
}
1140+
if (!is_latch) {
1141+
logger_->error(RAM,
1142+
37,
1143+
"-storage_cell {} is not a latch cell. Please provide a "
1144+
"latch cell when setting -use_latch 1.",
1145+
storage_cell_->getName());
1146+
}
1147+
latch_cell_ = storage_cell_;
1148+
storage_cell_ = nullptr;
1149+
}
1150+
10761151
findMasters();
10771152

10781153
auto chip = db_->getChip();
@@ -1275,6 +1350,17 @@ void RamGen::generate(const int mask_size,
12751350
}
12761351
}
12771352

1353+
// For latch-based ram: mid_nets carry data from negative latch down to all
1354+
// pos latches per bit in the same column
1355+
vector<dbNet*> mid_nets(word_size);
1356+
dbNet* clk_b_net = nullptr;
1357+
if (use_latch_) {
1358+
clk_b_net = makeNet("global", "clk_b");
1359+
for (int bit = 0; bit < word_size; ++bit) {
1360+
mid_nets[bit] = makeNet("mid", fmt::format("b{}", bit));
1361+
}
1362+
}
1363+
12781364
// For column_mux_ratio > 1, create one shared select_b net per row before
12791365
// iterating over word_idx. All words in the same row share this net as the
12801366
// tristate TE_B enable signal. Only word_idx=0 instantiates the select_inv
@@ -1311,7 +1397,7 @@ void RamGen::generate(const int mask_size,
13111397
word_decoder_nets[row],
13121398
shared_select_b_nets,
13131399
(word_idx == 0 || column_mux_ratio == 1),
1314-
D_nets,
1400+
use_latch_ ? mid_nets : D_nets,
13151401
word_output_nets);
13161402
}
13171403
}
@@ -1411,6 +1497,54 @@ void RamGen::generate(const int mask_size,
14111497
}
14121498
}
14131499

1500+
if (use_latch_) {
1501+
auto clk_inv_cell = std::make_unique<Cell>();
1502+
makeInst(clk_inv_cell.get(),
1503+
"neg_row",
1504+
"clk_inv",
1505+
inv_cell_,
1506+
{{inv_ports_[{PortRoleType::DataIn, 0}], clock->getNet()},
1507+
{inv_ports_[{PortRoleType::DataOut, 0}], clk_b_net}});
1508+
ram_grid_.addCell(std::move(clk_inv_cell), col_cell_count);
1509+
1510+
const int nl_col_offset = (column_mux_ratio == 4) ? 1 : 0;
1511+
1512+
for (int slice = 0; slice < slices_per_word; ++slice) {
1513+
for (int bit = 0; bit < mask_size; ++bit) {
1514+
const int global_bit = slice * mask_size + bit;
1515+
const int group_start
1516+
= slice * (mask_size * column_mux_ratio + column_mux_ratio)
1517+
+ bit * column_mux_ratio;
1518+
const int nl_col = group_start + nl_col_offset;
1519+
for (int c = group_start; c < group_start + column_mux_ratio; ++c) {
1520+
if (c == nl_col) {
1521+
auto nl_cell = std::make_unique<Cell>();
1522+
makeInst(
1523+
nl_cell.get(),
1524+
// NOLINTNEXTLINE(misc-include-cleaner)
1525+
fmt::format("neg_lat_b{}", global_bit),
1526+
"nlat",
1527+
latch_cell_,
1528+
{{latch_ports_[{PortRoleType::Clock, 0}], clk_b_net},
1529+
{latch_ports_[{PortRoleType::DataIn, 0}], D_nets[global_bit]},
1530+
{latch_ports_[{PortRoleType::DataOut, 0}],
1531+
mid_nets[global_bit]}});
1532+
ram_grid_.addCell(std::move(nl_cell), c);
1533+
} else {
1534+
ram_grid_.addCell(nullptr, c);
1535+
}
1536+
}
1537+
}
1538+
}
1539+
for (int slice = 0; slice < slices_per_word; ++slice) {
1540+
for (int w = 0; w < column_mux_ratio; ++w) {
1541+
int sel_col = slice * (mask_size * column_mux_ratio + column_mux_ratio)
1542+
+ mask_size * column_mux_ratio + w;
1543+
ram_grid_.addCell(nullptr, sel_col);
1544+
}
1545+
}
1546+
}
1547+
14141548
for (int slice = 0; slice < slices_per_word; ++slice) {
14151549
for (int bit = 0; bit < mask_size; ++bit) {
14161550
int bit_idx = bit + slice * mask_size;
@@ -1434,6 +1568,10 @@ void RamGen::generate(const int mask_size,
14341568
ram_grid_.addCell(std::move(word_sel_cell), col_cell_count - 1);
14351569
}
14361570

1571+
// One extra row at the top for placing input buffers, one extra row for
1572+
// negative latches if use latch-based ram
1573+
const int num_rows_grid = num_rows + 1 + (use_latch_ ? 1 : 0);
1574+
14371575
auto cell_inv_layout = std::make_unique<Layout>(odb::vertical);
14381576
// check for AND gate, specific case for 2 words
14391577
int inv_col_cells = 0;
@@ -1474,8 +1612,8 @@ void RamGen::generate(const int mask_size,
14741612
++inv_col_cells;
14751613
}
14761614
}
1477-
// Pad remaining slots so this column matches the grid height (num_rows + 1)
1478-
while (inv_col_cells < num_rows + 1) {
1615+
// Pad remaining slots so this column matches the grid height (num_rows_grid)
1616+
while (inv_col_cells < num_rows_grid) {
14791617
cell_inv_layout->addCell(nullptr);
14801618
++inv_col_cells;
14811619
}
@@ -1489,7 +1627,7 @@ void RamGen::generate(const int mask_size,
14891627
if (tapcell_) {
14901628
// max tap distance specified is greater than the length of ram
14911629
if (ram_grid_.getRowWidth() <= max_tap_dist) {
1492-
auto tapcell_layout = generateTapColumn(num_rows, 0);
1630+
auto tapcell_layout = generateTapColumn(num_rows_grid - 1, 0);
14931631
ram_grid_.insertLayout(std::move(tapcell_layout), 0);
14941632
} else {
14951633
// needed this calculation so first cells have right distance
@@ -1500,7 +1638,8 @@ void RamGen::generate(const int mask_size,
15001638
for (int col = 0; col < ram_grid_.numLayouts(); ++col) {
15011639
if (nearest_tap + ram_grid_.getLayoutWidth(col) >= max_tap_dist) {
15021640
// if the nearest_tap is too far, generate tap column
1503-
auto tapcell_layout = generateTapColumn(num_rows, tapcell_count);
1641+
auto tapcell_layout
1642+
= generateTapColumn(num_rows_grid - 1, tapcell_count);
15041643
ram_grid_.insertLayout(std::move(tapcell_layout), col);
15051644
++col; // col adjustment after insertion
15061645
nearest_tap = 0;
@@ -1510,7 +1649,8 @@ void RamGen::generate(const int mask_size,
15101649
}
15111650
// check for last column in the grid
15121651
if (nearest_tap >= max_tap_dist) {
1513-
auto tapcell_layout = generateTapColumn(num_rows, tapcell_count);
1652+
auto tapcell_layout
1653+
= generateTapColumn(num_rows_grid - 1, tapcell_count);
15141654
ram_grid_.addLayout(std::move(tapcell_layout));
15151655
}
15161656
}
@@ -1524,8 +1664,6 @@ void RamGen::generate(const int mask_size,
15241664

15251665
int num_sites = ram_grid_.getRowWidth() / db_sites->getWidth();
15261666

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

src/ram/src/ram.i

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ generate_ram_netlist_cmd(int mask_size,
3737
const char* tristate_cell_name,
3838
const char* inv_cell_name,
3939
const int read_ports,
40+
bool use_latch,
4041
const char* tapcell_name,
4142
const int max_tap_dist)
4243
{
@@ -88,7 +89,7 @@ generate_ram_netlist_cmd(int mask_size,
8889
}
8990
}
9091

91-
ram_gen->generate(mask_size, word_size, num_words, column_mux_ratio, read_ports,
92+
ram_gen->generate(mask_size, word_size, num_words, column_mux_ratio, read_ports, use_latch,
9293
storage_cell, tristate_cell, inv_cell, tapcell,
9394
max_tap_dist);
9495
}

0 commit comments

Comments
 (0)