blob: 0c15a71b694cb8e94c31277e622589793d5404c9 [file] [log] [blame]
Chris Frantz9b34e4a2021-11-24 17:03:12 -08001# Copyright lowRISC contributors.
2# Licensed under the Apache License, Version 2.0, see LICENSE for details.
3# SPDX-License-Identifier: Apache-2.0
4
Miguel Young de la Sota3b5a9f52022-03-24 16:11:42 -04005load("//rules:rv.bzl", "rv_rule")
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -04006load("@rules_cc//cc:find_cc_toolchain.bzl", "find_cc_toolchain")
Chris Frantz9b34e4a2021-11-24 17:03:12 -08007
Jade Philipoom39c9b322022-03-08 12:27:10 +00008def _get_assembler(cc_toolchain):
9 """Find the path to riscv-unknown-elf-as."""
10
Miguel Young de la Sota8fce79d2022-03-23 15:37:39 -040011 # Note: the toolchain config doesn"t appear to have a good way to get
12 # access to the assembler. We should be able to access it via the
Timothy Trippelefe736b2022-04-20 12:50:40 -070013 # the compiler, but I had trouble with //hw/ip/otbn/util/otbn_as.py invoking
Miguel Young de la Sota8fce79d2022-03-23 15:37:39 -040014 # the compiler as assembler.
Jade Philipoom39c9b322022-03-08 12:27:10 +000015 return [f for f in cc_toolchain.all_files.to_list() if f.basename.endswith("as")][0]
16
17def _otbn_assemble_sources(ctx):
18 """Helper function that, for each source file in the provided context, adds
Timothy Trippelefe736b2022-04-20 12:50:40 -070019 an action to the context that invokes the otbn assember (otbn_as.py),
Jade Philipoom39c9b322022-03-08 12:27:10 +000020 producing a corresponding object file. Returns a list of all object files
21 that will be generated by these actions.
22 """
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -040023 cc_toolchain = find_cc_toolchain(ctx).cc
Jade Philipoom39c9b322022-03-08 12:27:10 +000024 assembler = _get_assembler(cc_toolchain)
25
26 objs = []
27 for src in ctx.files.srcs:
28 obj = ctx.actions.declare_file(src.basename.replace("." + src.extension, ".o"))
29 objs.append(obj)
30 ctx.actions.run(
31 outputs = [obj],
32 inputs = ([src] +
33 cc_toolchain.all_files.to_list() +
Timothy Trippelfc197342022-04-20 14:38:11 -070034 [ctx.executable._otbn_as]),
Jade Philipoom39c9b322022-03-08 12:27:10 +000035 env = {
36 "RV32_TOOL_AS": assembler.path,
37 },
38 arguments = ["-o", obj.path, src.path],
Timothy Trippelfc197342022-04-20 14:38:11 -070039 executable = ctx.executable._otbn_as,
Jade Philipoom39c9b322022-03-08 12:27:10 +000040 )
41
42 return objs
43
44def _otbn_library(ctx):
45 """Produces a collection of object files, one per source file, that can be
46 used as a dependency for otbn binaries."""
47 objs = _otbn_assemble_sources(ctx)
48
49 return [
Timothy Trippelfc197342022-04-20 14:38:11 -070050 DefaultInfo(
51 files = depset(objs),
52 data_runfiles = ctx.runfiles(files = objs),
53 ),
Jade Philipoom39c9b322022-03-08 12:27:10 +000054 ]
55
Chris Frantz9b34e4a2021-11-24 17:03:12 -080056def _otbn_binary(ctx):
57 """The build process for otbn resources currently invokes
Timothy Trippelfc197342022-04-20 14:38:11 -070058 `//hw/ip/otbn/util/otbn_{as,ld,...}.py` to build the otbn resource.
Chris Frantz9b34e4a2021-11-24 17:03:12 -080059 These programs are python scripts which translate otbn special
60 instructions into the proper opcode sequences and _then_ invoke the normal
61 `rv32-{as,ld,...}` programs to produce the resource. These "native"
62 otbn resources are the `otbn_objs` and `elf` output groups.
63
64 In order to make the otbn resource useful to the the main CPU, the
65 otbn resource needs to be included as a blob of data that the main
66 CPU can dump into the otbn `imem` area and ask otbn to execute it.
67 `util/otbn-build.py` does this with some objcopy-fu, emitting
68 `foo.rv32embed.o`. Bazel's `cc_*` rules really want dependency objects
69 expressed as archives rather than raw object files, so I've modified
70 `otbn-build` to also emit an archive file.
71
72 _Morally_, the otbn resource is a data dependency. However the
73 practical meaning of a `data` dependency in bazel is a file made
74 available at runtime, which is not how we're using the otbn resource.
75 The closest analog is something like `cc_embed_data`, which is like
76 a data dependency that needs to be linked into the main program.
Timothy Trippelfc197342022-04-20 14:38:11 -070077 We achieve by having `otbn_build.py` emit a conventional RV32I library
Chris Frantz9b34e4a2021-11-24 17:03:12 -080078 that other rules can depend on in their `deps`.
79 """
Miguel Young de la Sota4b01c7a2022-04-21 17:25:52 -040080 cc_toolchain = find_cc_toolchain(ctx).cc
Jade Philipoom39c9b322022-03-08 12:27:10 +000081 assembler = _get_assembler(cc_toolchain)
82
83 # Run the otbn assembler on source files to produce object (.o) files.
84 objs = _otbn_assemble_sources(ctx)
85
86 # Declare output files.
Chris Frantz9b34e4a2021-11-24 17:03:12 -080087 elf = ctx.actions.declare_file(ctx.attr.name + ".elf")
88 rv32embed = ctx.actions.declare_file(ctx.attr.name + ".rv32embed.o")
89 archive = ctx.actions.declare_file(ctx.attr.name + ".rv32embed.a")
Chris Frantz9b34e4a2021-11-24 17:03:12 -080090
Jade Philipoom39c9b322022-03-08 12:27:10 +000091 deps = [f for dep in ctx.attr.deps for f in dep.files.to_list()]
Chris Frantz9b34e4a2021-11-24 17:03:12 -080092
Jade Philipoom39c9b322022-03-08 12:27:10 +000093 # Run the otbn_build.py script to link object files from the sources and
94 # dependencies.
Chris Frantz9b34e4a2021-11-24 17:03:12 -080095 ctx.actions.run(
Jade Philipoom39c9b322022-03-08 12:27:10 +000096 outputs = [elf, rv32embed, archive],
97 inputs = (objs +
98 deps +
Chris Frantz9b34e4a2021-11-24 17:03:12 -080099 cc_toolchain.all_files.to_list() +
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800100 ctx.files._otbn_data +
Timothy Trippel024e3932022-04-20 15:40:55 -0700101 [ctx.executable._wrapper]),
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800102 env = {
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800103 "RV32_TOOL_AS": assembler.path,
104 "RV32_TOOL_AR": cc_toolchain.ar_executable,
105 "RV32_TOOL_LD": cc_toolchain.ld_executable,
106 "RV32_TOOL_OBJCOPY": cc_toolchain.objcopy_executable,
107 },
108 arguments = [
109 "--app-name={}".format(ctx.attr.name),
110 "--archive",
Jade Philipoom39c9b322022-03-08 12:27:10 +0000111 "--no-assembler",
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800112 "--out-dir={}".format(elf.dirname),
Jade Philipoom39c9b322022-03-08 12:27:10 +0000113 ] + [obj.path for obj in (objs + deps)],
Timothy Trippel024e3932022-04-20 15:40:55 -0700114 executable = ctx.executable._wrapper,
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800115 )
116
117 feature_configuration = cc_common.configure_features(
118 ctx = ctx,
119 cc_toolchain = cc_toolchain,
120 requested_features = ctx.features,
121 unsupported_features = ctx.disabled_features,
122 )
123
Jade Philipoom39c9b322022-03-08 12:27:10 +0000124 outputs = objs + [elf, rv32embed, archive]
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800125 return [
126 DefaultInfo(files = depset(outputs), data_runfiles = ctx.runfiles(files = outputs)),
127 OutputGroupInfo(
Jade Philipoom39c9b322022-03-08 12:27:10 +0000128 otbn_objs = depset(objs + deps),
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800129 elf = depset([elf]),
130 rv32embed = depset([rv32embed]),
131 archive = depset([archive]),
132 ),
133 # Emit a CcInfo provider so that this rule can be a dependency in other
134 # cc_* rules.
135 CcInfo(
136 linking_context = cc_common.create_linking_context(
137 linker_inputs = depset([cc_common.create_linker_input(
138 owner = ctx.label,
139 libraries = depset([cc_common.create_library_to_link(
140 actions = ctx.actions,
141 feature_configuration = feature_configuration,
142 cc_toolchain = cc_toolchain,
143 static_library = archive,
144 )]),
145 )]),
146 ),
147 ),
148 ]
149
Jade Philipoom6195eef2022-04-19 10:32:36 +0100150def _otbn_sim_test(ctx):
151 """This rule is for standalone OTBN unit tests, which are run on the host
152 via the OTBN simulator.
153
154 It first generates binaries using the same method as otbn_binary, then runs
155 them on the simulator. Tests are expected to count failures in the w0
156 register; the test checks that w0=0 to determine if the test passed.
157 """
158 providers = _otbn_binary(ctx)
159
160 # Extract the output .elf file from the output group.
161 elf = providers[1].elf.to_list()[0]
162
163 # Create a simple script that runs the OTBN simulator on the .elf file and
164 # checks if the w0 register is 0.
165 simulator_cmd = "{} {} --dump-regs -".format(ctx.file._simulator.path, elf.short_path)
166 expected_string = "0x" + ("0" * 64)
167 script = '''
168 echo "Running simulator: {simulator_cmd}"
169 result=$({simulator_cmd} | grep -m 1 -P "w0 = [0-9A-Fa-f]+")
170 echo "Got : $result"
171 echo "Expected: w0 = {expected_string}"
172 if [[ "$result" == *"{expected_string}" ]]; then
173 echo "PASS"
174 exit 0
175 fi
176 echo "FAIL"
177 exit 1
178 '''.format(simulator_cmd = simulator_cmd, expected_string = expected_string)
179 script_file = ctx.actions.declare_file("run_{}.sh".format(elf.basename))
180 ctx.actions.write(
181 output = script_file,
182 content = script,
183 )
184
185 # The simulator and .elf file must be added to runfiles in order to be
186 # visible to the test at runtime.
187 runfiles = ctx.runfiles(files = (ctx.files.srcs + [elf, ctx.file._simulator]))
Jade Philipoom6b746f72022-08-01 09:52:15 +0100188 return [
189 DefaultInfo(runfiles = runfiles, executable = script_file),
190 providers[1],
191 ]
192
193def _otbn_consttime_test_impl(ctx):
194 """This rule checks if a program or subroutine is constant-time.
195
196 There are some limitations to this check; see the Python script's
197 documentation for details. In particular, the check may not be able to
198 determine that a program runs in constant-time when in fact it does.
199 However, if the check passes, the program should always run in constant
200 time; that is, the check can produce false negatives but never false
201 positives.
202
203 This rule expects one dependency of an otbn_binary or otbn_sim_test type,
204 which should provide exactly one `.elf` file.
205 """
206
207 # Extract the output .elf file from the output group.
208 elf = [f for t in ctx.attr.deps for f in t[OutputGroupInfo].elf.to_list()]
209 if len(elf) != 1:
210 fail("Expected only one .elf file in dependencies, got: " + str(elf))
211 elf = elf[0]
212
213 # Write a very simple script that runs the checker.
214 script_content = "{} {} --verbose".format(ctx.executable._checker.short_path, elf.short_path)
215 if ctx.attr.subroutine:
216 script_content += " --subroutine {}".format(ctx.attr.subroutine)
217 if ctx.attr.secrets:
218 script_content += " --secrets {}".format(" ".join(ctx.attr.secrets))
Jade Philipoom18a74792022-08-08 17:15:19 +0100219 if ctx.attr.initial_constants:
220 script_content += " --constants {}".format(" ".join(ctx.attr.initial_constants))
Jade Philipoom6b746f72022-08-01 09:52:15 +0100221 ctx.actions.write(
222 output = ctx.outputs.executable,
223 content = script_content,
224 )
225
226 # The .elf file must be added to runfiles in order to be visible to the
227 # test at runtime. In addition, we need to add all the runfiles from the
228 # checker script itself (e.g. the Python runtime and dependencies).
229 runfiles = ctx.runfiles(files = [elf])
230 runfiles = runfiles.merge(ctx.attr._checker[DefaultInfo].default_runfiles)
231 return [DefaultInfo(runfiles = runfiles)]
Jade Philipoom6195eef2022-04-19 10:32:36 +0100232
Jade Philipoomf05ed4d2022-07-28 17:27:21 +0100233def _otbn_insn_count_range(ctx):
234 """This rule gets min/max possible instruction counts for an OTBN program.
235 """
236
237 # Extract the .elf file to check from the dependency list.
238 elf = [f for t in ctx.attr.deps for f in t[OutputGroupInfo].elf.to_list()]
239 if len(elf) != 1:
240 fail("Expected only one .elf file in dependencies, got: " + str(elf))
241 elf = elf[0]
242
243 # Command to run the counter script and extract the min/max values.
244 out = ctx.actions.declare_file(ctx.attr.name + ".txt")
245 ctx.actions.run_shell(
246 outputs = [out],
247 inputs = [ctx.file._counter, elf],
248 command = "{} {} > {}".format(ctx.file._counter.path, elf.path, out.path),
249 )
250
251 runfiles = ctx.runfiles(files = ([out]))
252 return [DefaultInfo(files = depset([out]), runfiles = runfiles)]
253
Miguel Young de la Sota3b5a9f52022-03-24 16:11:42 -0400254otbn_library = rv_rule(
Jade Philipoom39c9b322022-03-08 12:27:10 +0000255 implementation = _otbn_library,
Jade Philipoom39c9b322022-03-08 12:27:10 +0000256 attrs = {
257 "srcs": attr.label_list(allow_files = True),
Timothy Trippelfc197342022-04-20 14:38:11 -0700258 "_cc_toolchain": attr.label(
259 default = Label("@bazel_tools//tools/cpp:current_cc_toolchain"),
260 ),
261 "_otbn_as": attr.label(
262 default = "//hw/ip/otbn/util:otbn_as",
263 executable = True,
264 cfg = "exec",
265 ),
Jade Philipoom39c9b322022-03-08 12:27:10 +0000266 },
267 fragments = ["cpp"],
268 toolchains = ["@rules_cc//cc:toolchain_type"],
269 incompatible_use_toolchain_transition = True,
270)
271
Miguel Young de la Sota3b5a9f52022-03-24 16:11:42 -0400272otbn_binary = rv_rule(
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800273 implementation = _otbn_binary,
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800274 attrs = {
275 "srcs": attr.label_list(allow_files = True),
Jade Philipoom39c9b322022-03-08 12:27:10 +0000276 "deps": attr.label_list(providers = [DefaultInfo]),
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800277 "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
Timothy Trippelfc197342022-04-20 14:38:11 -0700278 "_otbn_as": attr.label(
279 default = "//hw/ip/otbn/util:otbn_as",
280 executable = True,
281 cfg = "exec",
282 ),
Timothy Trippelfc197342022-04-20 14:38:11 -0700283 "_otbn_data": attr.label(
284 default = "//hw/ip/otbn/data:all_files",
285 allow_files = True,
286 ),
287 "_wrapper": attr.label(
Timothy Trippel024e3932022-04-20 15:40:55 -0700288 default = "//util:otbn_build",
289 executable = True,
290 cfg = "exec",
Timothy Trippelfc197342022-04-20 14:38:11 -0700291 ),
Chris Frantz9b34e4a2021-11-24 17:03:12 -0800292 },
293 fragments = ["cpp"],
294 toolchains = ["@rules_cc//cc:toolchain_type"],
295 incompatible_use_toolchain_transition = True,
296)
Jade Philipoom6195eef2022-04-19 10:32:36 +0100297
298otbn_sim_test = rv_rule(
299 implementation = _otbn_sim_test,
300 test = True,
301 attrs = {
302 "srcs": attr.label_list(allow_files = True),
303 "deps": attr.label_list(providers = [DefaultInfo]),
304 "_cc_toolchain": attr.label(default = Label("@bazel_tools//tools/cpp:current_cc_toolchain")),
305 "_otbn_as": attr.label(
306 default = "//hw/ip/otbn/util:otbn_as",
307 executable = True,
308 cfg = "exec",
309 ),
310 "_otbn_data": attr.label(
311 default = "//hw/ip/otbn/data:all_files",
312 allow_files = True,
313 ),
314 # TODO: make the simulator target an executable and update this
315 # dependency to match the others.
316 "_simulator": attr.label(default = "//hw/ip/otbn/dv/otbnsim:standalone.py", allow_single_file = True),
317 "_wrapper": attr.label(
318 default = "//util:otbn_build",
319 executable = True,
320 cfg = "exec",
321 ),
322 },
323 fragments = ["cpp"],
324 toolchains = ["@rules_cc//cc:toolchain_type"],
325 incompatible_use_toolchain_transition = True,
326)
Jade Philipoom6b746f72022-08-01 09:52:15 +0100327
328otbn_consttime_test = rule(
329 implementation = _otbn_consttime_test_impl,
330 test = True,
331 attrs = {
332 "srcs": attr.label_list(allow_files = True),
333 "deps": attr.label_list(providers = [OutputGroupInfo]),
334 "subroutine": attr.string(),
335 "secrets": attr.string_list(),
Jade Philipoom18a74792022-08-08 17:15:19 +0100336 "initial_constants": attr.string_list(),
Jade Philipoom6b746f72022-08-01 09:52:15 +0100337 "_checker": attr.label(
338 default = "//hw/ip/otbn/util:check_const_time",
339 executable = True,
340 cfg = "exec",
341 ),
342 },
343)
Jade Philipoomf05ed4d2022-07-28 17:27:21 +0100344
345otbn_insn_count_range = rule(
346 implementation = _otbn_insn_count_range,
347 attrs = {
348 "deps": attr.label_list(providers = [OutputGroupInfo]),
349 "_counter": attr.label(
350 default = "//hw/ip/otbn/util:get_instruction_count_range.py",
351 allow_single_file = True,
352 ),
353 },
354)