Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

dft: per-chain enables, user-configurable name patterns #6412

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ TAGS
.cache
Makefile
__pycache__
venv/

include/ord/Version.hh

Expand Down
2 changes: 2 additions & 0 deletions src/dft/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ target_link_libraries(dft
dft_config_lib
dft_replace_lib
dft_clock_domain_lib
dft_reset_domain_lib
dft_utils_lib
dft_stitch_lib
)
Expand Down Expand Up @@ -105,6 +106,7 @@ add_subdirectory(src/cells)
add_subdirectory(src/clock_domain)
add_subdirectory(src/config)
add_subdirectory(src/replace)
add_subdirectory(src/reset_domain)
add_subdirectory(src/stitch)
add_subdirectory(src/utils)
if(ENABLE_TESTS)
Expand Down
42 changes: 40 additions & 2 deletions src/dft/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,13 +77,51 @@ preview_dft

### Insert DFT

Architect scan chains and connect them up in a way that minimises wirelength. As a result, this
should be run after placement, and after `scan_replace`.
Architect scan chains and connect them up in a way that minimises wirelength. As
a result, this should be run after placement, and after `scan_replace`.

```tcl
insert_dft
[-per_chain_enable]
[-scan_enable_name_pattern <string>]
[-scan_in_name_pattern <string>]
[-scan_out_name_pattern <string>]
```

#### Options

| Switch Name | Description |
| ---- | ---- |
| `-per_chain_enable` | Creates one enable signal per chain instead of reusing the same one for every chain. |
| `-scan_enable_name_pattern` | A format pattern with exactly one set of braces (`{}`) to use to find or create scan enable drivers. The braces will be replaced with the chain's ordinal number (starting at `0`) if `-per_chain_enable` is defined, otherwise, all will share `0`. If an un-escaped forward slash (`/`) is found, instead of searching for and/or creating a top-level port, an instance's pin will be searched for instead where the part of the string preceding the `/` is interpreted as the instance name and part succeeding it will be interpreted as the pin's name. |
| `-scan_in_name_pattern` | A format pattern with exactly one set of braces (`{}`) to use to find or create scan in drivers. The braces will be replaced with the chain's ordinal number (starting at `0`). If an un-escaped forward slash (`/`) is found, instead of searching for and/or creating a top-level port, an instance's pin will be searched for instead where the part of the string preceding the `/` is interpreted as the instance name and part succeeding it will be interpreted as the pin's name. |
| `-scan_out_name_pattern` | A format pattern with exactly one set of braces (`{}`) to use to find or create scan in loads. The braces will be replaced with the chain's ordinal number (starting at `0`). If an un-escaped forward slash (`/`) is found, instead of searching for and/or creating a top-level port, an instance's pin will be searched for instead where the part of the string preceding the `/` is interpreted as the instance name and part succeeding it will be interpreted as the pin's name. |

### Write Scan Chains

Writes a JSON file containing metadata about the current architected scan chain.

The JSON file is currently in this format:

```jsonc
{
"chain_0": {
"cells": [
"scanflop_0",
"scanflop_1",
"scanflop_2"
]
},
/* … other chains … */
}
```

…where the order of `.chain_name.cells` corresponds to the order of the elements
in the scan-chain.

```tcl
write_scan_chains
```

## Example scripts

Expand Down
10 changes: 9 additions & 1 deletion src/dft/include/dft/Dft.hh
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@
// POSSIBILITY OF SUCH DAMAGE.
#pragma once

#include "ClockDomain.hh"
#include "ResetDomain.hh"
#include "db_sta/dbSta.hh"
#include "odb/db.h"
#include "utl/Logger.h"
Expand Down Expand Up @@ -95,7 +97,10 @@ class Dft
//
void scanReplace();

void insertDft();
void insertDft(bool per_chain_enable,
const std::string& scan_enable_name_pattern,
const std::string& scan_in_name_pattern,
const std::string& scan_out_name_pattern);

// Returns a mutable version of DftConfig
DftConfig* getMutableDftConfig();
Expand All @@ -106,6 +111,9 @@ class Dft
// Prints to stdout
void reportDftConfig() const;

// Writes scan chains in a JSON format
void writeScanChains(const std::string& filename);

private:
// If we need to run pre_dft to create the internal state
bool need_to_run_pre_dft_;
Expand Down
52 changes: 49 additions & 3 deletions src/dft/src/Dft.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@

#include "dft/Dft.hh"

#include <boost/property_tree/json_parser.hpp>
#include <boost/property_tree/ptree.hpp>
#include <iostream>

#include "ClockDomain.hh"
Expand Down Expand Up @@ -104,15 +106,59 @@ void Dft::scanReplace()
scan_replace_->scanReplace();
}

