From 2a92fe945b0667c8913289690270299c0b87e5d4 Mon Sep 17 00:00:00 2001 From: rohithsiddi Date: Fri, 20 Mar 2026 17:37:36 +0530 Subject: [PATCH 1/2] Use yosys stat -json for metrics extraction Replace text-based synth_stat.txt with JSON output from stat -json. Update genMetrics.py to parse the JSON instead of using regex. Fixes #3485 Signed-off-by: rohithsiddi --- docs/tutorials/FlowTutorial.md | 4 +-- flow/scripts/synth.tcl | 2 +- flow/util/genMetrics.py | 53 +++++++++++++++++++++++++--------- 3 files changed, 43 insertions(+), 16 deletions(-) diff --git a/docs/tutorials/FlowTutorial.md b/docs/tutorials/FlowTutorial.md index ecd403461b..4752e8eb72 100644 --- a/docs/tutorials/FlowTutorial.md +++ b/docs/tutorials/FlowTutorial.md @@ -311,7 +311,7 @@ abc.constr klayout.lyt klayout_tech.lef lib | `congestion.rpt` | `VDD.rpt` | `VSS.rpt` | | `5_route_drc.rpt` | `final_clocks.webp` | `final_placement.webp` | | `antenna.log` | `final_clocks.webp` | `final.webp` | -| `synth_stat.txt` | `synth_check.txt` | `final_resizer.webp` | +| `synth_stat.json` | `synth_check.txt` | `final_resizer.webp` | The table below briefly describes the reports directory files. @@ -325,7 +325,7 @@ The table below briefly describes the reports directory files. | `antenna.log` | Antenna check log report. | | `final_placement.webp` | Extracted image after final placement. | | `final.webp` | Extracted image after routing. | -| `synth_stat.txt` | Post synthesis design statistics log saved here. | +| `synth_stat.json` | Post synthesis design statistics in JSON format. | The flow completes with the message below by creating a merged final GDS file. diff --git a/flow/scripts/synth.tcl b/flow/scripts/synth.tcl index 8554f6d61c..fbe763b3c5 100644 --- a/flow/scripts/synth.tcl +++ b/flow/scripts/synth.tcl @@ -226,7 +226,7 @@ if { $::env(SYNTH_INSBUF) } { # Reports tee -o $::env(REPORTS_DIR)/synth_check.txt check -tee -o $::env(REPORTS_DIR)/synth_stat.txt stat {*}$lib_args +tee -o $::env(REPORTS_DIR)/synth_stat.json stat -json {*}$lib_args # check the design is composed exclusively of target cells, and # check for other problems diff --git a/flow/util/genMetrics.py b/flow/util/genMetrics.py index fbf746cdfc..a078f3d95e 100755 --- a/flow/util/genMetrics.py +++ b/flow/util/genMetrics.py @@ -231,20 +231,47 @@ def extract_metrics( # Synthesis # ========================================================================= - # The new format (>= 0.57) is: cells - extractTagFromFile( - "synth__design__instance__count__stdcell", - metrics_dict, - "^\\s+(\\d+)\\s+[-0-9.]+\\s+cells$", - rptPath + "/synth_stat.txt", - ) + synth_stat_file = rptPath + "/synth_stat.json" + try: + with open(synth_stat_file) as f: + synth_stat = json.load(f) + modules = synth_stat.get("modules", {}) + # Prefer lookup by design name; yosys may prefix module names with a + # backslash in JSON output, so try both forms. Fall back to the last + # module in the output, which yosys emits last for the top-level module. + top_module = ( + modules.get(design) + or modules.get("\\" + design) + or (list(modules.values())[-1] if modules else None) + ) + + num_cells = top_module.get("num_cells") if top_module else None + if num_cells is None: + print( + "[WARN] Tag synth__design__instance__count__stdcell not found in {}.".format( + synth_stat_file + ), + "Will use N/A.", + ) + metrics_dict["synth__design__instance__count__stdcell"] = "N/A" + else: + metrics_dict["synth__design__instance__count__stdcell"] = int(num_cells) - extractTagFromFile( - "synth__design__instance__area__stdcell", - metrics_dict, - "Chip area for (?:top )?module.*: +(\\S+)", - rptPath + "/synth_stat.txt", - ) + area = top_module.get("area") if top_module else None + if area is None: + print( + "[WARN] Tag synth__design__instance__area__stdcell not found in {}.".format( + synth_stat_file + ), + "Will use N/A.", + ) + metrics_dict["synth__design__instance__area__stdcell"] = "N/A" + else: + metrics_dict["synth__design__instance__area__stdcell"] = float(area) + except (IOError, json.JSONDecodeError): + print("[ERROR] Failed to open file:", synth_stat_file) + metrics_dict["synth__design__instance__count__stdcell"] = "ERR" + metrics_dict["synth__design__instance__area__stdcell"] = "ERR" # Clocks # ========================================================================= From 547d496abbfbbeeaf9a7fd26470161dd3555eef6 Mon Sep 17 00:00:00 2001 From: rohithsiddi Date: Fri, 20 Mar 2026 22:07:37 +0530 Subject: [PATCH 2/2] genMetrics: skip non-JSON prefix in synth_stat.json Signed-off-by: rohithsiddi --- flow/util/genMetrics.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/flow/util/genMetrics.py b/flow/util/genMetrics.py index a078f3d95e..07151a59ee 100755 --- a/flow/util/genMetrics.py +++ b/flow/util/genMetrics.py @@ -234,7 +234,10 @@ def extract_metrics( synth_stat_file = rptPath + "/synth_stat.json" try: with open(synth_stat_file) as f: - synth_stat = json.load(f) + content = f.read() + # yosys may emit log messages (e.g. "Found gzip magic in file...") + # before the JSON when reading compressed liberty files. Skip them. + synth_stat = json.loads(content[content.index("{") :]) modules = synth_stat.get("modules", {}) # Prefer lookup by design name; yosys may prefix module names with a # backslash in JSON output, so try both forms. Fall back to the last @@ -268,7 +271,7 @@ def extract_metrics( metrics_dict["synth__design__instance__area__stdcell"] = "N/A" else: metrics_dict["synth__design__instance__area__stdcell"] = float(area) - except (IOError, json.JSONDecodeError): + except (IOError, json.JSONDecodeError, ValueError): print("[ERROR] Failed to open file:", synth_stat_file) metrics_dict["synth__design__instance__count__stdcell"] = "ERR" metrics_dict["synth__design__instance__area__stdcell"] = "ERR"