mirror of
https://github.com/chipsalliance/f4pga.git
synced 2025-01-03 03:43:37 -05:00
273 lines
8.5 KiB
Python
273 lines
8.5 KiB
Python
#!/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)
|