-
Notifications
You must be signed in to change notification settings - Fork 792
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[multitop] Implement HW rules RFC #25580
base: master
Are you sure you want to change the base?
Conversation
6a33b1c
to
3ad7c24
Compare
util/rewrite_all_hw.py
Outdated
is_template = info.get("is_template", False) | ||
|
||
def_file_path = ippath / ("defs.bzl.tpl" if is_template else "defs.bzl") | ||
# build_file_path = ippath / ("BUILD.tpl" if is_template else "BUILD") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we also templating BUILD files?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes some (all?) ipgen IP template BUILD
file. Well technically they are not templates because their content does not depend on the template but we can't name them just BUILD
because then bazel considers them as a package and this can cause all sorts of weird issues potentially. Therefore calling BUILD.tpl
seems like an easy way to avoid this issue. Unless you have a better idea on how to handle this?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Will we be able to dispense with the "all_files" target? That may make these BUILD and BUILD.tpl files unnecessary.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Unfortunately, there was quite a lot of pushback against touching the "all_files" targets so I have given up on touching them for the time being.
util/rewrite_all_hw.py
Outdated
else: | ||
assert False, "unknown step" | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
if __name__ == '__main__':
main()
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah yes, although I should point out that this script will disappear at the end of the PR (I just forgot to add it yet). Still good practice though.
hw/top/defs.bzl
Outdated
names = {} | ||
for top in ALL_TOPS: | ||
for ip in top.ips: | ||
names[ip.name] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Would this be better as a comprehension? (also, maybe use an integer as the value, rather than empty dict?)
names = {ip.name: 1 for ip in top.ips for top in ALL_TOPS}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too bad the set is only in bazel nightly, hehe.
# WARNING This is a horrible hack: when we transition to host, we pretend | ||
# that this is earlgrey so opentitantool can compile... |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Agree. Longer term, we probably need to distinguish between chip-specific constants in opentitantool, stick them into something like a hashmap by chip, and add a --chip=...
cmdline argument to select which set of constants we care about.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes that's a big issue. I would like to raise it in the SW WG tomorrow, there is clearly some work to be done here. I think the multitop SW PR is a good starting point to generate such a data structure.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
One route: I could revive my devicetree generator, and opentitantool could consume its output for a description of the system. That would also leave open support for out-of-tree tops (instead of hard-coding entire known chips).
Out of curiosity, did you already consider a bazel module extension to create a repo that fills out the information from the hjson files? From the description, this starts to sound rather similar to a "foreign build system" or "foreign packaging" sort of problem. The hjson files seem to contain critical information about the build graph, such that it exists outside of bazel's BUILD files. It looks like this PR goes the route of requiring the user to generate the graph with a separate call, but could we do it on the fly (in a bazel module extension) with what we already have with topgen, reggen, and the hjson files? |
Everything top related in this PR is generated by topgen (+one defs.bzl file per IP that needs to be created manually). I agree is not ideal but since we already need to run topgen to get the top library, top linker file and so on, adding one more file doesn't sound too bad and definitely was the simplest option (not the most elegant). When I started this, it didn't really look like that and then evolved into this. If I was starting from scratch now, I would write it as a bazel extension ^^ Eventually we should probably replace the various I can look at how much work it would be to "port" to an extension, if it's not too large, maybe it can be done in this PR. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Just a few quick, probably ill-considered comments. Sorry I haven't had much time to look yet!
string_flag( | ||
name = "top", | ||
build_setting_default = "earlgrey", | ||
values = ALL_TOP_NAMES, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As written, this may be harmful to out-of-tree tops. The moniker here could probably be reused, but at some point, we'd probably want a registration setup instead of hard-coded, known in-tree tops. Though... could possibly get away with making it future work (but not delay too long).
Technically short names are less robust against collisions than labels, but the probability of that seems quite low, even in a zany ecosystem where someone registers many subsystems. Many is probably no more than dozens, hehe.
I'll note that this "top" isn't actually the top descriptor needed to determine how to compile or test software. For that, we'd need that "board" or "machine" selection. For example, it might be earlgrey on the CW340+hyperdebug machine.
It might be that this is a separate attribute that is okay on its own, though!
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I thought about out-of-tree tops and I think it is ok: behind the scene, everything is just labels, the top and/or IPs can be out of tree. Potentially the only issue is the name itself: as you said it seems unlikely to have a collision but technically not impossible. If we wanted to make it really really future proof, we could replace the "names" (earlgrey, darjeeling) by a full bazel path and turn that into a label_flag
, and maybe intro some aliases in .bazelrc
like "earlgrey" and "darjeeling" for well-known tops.
A bazel extension might help with the registration step.
Regarding the last point, yes that's on purpose: with @cfrantz we want to make the exec_env more modular. Eventually we envision that a full description will be the top, the platform (fpga, sim_dv, probably platform is not good name, maybe call it target) and the board (hyperdebug, CW310). Then the exec_env would be replaced by a set of toolchain to provide the tools for the various building steps (compile, sign, transform, dispatch test). It's still at the design stage however, we only have some vague notes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The life-cycle stage may also be part of ingredients that together create the exec env.
target_compatible_with = opentitan_require_ip(ip), | ||
top = ":top_desc", | ||
) | ||
for ip in ALL_IP_NAMES |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is another moniker where a registration setup would be good, at least at some point.
hw/top/defs.bzl
Outdated
names = {} | ||
for top in ALL_TOPS: | ||
for ip in top.ips: | ||
names[ip.name] = {} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Too bad the set is only in bazel nightly, hehe.
hw/top/defs.bzl
Outdated
for top in ALL_TOPS: | ||
for ip in top.ips: | ||
names[ip.name] = {} | ||
return names.keys() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Does this need to be sorted? Will it affect configuration hashes anywhere?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sorting is not necessary for this PR but I guess it doesn't hurt to do it anyway if this ever gets used in a way where the order is important.
Regarding the creation of the ips for a given top, the info is in the top hjson file. Perhaps we can at least enable the creation of a test to make sure all required IPS match between bazel and hjson, prior to having bazel do it from the file itself? |
util/rewrite_all_hw.py
Outdated
# Copyright lowRISC contributors (OpenTitan project). | ||
# Licensed under the Apache License, Version 2.0, see LICENSE for details. | ||
# SPDX-License-Identifier: Apache-2.0 | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Not clear what this script does. Could you add a file level doc string to describe it? The text can also be referred to via __doc__ and can be made to show up in the --help message. However, it is not clear from your comments if this script is here to stay.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This script was just used to create some large commits in this PR, it should be removed at the end of the PR as it will never be used anymore. I'll delete it when I update the PR.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can alternatively split the script into 3 (for each stage) and then just include the script content into the commit message that applies its changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Since the script is removed at the end, I thought it was a bit too much to split it into three. I can still do it if you think it's really necessary though?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I meant to only have the script in the commit message so it's never added or removed as part of repository. This makes the script and what it produces atomic in git history, and cause less confusion when reviewing by commits.
Add and then remove is also fine to me, I don't have too strong of an opinion about this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ah I see, that makes a lot of sense. I will do that.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done
util/rewrite_all_hw.py
Outdated
|
||
|
||
def main(): | ||
parser = argparse.ArgumentParser() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Adding args like this to ArgumentParser should print the doc string in the help message:
description=doc, formatter_class=RawTextHelpFormatter
Those macros record the IPs/top description in a struct. This struct can then be used to implement various build system rules and macros that need access to the top description. The main reason why these are macros and not rules is that some of this information needs to be available during bazel's loading phase, where only macro and variable expansion are available. Signed-off-by: Amaury Pouly <[email protected]>
3ad7c24
to
b9f02aa
Compare
I have looked into using a bazel extension and I think I can make it work with #25697. However, I think that for this particular PR, it is better to not use it. Indeed, the only file which gets autogenerated in the per-top Longer term, I have some changes to topgen that will lead us to checking less software files into the repository. It might be a good point to revisit this choice. The most important aspect is that even if we used a bazel extension to generated them, it would not change anything to how this information is used, i.e. it would generated the same things except that you would not see it. The only difference it would make is that the registration step is made a little bit easier. Also regarding the |
I'd quibble with that point when we start to support out-of-tree tops. Right now, the topgen templates assume the definitions's labels are at particular paths in the main upstream bazel repo. At some point, the .bzl files (or whatever is used to assemble the graph) are going to need cross-repo paths to be filled in, and I suspect that will strongly indicate a desire to write the module extension. The function of We don't have to add this support right now, though! But we might want to get that on a roadmap.
Yeah, that seems fine to me. I was "thinking aloud" and figured I'd raise the point for discussion. Thanks for considering it! 🙂 |
Agreed and thanks for your comments, it's definitely helpful to think about all that. One issue with the paths is that topgen can access a CLI particular to search for IPs outside of the repo but it has no way to know what bazel repo they are in (if any!). So yeah, I agree that generally speaking, bazel modules and extensions are the better way to go to support out of tree stuff, and avoid use of ugly environment variables. It might actually be worth thinking about this more generally: we have a similar issue for hooks and other things that could benefit from bazel extensions. |
b9f02aa
to
da60fd9
Compare
This commit was generated by the following script: from pathlib import Path import subprocess all_tops = ["darjeeling", "earlgrey"] all_ips = { "hw/ip/adc_ctrl": {}, "hw/ip/aes": {}, "hw/ip/aon_timer": {}, "hw/ip/csrng": {}, "hw/ip/dma": {}, "hw/ip/edn": {}, "hw/ip/entropy_src": {}, "hw/ip/gpio": {}, "hw/ip/hmac": {}, "hw/ip/i2c": {}, "hw/ip/keymgr": {}, "hw/ip/keymgr_dpe": {}, "hw/ip/kmac": {}, "hw/ip/lc_ctrl": {}, "hw/ip/otbn": {}, "hw/ip/otp_ctrl": {}, "hw/ip/mbx": {}, "hw/ip/pattgen": {}, "hw/ip/pwm": {}, "hw/ip/rom_ctrl": {}, "hw/ip/rv_core_ibex": {}, "hw/ip/rv_dm": {}, "hw/ip/rv_timer": {}, "hw/ip/soc_dbg_ctrl": {}, "hw/ip/spi_device": {}, "hw/ip/spi_host": {}, "hw/ip/sram_ctrl": {}, "hw/ip/sysrst_ctrl": {}, "hw/ip/uart": {}, "hw/ip/usbdev": {}, # templates "hw/ip_templates/alert_handler": {"is_template": True, "tops": all_tops}, "hw/ip_templates/clkmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/rstmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/pwrmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/rv_plic": {"is_template": True, "tops": all_tops}, "hw/ip_templates/flash_ctrl": {"is_template": True, "tops": ["earlgrey"]}, "hw/ip_templates/pinmux": {"is_template": True, "tops": all_tops}, # top_earlgrey 'hw/top_earlgrey/ip/ast': {}, 'hw/top_earlgrey/ip/sensor_ctrl': {}, # top_darjeeling 'hw/top_darjeeling/ip/ast': {}, 'hw/top_darjeeling/ip/sensor_ctrl': {}, 'hw/top_darjeeling/ip/soc_proxy': {}, } project_root = Path(__file__).parents[1].resolve() def run_buildifier(project_root): subprocess.run( ["./bazelisk.sh", "run", "//quality:buildifier_fix"], check=True, cwd = project_root ) def run_topgen(project_root): for top in all_tops: subprocess.run( ["./util/topgen.py", "-t", f"hw/top_{top}/data/top_{top}.hjson"], check=True, cwd = project_root, ) subprocess.run( ["make", "-C", "hw", "cmdgen"], check=False, cwd = project_root, ) def step1(project_root): new_files = [] for (_ippath, info) in all_ips.items(): ippath = project_root / Path(_ippath) ip_name = ippath.name is_template = info.get("is_template", False) def_file_path = ippath / ("defs.bzl.tpl" if is_template else "defs.bzl") # build_file_path = ippath / ("BUILD.tpl" if is_template else "BUILD") # If file does not exist, create one. if def_file_path.exists(): print(f"File {def_file_path} already exists, will overwrite") new_files.append(def_file_path) if is_template: for top in info["tops"]: new_files.append(project_root / f"hw/top_{top}/ip_autogen" / ip_name / "defs.bzl") if is_template: hjson_bazel_target = f"//hw/top_${{topname}}/ip_autogen/{ip_name}:data/{ip_name}.hjson" # noqa: E231, E501 else: hjson_bazel_target = f"//{_ippath}/data:{ip_name}.hjson" # noqa: E231 print(hjson_bazel_target) def_file = [ '# Copyright lowRISC contributors (OpenTitan project).\n', '# Licensed under the Apache License, Version 2.0, see LICENSE for details.\n', '# SPDX-License-Identifier: Apache-2.0\n', 'load("//rules/opentitan:hw.bzl", "opentitan_ip")\n', '\n', '{} = opentitan_ip(\n'.format(ip_name.upper()), ' name = "{}",\n'.format(ip_name), ' hjson = "{}",\n'.format(hjson_bazel_target), ')\n', ] def_file_path.write_text(''.join(def_file)) # Run buildifier. run_buildifier(project_root) run_topgen(project_root) subprocess.run( ["git", "add"] + new_files, check = True, cwd = project_root, ) subprocess.run( [ "git", "commit", "-vas", "-m", "[bazel] Use new rules to describe IPs", # noqa: E231 "-m", "This commit was generated by the following script:", "-m", Path(__file__).read_text(), ], check=True, cwd = project_root ) step1(project_root) Signed-off-by: Amaury Pouly <[email protected]>
Signed-off-by: Amaury Pouly <[email protected]>
For now this package contains nothing but a definition file that collects all tops. Signed-off-by: Amaury Pouly <[email protected]>
Now that the top description is available from the opentitan_top macro, we can create rules to extract it in a form that is more convenient for the build system. This commit introduces three rules: - two to extract the top's C library and linker files - one to construct a top description in the form of a provider This rules essentially reformats the opentitan_top's struct content as a provider. The reason for this indirection is that for rules that will depend on the top description in the analysis phase (and not the loading phase), getting the information via a provider is much cleaner and useful that via a struct. Signed-off-by: Amaury Pouly <[email protected]>
Signed-off-by: Amaury Pouly <[email protected]>
The new rule takes an input a target created by opentitan_top and will eventually replace the old one that directly used an hjson file. Signed-off-by: Amaury Pouly <[email protected]>
This commit add a new ALL_IP_NAMES variable that collects the names of all IPs in all tops listed in ALL_TOPS. It also introduces two new macros opentitan_if_ip and opentitan_require_ip that can be used to conditional include starlark expression based on the availability of a particular IP for the selected top, or express compatibility requirements for targets. Signed-off-by: Amaury Pouly <[email protected]>
These new targets are multitop aware and will replace the existing ones. Note that those targets are marked with compatibility requirements on the top. For example, //hw/top:mbx_c_regs cannot be compiled with --//hw/top=earlgrey. This guarantees that by transitivity, targets that depends on, .e.g mbx headers, can only be compiled on relevant targets. Signed-off-by: Amaury Pouly <[email protected]>
This commit was generated by the following script: from pathlib import Path import subprocess import os all_tops = ["darjeeling", "earlgrey"] all_ips = { "hw/ip/adc_ctrl": {}, "hw/ip/aes": {}, "hw/ip/aon_timer": {}, "hw/ip/csrng": {}, "hw/ip/dma": {}, "hw/ip/edn": {}, "hw/ip/entropy_src": {}, "hw/ip/gpio": {}, "hw/ip/hmac": {}, "hw/ip/i2c": {}, "hw/ip/keymgr": {}, "hw/ip/keymgr_dpe": {}, "hw/ip/kmac": {}, "hw/ip/lc_ctrl": {}, "hw/ip/otbn": {}, "hw/ip/otp_ctrl": {}, "hw/ip/mbx": {}, "hw/ip/pattgen": {}, "hw/ip/pwm": {}, "hw/ip/rom_ctrl": {}, "hw/ip/rv_core_ibex": {}, "hw/ip/rv_dm": {}, "hw/ip/rv_timer": {}, "hw/ip/soc_dbg_ctrl": {}, "hw/ip/spi_device": {}, "hw/ip/spi_host": {}, "hw/ip/sram_ctrl": {}, "hw/ip/sysrst_ctrl": {}, "hw/ip/uart": {}, "hw/ip/usbdev": {}, # templates "hw/ip_templates/alert_handler": {"is_template": True, "tops": all_tops}, "hw/ip_templates/clkmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/rstmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/pwrmgr": {"is_template": True, "tops": all_tops}, "hw/ip_templates/rv_plic": {"is_template": True, "tops": all_tops}, "hw/ip_templates/flash_ctrl": {"is_template": True, "tops": ["earlgrey"]}, "hw/ip_templates/pinmux": {"is_template": True, "tops": all_tops}, # top_earlgrey 'hw/top_earlgrey/ip/ast': {}, 'hw/top_earlgrey/ip/sensor_ctrl': {}, # top_darjeeling 'hw/top_darjeeling/ip/ast': {}, 'hw/top_darjeeling/ip/sensor_ctrl': {}, 'hw/top_darjeeling/ip/soc_proxy': {}, } project_root = Path(__file__).parents[1].resolve() def find_all_files(project_root, search): # Use ripgrep to find all matching files res = subprocess.run( ["rg", "-l", search], capture_output = True, cwd = project_root, ) # ripgrep returns 1 if there are no matches, 2 on error if res.returncode == 1: return [] assert res.returncode == 0, "ripgrep command failed" return [Path(os.fsdecode(path)) for path in res.stdout.splitlines()] def global_replace(project_root, search, replace, verbose): print(f'global replace "{search}" by "{replace}"') for path in find_all_files(project_root, search): path = project_root / path if verbose: print(f"Patching {path}") # Read, patch, write f = path.read_text() f = f.replace(search, replace) path.write_text(f) def run_buildifier(project_root): subprocess.run( ["./bazelisk.sh", "run", "//quality:buildifier_fix"], check=True, cwd = project_root ) def run_topgen(project_root): for top in all_tops: subprocess.run( ["./util/topgen.py", "-t", f"hw/top_{top}/data/top_{top}.hjson"], check=True, cwd = project_root, ) subprocess.run( ["make", "-C", "hw", "cmdgen"], check=False, cwd = project_root, ) def step2(project_root): for (_ippath, info) in all_ips.items(): ippath = project_root / Path(_ippath) ip_name = ippath.name is_template = info.get("is_template", False) replacements = {} for typ in ["c", "rust"]: new_target = f"//hw/top:{ip_name}_{typ}_regs" # noqa: E231 if is_template: for top in info["tops"]: # Some exceptions if ip_name in ["rv_plic", "alert_handler"]: replacements[f"//hw/top_{top}:{ip_name}_{typ}_regs"] = new_target # noqa: E231, E501 else: replacements[f"//hw/top_{top}/ip_autogen/{ip_name}:{ip_name}_{typ}_regs"] = new_target # noqa: E231, E501 else: replacements[f"//{_ippath}/data:{ip_name}_{typ}_regs"] = new_target # noqa: E231 for (old, new) in replacements.items(): global_replace(project_root, old, new, verbose=False) # Run buildifier. run_buildifier(project_root) run_topgen(project_root) subprocess.run( [ "git", "commit", "-vas", "-m", "Replace old header targets by new ones", # noqa: E231 "-m", "This commit was generated by the following script:", "-m", Path(__file__).read_text(), ], check=True, cwd = project_root ) step2(project_root) Signed-off-by: Amaury Pouly <[email protected]>
This commit was generated by the following script: from pathlib import Path import subprocess import os all_tops = ["darjeeling", "earlgrey"] project_root = Path(__file__).parents[1].resolve() def find_all_files(project_root, search): # Use ripgrep to find all matching files res = subprocess.run( ["rg", "-l", search], capture_output = True, cwd = project_root, ) # ripgrep returns 1 if there are no matches, 2 on error if res.returncode == 1: return [] assert res.returncode == 0, "ripgrep command failed" return [Path(os.fsdecode(path)) for path in res.stdout.splitlines()] def run_buildifier(project_root): subprocess.run( ["./bazelisk.sh", "run", "//quality:buildifier_fix"], check=True, cwd = project_root ) def run_topgen(project_root): for top in all_tops: subprocess.run( ["./util/topgen.py", "-t", f"hw/top_{top}/data/top_{top}.hjson"], check=True, cwd = project_root, ) subprocess.run( ["make", "-C", "hw", "cmdgen"], check=False, cwd = project_root, ) def delete_rule(lines, rule_name, target_name, file_name): try: start_idx = 0 while start_idx < len(lines): start_idx = lines.index(f'{rule_name}(\n', start_idx) if target_name is None or lines[start_idx + 1] == f' {target_name},\n': # noqa: E231 break start_idx = start_idx + 1 except ValueError: assert False, \ f"did not find beginning of rule {rule_name} (target name {target_name}) in {file_name}" try: end_idx = lines.index(')\n', start_idx + 1) except ValueError: assert False, \ f"did not find end of rule {rule_name} (target name {target_name}) in {file_name}" if start_idx > 0 and lines[start_idx - 1] == '\n' and \ end_idx + 1 < len(lines) and lines[end_idx + 1] == '\n': end_idx += 1 return lines[:start_idx] + lines[end_idx + 1:], start_idx, lines[start_idx:end_idx + 1] def delete_all_rules(lines, rule_name, target_name, file_name): while True: try: lines, _, _ = delete_rule(lines, rule_name, target_name, file_name) except: # noqa: E722 break return lines def step3(project_root): files = list(set( find_all_files(project_root, "autogen_hjson_c_header\\(") + find_all_files(project_root, "autogen_hjson_rust_header\\(") )) print(files) for path in files: build_file_path = project_root / path build_file = build_file_path.read_text().splitlines(keepends=True) # Remove load to //rules:autogen.bzl build_file, _, _ = delete_rule( build_file, 'load', '"//rules:autogen.bzl"', build_file_path ) # Remove autogen_hjson_c_header and autogen_hjson_rust_header build_file = delete_all_rules( build_file, 'autogen_hjson_c_header', None, build_file_path ) build_file = delete_all_rules( build_file, 'autogen_hjson_rust_header', None, build_file_path ) build_file_path.write_text(''.join(build_file)) # Run buildifier. run_buildifier(project_root) run_topgen(project_root) subprocess.run( [ "git", "commit", "-vas", "-m", "Remove old header targets", # noqa: E231 "-m", "This commit was generated by the following script:", "-m", Path(__file__).read_text(), ], check=True, cwd = project_root ) step3(project_root) Signed-off-by: Amaury Pouly <[email protected]>
Those rules are now unused Signed-off-by: Amaury Pouly <[email protected]>
With the new rule, a very easy way to get the list of all Hjson files for IPs is to look at the `ip_hjson` attribute of the definition of //hw/top:top_<name>_desc. Since this script kind of assumes earlgrey anyway, explicitely point to the earlgrey description of now. Signed-off-by: Amaury Pouly <[email protected]>
Unfortunately, opentitantool is far from compiling with darjeeling due to many explicit dependencies on earlgrey. At the same time, opentitantool provides many operations which are not earlgrey-specific at all such as flash image generation, signing, modid checks, etc This commit introduces a necessary hack: in the host transition for the golden toolchain, always set the top to earlgrey. This way we can ensure that opentitantool will compile. For now this does not introduce any problem but this is not a proper fix. Signed-off-by: Amaury Pouly <[email protected]>
da60fd9
to
943c94a
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Ran out of time, so I'll have to come back: I still need to look at the particular implementation of the rules. However, the general way that this setup works seems good for now.
Thanks for the hard work!
break | ||
|
||
return select({ | ||
"//hw/top:is_{}".format(top): obj |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This top matching may be okay, too, but we could also add constraint values for the IPs directly and define platforms for the tops. Since it's all hidden underneath opentitan_require_ip()
, I think we can defer that decision as well, though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The decision to hide the details behind macros is a deliberate one so we are not bound by the current implementation and could switch to platforms in the future. We had a long discussion about platforms with @cfrantz and I am not convinced there are the way to go for tops, at least in their current state (there were some improvements in bazel 8.0). It would require one constraint value per IP and top, and in the future we might want more (like "does the top have an embedded flash"). All of this for no obvious benefit over the current implementation with a string flag?
It seems to me that platforms are more useful for toolchain related decisions, and therefore we might want to use them to replace execution environment. Constraint value could be very useful to represent the test platform/board.
top_lib = "//hw/top_darjeeling/sw/autogen:top_darjeeling", | ||
top_ld = "//hw/top_darjeeling/sw/autogen:top_darjeeling_memory", |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might get a bit more interesting with support for different address spaces (depending on how software uses them), but we can iterate. There aren't terribly many tops at this time, so it's easy enough to update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This file is autogenerated by topgen so changing is indeed very simple. For addresses spaces, we don't seem to have any use case yet where the software would like to know about address spaces other than its own. In particular, I imagine that if there were two address spaces for SW (e.g. one per CPU?) then you would compile your software for either one or the other and that would become part of the "top selection". All other cases could be handled by adding information to the DT but again I haven't yet seen this requirement.
Maybe @Razer6 has more information on this topic?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, except I think the alias feature for C header generation may need to be preserved.
"alias": attr.label( | ||
mandatory = False, | ||
allow_single_file = True, | ||
doc = "A path to an alias file", | ||
), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The alias feature might be used out-of-tree, to replace some of those integrator-specific CSRs with the real definitions. Dropping support for it might be a problem. See #10982 for the motivations.
Probably the Rust generation should support it as well, but that's a separate problem, haha.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the explanation, I had no idea what aliases were (I could not find any example or documentation). I can add it back to the rule and add an alias
attribute to the opentitan_ip
macros. This way integrators can just edit the defs.bzl
files to add the alias if them want to.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This would be used in a separate header generation target out-of-tree, I think. In other words, in a separate bazel workspace, someone would write their own opentitan_ip_c_header
target that uses our rule, and our rule's responsibility is simply to pass through the file to the command line option and into the sandbox.
I'll note that we have no uses of the alias attribute in our repo. One of our integrators might be able to help, but we probably ought to document this, haha.
This PR implements the Multitop HW desc rule RFC. It replaces #24791 which is the original implementation.
There is one notable difference between this implementation and the one suggested in the RFC.
RFC suggested implementation
The RFC suggested to build the top description entire out of rules and providers:
The information is then used to e.g. build headers:
Problem with this approach
While this approach works fine, we need to consider what happens when we mix this multitop. Suppose that we have a way to selecting a top though a bazel config and we use that to point to the relevant top description:
This works fine because both Earlgrey and Darjeeling have a UART. Now consider an IP block that only exists in Earlgrey or Darjeeling:
We run into an issue: the rule
opentitan_ip_c_headers
cannot work forip = dma
if run on earlgrey because Earlgrey does not have a DMA and therefore does not even know where the DMA hjson file is. One solution could be to output an empty header file but this would just push the problem onto the users: if they try to use it and depend on DMA register offsets, they will fail to compile. Therefore, the proper definition should be:Where
opentitan_require_top
is some unspecified-as-of-yet macro, for example:But now run into another issue: we have to manually specify for each IP the list of tops that it is compatible with! This means that adding a top require adding 35+ conditions everywhere! Worse, if we change the definition of a top (e.g. Darjeeling which is supposed to be easily tweakable) then we have to change this as well. This defeats the entire point of have a top description generated by bazel.
Formalizing the problem
Thinking back, what we really would like to write is this:
where
opentitan_require_ip
is a magical macro that somehow expands to the list of tops with the DMA block. In order to write such a macro, we need to remember that macros are expanded during Bazel's loading phase, i.e. before the build graph is even constructed. During the loading phase, the only "symbols" available to macros are:.bzl
files.bzl
filesIt is clear that in order to work,
opentitan_require_ip
needs access to a (possibly scaled-down) description of every top. Therefore the conclusion is that the top descriptions needs to be available in the loading phase. With the rule+provider approach from the RFC, it is only available in the analysis phase.Solution
The solution proposed in the RFC is therefore to still use the idea of the RFC but at the loading phase. Here
opentitan_ip
andopentitan_top
become macros returningstruct
. Those need to be placed in newly created.bzl
files to be usable in the loading phase:A simple implementation of those macros is to simply wrap the information in a
struct
:This allows use to implement
opentitan_require_ip
simply as follows:Of course, we do not want to carry those struct around for the analysis phase so we still want to create a target that exports an
OpenTitanTopInfo
provider as before. This is implemented in the PR and a simplified version of the code is the following: