blob: 70f6fdc04fac28712f90fd9956b431797601f137 [file] [log] [blame]
#!/usr/bin/env python3
# Copyright 2020-2021 Efabless Corporation
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
# Original code by M. Shalan, Translated to Python by Donn
import io
import re
import math
import click
import textwrap
@click.command()
@click.option("--fanout", required=True)
@click.option("--clk-net", required=True)
@click.option("--root-clkbuf", required=True, help="Clock tree root buffer type")
@click.option("--clkbuf", required=True, help="Clock tree branching buffer type")
@click.option(
"--clkbuf-input-pin", required=True, help="Name of input pin in clock buffers"
)
@click.option(
"--clkbuf-output-pin", required=True, help="Name of output pin in clock buffers"
)
@click.option("--clk-port", required=True, help="Name of clock pin in storage elements")
@click.option("-o", "--output", required=True, help="Name of output netlist")
@click.argument("input_netlist")
def cli(
fanout,
clk_net,
clkbuf,
root_clkbuf,
clkbuf_input_pin,
clkbuf_output_pin,
clk_port,
output,
input_netlist,
):
"""
Shalan's Simple Clock Tree Synthesizer
Made to work on Yosys netlists.
"""
fanout = int(fanout)
netlist_str = open(input_netlist).read()
clock_port_rx = re.compile(rf"{re.escape(clk_port)}\(\s*{re.escape(clk_net)}\s*\)")
leaves = len(clock_port_rx.findall(netlist_str))
levels = math.ceil(math.log(leaves, fanout))
buffers = [0] * (levels + 1)
buffers[0] = leaves
verilog_wires = []
verilog_cells = []
cell_count = 0
for level in range(1, levels):
level_before = level - 1
instance = f"_CTS_buf_{level}_"
input_wire = f"clk_{level}_"
output_wire = f"clk_{level_before}_"
# obviously translated c-style for loop
i = 0
while i < leaves:
next_level_fanout = fanout ** (level + 1)
ii = i // next_level_fanout * next_level_fanout
cell_name = f"{instance}{i}"
verilog_cells.append(
textwrap.dedent(
f"""
{clkbuf} {cell_name} (
\t.{clkbuf_input_pin}({input_wire}{ii}),
\t.{clkbuf_output_pin}({output_wire}{i})
);"""
)
)
verilog_wires.append(f"wire {output_wire}{i};")
buffers[level] += 1
cell_count += 1
i += fanout**level
root_net = f"clk_{levels - 1}_0"
verilog_cells.append(
textwrap.dedent(
f"""
{root_clkbuf} _CTS_root(
\t.{clkbuf_input_pin}({clk_net}),
\t.{clkbuf_output_pin}({root_net})
);"""
)
)
verilog_wires.append(f"wire clk_{levels - 1}_0;\n")
with io.StringIO() as sio:
state = 0
ff_count = 0
for line in netlist_str.split("\n"):
if state == 0:
if "wire" in line:
state = 1
print(line, file=sio)
elif state == 1:
if "wire" not in line and "input" not in line and "output" not in line:
state = 2
print("\n// CTS added wires:", file=sio)
print("\n".join(verilog_wires), file=sio)
print("\n// CTS added buffers:", file=sio)
print("\n".join(verilog_cells), file=sio)
print(line, file=sio)
else:
print(line, file=sio)
elif state == 2:
clk_port = clock_port_rx.search(line)
if clk_port is not None:
clk_wire_name = f"clk_0_{ff_count // fanout * fanout}"
line_replaced = re.sub(re.escape(clk_net), clk_wire_name, line)
ff_count += 1
print(line_replaced, file=sio)
else:
print(line, file=sio)
with open(output, "w") as f:
f.write(sio.getvalue())
if __name__ == "__main__":
cli()