Skip to content

Commit

Permalink
clock_gate: prototype with demo
Browse files Browse the repository at this point in the history
  • Loading branch information
widlarizer committed Sep 5, 2024
1 parent 0fc5812 commit 33ac046
Show file tree
Hide file tree
Showing 3 changed files with 234 additions and 0 deletions.
1 change: 1 addition & 0 deletions passes/techmap/Makefile.inc
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
205 changes: 205 additions & 0 deletions passes/techmap/clock_gate.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
#include "kernel/yosys.h"
#include "kernel/ff.h"
#include <optional>

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<std::string> args, RTLIL::Design *design) override {
log_header(design, "Executing CLOCK_GATE pass (extract clock gating out of flip flops).\n");

std::optional<ClockGateCell> pos_icg_desc;
std::optional<ClockGateCell> neg_icg_desc;
std::vector<std::string> 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<Cell*> ce_ffs;
dict<ClkNetInfo, GClkNetInfo> 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<ClockGateCell> 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
28 changes: 28 additions & 0 deletions tests/techmap/clock_gate.ys
Original file line number Diff line number Diff line change
@@ -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

0 comments on commit 33ac046

Please sign in to comment.