f4pga: split_inouts moved from arch-defs

Signed-off-by: Unai Martinez-Corral <umartinezcorral@antmicro.com>
This commit is contained in:
Unai Martinez-Corral 2022-08-27 03:04:19 +01:00
parent 831492aa0d
commit 947b0807a0
6 changed files with 302 additions and 12 deletions

View File

@ -23,6 +23,7 @@ from pathlib import Path
from f4pga.flows.common import decompose_depname, get_verbosity_level, sub as common_sub
from f4pga.flows.module import Module, ModuleContext
from f4pga.wrappers.tcl import get_script_path as get_tcl_wrapper_path
from f4pga.utils import split_inouts
class SynthModule(Module):
@ -83,14 +84,7 @@ class SynthModule(Module):
)
yield f"Splitting in/outs..."
common_sub(
"python3",
str(Path(tcl_env["UTILS_PATH"]) / "split_inouts.py"),
"-i",
ctx.outputs.json,
"-o",
ctx.outputs.synth_json,
)
split_inouts(ctx.outputs.json, ctx.outputs.synth_json)
if not Path(ctx.produces.fasm_extra).is_file():
with Path(ctx.produces.fasm_extra).open("w") as wfptr:

View File

@ -78,7 +78,6 @@ setuptools_setup(
author="F4PGA Authors",
description="F4PGA.",
url="https://github.com/chipsalliance/f4pga",
packages=["f4pga", "f4pga.flows", "f4pga.flows.common_modules", "f4pga.wrappers.sh", "f4pga.wrappers.tcl"],
package_dir={"f4pga": "."},
package_data={
"f4pga.flows": [

20
f4pga/utils/__init__.py Normal file
View File

@ -0,0 +1,20 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2022 F4PGA Authors
#
# 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.
#
# SPDX-License-Identifier: Apache-2.0
from f4pga.utils.split_inouts import main as split_inouts

278
f4pga/utils/split_inouts.py Normal file
View File

@ -0,0 +1,278 @@
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2019-2022 F4PGA Authors
#
# 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.
#
# SPDX-License-Identifier: Apache-2.0
"""
This script provides a temporary solution for the problem of inout top level
port representation of the BLIF format.
The script loads the design from a JSON file generated by Yosys. Then it splits
all inout ports along with their nets and connections to cell ports into two.
Suffixes are automatically added to distinguish between the input and the
output part.
The JSON design format used by Yosys is documented there:
- http://www.clifford.at/yosys/cmd_write_json.html
- http://www.clifford.at/yosys/cmd_read_json.html
For example in the given design (in verilog):
module top(
input A,
output B,
inout C
);
IOBUF iobuf (
.I(A),
.O(B),
.IO_$inp(C),
.IO_$out(C)
);
endmodule
the resulting design would be:
module top(
input A,
output B,
input C_$inp,
output C_$out
);
IOBUF iobuf (
.I(A),
.O(B),
.IO_$inp(C_$inp),
.IO_$out(C_$out)
);
endmodule
"""
from pathlib import Path
from os.path import splitext
import simplejson as json
from argparse import ArgumentParser, RawDescriptionHelpFormatter
def find_top_module(design):
"""
Looks for the top-level module in the design. Returns its name. Throws
an exception if none was found.
"""
for name, module in design["modules"].items():
attrs = module["attributes"]
if "top" in attrs and int(attrs["top"]) == 1:
return name
raise RuntimeError("No top-level module found in the design!")
def get_nets(bits):
"""
Returns a set of numbers corresponding to net indices effectively skipping
connections to consts ("0", "1", "x").
>>> get_nets([0, 1, 2, "0", "1", "x", 3, 4, 5])
{0, 1, 2, 3, 4, 5}
"""
return set([n for n in bits if isinstance(n, int)])
def get_free_net(nets):
"""
Given a set of used net indices, returns a new, free index.
>>> get_free_net({0, 1, 2, 4, 5, 6})
3
>>> get_free_net({0, 1, 2, 3, 4, 5, 6})
7
"""
sorted_nets = sorted(list(nets))
# Find a gap in the sequence
for i in range(len(nets) - 1):
n0 = sorted_nets[i]
n1 = sorted_nets[i + 1]
if n1 != (n0 + 1):
return n0 + 1
# No gap was found, return max + 1.
return sorted_nets[-1] + 1
def main(input: str, output: str = None):
if output is None:
output = splitext(input)[0] + "_out.json"
with Path(input).open("r") as fp:
design = json.load(fp)
module_name = find_top_module(design)
# Take a module from the design and split all of its inout ports into pairs of inputs and outputs.
# Newly created ports are given suffixed.
# For example an inout port named "A" is going to be replaced by a pair consisting of "A_$inp" and "A_$out" ports.
# The function also looks for "netnames" that correspond to inout ports being split.
# These ones are removed and replaced with new ones related to newly added input and output ports.
# If any other "netname" mentions a net index connected to a former inout port, then the index is removed from the
# "netname" (replaced by "x").
# If there are only "x" left in the "netname", then it is removed.
# The function returns port name map and net index map.
# The port map is a list of pairs (input name, input/output name).
# There are two entries per an inout.
# The net map is a dict indexed by indices of nets associated with inout ports.
# Each item contains a dict like {"i": int, "o": int} with indices of the inout net split products.
# Get the module
module = design["modules"][module_name]
# Find indices of all used nets
nets = set()
for port in module["ports"].values():
nets |= get_nets(port["bits"])
for netname in module["netnames"].values():
nets |= get_nets(netname["bits"])
for cell in module["cells"].values():
for connection in cell["connections"].values():
nets |= get_nets(connection)
# Get all inout ports
inouts = {k: v for k, v in module["ports"].items() if v["direction"] == "inout"}
# Split ports
new_ports = {}
net_map = {}
port_map = []
for name, port in inouts.items():
# Remove the inout port from the module
del module["ports"][name]
nets -= get_nets(port["bits"])
# Make an input and output port
for dir in ["input", "output"]:
new_name = name + "_$" + dir[:3]
new_port = {"direction": dir, "bits": []}
print("Mapping port '{}' to '{}'".format(name, new_name))
for n in port["bits"]:
if isinstance(n, int):
mapped_n = get_free_net(nets)
print("Mapping net {} to {} ({})".format(n, mapped_n, dir))
if n not in net_map:
net_map[n] = {}
net_map[n][dir[0]] = mapped_n
nets.add(mapped_n)
new_port["bits"].append(mapped_n)
else:
new_port["bits"].append(n)
port_map.append(
(
name,
new_name,
)
)
new_ports[new_name] = new_port
# Add inputs and outputs
module["ports"].update(new_ports)
netnames = module["netnames"]
# Remove netnames related to inout ports
for name, net in list(netnames.items()):
if name in inouts:
print(f"Removing netname '{name}'")
del netnames[name]
# Remove remapped nets
for name, net in list(netnames.items()):
# Remove "bits" used by the net that were re-mapped.
if len(set(net["bits"]) & set(net_map.keys())):
# Remove
net["bits"] = ["x" if b in net_map else b for b in net["bits"]]
# If there is nothing left, remove the whole net.
if all([b == "x" for b in net["bits"]]):
print(f"Removing netname '{name}'")
del netnames[name]
# Add netnames related to new input and output ports
for name, port in new_ports.items():
netnames[name] = {"hide_name": 0, "bits": port["bits"], "attributes": {}}
# Remap cell connections that mention inout ports being split.
# Loop over all cells and their ports.
# If a port contains a connection to an inout net, then the connection is remapped according to the given
# net_map.
# Only ports which names ends on "_$inp" and "_$out" are affected.
module = design["modules"][module_name]
cells = module["cells"]
# Process cells
for name, cell in cells.items():
if "port_directions" not in cell:
continue
port_directions = cell["port_directions"]
connections = cell["connections"]
# Process cell connections
for port_name, port_nets in list(connections.items()):
# Skip if no net of this connection were remapped
if len(set(net_map.keys()) & set(port_nets)) == 0:
continue
# Remove connections to the output net from input port and vice
# versa.
for dir in ["input", "output"]:
if port_directions[port_name] == dir and port_name.endswith("$" + dir[:3]):
for i, n in enumerate(port_nets):
if n in net_map:
mapped_n = net_map[n][dir[0]]
port_nets[i] = mapped_n
print("Mapping connection {}.{}[{}] from {} to {}".format(name, port_name, i, n, mapped_n))
with Path(output).open("w") as fp:
json.dump(design, fp, sort_keys=True, indent=2)
if __name__ == "__main__":
parser = ArgumentParser(description=__doc__, formatter_class=RawDescriptionHelpFormatter)
parser.add_argument("-i", required=True, type=str, help="Input JSON")
parser.add_argument("-o", default=None, type=str, help="Output JSON")
args = parser.parse_args()
main(args.i, args.o)

View File

@ -18,7 +18,6 @@
set -e
SPLIT_INOUTS="${F4PGA_SHARE_DIR}"/scripts/split_inouts.py
CONVERT_OPTS="${F4PGA_SHARE_DIR}"/scripts/convert_compile_opts.py
print_usage () {
@ -177,5 +176,5 @@ if [ ! -z "${YOSYS_COMMANDS}" ]; then
fi
`which yosys` -p "${YOSYS_SCRIPT}" -l $LOG
`which python3` ${SPLIT_INOUTS} -i ${OUT_JSON} -o ${SYNTH_JSON}
`which python3` -m f4pga.utils.split_inouts -i ${OUT_JSON} -o ${SYNTH_JSON}
`which yosys` -p "read_json $SYNTH_JSON; tcl ${CONV_TCL_PATH}"

View File

@ -139,5 +139,5 @@ else
yosys -p "plugin -i uhdm" -p "read_verilog_with_uhdm ${SURELOG_CMD[*]} ${VERILOG_FILES[*]}" -p "tcl ${SYNTH_TCL_PATH}" -l $LOG
fi
python3 ${UTILS_PATH}/split_inouts.py -i ${OUT_JSON} -o ${SYNTH_JSON}
python3 -m f4pga.utils.split_inouts -i ${OUT_JSON} -o ${SYNTH_JSON}
yosys -p "read_json $SYNTH_JSON; tcl $(python3 -m f4pga.wrappers.tcl conv)"