From 8c5d2f750f113ab21f937c6f465e26eaef14faa1 Mon Sep 17 00:00:00 2001 From: "Emil J. Tywoniak" Date: Thu, 5 Sep 2024 16:47:28 +0200 Subject: [PATCH] clock_gate: prototype with demo --- passes/techmap/Makefile.inc | 1 + passes/techmap/clock_gate.cc | 205 +++++++++++++++++++++++++++++++++++ tests/techmap/clock_gate.ys | 28 +++++ 3 files changed, 234 insertions(+) create mode 100644 passes/techmap/clock_gate.cc create mode 100644 tests/techmap/clock_gate.ys diff --git a/passes/techmap/Makefile.inc b/passes/techmap/Makefile.inc index 74813bca93f..e0c7858f995 100644 --- a/passes/techmap/Makefile.inc +++ b/passes/techmap/Makefile.inc @@ -49,6 +49,7 @@ OBJS += passes/techmap/dffunmap.o OBJS += passes/techmap/flowmap.o OBJS += passes/techmap/extractinv.o OBJS += passes/techmap/cellmatch.o +OBJS += passes/techmap/clock_gate.o endif ifeq ($(DISABLE_SPAWN),0) diff --git a/passes/techmap/clock_gate.cc b/passes/techmap/clock_gate.cc new file mode 100644 index 00000000000..10b24ad52a1 --- /dev/null +++ b/passes/techmap/clock_gate.cc @@ -0,0 +1,205 @@ +#include "kernel/yosys.h" +#include "kernel/ff.h" +#include + +USING_YOSYS_NAMESPACE +PRIVATE_NAMESPACE_BEGIN + +struct ClockGateCell { + std::string name; + std::string ce_pin; + std::string clk_in_pin; + std::string clk_out_pin; + + ClockGateCell(std::string& name, std::string& str) : name(RTLIL::escape_id(name)) { + char delimiter = ':'; + size_t pos1 = str.find(delimiter); + if (pos1 == std::string::npos) + log_assert(false && "Not enough ports in descriptor string"); + size_t pos2 = str.find(delimiter, pos1 + 1); + if (pos2 == std::string::npos) + log_assert(false && "Not enough ports in descriptor string"); + size_t pos3 = str.find(delimiter, pos2 + 1); + if (pos3 != std::string::npos) + log_assert(false && "Too many ports in descriptor string"); + + ce_pin = str.substr(0, pos1); + ce_pin = RTLIL::escape_id(ce_pin); + + clk_in_pin = str.substr(pos1 + 1, pos2 - (pos1 + 1)); + clk_in_pin = RTLIL::escape_id(clk_in_pin); + + clk_out_pin = str.substr(pos2 + 1, str.size() - (pos2 + 1)); + clk_out_pin = RTLIL::escape_id(clk_out_pin); + } +}; + +struct ClockgatePass : public Pass { + ClockgatePass() : Pass("clock_gate", "extract clock gating out of flip flops") { } + void help() override { + // TODO + } + + SigMap sigmap; + FfInitVals initvals; + + // One ICG will be generated per ClkNetInfo + // if the number of FFs associated with it is sufficent + struct ClkNetInfo { + // Original, ungated clock into enabled FF + Wire* clk_net; + // Original clock enable into enabled FF + Wire* ce_net; + bool pol_clk; + bool pol_ce; + unsigned int hash() const { + unsigned int h = mkhash_init; + h = mkhash(h, hash_ptr_ops::hash(clk_net)); + h = mkhash(h, hash_ptr_ops::hash(ce_net)); + h = mkhash(h, pol_clk); + h = mkhash(h, pol_ce); + return h; + } + bool operator==(const ClkNetInfo& other) const { + return (clk_net == other.clk_net) && + (ce_net == other.ce_net) && + (pol_clk == other.pol_clk) && + (pol_ce == other.pol_ce); + } + }; + + struct GClkNetInfo { + // How many CE FFs on this CLK net have we seen? + int net_size; + // After ICG generation, we have new gated CLK signals + Wire* new_net; + }; + + ClkNetInfo clk_info_from_ff(FfData& ff) { + Wire* clk = ff.sig_clk.as_wire(); + Wire* ce = ff.sig_ce.as_wire(); + ClkNetInfo info{clk, ce, ff.pol_clk, ff.pol_ce}; + return info; + } + + void execute(std::vector args, RTLIL::Design *design) override { + log_header(design, "Executing CLOCK_GATE pass (extract clock gating out of flip flops).\n"); + + std::optional pos_icg_desc; + std::optional neg_icg_desc; + std::vector tie_lo_ports; + int min_net_size = 0; + + size_t argidx; + for (argidx = 1; argidx < args.size(); argidx++) { + if (args[argidx] == "-pos" && argidx+2 < args.size()) { + auto name = args[++argidx]; + auto rest = args[++argidx]; + pos_icg_desc = ClockGateCell(name, rest); + } + if (args[argidx] == "-neg" && argidx+2 < args.size()) { + auto name = args[++argidx]; + auto rest = args[++argidx]; + neg_icg_desc = ClockGateCell(name, rest); + } + if (args[argidx] == "-tie_lo" && argidx+1 < args.size()) { + tie_lo_ports.push_back(RTLIL::escape_id(args[++argidx])); + } + if (args[argidx] == "-min_net_size" && argidx+1 < args.size()) { + min_net_size = atoi(args[++argidx].c_str()); + } + } + + extra_args(args, argidx, design); + + pool ce_ffs; + dict clk_nets; + + int gated_flop_count = 0; + for (auto module : design->selected_whole_modules()) { + sigmap.set(module); + initvals.set(&sigmap, module); + for (auto cell : module->cells()) { + if (!RTLIL::builtin_ff_cell_types().count(cell->type)) + continue; + + FfData ff(&initvals, cell); + if (ff.has_ce) { + ce_ffs.insert(cell); + + ClkNetInfo info = clk_info_from_ff(ff); + auto it = clk_nets.find(info); + if (it != clk_nets.end()) + it->second.net_size++; + else + clk_nets[info] = GClkNetInfo(); + } + } + + for (auto& clk_net : clk_nets) { + log_debug("checking clk net %s\n", clk_net.first.clk_net->name.c_str()); + auto& clk = clk_net.first; + auto& gclk = clk_net.second; + + if (gclk.net_size < min_net_size) + continue; + + std::optional matching_icg_desc; + + if (pos_icg_desc && clk.pol_clk) + matching_icg_desc = pos_icg_desc; + else if (neg_icg_desc && !clk.pol_clk) + matching_icg_desc = neg_icg_desc; + + if (!matching_icg_desc) + continue; + + log_debug("building ICG for clk net %s\n", clk_net.first.clk_net->name.c_str()); + Cell* icg = module->addCell(NEW_ID, matching_icg_desc->name); + icg->setPort(matching_icg_desc->ce_pin, clk.ce_net); + icg->setPort(matching_icg_desc->clk_in_pin, clk.clk_net); + gclk.new_net = module->addWire(NEW_ID); + icg->setPort(matching_icg_desc->clk_out_pin, gclk.new_net); + // Tie low DFT ports like scan chain enable + for (auto port : tie_lo_ports) + icg->setPort(port, Const(0)); + // Fix CE polarity if needed + if (!clk.pol_ce) { + SigBit ce_fixed_pol = module->NotGate(NEW_ID, clk.ce_net); + icg->setPort(matching_icg_desc->ce_pin, ce_fixed_pol); + } + } + + for (auto cell : ce_ffs) { + FfData ff(&initvals, cell); + ClkNetInfo info = clk_info_from_ff(ff); + auto it = clk_nets.find(info); + log_assert(it != clk_nets.end() && "Bug: desync ce_ffs and clk_nets"); + + if (!it->second.new_net) + continue; + + log_debug("Fix up FF %s\n", cell->name.c_str()); + // Now we start messing with the design + ff.has_ce = false; + // Construct the clock gate + // ICG = integrated clock gate, industry shorthand + ff.sig_clk = (*it).second.new_net; + + // Rebuild the flop + (void)ff.emit(); + + gated_flop_count++; + } + ce_ffs.clear(); + clk_nets.clear(); + } + + // TODO add tests like tests/sim/dffe.v + + log("Converted %d FFs.\n", gated_flop_count); + } +} ClockgatePass; + + +PRIVATE_NAMESPACE_END diff --git a/tests/techmap/clock_gate.ys b/tests/techmap/clock_gate.ys new file mode 100644 index 00000000000..15f570c8602 --- /dev/null +++ b/tests/techmap/clock_gate.ys @@ -0,0 +1,28 @@ +read_verilog << EOT + +module dffe( input clk, en, + input d1, output reg q1, + input d2, output reg q2, + input d3, output reg q3, + input d4, output reg q4, + ); + always @( posedge clk ) begin + if ( en ) + q1 <= d1; + if ( ~en ) + q2 <= d2; + end + always @( negedge clk ) begin + if ( en ) + q3 <= d3; + if ( ~en ) + q4 <= d4; + end +endmodule + +EOT + +proc +opt +clock_gate -pos pdk_icg ce:clkin:clkout -tie_lo scanen +show