-
Notifications
You must be signed in to change notification settings - Fork 754
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
Dynamic discovery of technology-dependent primitives #1679
Conversation
@msfschaffner can you try this PR and see if you're able to produce a technology library for your preferred ASIC target? |
Thanks for the PR, @imphil! Will give this a go tomorrow. |
I won't get around to it today most likely; so feel free to have an initial look, but don't expect it to fully work yet 😄 |
d0942dc
to
a871a32
Compare
OK. Should be all sorted now, over to you @msfschaffner for testing! |
72e9b57
to
7f3ccb8
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.
Ok great, this seems to work! Although in cases where only one library is found, the degenerate generate begin
block can cause tooling issues... (see below). Looks like this is easily fixable, though.
e4feccd
to
d7fbd2a
Compare
Looks like Private CI is useful! Fixed a case where I didn't update the path of a generated flash memory in the chip DV environment. |
Reran with the newest version. It seems to work fine for our usecase (also the degenerate The only thing I keep on seeing are the following warnings:
the warnings persist even if I set the filetype to |
OK, the sporadic failures in private CI are caused by a race condition in fusesoc when creating the generated core files/primitives in multiple fusesoc runs at the same time. I'll look into it. |
Welcome to the rabbit hole ...
The core dependency issue needs to be merged first before CI runs on this one can pass. |
Ok, SGTM! |
Some primitives are intended to be implemented in a technology-specific way, e.g. differently for Xilinx or Altera FPGAs. Which implementation is to be used can be specified at synthesis time. Target-specific implementations are collected in "technology libraries", or "techlibs". When writing code, only "abstract primitives" are used. These are then implemented by a techlib. Currently, all techlibs are part of the OpenTitan repository, and all abstract primitives are hand-crafted. This commit adds a script to auto-generate these abstract primitives. The script is to be used as FuseSoC generator. Note that the script currently is in a rather "rough" form. The SystemVerilog "parsing" is done using a regex instead of a proper parser, and speed isn't great either. Signed-off-by: Philipp Wagner <phw@lowrisc.org>
Instead of handcoding the abstract primitives, they are now auto-generated through the primgen script, hooked up as fusesoc generator. This requires a not-yet-released version of fusesoc to have all information available that our generator needs. Use `pip3 install --user -U -r python-requirements.txt` to get a supported fusesoc version for this change. After this commit, technology libraries can simply be added to the fusesoc search path. A technology library is expected to be similar to the `prim_xilinx` library we have in the tree today; copy this library as a starting point. ``` fusesoc --cores-root=. --cores-root=path-to-your-other-techlib run .... ``` Signed-off-by: Philipp Wagner <phw@lowrisc.org>
This is a starting point for documenting the primmitives system, especially regarding technology-dependent primitives and how they are used/generated. Signed-off-by: Philipp Wagner <phw@lowrisc.org>
So, this should be the last update; I'm reasonably convinced that the implementation was/is correct and #1750 was really just a side effect of a previous implementation detail. To make our life easier tracking down dependency issues in the future I've included a patch to fusesoc which always dumps a |
Oh wow, looks like you had a hell of a debugging evening! |
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Generators now can be used to dynamically generate other cores depending on which cores were discovered during a fusesoc run. To enable this functionanlity, a list of all discovered cores is passed to the generator inside the GAPI file structure. This modifies the GAPI schema; but since it is a strict addition of a key which shouldn't affect users who did not use this functionality before, the GAPI version is not incremented. The motivating need for this work can be found in lowRISC/opentitan#1679. In short: RTL primitives, like a RAM module, exist in various specializations for different target technologies (e.g. one RAM implementation for Xilinx FPGAs, another one for Altera FPGAs). Such target-specific implementations of the RAM primitive are collected in a technology library. Not all users have access to all technology libraries, that's why these libraries need to be plug-and-play. A generator is now used to create a "wrapper primitive". The wrapper primitive is a simple RTL file with if/else blocks, choosing a target-specific implementation based on a parameter. To create this wrapper primitive, fusesoc searches through its list of core library paths, and passes all cores it found to a generator. The generator picks out (e.g. based on a standardized naming scheme) the implementations of a given primitive, and includes them in the "wrapper primitive".
Currently, all primitives are implemented in-tree. This is a reasonable approach as long as we can share them. But that's not the case for many ASIC targets. We therefore need a way to keep the target-dependent implementations of primitives out of the tree. At the same time, we don't want to hard-code dependencies on these targets, as different users of OpenTitan might have different libraries available.
This pull request solves the above problem by introducing the dynamic discovery of "technology libraries", groups of implementations of our primitives. The implementation heavily relies on FuseSoC generators, which enables us to dynamically discover technology libraries by simply making another directory of cores (a "FuseSoC library") known to FuseSoC. This can be done by adding another
--cores-root
parameter to a fusesoc call, or by setting theFUSESOC_CORES
environment variable (multiple directories can be specified colon-separated, like inPATH
).Note that this implementation relies on features in FuseSoC which are not yet upstreamed. It is therefore necessary to update fusesoc by running
Further documentation for this feature is contained in the
hw/ip/prim/README.md
added as part of this pull request. Please also have a look at the individual commit messages.Note: This PR currently contains #1671. It will be rebased to remove these commits once that PR lands.