void Dft::insertDft()
void Dft::writeScanChains(const std::string& filename)
{
using boost::property_tree::ptree;
if (need_to_run_pre_dft_) {
pre_dft();
}

std::ofstream json_file(filename);
if (!json_file.is_open()) {
logger_->error(
utl::DFT, 13, "Failed to open file {} for writing.", filename);
}
try {
ptree root;

std::vector<std::unique_ptr<ScanChain>> scan_chains = scanArchitect();

for (auto& chain : scan_chains) {
ptree current_chain;
ptree cells;
auto& scan_cells = chain->getScanCells();
for (auto& cell : scan_cells) {
ptree name;
name.put("", cell->getName());
cells.push_back(std::make_pair("", name));
}
current_chain.add_child("cells", cells);
root.add_child(chain->getName(), current_chain);
}

boost::property_tree::write_json(json_file, root);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What is the use case for writing the scan chains into a json?

If you want to provide a command like that, I would suggest to store the created scan chains after insertDft into odb (see the test here: odb/test/cpp/scan/TestScanChain.cpp)

Later in your write json command, you can read that dbScanChain and write the information out, otherwise you will be running architect again on a potentially already scan stitched design.

Copy link
Contributor Author

@donn donn Jan 5, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have a utility that uses that JSON file and ATPG results to create test vectors. I was operating under the assumption that ScanArchitect is a function of the current placement data (and thus re-entrant), so that's why I didn't bother storing its result.

I suppose this would be the more "correct" way to do it, but I'd have to get the extra time authorized to both do this and expose the appropriate data in libopenroad so the utility can consume the data from storage instead (or integrate a DEF parser into the utility)

} catch (std::exception& ex) {
logger_->error(
utl::DFT, 14, "Failed to write JSON report. Exception: {}", ex.what());
}
}

void Dft::insertDft(bool per_chain_enable,
const std::string& scan_enable_name_pattern,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please, add those patterns arguments to DftConfig.hh. You can even create a new class for stitching options like "ScanStitchConfig" to hold them.

See https://abseil.io/tips/173

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Working on it…

const std::string& scan_in_name_pattern,
const std::string& scan_out_name_pattern)
{
if (need_to_run_pre_dft_) {
pre_dft();
}
std::vector<std::unique_ptr<ScanChain>> scan_chains = scanArchitect();

ScanStitch stitch(db_);
stitch.Stitch(scan_chains);
ScanStitch stitch(db_,
per_chain_enable,
scan_enable_name_pattern,
donn marked this conversation as resolved.
Show resolved Hide resolved
scan_in_name_pattern,
donn marked this conversation as resolved.
Show resolved Hide resolved
scan_out_name_pattern);
donn marked this conversation as resolved.
Show resolved Hide resolved
stitch.Stitch(scan_chains, logger_);
}

DftConfig* Dft::getMutableDftConfig()
Expand Down
5 changes: 5 additions & 0 deletions src/dft/src/architect/ScanChain.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -119,4 +119,9 @@ const std::string& ScanChain::getName() const
return name_;
}

void ScanChain::rename(std::string& new_name)
{
name_ = new_name;
}

} // namespace dft
3 changes: 3 additions & 0 deletions src/dft/src/architect/ScanChain.hh
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,9 @@ class ScanChain
// Returns the name of the scan chain
const std::string& getName() const;

// Changes the name of the scan chain
void rename(std::string& new_name);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see this being used anywhere. Please remove.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure


private:
std::string name_;
std::vector<std::unique_ptr<ScanCell>> rising_edge_scan_cells_;
Expand Down
9 changes: 8 additions & 1 deletion src/dft/src/cells/ScanCell.hh
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,15 @@ class ScanCell

