642 lines
22 KiB
Python
Executable File
642 lines
22 KiB
Python
Executable File
#!/usr/bin/env python3
|
|
import os
|
|
import re
|
|
import json
|
|
import argparse
|
|
import math
|
|
|
|
vl_include_re = re.compile(r"^\s*`include\s+\"(.+)\"")
|
|
vl_define_re = re.compile(r"^\s*`define\s+(\w+)(\([\w\s,]*\))?(.*)")
|
|
vl_ifdef_re = re.compile(r"^\s*`(ifdef|ifndef|elsif)\s+(\w+)\s*$")
|
|
vl_endif_re = re.compile(r"^\s*`(endif|else)\s*$")
|
|
vl_expand_re = re.compile(r"`([0-9a-zA-Z_]+)")
|
|
|
|
parameters = []
|
|
exclude_files = []
|
|
include_dirs = []
|
|
macros = []
|
|
br_stack = []
|
|
|
|
def parse_func_args(text):
|
|
args = []
|
|
arg = ''
|
|
l = len(text)
|
|
if text[0] != '(':
|
|
raise Exception("missing leading parenthesis: " + text)
|
|
paren = 1
|
|
for i in range(1, l):
|
|
c = text[i]
|
|
|
|
if c == '(':
|
|
paren += 1
|
|
elif c == ')':
|
|
if paren == 0:
|
|
raise Exception("mismatched parenthesis: (" + i + ") " + text)
|
|
paren -= 1
|
|
if paren == 0:
|
|
l = i
|
|
break
|
|
|
|
if c == ',' and paren == 1:
|
|
if arg.strip():
|
|
args.append(arg)
|
|
arg = ''
|
|
else:
|
|
arg += c
|
|
|
|
if paren != 0:
|
|
raise Exception("missing closing parenthesis: " + text)
|
|
|
|
if arg.strip():
|
|
args.append(arg)
|
|
|
|
return (args, l)
|
|
|
|
def resolve_include_path(filename, parent_dir):
|
|
if os.path.basename(filename) in exclude_files:
|
|
return None
|
|
if os.path.isfile(filename):
|
|
return os.path.abspath(filename)
|
|
search_dirs = include_dirs
|
|
if parent_dir:
|
|
search_dirs.append(parent_dir)
|
|
for dir in search_dirs:
|
|
filepath = os.path.join(dir, filename)
|
|
if os.path.isfile(filepath):
|
|
return os.path.abspath(filepath)
|
|
raise Exception("couldn't find include file: " + filename)
|
|
|
|
def remove_comments(text):
|
|
text = re.sub(re.compile("/\*.*?\*/",re.DOTALL ), "", text) # multiline
|
|
text = re.sub(re.compile("//.*?\n" ), "\n", text) # singleline
|
|
return text
|
|
|
|
def add_macro(name, args, value):
|
|
macro = (name, args, value)
|
|
macros.append(macro)
|
|
if not args is None:
|
|
print("*** token: " + name + "(", end='')
|
|
for i in range(len(args)):
|
|
if i > 0:
|
|
print(', ', end='')
|
|
print(args[i], end='')
|
|
print(")=" + value)
|
|
else:
|
|
print("*** token: " + name + "=" + value)
|
|
|
|
def find_macro(name):
|
|
for macro in macros:
|
|
if macro[0] == name:
|
|
return macro
|
|
return None
|
|
|
|
def expand_text(text):
|
|
|
|
class DoRepl(object):
|
|
def __init__(self):
|
|
self.expanded = False
|
|
self.has_func = False
|
|
def __call__(self, match):
|
|
name = match.group(1)
|
|
macro = find_macro(name)
|
|
if macro:
|
|
if not macro[1] is None:
|
|
self.has_func = True
|
|
else:
|
|
self.expanded = True
|
|
return macro[2]
|
|
return "`" + name
|
|
|
|
class DoRepl2(object):
|
|
def __init__(self, args, f_args):
|
|
map = {}
|
|
for i in range(len(args)):
|
|
map[args[i]] = f_args[i]
|
|
self.map = map
|
|
def __call__(self, match):
|
|
for key in match.groups():
|
|
return self.map[key]
|
|
return group
|
|
|
|
def repl_func_macro(text):
|
|
expanded = False
|
|
match = re.search(vl_expand_re, text)
|
|
if match:
|
|
name = match.group(1)
|
|
macro = find_macro(name)
|
|
if macro:
|
|
args = macro[1]
|
|
value = macro[2]
|
|
if not args is None:
|
|
str_args = text[match.end():].strip()
|
|
f_args = parse_func_args(str_args)
|
|
if len(args) == 0:
|
|
if len(f_args[0]) != 0:
|
|
raise Exception("invalid argments for macro '" + name + "': value=" + text)
|
|
else:
|
|
if len(args) != len(f_args[0]):
|
|
raise Exception("mismatch number of argments for macro '" + name + "': actual=" + len(f_args[0]) + ", expected=" + len(args))
|
|
|
|
pattern = "(?<![0-9a-zA-Z_])("
|
|
for i in range(len(args)):
|
|
if i > 0:
|
|
pattern += "|"
|
|
pattern += args[i]
|
|
pattern += ")(?![0-9a-zA-Z_])"
|
|
|
|
dorepl = DoRepl2(args, f_args[0])
|
|
value = re.sub(pattern, dorepl, value)
|
|
|
|
str_head = text[0:match.start()]
|
|
str_tail = text[match.end() + f_args[1]+1:]
|
|
text = str_head + value + str_tail
|
|
expanded = True
|
|
if expanded:
|
|
return text
|
|
return None
|
|
|
|
changed = False
|
|
iter = 0
|
|
|
|
while True:
|
|
if iter > 99:
|
|
raise Exception("Macro recursion!")
|
|
has_func = False
|
|
while True:
|
|
do_repl = DoRepl()
|
|
new_text = re.sub(vl_expand_re, do_repl, text)
|
|
has_func = do_repl.has_func
|
|
if not do_repl.expanded:
|
|
break
|
|
text = new_text
|
|
changed = True
|
|
if not has_func:
|
|
break
|
|
expanded = repl_func_macro(text)
|
|
if not expanded:
|
|
break
|
|
text = expanded
|
|
changed = True
|
|
iter += 1
|
|
|
|
if changed:
|
|
return text
|
|
return None
|
|
|
|
def parse_include(filename, nesting):
|
|
if nesting > 99:
|
|
raise Exception("include recursion!")
|
|
print("*** parsing '" + filename + "'...")
|
|
content = None
|
|
with open(filename, "r") as f:
|
|
content = f.read()
|
|
# remove comments
|
|
content = remove_comments(content)
|
|
# parse content
|
|
prev_line = None
|
|
for line in content.splitlines(False):
|
|
# skip empty lines
|
|
if re.match(re.compile(r'^\s*$'), line):
|
|
continue
|
|
# merge multi-line lines
|
|
if line.endswith('\\'):
|
|
if prev_line:
|
|
prev_line += line[:len(line) - 1]
|
|
else:
|
|
prev_line = line[:len(line) - 1]
|
|
continue
|
|
if prev_line:
|
|
line = prev_line + line
|
|
prev_line = None
|
|
# parse ifdef
|
|
m = re.match(vl_ifdef_re, line)
|
|
if m:
|
|
key = m.group(1)
|
|
cond = m.group(2)
|
|
taken = find_macro(cond) is not None
|
|
if key == 'ifndef':
|
|
taken = not taken
|
|
elif key == '"elsif':
|
|
br_stack.pop()
|
|
br_stack.append(taken)
|
|
print("*** " + key + "(" + cond + ") => " + str(taken))
|
|
continue
|
|
# parse endif
|
|
m = re.match(vl_endif_re, line)
|
|
if m:
|
|
key = m.group(1)
|
|
top = br_stack.pop()
|
|
if key == 'else':
|
|
br_stack.append(not top)
|
|
print("*** " + key)
|
|
continue
|
|
# skip disabled blocks
|
|
if not all(br_stack):
|
|
continue
|
|
|
|
# parse include
|
|
m = re.match(vl_include_re, line)
|
|
if m:
|
|
include = m.group(1)
|
|
include = resolve_include_path(include, os.path.dirname(filename))
|
|
if include:
|
|
parse_include(include, nesting + 1)
|
|
continue
|
|
# parse define
|
|
m = re.match(vl_define_re, line)
|
|
if m:
|
|
name = m.group(1)
|
|
args = m.group(2)
|
|
if args:
|
|
args = args[1:len(args)-1].strip()
|
|
if args != '':
|
|
args = args.split(',')
|
|
for i in range(len(args)):
|
|
args[i] = args[i].strip()
|
|
else:
|
|
args = []
|
|
value = m.group(3)
|
|
add_macro(name, args, value.strip())
|
|
continue
|
|
|
|
def parse_includes(includes):
|
|
# change current directory to include directory
|
|
old_dir = os.getcwd()
|
|
script_dir = os.path.dirname(os.path.realpath(__file__))
|
|
os.chdir(script_dir)
|
|
|
|
for include in includes:
|
|
parse_include(include, 0)
|
|
|
|
# restore current directory
|
|
os.chdir(old_dir)
|
|
|
|
def load_include_dirs(dirs):
|
|
for dir in dirs:
|
|
print("*** include dir: " + dir)
|
|
include_dirs.append(dir)
|
|
|
|
def load_defines(defines):
|
|
for define in defines:
|
|
key_value = define.split('=', 2)
|
|
name = key_value[0]
|
|
value = ''
|
|
if len(key_value) == 2:
|
|
value = key_value[1]
|
|
add_macro(name, None, value)
|
|
|
|
def load_config(filename):
|
|
with open(filename, "r") as f:
|
|
config = json.load(f)
|
|
print("condfig=", config)
|
|
return config
|
|
|
|
def gen_vl_header(file, taps, triggers):
|
|
|
|
header = '''
|
|
`ifndef VX_SCOPE_DEFS
|
|
`define VX_SCOPE_DEFS
|
|
'''
|
|
footer = '`endif'
|
|
|
|
def signal_size(size, asize):
|
|
str_asize = ""
|
|
for s in asize:
|
|
if type(s) == int:
|
|
str_asize += "[" + str(s-1) + ":0]"
|
|
else:
|
|
str_asize += "[" + str(s) + "-1:0]"
|
|
|
|
if type(size) == int:
|
|
size1 = (size-1)
|
|
if size1 != 0:
|
|
return str_asize + "[" + str(size1) + ":0]"
|
|
else:
|
|
return str_asize
|
|
else:
|
|
return str_asize + "[(" + size + ")-1:0]"
|
|
|
|
def generate_ports(tclass, tap, ports, new_taps):
|
|
|
|
def emit_io(tap, ports, prefix, asize, return_list, new_taps, is_enabled):
|
|
stap = tap + "_IO"
|
|
new_taps.append(stap)
|
|
print("`define " + stap + " \\", file=f)
|
|
if is_enabled:
|
|
for key in ports:
|
|
size = ports[key]
|
|
name = key
|
|
is_trigger = False
|
|
if name[0] == '!':
|
|
name = name[1:]
|
|
is_trigger = True
|
|
if not return_list is None:
|
|
return_list.append((name + prefix, size, asize, is_trigger))
|
|
print("\toutput wire" + signal_size(size, asize) + " " + name + prefix + ", \\", file=f)
|
|
print("", file=f)
|
|
emit_bind(tap, ports, prefix, prefix, new_taps, is_enabled)
|
|
|
|
def emit_bind(tap, ports, from_prefix, to_prefix, new_taps, is_enabled):
|
|
stap = tap + "_BIND"
|
|
new_taps.append(stap)
|
|
print("`define " + stap + " \\", file=f)
|
|
for key in ports:
|
|
name = key
|
|
if name[0] == '!':
|
|
name = name[1:]
|
|
if is_enabled:
|
|
print("\t." + name + to_prefix + " (" + name + from_prefix + "), \\", file=f)
|
|
else:
|
|
if (from_prefix != to_prefix):
|
|
print("\t`UNUSED_PIN (" + name + to_prefix + "), \\", file=f)
|
|
print("", file=f)
|
|
|
|
def emit_select(tap, ports, from_prefix, to_prefix, new_taps, is_enabled):
|
|
stap = tap + "_SELECT(__i__)"
|
|
new_taps.append(stap)
|
|
print("`define " + stap + " \\", file=f)
|
|
if is_enabled:
|
|
for key in ports:
|
|
name = key
|
|
if name[0] == '!':
|
|
name = name[1:]
|
|
print("\t." + name + to_prefix + " (" + name + from_prefix + "[__i__]), \\", file=f)
|
|
print("", file=f)
|
|
|
|
def do_top(tap, ports, new_taps):
|
|
out_ports = []
|
|
for p in ports:
|
|
name = p
|
|
is_trigger = False
|
|
if name[0] == '!':
|
|
name = name[1:]
|
|
is_trigger = True
|
|
out_ports.append((name, ports[p], [], is_trigger))
|
|
return out_ports
|
|
|
|
def do_core(tap, ports, new_taps):
|
|
out_ports = []
|
|
nclusters = parameters["NUM_CLUSTERS"]
|
|
ncores = parameters["NUM_CORES"]
|
|
emit_io(tap + "_TOP", ports, "_top", [nclusters, ncores], out_ports, new_taps, True)
|
|
emit_io(tap + "_CLUSTER", ports, "_cluster", [ncores], None, new_taps, True)
|
|
emit_io(tap + "", ports, "", [], None, new_taps, True)
|
|
emit_select(tap + "_CLUSTER", ports, "_top", "_cluster", new_taps, True)
|
|
emit_select(tap + "", ports, "_cluster", "", new_taps, True)
|
|
return out_ports
|
|
|
|
def do_bank(tap, ports, new_taps):
|
|
out_ports = []
|
|
|
|
nclusters = parameters["NUM_CLUSTERS"]
|
|
ncores = parameters["NUM_CORES"]
|
|
has_l3 = (parameters["L3_ENABLE"] != 0)
|
|
has_l2 = (parameters["L2_ENABLE"] != 0)
|
|
|
|
emit_io(tap + "_L3_TOP", ports, "_l3_cache", [parameters["L3NUM_BANKS"]], out_ports, new_taps, has_l3)
|
|
emit_io(tap + "_L2_TOP", ports, "_l2_top", [nclusters, parameters["L2NUM_BANKS"]], out_ports, new_taps, has_l2)
|
|
emit_io(tap + "_L1D_TOP", ports, "_l1d_top", [nclusters, ncores, parameters["DNUM_BANKS"]], out_ports, new_taps, True)
|
|
emit_io(tap + "_L1I_TOP", ports, "_l1i_top", [nclusters, ncores, parameters["INUM_BANKS"]], out_ports, new_taps, True)
|
|
emit_io(tap + "_L1S_TOP", ports, "_l1s_top", [nclusters, ncores, parameters["SNUM_BANKS"]], out_ports, new_taps, True)
|
|
|
|
emit_io(tap + "_L2_CLUSTER", ports, "_l2_cache", [parameters["L2NUM_BANKS"]], None, new_taps, has_l2)
|
|
emit_io(tap + "_L1D_CLUSTER", ports, "_l1d_cluster", [ncores, parameters["DNUM_BANKS"]], None, new_taps, True)
|
|
emit_io(tap + "_L1I_CLUSTER", ports, "_l1i_cluster", [ncores, parameters["INUM_BANKS"]], None, new_taps, True)
|
|
emit_io(tap + "_L1S_CLUSTER", ports, "_l1s_cluster", [ncores, parameters["SNUM_BANKS"]], None, new_taps, True)
|
|
|
|
emit_io(tap + "_L1D_CORE", ports, "_l1d_cache", [parameters["DNUM_BANKS"]], None, new_taps, True)
|
|
emit_io(tap + "_L1I_CORE", ports, "_l1i_cache", [parameters["INUM_BANKS"]], None, new_taps, True)
|
|
emit_io(tap + "_L1S_CORE", ports, "_l1s_cache", [parameters["SNUM_BANKS"]], None, new_taps, True)
|
|
|
|
emit_io(tap + "_CACHE", ports, "_cache", ["NUM_BANKS"], None, new_taps, True)
|
|
emit_io(tap + "", ports, "", [], None, new_taps, True)
|
|
|
|
emit_select(tap + "_L2_CLUSTER", ports, "_l2_top", "_l2_cache", new_taps, has_l2)
|
|
emit_select(tap + "_L1D_CLUSTER", ports, "_l1d_top", "_l1d_cluster", new_taps, True)
|
|
emit_select(tap + "_L1I_CLUSTER", ports, "_l1i_top", "_l1i_cluster", new_taps, True)
|
|
emit_select(tap + "_L1S_CLUSTER", ports, "_l1s_top", "_l1s_cluster", new_taps, True)
|
|
|
|
emit_select(tap + "_L1D_CORE", ports, "_l1d_cluster", "_l1d_cache", new_taps, True)
|
|
emit_select(tap + "_L1I_CORE", ports, "_l1i_cluster", "_l1i_cache", new_taps, True)
|
|
emit_select(tap + "_L1S_CORE", ports, "_l1s_cluster", "_l1s_cache", new_taps, True)
|
|
|
|
emit_bind(tap + "_L3_CACHE", ports, "_l3_cache", "_cache", new_taps, has_l3)
|
|
emit_bind(tap + "_L2_CACHE", ports, "_l2_cache", "_cache", new_taps, has_l2)
|
|
emit_bind(tap + "_L1D_CACHE", ports, "_l1d_cache", "_cache", new_taps, True)
|
|
emit_bind(tap + "_L1I_CACHE", ports, "_l1i_cache", "_cache", new_taps, True)
|
|
emit_bind(tap + "_L1S_CACHE", ports, "_l1s_cache", "_cache", new_taps, True)
|
|
|
|
emit_select(tap + "", ports, "_cache", "", new_taps, True)
|
|
|
|
return out_ports
|
|
|
|
callbacks = {
|
|
"top": do_top,
|
|
"core": do_core,
|
|
"bank": do_bank
|
|
}
|
|
|
|
return callbacks[tclass](tap, ports, new_taps)
|
|
|
|
def trigger_size(name, ports):
|
|
for port in ports:
|
|
if port[0] == name:
|
|
return (port[1], port[2])
|
|
return None
|
|
|
|
def trigger_prefices(asize):
|
|
def Q(arr, ss, asize, idx, N):
|
|
for i in range(asize[idx]):
|
|
tmp = ss + '[' + str(i) + ']'
|
|
if (idx + 1) < N:
|
|
Q(arr, tmp, asize, idx + 1, N)
|
|
else:
|
|
arr.append(tmp)
|
|
|
|
l = len(asize)
|
|
if l == 0:
|
|
return [""]
|
|
arr = []
|
|
Q(arr, "", asize, 0, l)
|
|
return arr
|
|
|
|
def trigger_name(name, size):
|
|
if type(size) == int:
|
|
size1 = (size-1)
|
|
if size1 != 0:
|
|
return "(| " + name + ")"
|
|
else:
|
|
return name
|
|
else:
|
|
return "(| " + name + ")"
|
|
|
|
with open(file, 'w') as f:
|
|
print(header, file=f)
|
|
|
|
all_ports = []
|
|
new_taps = []
|
|
|
|
for key in taps:
|
|
[tclass, tap] = key.split('::')
|
|
ports = generate_ports(tclass, tap, taps[key], new_taps)
|
|
for port in ports:
|
|
all_ports.append(port)
|
|
|
|
print("`define SCOPE_SIGNALS_DECL \\", file=f)
|
|
i = 0
|
|
for port in all_ports:
|
|
if i > 0:
|
|
print(" \\", file=f)
|
|
print("\twire" + signal_size(port[1], port[2]) + " " + port[0] + ";", file=f, end='')
|
|
i += 1
|
|
print("", file=f)
|
|
print("", file=f)
|
|
|
|
print("`define SCOPE_SIGNALS_DATA_LIST \\", file=f)
|
|
i = 0
|
|
for port in all_ports:
|
|
if port[3]:
|
|
continue
|
|
if i > 0:
|
|
print(", \\", file=f)
|
|
print("\t" + port[0], file=f, end='')
|
|
i += 1
|
|
print("", file=f)
|
|
print("", file=f)
|
|
|
|
print("`define SCOPE_SIGNALS_UPD_LIST \\", file=f)
|
|
i = 0
|
|
for port in all_ports:
|
|
if not port[3]:
|
|
continue
|
|
if i > 0:
|
|
print(", \\", file=f)
|
|
print("\t" + port[0], file=f, end='')
|
|
i += 1
|
|
print("", file=f)
|
|
print("", file=f)
|
|
|
|
print("`define SCOPE_TRIGGERS \\", file=f)
|
|
i = 0
|
|
for trigger in triggers:
|
|
arr = trigger_size(trigger[0], all_ports)
|
|
if arr is None:
|
|
continue
|
|
[size, asize] = arr
|
|
for prefix in trigger_prefices(asize):
|
|
if i > 0:
|
|
print(" | \\", file=f)
|
|
print("\t(", file=f, end='')
|
|
for j in range(len(trigger)):
|
|
if j > 0:
|
|
print(" && ", file=f, end='')
|
|
print(trigger_name(trigger[j] + prefix, size), file=f, end='')
|
|
print(")", file=f, end='')
|
|
i += 1
|
|
print("", file=f)
|
|
print("", file=f)
|
|
|
|
print(footer, file=f)
|
|
|
|
return all_ports
|
|
|
|
def gen_cc_header(file, ports):
|
|
|
|
header = '''
|
|
#pragma once\n
|
|
struct scope_signal_t {
|
|
int width;
|
|
const char* name;
|
|
};\n
|
|
inline constexpr int __clog2(int n) { return (n > 1) ? 1 + __clog2((n + 1) >> 1) : 0; }\n
|
|
static constexpr scope_signal_t scope_signals[] = {'''
|
|
|
|
footer = "};"
|
|
|
|
def eval_macro(text):
|
|
expanded = expand_text(text)
|
|
if expanded:
|
|
text = expanded
|
|
text = text.replace('$clog2', '__clog2')
|
|
return text
|
|
|
|
def asize_name(asize):
|
|
def Q(arr, ss, asize, idx, N):
|
|
for i in range(asize[idx]):
|
|
tmp = ss + "_" + str(i)
|
|
if (idx + 1) < N:
|
|
Q(arr, tmp, asize, idx + 1, N)
|
|
else:
|
|
arr.append(tmp)
|
|
|
|
l = len(asize)
|
|
if l == 0:
|
|
return [""]
|
|
arr = []
|
|
Q(arr, "", asize, 0, l)
|
|
return arr
|
|
|
|
with open(file, 'w') as f:
|
|
print(header, file=f)
|
|
i = 0
|
|
for port in ports:
|
|
if port[3]:
|
|
continue
|
|
name = port[0]
|
|
size = eval_macro(str(port[1]))
|
|
for ss in asize_name(port[2]):
|
|
if i > 0:
|
|
print(",", file=f)
|
|
print("\t{" + size + ", \"" + name + ss + "\"}", file=f, end='')
|
|
i += 1
|
|
for port in ports:
|
|
if not port[3]:
|
|
continue
|
|
name = port[0]
|
|
size = eval_macro(str(port[1]))
|
|
for ss in asize_name(port[2]):
|
|
if i > 0:
|
|
print(",", file=f)
|
|
print("\t{" + size + ", \"" + name + ss + "\"}", file=f, end='')
|
|
i += 1
|
|
print("", file=f)
|
|
print(footer, file=f)
|
|
|
|
def main():
|
|
parser = argparse.ArgumentParser(description='Scope headers generator.')
|
|
parser.add_argument('-vl', nargs='?', default='scope-defs.vh', metavar='file', help='Output Verilog header')
|
|
parser.add_argument('-cc', nargs='?', default='scope-defs.h', metavar='file', help='Output C++ header')
|
|
parser.add_argument('-D', nargs='?', action='append', metavar='macro[=value]', help='define macro')
|
|
parser.add_argument('-I', nargs='?', action='append', metavar='<includedir>', help='include directory')
|
|
parser.add_argument('config', help='Json config file')
|
|
args = parser.parse_args()
|
|
print("args=", args)
|
|
|
|
global parameters
|
|
global exclude_files
|
|
global include_dirs
|
|
global macros
|
|
global br_stack
|
|
|
|
if args.I:
|
|
load_include_dirs(args.I)
|
|
|
|
if args.D:
|
|
load_defines(args.D)
|
|
|
|
config = load_config(args.config)
|
|
|
|
exclude_files.append(os.path.basename(args.vl))
|
|
|
|
if "includes" in config:
|
|
parse_includes(config["includes"])
|
|
|
|
parameters = config["parameters"]
|
|
for key in parameters:
|
|
parameters[key] = int(eval(expand_text(str(parameters[key]))))
|
|
|
|
ports = gen_vl_header(args.vl, config["taps"], config["triggers"])
|
|
gen_cc_header(args.cc, ports)
|
|
|
|
if __name__ == "__main__":
|
|
main() |