Skip to content
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

Merged
merged 3 commits into from
Mar 19, 2020

Conversation

imphil
Copy link
Contributor

@imphil imphil commented Mar 4, 2020

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 the FUSESOC_CORES environment variable (multiple directories can be specified colon-separated, like in PATH).

Note that this implementation relies on features in FuseSoC which are not yet upstreamed. It is therefore necessary to update fusesoc by running

cd $REPO_TOP
pip3 install --user -r python-requirements.txt

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.

@imphil
Copy link
Contributor Author

imphil commented Mar 4, 2020

@msfschaffner can you try this PR and see if you're able to produce a technology library for your preferred ASIC target?

@msfschaffner
Copy link
Contributor

Thanks for the PR, @imphil! Will give this a go tomorrow.

@imphil
Copy link
Contributor Author

imphil commented Mar 5, 2020

  • CI is currently failing for unknown reasons (it works on my machine). I'll dig into that. (Edit: The hierarchical names changed, but I'm not quite sure why my updates to them are not yet fully correct. Continuing to look.)
  • I didn't account for the fact that some techlibs don't implement primitives, in which case we want to rely on the generic one (flash and ram_1p are examples). I need to add this functionality. (That's now implemented)

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 😄

@imphil
Copy link
Contributor Author

imphil commented Mar 6, 2020

OK. Should be all sorted now, over to you @msfschaffner for testing!

Copy link
Contributor

@msfschaffner msfschaffner left a 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.

hw/ip/prim/util/primgen.py Outdated Show resolved Hide resolved
hw/ip/prim/util/primgen.py Outdated Show resolved Hide resolved
hw/ip/prim/util/primgen.py Show resolved Hide resolved
@imphil imphil force-pushed the primgen branch 2 times, most recently from e4feccd to d7fbd2a Compare March 11, 2020 16:30
@imphil
Copy link
Contributor Author

imphil commented Mar 11, 2020

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.

@msfschaffner
Copy link
Contributor

Reran with the newest version. It seems to work fine for our usecase (also the degenerate generate begin block works fine now in DC).

The only thing I keep on seeing are the following warnings:

INFO: Setting up project
INFO:edalize.edatool:Setting up project
WARNING: util/check_tool_requirements.py has unknown file type ''
WARNING:edalize.icarus:util/check_tool_requirements.py has unknown file type ''
WARNING: tool_requirements.py has unknown file type ''
WARNING:edalize.icarus:tool_requirements.py has unknown file type ''

the warnings persist even if I set the filetype to python, python3, py or py3, so it looks like this file type is not known to edalize yet.

@imphil
Copy link
Contributor Author

imphil commented Mar 12, 2020

These warnings have nothing to do with this PR, they are an artifact from #1664 most likely (and they are harmless). (Filed #1734 for that.)

@imphil
Copy link
Contributor Author

imphil commented Mar 13, 2020

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.

@imphil
Copy link
Contributor Author

imphil commented Mar 13, 2020

Welcome to the rabbit hole ...

The core dependency issue needs to be merged first before CI runs on this one can pass.

@msfschaffner
Copy link
Contributor

Hey @imphil , I think #1750 is good to go. Is there something else down in this rabbit hole that needs to be fixed :), or can we move this in?

@imphil
Copy link
Contributor Author

imphil commented Mar 18, 2020

I'd like to understand first what caused the change in behavior in #1750. I think I'm close (and #1750 is certainly a correct fix), but I'd like to be on the safe side there, as I don't want to randomly break the build for everyone. I'll get back to you probably later today.

@msfschaffner
Copy link
Contributor

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>
@imphil
Copy link
Contributor Author

imphil commented Mar 19, 2020

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 .dot file next to the eda.yml file, showing the whole dependency graph in GraphViz format. The whole thing looks like this:

image

@msfschaffner
Copy link
Contributor

Oh wow, looks like you had a hell of a debugging evening!
This looks like a really useful feature, thanks!

@imphil imphil merged commit e4fecf3 into lowRISC:master Mar 19, 2020
@imphil imphil deleted the primgen branch March 19, 2020 00:45
imphil added a commit to lowRISC/fusesoc that referenced this pull request Apr 6, 2020
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".
msfschaffner pushed a commit to msfschaffner/fusesoc that referenced this pull request Apr 17, 2020
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".
rswarbrick pushed a commit to rswarbrick/fusesoc that referenced this pull request Jun 12, 2020
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".
rswarbrick pushed a commit to rswarbrick/fusesoc that referenced this pull request Jul 1, 2020
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".
rswarbrick pushed a commit to lowRISC/fusesoc that referenced this pull request Sep 23, 2020
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".
imphil added a commit to lowRISC/fusesoc that referenced this pull request Mar 24, 2021
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".
imphil added a commit to lowRISC/fusesoc that referenced this pull request Oct 1, 2021
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".
vogelpi pushed a commit to vogelpi/fusesoc that referenced this pull request Jul 15, 2022
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".
bwitherspoon pushed a commit to bwitherspoon/edalize that referenced this pull request Dec 17, 2023
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".
bwitherspoon pushed a commit to bwitherspoon/edalize that referenced this pull request Dec 17, 2023
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".
bwitherspoon pushed a commit to bwitherspoon/fusesoc that referenced this pull request Dec 17, 2023
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".
bwitherspoon pushed a commit to bwitherspoon/fusesoc that referenced this pull request Mar 9, 2024
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".
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

2 participants