odb::dbNet* driver_net = driver->getNet();
if (!driver_net) {
driver_net = odb::dbNet::create(driver->getBlock(), GetTermName(driver));
driver_net = odb::dbNet::create(driver->getBlock(), driver->getName().c_str());
if (!driver_net) {
logger_->error(utl::DFT,
30,
"Failed to create driver net named '{}'",
driver->getName());
}
driver_net->setSigType(odb::dbSigType::SCAN);
driver->connect(driver_net);
}
debugPrint(logger_,
utl::DFT,
Expand Down
56 changes: 54 additions & 2 deletions src/dft/src/dft.i
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@
#include "DftConfig.hh"
#include "ord/OpenRoad.hh"
#include "ScanArchitect.hh"
#include "ClockDomain.hh"
#include "ResetDomain.hh"

dft::Dft * getDft()
{
Expand All @@ -51,6 +53,48 @@ utl::Logger* getLogger()

%}

%include "../../Exception.i"

%typemap(typecheck) dft::ResetActiveEdge {
char *str = Tcl_GetStringFromObj($input, 0);
if (strcasecmp(str, "LOW") == 0) {
$1 = 1;
} else if (strcasecmp(str, "HIGH") == 0) {
$1 = 1;
} else {
$1 = 0;
}
}

%typemap(in) dft::ResetActiveEdge {
char *str = Tcl_GetStringFromObj($input, 0);
if (strcasecmp(str, "LOW") == 0) {
$1 = dft::ResetActiveEdge::Low;
} else /* other values eliminated in typecheck */ {
$1 = dft::ResetActiveEdge::High;
};
}

%typemap(typecheck) dft::ClockEdge {
char *str = Tcl_GetStringFromObj($input, 0);
if (strcasecmp(str, "RISING") == 0) {
$1 = 1;
} else if (strcasecmp(str, "FALLING") == 0) {
$1 = 1;
} else {
$1 = 0;
}
}

%typemap(in) dft::ClockEdge {
char *str = Tcl_GetStringFromObj($input, 0);
if (strcasecmp(str, "FALLING") == 0) {
$1 = dft::ClockEdge::Falling;
} else /* other values eliminated in typecheck */ {
$1 = dft::ClockEdge::Rising;
};
}

%inline
%{

Expand All @@ -64,10 +108,18 @@ void scan_replace()
getDft()->scanReplace();
}

void insert_dft(bool per_chain_enable,
const char* scan_enable_name_pattern,
const char* scan_in_name_pattern,
const char* scan_out_name_pattern)
{
getDft()->insertDft(per_chain_enable, scan_enable_name_pattern, scan_in_name_pattern, scan_out_name_pattern);
}

void insert_dft()
void write_scan_chains(const char* filename_p)
{
getDft()->insertDft();
std::string filename(filename_p);
getDft()->writeScanChains(filename);
}

void set_dft_config_max_length(int max_length)
Expand Down
39 changes: 35 additions & 4 deletions src/dft/src/dft.tcl
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
# ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.

sta::define_cmd_args "preview_dft" { [-verbose]}
sta::define_cmd_args "preview_dft" {[-verbose]}

proc preview_dft { args } {
sta::parse_key_args "preview_dft" args \
Expand Down Expand Up @@ -58,15 +58,46 @@ proc scan_replace { args } {
dft::scan_replace
}

sta::define_cmd_args "insert_dft" { }
sta::define_cmd_args "write_scan_chains" {[json_file_out]}
proc write_scan_chains { args } {
sta::parse_key_args "write_scan_chains" args \
keys {} flags {}

if { [ord::get_db_block] == "NULL" } {
utl::error DFT 15 "No design block found."
}

dft::write_scan_chains [lindex $args 0]
}

sta::define_cmd_args "insert_dft" {
[-per_chain_enable]
[-scan_enable_name_pattern scan_enable_name_pattern]
[-scan_in_name_pattern scan_in_name_pattern]
[-scan_out_name_pattern scan_out_name_pattern]
}
proc insert_dft { args } {
sta::parse_key_args "insert_dft" args \
keys {} flags {}
keys {-scan_enable_name_pattern -scan_in_name_pattern -scan_out_name_pattern} \
flags {-per_chain_enable}

if { [ord::get_db_block] == "NULL" } {
utl::error DFT 9 "No design block found."
}
dft::insert_dft

foreach {flag default} {
-scan_enable_name_pattern "scan_enable_{}"
-scan_in_name_pattern "scan_in_{}"
-scan_out_name_pattern "scan_out_{}"
} {
if { ![info exists keys($flag)] } {
set keys($flag) $default
}
}
dft::insert_dft [info exists flags(-per_chain_enable)] \
$keys(-scan_enable_name_pattern) \
$keys(-scan_in_name_pattern) \
$keys(-scan_out_name_pattern)
}

sta::define_cmd_args "set_dft_config" { [-max_length max_length] \
Expand Down
15 changes: 1 addition & 14 deletions src/dft/src/replace/ScanReplace.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -45,19 +45,6 @@
namespace dft {

namespace {

// Checks if the given LibertyCell is really a Scan Cell with a Scan In and a
// Scan Enable
bool IsScanCell(const sta::LibertyCell* libertyCell)
{
const sta::TestCell* test_cell = libertyCell->testCell();
if (test_cell) {
return getLibertyScanIn(test_cell) != nullptr
&& getLibertyScanEnable(test_cell) != nullptr;
}
return false;
}

// Checks the ports
sta::LibertyPort* FindEquivalentPortInScanCell(
const sta::LibertyPort* non_scan_cell_port,
Expand Down Expand Up @@ -309,7 +296,7 @@ void ScanReplace::collectScanCellAvailable()
continue;
}

if (IsScanCell(liberty_cell)) {
if (utils::IsScanCell(liberty_cell)) {
available_scan_lib_cells_.insert(liberty_cell);
} else {
non_scan_cells.push_back(liberty_cell);
Expand Down
Loading