ram: Cross platform testing and support#9991
ram: Cross platform testing and support#9991braydenlouie wants to merge 19 commits intoThe-OpenROAD-Project:masterfrom
Conversation
add test make_7x7_nangate45 exercising generate_ram with Nangate45 standard cells. Uses an intentionally odd 7-word x 1-byte (56-bit) configuration to stress the decoder logic on a non-power-of-2 count. also fix a latent bug in makeCellBit() where the clock-pin name probe only checked for CLK (sky130hd) or GATE (latch cells), silently missing CK which is the Nangate45 DFF_X1 clock pin name. The probe chain is extended to CLK -> CK -> GATE. Resolves The-OpenROAD-Project#9468. Signed-off-by: OpenROAD Contributor <contributor@openroad.org>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
|
@rovinski I haven't completed testing with |
There was a problem hiding this comment.
Code Review
This pull request introduces a more robust mechanism for resolving technology-specific pin names in the RamGen class by utilizing a new PinRole system and a buildPinMap method. It also adds a new regression test for a 7x7 RAM configuration using the Nangate45 library. Several issues were identified in the review: a critical copy-paste error in the decoder logic, incorrect parameters in the new 7x7 test file and its golden Verilog output, leftover debug logging, and redundant code blocks that should be cleaned up before merging.
src/ram/src/ram.cpp
Outdated
| {{and2_pins_[{PinRoleType::DataIn, 0}], addr_nets[i]}, | ||
| {and2_pins_[{PinRoleType::DataIn, 0}], input_net}, | ||
| {and2_pins_[{PinRoleType::DataIn, 0}], prev_net}}); |
There was a problem hiding this comment.
There appears to be a bug in the connections for this makeInst call. All three connections are incorrectly mapped to PinRoleType::DataIn with index 0, and there is no output connection. This looks like a copy-paste error.
Based on the logic of the surrounding code, the connections should likely be two distinct inputs and one output.
{{and2_pins_[{PinRoleType::DataIn, 0}], addr_nets[i]},
{and2_pins_[{PinRoleType::DataIn, 1}], input_net},
{and2_pins_[{PinRoleType::DataOut, 0}], prev_net}};| module RAM7x8 ( | ||
| clk, | ||
| D, | ||
| Q, | ||
| addr_rw, | ||
| we | ||
| ); | ||
| input clk; | ||
| input [7:0] D; | ||
| output reg [7:0] Q; | ||
| input [2:0] addr_rw; | ||
| input [0:0] we; | ||
|
|
||
| // memory array declaration | ||
| reg [7:0] mem[0:6]; | ||
|
|
||
| // write logic | ||
| integer i; | ||
| always @(posedge clk) begin | ||
| for (i = 0; i < 1; i = i + 1) begin | ||
| if (we[i]) begin | ||
| mem[addr_rw][i*8 +:8] <= D[i*8 +:8]; | ||
| end | ||
| end | ||
| end | ||
|
|
||
| // read logic | ||
| always @(*) begin | ||
| Q = mem[addr_rw]; | ||
| end | ||
|
|
||
| endmodule |
There was a problem hiding this comment.
The contents of this golden Verilog file seem incorrect for a 7x7 RAM. It appears to have been copied from an 8-bit wide RAM test and not fully updated.
Specifically:
- The module name is
RAM7x8, but should beRAM7x7. - The
DandQports are 8 bits wide ([7:0]), but should be 7 bits ([6:0]). - The memory array
memis declared asreg [7:0], but should bereg [6:0]. - The write logic uses an 8-bit part-select (
i*8 +:8), but should use 7.
Please update this file to reflect the correct behavior for a 7-word by 7-bit RAM.
There was a problem hiding this comment.
Gemini is correct here. Something not right with the behavioral model.
src/ram/src/ram.cpp
Outdated
| // Resolve the clock-pin name for the storage cell by querying the | ||
| // Liberty data. This avoids hard-coding technology-specific pin names | ||
| // (e.g. "CLK", "CK") and works with any technology node. | ||
| // const char* clk_pin = nullptr; | ||
| // const char* data_in_pin = nullptr; | ||
| // auto sta_cell = network_->dbToSta(storage_cell_); | ||
| // if (sta_cell) { | ||
| // auto liberty = network_->libertyCell(sta_cell); | ||
| // if (liberty) { | ||
| // auto port_iter = liberty->portIterator(); | ||
| // while (port_iter->hasNext()) { | ||
| // auto lib_port = port_iter->next(); | ||
| // if (lib_port->libertyPort() && lib_port->libertyPort()->isClock()) { | ||
| // clk_pin = lib_port->name(); | ||
| // } | ||
| // if (lib_port->libertyPort() && | ||
| // lib_port->libertyPort()->isLatchData()) { | ||
| // data_in_pin = lib_port->name(); | ||
| // } | ||
| // } | ||
| // delete port_iter; | ||
| // } | ||
| // } | ||
| // if (!clk_pin) { | ||
| // logger_->error(RAM, | ||
| // 12, | ||
| // "No clock pin found on storage cell {}.", | ||
| // storage_cell_->getName()); | ||
| // } |
src/ram/src/ram.cpp
Outdated
| logger_->info(RAM, | ||
| 98, | ||
| "port name:{} dir:{} isPwrGnd:{}", | ||
| lib_port->name(), | ||
| dir->name(), | ||
| lib_port->isPwrGnd()); |
src/ram/src/ram.cpp
Outdated
| for (const auto& [role, name] : clock_gate_pins_) { | ||
| logger_->info(RAM, | ||
| 99, | ||
| "Clock gate pin - Role: {}/{}, Name: {}", | ||
| static_cast<int>(role.type), | ||
| role.index, | ||
| name); | ||
| } |
src/ram/test/make_7x7_nangate45.tcl
Outdated
| -word_size 7 \ | ||
| -num_words 7 \ | ||
| -read_ports 1 \ | ||
| -read_ports 1 \ |
| // for map so that keys are comparable | ||
| bool operator<(const PinRole& other) const | ||
| { | ||
| return std::tie(type, index) < std::tie(other.type, other.index); |
There was a problem hiding this comment.
warning: no header providing "std::tie" is directly included [misc-include-cleaner]
src/ram/include/ram/ram.h:9:
- #include <utility>
+ #include <tuple>
+ #include <utility>
src/ram/include/ram/ram.h
Outdated
|
|
||
| private: | ||
| void findMasters(); | ||
| std::map<PinRole, std::string> buildPinMap(odb::dbMaster*); |
There was a problem hiding this comment.
warning: no header providing "std::map" is directly included [misc-include-cleaner]
src/ram/include/ram/ram.h:7:
- #include <memory>
+ #include <map>
+ #include <memory>
src/ram/src/ram.cpp
Outdated
| return best; | ||
| } | ||
|
|
||
| std::map<PinRole, std::string> RamGen::buildPinMap(dbMaster* master) |
There was a problem hiding this comment.
warning: no header providing "std::map" is directly included [misc-include-cleaner]
src/ram/src/ram.cpp:10:
- #include <memory>
+ #include <map>
+ #include <memory>Signed-off-by: braydenlouie <braydenl9988@gmail.com>
| // for map so that keys are comparable | ||
| bool operator<(const PinRole& other) const | ||
| { | ||
| return std::tie(type, index) < std::tie(other.type, other.index); |
There was a problem hiding this comment.
warning: no header providing "std::tie" is directly included [misc-include-cleaner]
src/ram/include/ram/ram.h:10:
- #include <utility>
+ #include <tuple>
+ #include <utility>Signed-off-by: braydenlouie <braydenl9988@gmail.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
|
clang-tidy review says "All clean, LGTM! 👍" |
src/ram/include/ram/ram.h
Outdated
| Ground | ||
| }; | ||
|
|
||
| struct PinRole |
There was a problem hiding this comment.
I would change Pin --> Port in all cases as technically you are dealing with the logical port here.
| "ram" | ||
| TESTS | ||
| make_8x8 | ||
| make_7x7_nangate45 |
There was a problem hiding this comment.
Can you also update make_8x8 to make_8x8_sky130?
| } | ||
| delete port_iter; | ||
| return pin_map; | ||
| } |
There was a problem hiding this comment.
Should have some error checking to avoid using badly defined cells. e.g.
- Exactly 1 primary power
- Exactly 1 primary ground
- Exactly the number of pins which are expected for the given cell type
Or I guess I suppose you could check those things after the cell map has been made, if easier.
It doesn't need to be solved in this PR, but you should mark as a TODO item to check against various kinds of flip-flops to make sure invalid ones are flagged, e.g. scan flops, flip-flops with reset, etc. Anything that doesn't have only clk, D, and Q ports. There are occasionally some flip-flops that have a Qn (negated Q) output and those are ok, Qn is just left disconnected.
| module RAM7x8 ( | ||
| clk, | ||
| D, | ||
| Q, | ||
| addr_rw, | ||
| we | ||
| ); | ||
| input clk; | ||
| input [7:0] D; | ||
| output reg [7:0] Q; | ||
| input [2:0] addr_rw; | ||
| input [0:0] we; | ||
|
|
||
| // memory array declaration | ||
| reg [7:0] mem[0:6]; | ||
|
|
||
| // write logic | ||
| integer i; | ||
| always @(posedge clk) begin | ||
| for (i = 0; i < 1; i = i + 1) begin | ||
| if (we[i]) begin | ||
| mem[addr_rw][i*8 +:8] <= D[i*8 +:8]; | ||
| end | ||
| end | ||
| end | ||
|
|
||
| // read logic | ||
| always @(*) begin | ||
| Q = mem[addr_rw]; | ||
| end | ||
|
|
||
| endmodule |
There was a problem hiding this comment.
Gemini is correct here. Something not right with the behavioral model.
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
1 similar comment
|
clang-tidy review says "All clean, LGTM! 👍" |
There was a problem hiding this comment.
It isn't sufficient to just change the vok file, if the verilog generator is wrong, then it needs to be fixed. How was the vok originally created?
There was a problem hiding this comment.
The Verilog Generator was fine. The original file was generated before we added the bit masking, so it defaulted to 8 bits in the mask. The test has been changed so that it has 7 bits per mask; the dimensions have changed, but so have the input and output port lengths.
src/ram/test/make_7x7_nangate45.tcl
Outdated
| # | ||
| # Test: ram/make_7x7_nangate45 | ||
| # Verifies that generate_ram works correctly with the Nangate45 technology | ||
| # node using an intentionally odd 7-word x 1-byte (56-bit) configuration |
There was a problem hiding this comment.
| # node using an intentionally odd 7-word x 1-byte (56-bit) configuration | |
| # node using an intentionally odd 7-word x 7-bit (49-bit) configuration |
src/ram/test/make_7x7_nangate45.tcl
Outdated
| # Verifies that generate_ram works correctly with the Nangate45 technology | ||
| # node using an intentionally odd 7-word x 1-byte (56-bit) configuration | ||
| # to exercise the decoder logic on a non-power-of-2 word count. | ||
| # Addresses Issue #9468. |
There was a problem hiding this comment.
Don't need to include that here
| # Addresses Issue #9468. |
src/ram/src/ram.cpp
Outdated
| ground_net->setSigType(odb::dbSigType::GROUND); | ||
|
|
||
| // find a way to get the power and ground net names associated with cells used | ||
| // finds power and gorund of a cell if not given |
There was a problem hiding this comment.
| // finds power and gorund of a cell if not given | |
| // finds power and ground of a cell if not given |
src/ram/src/ram.cpp
Outdated
| if (!power_pin || power_pin[0] == '\0') { | ||
| power_pin = inv_ports_[{PortRoleType::Power, 0}].c_str(); | ||
| } | ||
| if (!ground_pin || ground_pin[0] == '\0') { | ||
| ground_pin = inv_ports_[{PortRoleType::Ground, 0}].c_str(); | ||
| } |
There was a problem hiding this comment.
This isn't technically sufficient. Although it would be really odd, it is still technically possible for other masters to have different power/ground names. A complete solution would be to iterate through all of the masters and collect the names into a std::set. Then, you would do a global connect on each member of the set.
99% of the time the set will only have 1 member, but it is good to handle all cases.
This is actually a separate item in #9392: "Add automatic detection of standard cell power/ground names"
It would be nice to keep that as a separate PR to only focus on one change.
There was a problem hiding this comment.
Got it, I'll just keep the 'power_pin' and 'ground_pin' as required user-defined parameters now, and we can fix this in another PR.
|
clang-tidy review says "All clean, LGTM! 👍" |
Signed-off-by: Brayden Louie <braydenl9988@gmail.com>
Signed-off-by: Brayden Louie <braydenl9988@gmail.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
src/ram/test/make_7x7_nangate45.ok
Outdated
| [WARNING ODB-0217] duplicate VIARULE (Via9Array-0) ignoring... | ||
| [INFO ODB-0394] Duplicate site FreePDK45_38x28_10R_NP_162NW_34O in Nangate45 already seen in Nangate45_tech | ||
| [INFO ODB-0227] LEF file: Nangate45/Nangate45.lef, created 135 library cells | ||
| [WARNING RAM-0022] No tapcell is specified. |
There was a problem hiding this comment.
I think ng45 has a tapcell, right? If not, use an x1 fill cell as the tap cell.
src/ram/test/make_7x7_nangate45.tcl
Outdated
| -ver_layer {metal4 0.28 8} \ | ||
| -hor_layer {metal5 0.28 8} \ |
There was a problem hiding this comment.
Does this work? I thought the metals needed to be the first 2 above the first metal layer.
We generally don't want to use above M3 if it can be helped.
There was a problem hiding this comment.
I think Nangate45's density is just too high for us to route on metal2 and metal3. Any valid combination of pitch and width has resulted in violations with the detailed router. I will note that these pitch and width dimensions don't work when tapcells are inserted, so this test will probably change.
Signed-off-by: braydenlouie <braydenl9988@gmail.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
Signed-off-by: Brayden Louie <braydenl9988@gmail.com>
|
clang-tidy review says "All clean, LGTM! 👍" |
|
clang-tidy review says "All clean, LGTM! 👍" |
|
@maliberty @rovinski PR is ready for review |
|
clang-tidy review says "All clean, LGTM! 👍" |
| // for map so that keys are comparable | ||
| bool operator<(const PortRole& other) const | ||
| { | ||
| return std::tie(type, index) < std::tie(other.type, other.index); | ||
| } |
There was a problem hiding this comment.
In c++20 you can use
auto operator<=>(const PortRole&) const = default;
and get all six operators
| if (!tri_enable_name.empty()) { | ||
| // find and remove it from DataIn |
There was a problem hiding this comment.
Why would a tristate enable appear as a DataIn? That doesn't appear possible in the prior loop.
| master->getName(), | ||
| ground_count); | ||
| } | ||
| delete port_iter; |
There was a problem hiding this comment.
avoid manual memory management with a smart pointer and no delete
std::unique_ptr port_iter = liberty->portIterator();
Summary
Type of Change
Impact
RamGen is no longer tied to sky130 port naming conventions and allows using any PDK with a valid Liberty file, with pin names resolved automatically. The existing sky130 behavior is preserved.
Verification
./etc/Build.sh).Related Issues
#9468
#9392