Files
cheri-cap-lib/CHERICapWrap.py
Ivan Ribeiro f3284354d2 Remove "is" and "get" from generated SV fields
This should make using the fields a little prettier
2023-10-04 19:58:48 +01:00

262 lines
10 KiB
Python
Executable File

#! /usr/bin/env python3
import argparse
import re
from abc import ABC, abstractmethod
parser = argparse.ArgumentParser(description=
'''Generates a Blarney wrapper for the given Bluespec generated verilog file
containing a module definition of a purely combinational CHERI function.
''')
parser.add_argument('verilog_files', metavar='VERILOG_FILE', type=str, nargs='+',
help='The file(s) to process')
parser.add_argument('--output', '-o', metavar='OUTPUT_FILE', type=str, nargs='?',
default="",
help='The output Blarney Haskell module to generate')
parser.add_argument('--generator', metavar='GENERATOR', type=str, nargs='?',
choices=['blarney','sv','systemverilog'], default='blarney',
help='The generator to be used')
args = parser.parse_args()
# Generic wrapper for a Verilog module
class Wrapper:
def __init__(self, size, name, ins, out):
self.size = size
self.name = name
self.ins = ins
self.out = out
def verilogModuleName(self):
return "module_wrap{:d}_{:s}".format(self.size, self.name)
def verilogInputNames(self):
return ["wrap{:d}_{:s}_{:s}".format(self.size, self.name, nm)
for nm in [x[0] for x in self.ins]]
def verilogOutputName(self):
return "wrap{:d}_{:s}".format(self.size, self.name)
# Generic generator class
# Describes the minimum functionality that a generator needs to implement.
# A generator takes some list of Verilog modules (which includes information
# about the module name, inputs, outputs, etc) and generates a list of file
# contents that should be written.
class Generator(ABC):
# namehint is a hint for naming, and each specific generator subclass will
# interpret it in its own way. In many cases it may be the single generated
# filename
def __init__(self, namehint, mods = None):
self.namehint = namehint
if mods is not None:
self.modules = mods
else:
self.modules = list()
def addVerilogModule(self, mod):
self.modules.append(mod)
# Generates a list of tuples containing output file names and output file
# contents to be written to disk
@abstractmethod
def emit(self):
pass
# Generates Blarney files
# When the namehint is not empty, it is used as the filename and .hs is appended
# otherwise the old default filename of CHERIBlarneyWrappers.hs is used
class BlarneyGenerator(Generator):
def emit(self):
modname = "CHERIBlarneyWrappers"
filename = modname + ".hs"
if self.namehint is not None and self.namehint != "":
modname = self.namehint
filename = self.namehint + ".hs"
contents = "module " + modname + " where\n\n"
contents += "import Blarney\n"
contents += "import Blarney.Core.BV\n"
for mod in self.modules:
print(mod.name)
contents += "\n"
ins_names = [x[0] for x in mod.ins]
ins_wdths = [x[1] for x in mod.ins]
str_type = "{:s} :: {:s}{:s}{:s}".format(
mod.name,
" -> ".join(["Bit {:d}".format(n) for n in ins_wdths]),
" -> " if mod.ins else "",
"Bit {:d}".format(mod.out[1]))
str_decl = "{:s} {:s} = FromBV $\n makePrim1 (Custom \"{:s}\" [{:s}] [{:s}] [] False Nothing) [{:s}]".format(
mod.name, " ".join(ins_names),
mod.verilogModuleName(),
", ".join(["(\"{:s}\", {:d})".format(n, w)
for (n, w) in zip(mod.verilogInputNames(),
ins_wdths)]),
"(\"{:s}\", {:d})".format(mod.verilogOutputName(), mod.out[1]),
", ".join(["toBV {:s}".format(nm) for nm in ins_names]))
contents += "{:s}\n{:s}".format(str_type, str_decl)
contents += "\n".format(str_decl)
return [(filename, contents)]
# generates SystemVerilog files
# when the namehint is non-empty it is used as a prefix for the file name
# generates a _pkg.sv file containing:
# a typedef of cheri_cap_t which is an "opaque" capability
# a typedef of cheri_cap_dec_t which is a decompressed capability
# generates a _mod.sv file containing a module which combinationally takes an "opaque"
# capability as the input and gives a decompressed capability as the output
# In the filename, "cheri" is appended with the in-memory capability width
# (i.e. the filename will be "cheri64_pkg.sv") so that differently sized capabilities
# can exist
class SystemVerilogGenerator(Generator):
def emit(self):
# get the size of in-memory capability by assuming that the name of the
# modules matches "module_wrap$SIZE_..." and taking just the size part
in_mem_cap_size = self.modules[0].verilogModuleName()[11:]
in_mem_cap_size = in_mem_cap_size[:in_mem_cap_size.find("_")]
cap_type_name = "cheri_cap_t" # the name of the opaque cap type
cap_dec_type_name = "cheri_cap_dec_t" # the name of the expanded cap type
cap_dec_mod_name = "cheri{:s}_cap_expander".format(in_mem_cap_size) # the name of the expanding module
cap_in_signal_name = "cap_i" # the name of the input signal to the expanding module
cap_out_signal_name = "cap_o" # the name of the output signal to the expanding module
cap_search_string = "cap" # the string required for inferring capability width
# this wrapper generator is intended to make access to the "getter"
# modules (i.e. getAddr, getTop, etc) easier/cleaner
# ideally, to access the field it is cleaner to say "cap.address" rather than
# "cap.getAddress" or "cap.validCap" rather than "cap.isValidCap"
# the following is a list of "keywords" to remove from the field names
keywords_to_remove = ["get", "is"]
def mod_name_to_field_name(modname):
for kw in keywords_to_remove:
if modname.startswith(kw):
# remove the keyword from the start
modname = modname[len(kw):]
# make first letter lowercase
modname = modname[0].lower() + modname[1:]
# return after removing the first found keyword
return modname
return modname
pkg_file_name = "cheri{:s}_pkg.sv".format(in_mem_cap_size)
module_file_name = "{:s}.sv".format(cap_dec_mod_name)
# prepend namehint if non-empty
if self.namehint is not None and self.namehint != "":
pkg_file_name = self.namehint + "_" + pkg_file_name
module_file_name = self.namehint + "_" + module_file_name
pkg_name = pkg_file_name[:-3]
dec_mod_name = module_file_name[:-3]
# find the size of a capability by assuming that any no-input modules
# with "cap" in the name have a capability output
cap_size = None
for mod in self.modules:
if len(mod.ins) != 0 or cap_search_string not in mod.name.lower():
continue
cap_size = mod.out[1]
break
if cap_size == None:
# the above method failed to find a capability size
# to fix, can either implement a better method or just hard-code the capability size
raise NotImplementedError("Unable to determine capability size from input files")
cap_type_def_text = " typedef logic [{:d}:0] {:s};\n".format(cap_size-1, cap_type_name)
# assume all modules with one capability-sized input are "getters"
# these will be the fields of the decompressed capability struct
struct_elems = list()
for mod in self.modules:
if len(mod.ins) == 1 and mod.ins[0][1] == cap_size:
struct_elems.append(mod)
# structure definition
struct_def_text = " typedef struct packed {\n"
for mod in struct_elems:
struct_def_text += " logic [{:d}:{:d}] {:s};\n".format(mod.out[1]-1, 0, mod_name_to_field_name(mod.name))
struct_def_text += " }} {:s};\n".format(cap_dec_type_name)
# package definition
pkg_def_text = "package {:s};\n".format(pkg_name)
pkg_def_text += cap_type_def_text
pkg_def_text += struct_def_text
pkg_def_text += "endpackage\n"
# module definition
module_def_text = "module {:s} (\n".format(cap_dec_mod_name)
module_def_text += " input {:s}::{:s} {:s},\n".format(pkg_name, cap_type_name, cap_in_signal_name)
module_def_text += " output {:s}::{:s} {:s}\n".format(pkg_name, cap_dec_type_name, cap_out_signal_name)
module_def_text += ");\n"
module_def_text += " import {:s}::*;\n".format(pkg_name)
# module instantiations
for mod in struct_elems:
module_def_text += " {:s} {:s}_mod (\n".format(mod.verilogModuleName(), mod.name)
module_def_text += " .{:s}({:s}),\n".format(mod.verilogInputNames()[0], cap_in_signal_name)
module_def_text += " .{:s}({:s}.{:s})\n".format(mod.verilogOutputName(), cap_out_signal_name, mod_name_to_field_name(mod.name))
module_def_text += " );\n"
module_def_text += "endmodule\n"
return [(pkg_file_name, pkg_def_text),
(module_file_name, module_def_text)]
def main():
# define module regexp
modDecl = re.compile("^module\s+module_wrap(\d+)_(\w+)\(")
# TODO handle size 1
#
# gather the list of modules
wrappers = []
for fname in args.verilog_files:
size = 0
name = None
ins = []
out = ("",0)
with open(fname, "r") as f:
for ln in f:
modM = modDecl.match(ln)
if modM:
size = int(modM.group(1))
name = modM.group(2)
break
if not name:
print("Couldn't find a valid Verilog module definition")
exit(-1)
# define input/output regexp
inDecl = re.compile("^\s*input(\s+\[(\d+)\s+:\s+0\])?\s+wrap(\d+)_"+name+"_(\w+);")
outDecl = re.compile("^\s*output(\s+\[(\d+)\s+:\s+0\])?\s+wrap(\d+)_"+name+";")
for ln in f:
inM = inDecl.match(ln)
outM = outDecl.match(ln)
if inM:
ins.append((inM.group(4), (int(inM.group(2)) + 1) if inM.group(1) else 1))
elif outM:
out = (name, (int(outM.group(2)) + 1) if outM.group(1) else 1)
#else:
# print("===>> no match for line: {:s}".format(ln))
wrappers.append(Wrapper(size, name, ins, out))
# choose the right generator based on the input argument
gen = None
if args.generator.lower() in ["systemverilog", "sv"]:
gen = SystemVerilogGenerator(args.output, wrappers)
elif args.generator.lower() in ["blarney"]:
gen = BlarneyGenerator(args.output, wrappers)
else:
print("Invalid generator selected; exiting")
return
for out in gen.emit():
with open(out[0], "w") as f:
f.write(out[1])
if __name__ == "__main__":
main()