-
Notifications
You must be signed in to change notification settings - Fork 9
/
Copy pathCHERICapWrap.py
executable file
·261 lines (226 loc) · 10.4 KB
/
CHERICapWrap.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
#! /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()