blob: b5f02351a2494b8ae4c6b8ab63c9f219575ef38e [file] [log] [blame]
# Copyright lowRISC contributors.
# Licensed under the Apache License, Version 2.0, see LICENSE for details.
# SPDX-License-Identifier: Apache-2.0
import logging as log
from copy import deepcopy
from functools import partial
from .lib import *
import hjson
def amend_ip(top, ip):
""" Amend additional information into top module
Amended fields:
- size: register space
- clock: converted into ip_clock
- bus_device
- bus_host: none if doesn't exist
- available_input_list: empty list if doesn't exist
- available_output_list: empty list if doesn't exist
- available_inout_list: empty list if doesn't exist
- interrupt_list: empty list if doesn't exist
- alert_list: empty list if doesn't exist
"""
ip_list_in_top = [x["name"].lower() for x in top["module"]]
ipname = ip["name"].lower()
if not ipname in ip_list_in_top:
log.info("TOP doens't use the IP %s. Skip" % ip["name"])
return
# Find index of the IP
ip_idx = ip_list_in_top.index(ipname)
# Needed to detect async alert transitions below
ah_idx = ip_list_in_top.index("alert_handler")
ip_module = top["module"][ip_idx]
# Size
if not "size" in ip_module:
ip_module["size"] = "0x%x" % max(ip["gensize"], 0x1000)
elif ip_module["size"] < ip["gensize"]:
log.error(
"given 'size' field in IP %s is smaller than the required space" %
ip_module["name"])
# bus_device
ip_module["bus_device"] = ip["bus_device"]
# bus_host
if "bus_host" in ip and ip["bus_host"] != "":
ip_module["bus_host"] = ip["bus_host"]
else:
ip_module["bus_host"] = "none"
# available_input_list , available_output_list, available_inout_list
if "available_input_list" in ip:
ip_module["available_input_list"] = ip["available_input_list"]
for i in ip_module["available_input_list"]:
i.pop('desc', None)
i["type"] = "input"
i["width"] = int(i["width"])
else:
ip_module["available_input_list"] = []
if "available_output_list" in ip:
ip_module["available_output_list"] = ip["available_output_list"]
for i in ip_module["available_output_list"]:
i.pop('desc', None)
i["type"] = "output"
i["width"] = int(i["width"])
else:
ip_module["available_output_list"] = []
if "available_inout_list" in ip:
ip_module["available_inout_list"] = ip["available_inout_list"]
for i in ip_module["available_inout_list"]:
i.pop('desc', None)
i["type"] = "inout"
i["width"] = int(i["width"])
else:
ip_module["available_inout_list"] = []
# interrupt_list
if "interrupt_list" in ip:
ip_module["interrupt_list"] = ip["interrupt_list"]
for i in ip_module["interrupt_list"]:
i.pop('desc', None)
i["type"] = "interrupt"
i["width"] = int(i["width"])
else:
ip_module["interrupt_list"] = []
# alert_list
if "alert_list" in ip:
ip_module["alert_list"] = ip["alert_list"]
for i in ip_module["alert_list"]:
i.pop('desc', None)
i["type"] = "alert"
i["width"] = int(i["width"])
# automatically insert asynchronous transition if necessary
if ip_module["clock_connections"]["clk_i"] == \
top["module"][ah_idx]["clock_connections"]["clk_i"]:
i["async"] = 0
else:
i["async"] = 1
else:
ip_module["alert_list"] = []
# scan
if "scan" in ip:
ip_module["scan"] = ip["scan"]
else:
ip_module["scan"] = "false"
# TODO: Replace this part to be configurable from Hjson or template
predefined_modules = {
"corei": "rv_core_ibex",
"cored": "rv_core_ibex",
"dm_sba": "rv_dm",
"debug_mem": "rv_dm"
}
def is_xbar(top, name):
"""Check if the given name is crossbar
"""
xbars = list(filter(lambda node: node["name"] == name, top["xbar"]))
if len(xbars) == 0:
return False, None
if len(xbars) > 1:
log.error("Matching crossbar {} is more than one.".format(name))
raise SystemExit()
return True, xbars[0]
def xbar_addhost(top, xbar, host):
"""Add host nodes information
- xbar: bool, true if the host port is from another Xbar
"""
# Check and fetch host if exists in nodes
obj = list(filter(lambda node: node["name"] == host, xbar["nodes"]))
if len(obj) == 0:
log.warning(
"host %s doesn't exist in the node list. Using default values" %
host)
obj = {
"name": host,
"clock": xbar['clock'],
"reset": xbar['reset'],
"type": "host",
"inst_type": "",
# The default matches RTL default
# pipeline_byp is don't care if pipeline is false
"pipeline": "true",
"pipeline_byp": "true"
}
topxbar["nodes"].append(obj)
return
xbar_bool, xbar_h = is_xbar(top, host)
if xbar_bool:
log.info("host {} is a crossbar. Nothing to deal with.".format(host))
obj[0]["xbar"] = xbar_bool
if 'clock' not in obj[0]:
obj[0]["clock"] = xbar['clock']
if 'reset' not in obj[0]:
obj[0]["reset"] = xbar["reset"]
obj[0]["inst_type"] = predefined_modules[
host] if host in predefined_modules else ""
obj[0]["pipeline"] = obj[0]["pipeline"] if "pipeline" in obj[0] else "true"
obj[0]["pipeline_byp"] = obj[0]["pipeline_byp"] if obj[0][
"pipeline"] == "true" and "pipeline_byp" in obj[0] else "true"
def process_pipeline_var(node):
"""Add device nodes pipeline / pipeline_byp information
- Supply a default of true / true if not defined by xbar
"""
node["pipeline"] = node["pipeline"] if "pipeline" in node else "true"
node["pipeline_byp"] = node[
"pipeline_byp"] if "pipeline_byp" in node else "true"
def xbar_adddevice(top, xbar, device):
"""Add device nodes information
- clock: comes from module if exist, use xbar default otherwise
- reset: comes from module if exist, use xbar default otherwise
- inst_type: comes from module or memory if exist.
- base_addr: comes from module or memory, or assume rv_plic?
- size_byte: comes from module or memory
- xbar: bool, true if the device port is another xbar
"""
deviceobj = list(
filter(lambda node: node["name"] == device,
top["module"] + top["memory"]))
nodeobj = list(filter(lambda node: node["name"] == device, xbar["nodes"]))
xbar_list = [x["name"] for x in top["xbar"] if x["name"] != xbar["name"]]
# case 1: another xbar --> check in xbar list
if device in xbar_list and len(nodeobj) == 0:
log.error(
"Another crossbar %s needs to be specified in the 'nodes' list" %
device)
return
if len(deviceobj) == 0:
# doesn't exist,
# case 1: Crossbar handling
if device in xbar_list:
log.warning(
"device {} in Xbar {} is connected to another Xbar".format(
device, xbar["name"]))
assert len(nodeobj) == 1
nodeobj[0]["xbar"] = True
process_pipeline_var(nodeobj[0])
return
# case 2: predefined_modules (debug_mem, rv_plic)
# TODO: Find configurable solution not from predefined but from object?
if device in predefined_modules:
if device == "debug_mem":
if len(nodeobj) == 0:
# Add new debug_mem
xbar["nodes"].append({
"name": "debug_mem",
"type": "device",
"clock": xbar['clock'],
"reset": xbar['reset'],
"inst_type": predefined_modules["debug_mem"],
"addr_range": [{
"base_addr": top["debug_mem_base_addr"],
"size_byte": "0x1000",
}],
"xbar": False,
"pipeline" : "true",
"pipeline_byp" : "true"
}) # yapf: disable
else:
# Update if exists
node = nodeobj[0]
node["inst_type"] = predefined_modules["debug_mem"]
node["addr_range"] = [{
"base_addr":
top["debug_mem_base_addr"],
"size_byte":
"0x1000"
}]
node["xbar"] = False
process_pipeline_var(node)
else:
log.error("device %s shouldn't be host type" % device)
return
# case 3: not defined
else:
# Crossbar check
log.error(
"device %s doesn't exist in 'module', 'memory', or predefined"
% device)
return
# Search object from module or memory
elif len(nodeobj) == 0:
# found in module or memory but node object doesn't exist.
xbar["nodes"].append({
"name" : device,
"type" : "device",
"clock" : deviceobj[0]["clock"],
"reset" : deviceobj[0]["reset"],
"inst_type" : deviceobj[0]["type"],
"addr_range": [{"base_addr" : deviceobj[0]["base_addr"],
"size_byte": deviceobj[0]["size"]}],
"pipeline" : "true",
"pipeline_byp" : "true",
"xbar" : True if device in xbar_list else False
}) # yapf: disable
else:
# found and exist in the nodes too
node = nodeobj[0]
node["inst_type"] = deviceobj[0]["type"]
node["addr_range"] = [{
"base_addr": deviceobj[0]["base_addr"],
"size_byte": deviceobj[0]["size"]
}]
node["xbar"] = True if device in xbar_list else False
process_pipeline_var(node)
def amend_xbar(top, xbar):
"""Amend crossbar informations to the top list
Amended fields
- clock: Adopt from module clock if exists
- inst_type: Module instance some module will be hard-coded
the tool searches module list and memory list then put here
- base_addr: from top["module"]
- size: from top["module"]
"""
xbar_list = [x["name"] for x in top["xbar"]]
if not xbar["name"] in xbar_list:
log.info(
"Xbar %s doesn't belong to the top %s. Check if the xbar doesn't need"
% (xbar["name"], top["name"]))
return
topxbar = list(
filter(lambda node: node["name"] == xbar["name"], top["xbar"]))[0]
topxbar["connections"] = deepcopy(xbar["connections"])
if "nodes" in xbar:
topxbar["nodes"] = deepcopy(xbar["nodes"])
else:
topxbar["nodes"] = []
# xbar primary clock and reset
topxbar["clock"] = xbar["clock_primary"]
topxbar["reset"] = xbar["reset_primary"]
# Build nodes from 'connections'
device_nodes = set()
for host, devices in xbar["connections"].items():
# add host first
xbar_addhost(top, topxbar, host)
# add device if doesn't exist
device_nodes.update(devices)
log.info(device_nodes)
for device in device_nodes:
xbar_adddevice(top, topxbar, device)
def xbar_cross(xbar, xbars):
"""Check if cyclic dependency among xbars
And gather the address range for device port (to another Xbar)
@param node_name if not "", the function only search downstream
devices starting from the node_name
@param visited The nodes it visited to reach this port. If any
downstream port from node_name in visited, it means
circular path exists. It should be fatal error.
"""
# Step 1: Visit devices (gather the address range)
log.info("Processing circular path check for {}".format(xbar["name"]))
addr = []
for node in [
x for x in xbar["nodes"]
if x["type"] == "device" and "xbar" in x and x["xbar"] == False
]:
addr.extend(node["addr_range"])
# Step 2: visit xbar device ports
xbar_nodes = [
x for x in xbar["nodes"]
if x["type"] == "device" and "xbar" in x and x["xbar"] == True
]
# Now call function to get the device range
# the node["name"] is used to find the host_xbar and its connection. The
# assumption here is that there's only one connection from crossbar A to
# crossbar B.
#
# device_xbar is the crossbar has a device port with name as node["name"].
# host_xbar is the crossbar has a host port with name as node["name"].
for node in xbar_nodes:
xbar_addr = xbar_cross_node(node["name"], xbar, xbars, visited=[])
node["addr_range"] = xbar_addr
def xbar_cross_node(node_name, device_xbar, xbars, visited=[]):
# 1. Get the connected xbar
host_xbars = [x for x in xbars if x["name"] == node_name]
assert len(host_xbars) == 1
host_xbar = host_xbars[0]
log.info("Processing node {} in Xbar {}.".format(node_name,
device_xbar["name"]))
result = [] # [(base_addr, size), .. ]
# Sweep the devices using connections and gather the address.
# If the device is another xbar, call recursive
visited.append(host_xbar["name"])
devices = host_xbar["connections"][device_xbar["name"]]
for node in host_xbar["nodes"]:
if not node["name"] in devices:
continue
if "xbar" in node and node["xbar"] == True:
if not "addr_range" in node:
# Deeper dive into another crossbar
xbar_addr = xbar_cross_node(node["name"], host_xbar, xbars,
visited)
node["addr_range"] = xbar_addr
result.extend(deepcopy(node["addr_range"]))
visited.pop()
return result
def simplify_xbar(xbars):
""" Simplify the address of xbar device port.
It cannot be done without fully expanded addresses.
So, this should be done after adding all xbar addresses.
"""
for xbar in xbars:
xdev_list = [
x for x in xbar["nodes"] if x["type"] == "device" and x["xbar"]
]
for e in xdev_list:
e["addr_range"] = simplify_addr(e, xbar)
# TODO: Move to tlgen
def simplify_addr(dev, xbar):
"""Reduce the number of entries smaller by combining them
If any contiguous regions exist, concatenate them.
For instance, 0x1000 ~ 0x1FFF , 0x2000~ 0x2FFF ==> 0x1000 ~ 0x2FFF
It also checks if there's no device between the gap, then merge
the ranges. For instance:
{0x4000_0000, 0x1_0000}, {0x4008_0000, 0x1_0000} then it combines two
entries into {0x4000_0000, 0x9_0000}
@param addrs List of Dict[Addr] : {'base_addr':,'size_byte':}
"""
addrs = dev["addr_range"]
# Sort based on the base addr
newlist = sorted(addrs, key=lambda k: int(k['base_addr'], 0))
# check if overlap or contiguous
result = []
for e in newlist:
if len(result) == 0:
result.append(e)
continue
# if contiguous
if int(e["base_addr"], 0) == int(result[-1]["base_addr"], 0) + int(
result[-1]["size_byte"], 0):
# update previous entry size
result[-1]["size_byte"] = "0x{:x}".format(
int(result[-1]["size_byte"], 0) + int(e["size_byte"], 0))
continue
if no_device_in_range(xbar, dev["name"], result[-1], e):
# Determine if size can be power of 2 value
smallest_addr_gte = get_next_base_addr(e["base_addr"], xbar,
dev["name"])
# Choose next value
if smallest_addr_gte == -1:
next_value = 0x100000000
else:
next_value = int(smallest_addr_gte["base_addr"], 0)
calc_size = int(e["base_addr"], 0) + int(e["size_byte"], 0) - int(
result[-1]["base_addr"], 0)
# find power of 2 if possible
size_byte = find_pow2_size(result[-1], calc_size, next_value)
result[-1]["size_byte"] = "0x{:x}".format(size_byte)
continue
# If overlapping (Should it be consider? TlGen will catch it)
# Normal case
result.append(e)
# return result
return result
def no_device_in_range(xbar, name, f, t):
"""Check if other devices doesn't overlap with the from <= x < to
"""
from_addr = int(f["base_addr"], 0) + int(f["size_byte"], 0)
to_addr = int(t["base_addr"], 0)
for node in [
x for x in xbar["nodes"]
if x["type"] == "device" and not x["name"] == name
]:
if not "addr_range" in node:
# Xbar?
log.info("Xbar type node cannot be compared in this version.",
"Please use in caution")
continue
assert isinstance(node["addr_range"], list)
for addr in node["addr_range"]:
b_addr = int(addr["base_addr"], 0)
e_addr = b_addr + int(addr["size_byte"], 0)
if e_addr <= from_addr or b_addr >= to_addr:
# No overlap
continue
return False
return True
def get_next_base_addr(addr, xbar, name):
"""Return the least value of base_addr of the IP greater than addr
"""
if isinstance(addr, str):
value = int(addr, 0)
else:
assert isinstance(addr, int)
value = addr
device_list = [
x for x in xbar["nodes"]
if x["type"] == "device" and not x["name"] == name
]
try:
addrs = [a for r in device_list for a in r["addr_range"]]
except KeyError:
log.error("Address range is wrong.\n {}".format(
[x for x in device_list if not "addr_range" in x]))
raise SystemExit()
sorted_list = sorted(addrs, key=lambda k: int(k["base_addr"], 0))
gte_list = [x for x in sorted_list if int(x["base_addr"], 0) > value]
if len(gte_list) == 0:
return -1
return gte_list[0]
def find_pow2_size(addr, min_size, next_value):
"""Find smallest power of 2 value greater than min_size by given addr.
For instance, (0x4000_0000, 0x21000) and `next_value` as 0x40080000,
the result will be 0x4_0000
But it should return result not exceeding the base_addr's leading one bit
position. For instance, if the base_addr is 0x4003_0000, the return value
should be less than or equal to 0x1_0000. Cannot be 0x4_0000.
"""
base_addr = int(addr["base_addr"], 0)
diff = next_value - base_addr
# Find the least one bit position.
# If base_addr is 0, then any value can be used
if not base_addr == 0:
leading_one = 1
while True:
if base_addr & leading_one != 0:
break
leading_one = leading_one << 1
if leading_one <= diff:
diff = leading_one
i = 1
while True:
i = i << 1
if i >= min_size:
break
# If found pow2 value is greater tha diff, it cannot be used. Just use
# min_size then the tool will use comparators (>=, <=)
if i > diff:
i = min_size
# Shall be greater than 4kB
assert i >= 0x1000
return i
def amend_interrupt(top):
"""Check interrupt_module if exists, or just use all modules
"""
if not "interrupt_module" in top:
top["interrupt_module"] = [x["name"] for x in top["module"]]
if not "interrupt" in top or top["interrupt"] == "":
top["interrupt"] = []
for m in top["interrupt_module"]:
ip = list(filter(lambda module: module["name"] == m, top["module"]))
if len(ip) == 0:
log.warning(
"Cannot find IP %s which is used in the interrupt_module" % m)
continue
log.info("Adding interrupts from module %s" % ip[0]["name"])
top["interrupt"] += list(
map(partial(add_prefix_to_signal, prefix=m.lower()),
ip[0]["interrupt_list"]))
def amend_alert(top):
"""Check interrupt_module if exists, or just use all modules
"""
if not "alert_module" in top:
top["alert_module"] = [x["name"] for x in top["module"]]
if not "alert" in top or top["alert"] == "":
top["alert"] = []
for m in top["alert_module"]:
ip = list(filter(lambda module: module["name"] == m, top["module"]))
if len(ip) == 0:
log.warning("Cannot find IP %s which is used in the alert_module" %
m)
continue
log.info("Adding alert from module %s" % ip[0]["name"])
top["alert"] += list(
map(partial(add_prefix_to_signal, prefix=m.lower()),
ip[0]["alert_list"]))
def amend_pinmux_io(top):
""" Check dio_modules/ mio_modules. If not exists, add all modules to mio
"""
pinmux = top["pinmux"]
if not "dio_modules" in pinmux:
pinmux['dio_modules'] = []
# list out dedicated IO
pinmux['dio'] = []
for e in pinmux["dio_modules"]:
# Check name if it is module or signal
mname, sname = get_ms_name(e["name"])
# Parse how many signals
m = get_module_by_name(top, mname)
if sname != None:
signals = deepcopy([get_signal_by_name(m, sname)])
else:
# Get all module signals
signals = deepcopy(m["available_input_list"] +
m["available_output_list"] +
m["available_inout_list"])
sig_width = sum([s["width"] for s in signals])
# convert signal with module name
signals = list(
map(partial(add_prefix_to_signal, prefix=mname), signals))
# Parse how many pads are assigned
if not "pad" in e:
raise SystemExit("Should catch pad field in validate.py!")
total_width = 0
# pads are the list of individual pin, each entry is 1 bit width
pads = []
for p in e["pad"]:
pads += get_pad_list(p)
# check if #sig and #pads are matched
if len(pads) != sig_width:
raise SystemExit("# Pads and # Sig (%s) aren't same: %d" %
(mname, sig_width))
# add info to pads["dio"]
for s in signals:
p = pads[:s["width"]]
pads = pads[s["width"]:]
s["pad"] = p
pinmux["dio"].append(s)
dio_names = [p["name"] for p in pinmux["dio"]]
## Multiplexer IO
if not "mio_modules" in pinmux:
# Add all modules having available io to Multiplexer IO
pinmux["mio_modules"] = []
for m in top["module"]:
num_io = len(m["available_input_list"] +
m["available_output_list"] +
m["available_inout_list"])
if num_io != 0:
# Add if not in dio_modules
pinmux["mio_modules"].append(m["name"])
# List up the dedicated IO to exclude from inputs/outputs
# Add port list to `inputs` and `outputs` fields
if not "inputs" in pinmux:
pinmux["inputs"] = []
if not "outputs" in pinmux:
pinmux["outputs"] = []
if not "inouts" in pinmux:
pinmux["inouts"] = []
for e in pinmux["mio_modules"]:
tokens = e.split('.')
if len(tokens) not in [1, 2]:
raise SystemExit(
"Cannot parse signal/module in mio_modules {}".format(e))
# Add all ports from the module to input/outputs
m = get_module_by_name(top, tokens[0])
if m == None:
raise SystemExit("Module {} doesn't exist".format(tokens[0]))
if len(tokens) == 1:
pinmux["inputs"] += list(
filter(
lambda x: x["name"] not in dio_names,
map(
partial(add_prefix_to_signal,
prefix=m["name"].lower()),
m["available_input_list"])))
pinmux["outputs"] += list(
filter(
lambda x: x["name"] not in dio_names,
map(
partial(add_prefix_to_signal,
prefix=m["name"].lower()),
m["available_output_list"])))
pinmux["inouts"] += list(
filter(
lambda x: x["name"] not in dio_names,
map(
partial(add_prefix_to_signal,
prefix=m["name"].lower()),
m["available_inout_list"])))
elif len(tokens) == 2:
# Current version doesn't consider signal in mio_modules
# only in dio_modules
raise SystemExit(
"Curren version doesn't support signal in mio_modules {}".
format(e))
def merge_top(topcfg, ipobjs, xbarobjs):
gencfg = deepcopy(topcfg)
# Combine ip cfg into topcfg
for ip in ipobjs:
amend_ip(gencfg, ip)
# Combine the interrupt (should be processed prior to xbar)
amend_interrupt(gencfg)
# Combine the alert (should be processed prior to xbar)
amend_alert(gencfg)
# Creates input/output list in the pinmux
log.info("Processing PINMUX")
amend_pinmux_io(gencfg)
# Combine xbar into topcfg
for xbar in xbarobjs:
amend_xbar(gencfg, xbar)
# 2nd phase of xbar (gathering the devices address range)
for xbar in gencfg["xbar"]:
xbar_cross(xbar, gencfg["xbar"])
simplify_xbar(gencfg["xbar"])
# remove unwanted fields 'debug_mem_base_addr'
gencfg.pop('debug_mem_base_addr', None)
return gencfg