-
Notifications
You must be signed in to change notification settings - Fork 28
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
[major] Define option groups and instance choices #155
base: main
Are you sure you want to change the base?
Changes from all commits
823559c
6b11e2e
bce9deb
9e17563
ce20ae2
9cece18
a5dae25
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -307,6 +307,40 @@ circuit Foo : | |
public module Foo enablelayer A : | ||
``` | ||
|
||
## Targets | ||
|
||
A `target`{.firrtl} describes one way that a FIRRTL circuit may be specialized for a certain use case. | ||
Here, specialization means choosing a specific option for a target. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do targets themselves have a default value re:their options? (could they?) So if these /were/ emitted as parametric Verilog, we'd need to have implicit default value made explicit. A different design point is to require all values be enumerated, and probably the target itself indicates its default value. This fits with the framing of this as a sort of parameter --as "default" means "pick the default value" and isn't a value itself, at least in this framing :). This way all states are explicit and there's vocabulary for the default state, possibly a name that isn't known only as "unspecified" (such that "default" Platform could be named, not just the absence of being FPGA or ASIC), and flows/code working with this can just request "Target=X". This does have (hopefully minor?) implications re:macro / ABI details, but curious what you think about this and curious why it's done this way? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This was inherited from the original proposal. However, it occurred to me that the default could be made explicit. There's two ABI concerns, one current and one future, that motivate some notion of a default:
I do think it's entirely reasonable to roll the default into the target. Part of the changes here would be to decouple the target from instance choice and it therefore seems weird to handle the default (a target concern) with the instance choice. I think this means either every target has a reserved "default" option or the target indicates its default:
WDYT syntax-wise? For these, it would then be easiest to state that either instance choices must be fully specified or that the default is taken for any option not specified: instchoice foo, Platform:
FPGA => Bar
ASIC => Foo
default => Foo
instchoice foo, Platform:
default => Foo
FPGA => Bar |
||
|
||
It is often desirable to have one FIRRTL circuit that has different logic when simulated, synthesized and mapped to a field-programmable gate array (FPGA), or synthesized to a given process technology and standard cell library. | ||
While this per-target customizability can be expressed and specialized in a frontend language that produces FIRRTL, it is often desirable to expose the target specialization in the FIRRTL. | ||
By delaying the specialization, the specialization can either be done by a FIRRTL compiler or exposed in the artifacts of a FIRRTL compiler for specialization by the consumer. | ||
|
||
Practically, targets describe a limited form of parameterization and, if not specialized by a FIRRTL compiler, allow for FIRRTL compilers to generate parametric artifacts (e.g., parametric Verilog). | ||
|
||
The `option`{.firrtl} keyword declares an option group, which contains `case`{.firrtl} declarations naming the settings allotted to that option. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Drop this sentence, maybe this paragraph? |
||
The circuit can be specialized for a single case of a given option at any time. | ||
Multiple option groups can be declared to capture orthogonal dimensions of configuration. | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Are targets design-wide? Should they be? Or can they be set per-FIRRTL component, or maybe even per (public?) module (doesn't seem to be broken apart in that way, so I guess per-translation-unit)? If I specialize one component, can I use it with another component that has that option that hasn't been specialized? As-is I think you could include a On that, why are the macros per-module-instance (appears to be lowercase of at least the module name?), if they're bulk-enabled all at once? Thinking out loud a smidge -- so if these just set a single macro (or otherwise manipulate the global environment pre-elaboration) like Might be interesting to look at how neat we could make that with some light parameter/enum/generate emission, to perhaps avoid some messier ifdef chain. Offhand I think we'd have copies of the instantiation statements but I'm not sure that's a big loss and might be more readable when debugging / locally inspectable. It also makes explicitly that this is closed and must be one of those options, and makes that locally visible. Hmm. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They're design-wide/bulk-enabled just like layers. They can be set per-component, but only if a FIRRTL circuit is organized to make that possible. E.g., if you want to specialize every instance of a module, you are really describing different modules with different instance choices set by different targets. This is also motivated by the fact that I do not know of a way in Verilog to achieve per-instance specialization. There may be ways to do this that are tool-specific. It probably needs to be per-public module. However, there are some problems here for a module which has an instance choice which is instantiated by two public modules where one public module instantiates the other. In this situation, you need to give the option of enabling the instance choice from each public module (via two files). However, the two public modules have to agree on the choice. This could be avoided if public modules were not allowed to instantiated any common submodules. It could also be avoided with generate and parameters at the cost of exposing all instance choices in the design and not allowing easy directory pruning specialization. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I updated this to put the module name in the file. The issue of multiple public modules needing to agree is actually a red herring. While having multiple public modules is possible, a Verilog elaboration is always a tree rooted at a FIRRTL public module. Therefore, I think the only checking that needs to be done is if a macro is multiply defined. E.g., an error is possible for a design like the following with public modules The problem here is that there are six specialization files (Cartesian product This is going to need to be handled carefully in the implementation of the ABI based on how the internal names of defines are chosen. |
||
|
||
Specialization can occur either in the compiler or it can be materialized in the lowering. | ||
For details, consult the FIRRTL ABI specification. | ||
Specialization is not mandatory: options can be left unspecified, resorting to explicitly-defined default behaviour. | ||
|
||
A target may be declared using the `target`{.firrtl} keyword. | ||
The available options for which a target may take are listed using the `option`{.firrtl} keyword. | ||
An example FIRRTL circuit showing two targets, `Platform` and `Performance`, and their allowable options is shown below: | ||
|
||
``` firrtl | ||
circuit: | ||
target Platform: | ||
option FPGA | ||
option ASIC | ||
|
||
target Performance: | ||
option Slow | ||
option Fast | ||
``` | ||
|
||
# Circuit Components | ||
|
||
Circuit components are the named parts of a module corresponding to hardware. | ||
|
@@ -481,6 +515,47 @@ circuit Foo: | |
;; snippetend | ||
``` | ||
|
||
#### Instance Choice | ||
|
||
An instance choice is a submodule instance where the choice of submodule is conditioned based on the value of a `target`{.firrtl}. | ||
This enables per-instance specialization for different targets. | ||
Additionally, this is a mechanism for module replacement. | ||
|
||
An instance choice declaration specifies the instance name and names the option group based on which the choices are selected. | ||
A default module must be provided. | ||
The default module is instantiated by the instance choice when an option for its associated target is not specified. | ||
Subsequently, modules can be specified for the known choices of the selected option group. | ||
An instance choice does not need to specify modules for all cases. | ||
The instantiated modules must be either modules or external modules. | ||
|
||
An example of an instance choice is shown below. | ||
This instance choice is conditioned on the `Platform` target. | ||
By default, it will instantiate `DefaultClockGate` and when `Platform` is `FPGA` it will instantiate `FPGAClockGate`. | ||
|
||
``` firrtl | ||
circuit: | ||
target Platform: | ||
option FPGA | ||
option ASIC | ||
|
||
module DefaultClockGate: | ||
input clock_in: Clock | ||
output clock_out: Clock | ||
input enable: UInt<1> | ||
|
||
extmodule FPGAClockGate: | ||
input clock_in: Clock | ||
output clock_out: Clock | ||
input enable: UInt<1> | ||
|
||
module InstanceChoice: | ||
instchoice clock_gate of DefaultClockGate, Platform: | ||
FPGA => FPGAClockGate | ||
``` | ||
|
||
The type of an instance choice is the same as an instantiation of the default module. | ||
The ports of all module choices must be the same as the default module. | ||
|
||
nandor marked this conversation as resolved.
Show resolved
Hide resolved
|
||
### Memories | ||
|
||
Memories are stateful elements of a design. | ||
|
@@ -4128,6 +4203,7 @@ decl = | |
| decl_extmodule | ||
| decl_intmodule | ||
| decl_layer | ||
| decl_target | ||
| decl_type_alias ; | ||
|
||
decl_module = | ||
|
@@ -4156,6 +4232,11 @@ decl_layer = | |
{ decl_layer , newline } , | ||
dedent ; | ||
|
||
decl_target = | ||
"target" , id , ":" , [info] , newline, indent , | ||
{ "option" , id , ":" , [ info ] , newline } , | ||
dedent ; | ||
|
||
decl_type_alias = "type", id, "=", type ; | ||
|
||
port = ( "input" | "output" ) , id , ":" , (type | type_property) , [ info ] ; | ||
|
@@ -4177,11 +4258,17 @@ circuit_component = | |
| circuit_component_wire | ||
| circuit_component_reg | ||
| circuit_component_inst | ||
| circuit_component_instchoice | ||
| circuit_component_mem ; | ||
|
||
circuit_component_node = "node" , id , "=" , expr , [ info ] ; | ||
circuit_component_wire = "wire" , id , ":" , type , [ info ] ; | ||
circuit_component_inst = "inst" , id , "of" , id , [ info ] ; | ||
circuit_component_instchoice = | ||
"instchoice" , id , "of" , id , "," , id , ":" , newline , | ||
indent , | ||
{ id , "=>" , id , newline } , | ||
dedent; | ||
|
||
circuit_component_reg = | ||
"reg" , id , ":" , type , expr , [ info ] | ||
|
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.
Maybe it's bikeshedding too much, but could you explain the thinking regarding naming the keyword and word used to describe one dimension of optionality as a "target"?
Target seems like a great and compelling example of the optionality we want to capture, and we might even want to capture different dimensions of targets (maybe), but I kinda like calling these "options" better. "case" works for me too, although on the subject -- I'm not sure they need keywords really vs just listing them like in an enum or something.
"Target" seems to imply a more narrow use/purpose and does not itself imply any optionality, and just to be frank but not argumentative, I found it a bit confusing at first/as a result.
Commonly you specify a target as a known thing your compiler understands and MAYBE it'll produce different code as a result but also maybe not....
Anyway, I don't understand this change, would you mind sparing some words to explain?
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 motivation to call this "target" and not "option" is primarily by the use case. These are intended to allow for specializing the design for some target purpose, feature, technology, etc. FPGA vs. ASIC vs. Generic is closer to the more specific "target" than it is to a generic "option". You can counter argue that "target" doesn't make sense for parametric features that this would enable like cache size.
This is also try to permute the discussion with alternative language in response to confusion like this: #155 (comment)
I agree that "case" or "option" is unnecessary as these are really just describing a sum type. Any of the following would be fine and don't add a second superfluous keyword:
WDYT?