-
Notifications
You must be signed in to change notification settings - Fork 347
[FIRRTL] Export named references as macros to Verilog files #5180
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
Conversation
852b145
to
b1a309c
Compare
StrAttr:$refName, | ||
InnerRefArrayAttr:$namepath); |
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.
Can this instead use a symbol referring to a HierPathOp
? Or: is there a reason to not use one?
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.
HierPathOp
doesn't work as is because it enforces the namepath
to be at least 2 elements long. The path for a ref port can be one element long if it's a direct reference to a wire instead of through an instance. I'm not sure what the consequences of lifting the path length restriction are.
HierPathOp
also allows for the path to end with a FlatSymbolRef
to a module which doesn't make sense for this use case but that's not a big deal.
I can see if allowing for one element works.
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.
What about the approach of XMRRefOp
? This accepts either a symbol pointing at a HierPathOp
or an InnerRefAttr
? (It does this for exactly the reason of the former requiring namepath
size that is >= 2.)
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.
Thinking about your second point: we can lock this down with a verifier. However, I'd have to think if there is a fundamental reason why we don't want that ability in the exported refs. E.g., to get a handle to an instance. (I admit we don't use this now and the FIRRTL ABI doesn't mention it.)
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.
Good point, I'll try the XMRRefOp
approach
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.
That HierPathOp allows ending with a FlatSymbolRef doesn't really matter. We need to lower to a correct construct, not a construct that is only as wide as we need (for the same reason we don't have add and addWithConstant as separate).
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.
It does this for exactly the reason of the former requiring
namepath
size that is >= 2
We have examples in the test which only have one element: hw.hierpath private @ref2 [@wait_order::@baz]
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'm confused about that, because sizes less than 2 are disallowed here. Is verifyInnerRefs
not run when not inside a firrtl.circuit
block?
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, all of inner symbol verification + datastructures (+InnerSymDCE) do not work for HW, despite living there, which yes includes nothing driving verifyInnerRefs
-- which is driven as verification of op defining "InnerRefNamespace" (firrtl.circuit
), which contains InnerSymbolTable
s named by Symbols, and inner symbols point to things within those (so inner ref pair of symbol/innersymbol resolves that way). See #4453 and the graph of linked
10000
issues (or search for it). This should probably be reworked if cannot be adapted.
if (!topModule) | ||
return success(); |
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 had to double check why this was necessary, but this is catching the case of an external module being the top module.
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.
Is this even allowed? Verify seems to say allowed. Spec implies there has to be a real module, but is ambiguous.
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.
Both SFC and MFC accept the top module being an external module. We could disallow it without consequence I think.
Right now you can put an external module as the top module in a circuit and use that as a separately compiled unit. However, the compilation of that is basically a no-op. The client using this also has to have an external module pointing at this circuit containing another extmodule. I don't see why we need to enable such redirection (duplication?).
if (!refType || isZeroWidth(refType.getType()) || | ||
topModule.getPortDirection(portIndex) != Direction::Out) | ||
continue; |
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.
We may have to think about how to handle the zero width case. We may need to generate the macro as '0
. TBD.
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.
commented out macro? What would it point to? Since 0-width elements are generally removed, I don't see why a ref to a zero width element wouldn't also.
lib/Dialect/HW/HWOps.cpp
Outdated
ArrayAttr::get(odsBuilder.getContext(), namePath)); | ||
} | ||
|
||
LogicalResult ExportableRefOp::verifyInnerRefs(hw::InnerRefNamespace &ns) { |
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.
Using a HierPathOp
symbol would avoid the duplication of this checking here.
lib/Dialect/HW/HWOps.cpp
Outdated
return success(); | ||
} | ||
|
||
void ExportableRefOp::print(OpAsmPrinter &p) { |
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 custom printer/parsers necessary or can we get by with the default ones if using a HierPathOp
?
; CHECK: `define ref_Top_Top_direct_probe direct | ||
; CHECK-NEXT: `define ref_Top_Top_inner_probe inner.x | ||
; CHECK-NEXT: `define ref_Top_Top_keyword_probe always_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.
This looks awesome.
I'm wondering we can avoid introducing a new operation, instead I think we can directly use MacroDefOp at LowerXMROp by extending with symbol substitution as we are doing for VerbatimOp so that we can substitute legalized module names, e.g. Other comments if we will introduce a new operation:
|
I think this can work, although we then lose the verification of the namepath until we try to substitute it at the end.
I don't think so, because
That makes sense. Or, we can add a |
@uenoku wrote:
This is brittle as it locks in the structure of the string encoding the path. E.g., if module Everything is easier if we separate the hierarchical path from the string, like how |
Is there a test case where the reference is to a literal value rather than a wire or reg? As I think that is allowed...? |
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.
We really shouldn't need new ops for this and we shouldn't be making macros in export verilog. The macro should be created at lowering from firrtl.
/// Create a MacroDeclOp and MacroDefOp for the ExportableRefOp. | ||
/// This is called before the symbolCache has been frozen, | ||
/// so the MacroDefOp will not have it's format string set yet. | ||
static MacroDefOp createExportableRefMacro(ExportableRefOp op) { |
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.
ExportVerilog shouldn't be creating new macros, it should be emitting macros that already exist.
if (!refType || isZeroWidth(refType.getType()) || | ||
topModule.getPortDirection(portIndex) != Direction::Out) | ||
continue; |
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.
commented out macro? What would it point to? Since 0-width elements are generally removed, I don't see why a ref to a zero width element wouldn't also.
We can't generate the verbatim macro until names are legalized. Are you suggesting to do something similar to what @uenoku proposed with using symbol substitution? |
Not currently, I can add one |
Yes. |
Here's an example showing the zero width problem: //> using scala "2.13.10"
//> using repository "https://s01.oss.sonatype.org/content/repositories/snapshots"
//> using lib "org.chipsalliance::chisel::6.0.0-M1+3-35860474-SNAPSHOT"
//> using plugin "org.chipsalliance:::chisel-plugin::6.0.0-M1+3-35860474-SNAPSHOT"
//> using options "-unchecked", "-deprecation", "-language:reflectiveCalls", "-feature", "-Xcheckinit", "-Ywarn-dead-code", "-Ywarn-unused", "-Ymacro-annotations"
import chisel3._
import chisel3.util.experimental.BoringUtils
import circt.stage.ChiselStage
class Bar extends Module {
val a = WireInit(UInt(0.W), DontCare)
dontTouch(a)
}
class Foo extends Module {
val a = IO(Output(UInt(1.W)))
val bar = Module(new Bar)
a := BoringUtils.tapAndRead(bar.a)
}
object Main extends App {
println(
ChiselStage.emitSystemVerilog(
new Foo,
firtoolOpts = Array("-strip-debug-info")
)
)
} This currently produces the following FIRRTL:
This currently produces the following Verilog: module Bar();
endmodule
module Foo(
input clock,
reset,
output a
);
Bar bar ();
assign a = 1'h0;
endmodule The problem is that this has a legal reference to something, but that something is a zero-width thing. If This is further complicated if we continue to allow unknown width output ports as the signature of class Bar extends Module {
val a = Wire(UInt())
dontTouch(a)
a := WireInit(UInt(0.W), DontCare)
} And FIRRTL:
|
For reference, here was the PR I was working on for this: https://github.com/llvm/circt/pull/5169/files |
We don't allow uninferred widths on public modules, I don't see why we would allow probes of unknown widths on public modules. But I don't think we need to produce something for a ref to 0 as we've defined that lowering as not existing,. It may be convinient to allow it in operations at the firrtl level, but that doesn't mean those operations (on a ref to 0) will themselves lower to anything. |
b1a309c
to
6a95db3
Compare
I've removed the Since |
@@ -418,6 +425,62 @@ class LowerXMRPass : public LowerXMRBase<LowerXMRPass> { | |||
return success(); | |||
} | |||
|
|||
LogicalResult handleTopModuleRefPorts() { |
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.
Should this remove the ref ports from the module?
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.
All ref ports are already removed in the garbageCollect
function that's called after this.
I suppose you could do it for the top module here, although if we expand this to support multiple "public" modules in a circuit, it wouldn't be safe to start deleting ref ports here in case one public ref port has dataflow to another one in a separate module.
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.
we do need to do all public modules.
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 don't care were they are deleted, as long as they are.
I added a test-case on the mlir side. I'm not sure if you can actually express that in a fir file. |
}]; | ||
|
||
let arguments = (ins FlatSymbolRefAttr:$macroName, | ||
StrAttr:$format_string); | ||
StrAttr:$format_string, | ||
DefaultValuedAttr<NameRefArrayAttr,"{}">:$symbols); |
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.
OptionalAttr<FlatSymbolRefArrayAttr>:$symbols ?
DefaultValuedAttr<FlatSymbolRefArrayAttr,"{}">:$symbols ?
"build(odsBuilder, odsState, macroName, format_string," | ||
"odsBuilder.getArrayAttr({}));"> | ||
]; | ||
|
||
let assemblyFormat = [{ | ||
$macroName $format_string attr-dict |
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.
$macroName $format_string (`(` $symbols^ `)`)? attr-dict
They should be explicit.
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.
Updated
@@ -418,6 +425,62 @@ class LowerXMRPass : public LowerXMRBase<LowerXMRPass> { | |||
return success(); | |||
} | |||
|
|||
LogicalResult handleTopModuleRefPorts() { |
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.
we do need to do all public modules.
@@ -418,6 +425,62 @@ class LowerXMRPass : public LowerXMRBase<LowerXMRPass> { | |||
return success(); | |||
} | |||
|
|||
LogicalResult handleTopModuleRefPorts() { |
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 don't care were they are deleted, as long as they are.
if (!topModule) | ||
return success(); |
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.
Is this even allowed? Verify seems to say allowed. Spec implies there has to be a real module, but is ambiguous.
SmallVector<Attribute> refSendPath; | ||
SmallString<128> formatString; | ||
size_t formatIndex = 0; | ||
while (remoteOpPath) { |
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 needs to be building up a hierpath rather than a series of piece-wise substitutions.
test/Dialect/FIRRTL/lowerXMR.mlir
Outdated
// CHECK-LABEL: sv.macro.decl @ref_Top_Top_a | ||
// CHECK-LABEL: sv.macro.def @ref_Top_Top_a "{{[{][{]0[}][}]}}" {output_file = #hw.output_file<"ref_Top_Top.sv">, symbols = [#hw.innerNameRef<@Top::@w>]} | ||
// CHECK-LABEL: sv.macro.decl @ref_Top_Top_b | ||
// CHECK-LABEL: sv.macro.def @ref_Top_Top_b "{{[{][{]0[}][}]}}.{{[{][{]1[}][}]}}" {output_file = #hw.output_file<"ref_Top_Top.sv">, symbols = [#hw.innerNameRef<@Top::@foo>, #hw.innerNameRef<@Foo::@x>]} |
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 should be a single substitution of a hierpath. That way the macros are robust to hierarchy manipulation.
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.
Then should I remove the requirement for hierpath's size to be at least 2 (unless I add it outside the circuit op, in which case it's currently not checked at all)? Or should I do the XMRRefOp
thing where the symbol can point to a hierpath or be a single inner-ref?
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 don't know why we have XMRRefOp separate from hierpath. But you should dig into why hierpath op requires 2, rather than assuming it can't be changed.
Export refs for all public modules. Update macro assembly to explicitly include symbols. Fix paths that end in verbatim expressions.
Updated to handle all public modules. I also ran into an issue where a path pointing directly to a verbatim expression results in a null inner-ref attribute. I've handled the issue though I think it can come up in other situations such as resolving a reference to a verbatim expression in the same module. |
…ath op or containing its own inner ref
Updated to use the |
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
} else { | ||
symVerilogName = namify(sym, symOp); | ||
} | ||
} |
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 a change to the existing verbatim substitution behavior. However, I think this is the right one. If you come across a symbol that is a HierPathOp
, then you substitute that path.
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.
Yeah, otherwise it prints the symbol of the HierPathOp
itself
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 implementation looks good to me. It would be great if we can share the code with visitSV(XMRRefOp)
but not within 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.
Please file cleanup issues for stuff like this.
if (failed(resolveReferencePath(portValue, builder, ref, stringLeaf))) | ||
return failure(); |
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 will error if the path ends above the current module, yes?
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.
What do you mean by that exactly?
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 want to make sure this won't try to generate a path if there is an output reference fed by an input reference that starts "above" the current module.
However, the parser will categorically reject input probes. There shouldn't be a problem here.
A circuit that could lead to weirdness is something like the following. It's the classic "there is no value that you can substitute for this path because it is more than one path" type thing:
circuit Foo :
module Bar :
input a: Probe<UInt<1>>
output b: Probe<UInt<1>>
define b = a
module Foo :
wire x: UInt<1>
wire y: UInt<1>
inst bar of Bar
define bar.a = probe(x)
inst bar2 of Bar
define bar2.a = probe(y)
// ref_<circuit name>_<module name>_<ref name> <internal path | ||
// from module> |
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: the PR could use shorter names to get this into 80-characters:
// ref_<circuit name>_<module name>_<ref name> <internal path | |
// from module> | |
// ref_<circui-namet>_<module-name>_<ref-name> <path> |
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.
Updated
auto macroName = builder.getStringAttr("ref_" + circuitOp.getName() + | ||
"_" + module.getName() + "_" + | ||
module.getPortName(portIndex)); |
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.
What would happen if the circuit/main module, module, or ref are later renamed? Should this also be renamed or should it not? This is more of a question for the ABI doc about something like:
circuit always:
module always:
wire module: UInt<1>
Is this: ref_always_always_module module_0
, ref_always_0_always_0_module_0 module_0
, or something else?
I think it's reasonable to make a case that this follows the original FIRRTL name even if the ref changes. I.e., I think the current code is correct. The spec should probably make this explicit.
test/Dialect/FIRRTL/lowerXMR.mlir
Outdated
// CHECK-NEXT: sv.macro.def @ref_Top_Top_a "{{[{][{]0[}][}]}}"([#hw.innerNameRef<@Top::@w>]) {output_file = #hw.output_file<"ref_Top_Top.sv">} | ||
// CHECK-NEXT: sv.macro.def @ref_Top_Top_b "{{[{][{]0[}][}]}}"([@xmrPath]) {output_file = #hw.output_file<"ref_Top_Top.sv">} | ||
// CHECK-NEXT: sv.macro.def @ref_Top_Top_c "{{[{][{]0[}][}]}}.internal.path"([#hw.innerNameRef<@Top::@foo>]) {output_file = #hw.output_file<"ref_Top_Top.sv">} | ||
// CHECK-NEXT: sv.macro.def @ref_Top_Top_d "{{[{][{]0[}][}]}}"([#hw.innerNameRef<@Top::@xmr_sym>]) {output_file = #hw.output_file<"ref_Top_Top.sv">} | ||
// CHECK-NEXT: sv.macro.def @ref_Top_Foo_x "{{[{][{]0[}][}]}}"([#hw.innerNameRef<@Foo::@x>]) {output_file = #hw.output_file<"ref_Top_Foo.sv">} | ||
// CHECK-NEXT: sv.macro.def @ref_Top_Foo_y "internal.path" {output_file = #hw.output_file<"ref_Top_Foo.sv">} |
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.
Suggestion: CHECK-NEXT-LITERAL
would let you match {{0}}
directly without the regex escaping.
Informational: I find long matches hard to parse and will often split them up where the output_file
is a CHECK-SAME
on the next line with indentation. Only informational.
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'm very happy to learn about CHECK-NEXT-LITERAL
, {{[{][{]0[}][}]}}
looks like a nightmare
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.
Updated to be more readable.
FYI it's CHECK-NEXT{LITERAL}
.
&getContext(), "ref_" + circuitOp.getName() + "_" + | ||
module.getName() + ".sv")); |
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: could you avoid creating "ref_" + circuitOp.getName() + "_" + module.getName()
twice here and in macroName
creation?
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.
Updated
Co-authored-by: Hideto Ueno <uenoku.tokotoko@gmail.com>
This implements the proposed lowering of ref ports in the firrtl abi spec.
During the
LowerXMR
pass, for each public output ref port, anhw.exportableRef
op is created to track the ref. This op is a symbolic namepath for the ref. During theExportVerilog
pass,sv.macro.decl
andsv.macro.def
ops are created with the fully resolved namepath based on the legalized verilog names.Although the semantics are fairly firrtl specific, the exportable-ref op is part of the
hw
dialect since it needs to hang around untilExportVerilog
.