llvm / circt Goto Github PK
View Code? Open in Web Editor NEWCircuit IR Compilers and Tools
Home Page: https://circt.org
License: Other
Circuit IR Compilers and Tools
Home Page: https://circt.org
License: Other
RTL is currently signed-by-operation, but FIRRTL and verilog are sign-by-operand-type. RTL to verilog emission is broken for signed operations.
We now have an rtl.wire operation, and the verilog printer supports it as well, but we should implement lowering from firrtl.wire.
There are also other lowering that can be done: connect and partial connect, node, etc but those can be handled as a follow-on task.
For more information, see original Discourse discussion: https://llvm.discourse.group/t/handshake-lowerings-for-merge-like-and-branch-ops/2054.
These ops are all similar in that they deal with merging and branching, and their implementation is simplified by using the FIRRTL WhenOp
. For the reasons discussed, it makes sense to implement these operators directly using low-level logic instead of WhenOp
.
I plan to update the lowerings for each of the following ops (in separate PRs):
Since this repo started, the MLIR projected added a bunch of CMake support to make it easier to have out of tree dialects etc.
See more details and an example in this patch:
https://reviews.llvm.org/D77133
More generally, the existing cmake stuff should be reviewed, and I think there are a couple of hacks that would be great to fix.
Lots of stuff working with the FIRRTL dialect has to do things like op.getType().cast<FIRRTLType>()
, but we should know that only FIRRTL types are allowed. It think that ODS can express this, instead of:
let results = (outs AnyType:$result);
We should use:
let results = (outs FIRRTLType:$result);
and declare FIRRTLType in include/dialect/firrtl/Types.td
Currently and, or, xor, add operations are binary. They can be as well defined as variadic.
and
or
xor
add
mul
Based on discussion here, we would like to add some lowering passes to turn high- and mid-FIRRTL constructs into low-FIRRTL constructs. This is one such pass.
There was already some discussion in #18 about lowering aggregates, in the context of lowering to RTL. The pass I'm proposing would do this lowering within FIRRTL, so aggregates will already be flattened before lowering to RTL or LLHD.
From Discourse:
Flattening of aggregates is a property of low FIRRTL IR currently being handled by LowerTypes. This is pretty straightforward.
I'm proposing to add a similar pass to CIRCT.
The binary shift operation shall perform:
shru
- shall shift their left operand to the right by the number of bit positions given by the right operand. Fill the vacated bit positions with zerosshrs
- shall shift their left operand to the right by the number of bit positions given by the right operand. Fill the vacated bit positions with the value of the most significantshl
- shall shift their left operand to the left by the number of bit positions given by the right operand.Currently relying on FIRRTL circuit and module. RTL Dialect should have its own module ops.
The unary reduction operators shall perform a bitwise operation on a single operand to produce a single-bit result.
andr
orr
xorr
Currently, the syntax is:
rtl.module @B(%a: i1 { rtl.inout }) -> (i1 {rtl.name = "b"}, i1 {rtl.name = "c"})
With the not-really-optional port names for the output ports as hacky attributes on the results. Ideally, we'd have something like what was suggested in #93. Unfortunately (from #169):
On RTLModuleOp asm format: Ideally, the syntax would include the port names in the result specifications as shown in the #93. This would (best I can tell) require re-implementing the parseFunctionSignature helper in MLIR to modify the format of the result section. parseFunctionSignature is actually broken into two parts (args and results) internally but sadly those two functions are not exposed publicly. If anyone can think of a less heavyweight option than copy-pasting from MLIR, let me know!
LLVM files should include this block at the top of each source file (under the file description), it looks like this didn't get added.
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
Hi all. I am still ramping up to CIRCT/MLIR, and have been stuck on this for a while now. I'll explain what I think is happening, and you can proceed to tell me why I'm wrong :) (or, how I can improve X is welcome as well). If there's a better location for questions like this one, please let me know and I'll go there. Thanks!
So I added the following bit of code to the AndOp::getCanonicalizationPatterns
in Dialect/RTL/Ops.cpp
// and(..., x, x) -> and(..., x) -- idempotent
if (inputs[size - 1] == inputs[size - 2]) {
rewriter.replaceOpWithNewOp<AndOp>(op, op.getType(),
inputs.drop_back());
return success();
}
Now, I'd like to actually see that the input is actually dropped within test/rtl/bitwise.mlir
.
I wrote the following:
// CHECK-LABEL: func @and_equal_arguments_removed(%arg0: i7, %arg1: i7) -> i7 {
func @and_equal_arguments_removed(%a: i7, %b : i7) -> i7 {
// CHECK-NEXT: [[RES0:%[0-9]+]] = rtl.and %arg0, %arg1, %arg1 : i7
// CHECK-NEXT: return [[RESULT:.*]] : i7
%and1 = rtl.and %a, %b, %b : i7
return %and1 : i7
}
^ This passes, but from my understanding, it shouldn't. Instead, the 3rd line should be changed to:
// CHECK-NEXT: [[RES0:%[0-9]+]] = rtl.and %arg0, %arg1 : i7
, which says that the last %arg1
was dropped.
So, there's obviously something fundamentally flawed with my understanding here.
Edit: Here's the error message I get when removing the second %arg1 : i7
in line 3:
// CHECK-NEXT: [[RES0:%[0-9]+]] = rtl.and %arg0, %arg1 : i7
^
<stdin>:12:2: note: scanning from here
%0 = rtl.and %arg0, %arg1, %arg1 : i7
^
Input file: <stdin>
Check file: .../circt/test/rtl/bitwise.mlir
-dump-input=help explains the following input dump.
Input was:
<<<<<<
.
.
.
7: %2 = rtl.and %arg0, %arg1, %arg0 : i7
8: %3 = rtl.concat %0, %1, %2 : (i7, i7, i7) -> i21
9: return %3 : i21
10: }
11: func @and_equal_arguments_removed(%arg0: i7, %arg1: i7) -> i7 {
12: %0 = rtl.and %arg0, %arg1, %arg1 : i7
next:20 X~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ error: no match found
13: return %0 : i7
next:20 ~~~~~~~~~~~~~~~
14: }
next:20 ~~
15: }
next:20 ~
>>>>>>
--
********************
********************
Failed Tests (1):
CIRCT :: rtl/bitwise.mlir
firrtl produces tail operations on sint. Currently the firrtl dialect requires unsigned results.
Converting Affine loops directly to the handshake dialect gives the opportunity to use affine analysis of array index expressions to determine that access points are independent. This can result in more more memory parallelism.
Output from firrtl can have module instances before module declarations. firtool rejects these files.
This has been mentioned in a few PRs, but I think it makes sense for this to be its own thing, since all the tests should be changed to use more sophisticated pattern matching.
https://llvm.org/docs/CommandGuide/FileCheck.html#filecheck-regex-matching-syntax
It looks like this verification hook is just missing currently.
FIRRTL recently added support for assert/assume/cover operations. We should add support for these in the firrtl parser and IR dialect.
I am getting linker errors while building from master
/usr/bin/ld: lib/libMLIRHandshakeOps.a(HandshakeOps.cpp.o): in function `ValueRange<llvm::SmallVector<mlir::Value, 4> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
clang-10.0: error: linker command failed with exit code 1 (use -v to see invocation)
ldl -ltinfo -lpthread -lm ../llvm/build/lib/libLLVMDemangle.a && :
/usr/bin/ld: lib/libMLIRLLHDToLLVM.a(LLHDToLLVM.cpp.o): in function `ValueRange<std::array<mlir::Value, 2> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
/usr/bin/ld: lib/libMLIRLLHDToLLVM.a(LLHDToLLVM.cpp.o): in function `ValueRange<std::array<mlir::Value, 5> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
/usr/bin/ld: lib/libMLIRLLHDToLLVM.a(LLHDToLLVM.cpp.o): in function `ValueRange<std::array<mlir::Value, 7> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
/usr/bin/ld: lib/libMLIRLLHDToLLVM.a(LLHDToLLVM.cpp.o): in function `ValueRange<std::array<mlir::Value, 1> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
/usr/bin/ld: lib/libMLIRLLHDToLLVM.a(LLHDToLLVM.cpp.o): in function `ValueRange<std::array<mlir::Value, 3> &, void>':
/home/drom/work/github/circt/circt/build/../llvm/mlir/include/mlir/IR/OperationSupport.h:890: undefined reference to `mlir::ValueRange::ValueRange(llvm::ArrayRef<mlir::Value>)'
clang-10.0: error: linker command failed with exit code 1 (use -v to see invocation)
Anybody experiencing similar issues?
The dialect can currently use 'br' ops to break pipeline stages, but these are likely to be optimized in unintended ways by branch transformations, which makes this dialect difficult to compose with other dialects where those transformations will be needed.
With #184 we get a bunch of warnings:
/home/jodemme/circt/include/circt/Dialect/SV/Statements.td:74:1: warning: Op uses a deprecated, string-based OpBuilder format; use OpBuilderDAG with '(ins <...>)' instead
def AlwaysAtPosEdgeOp : SVOp<"alwaysat_posedge", [HasRegionTerminator]>,
Like many other Verilog keywords, reg
is an invalid identifier.
The Scala FIRRTL compiler adds an undocumented operand, dshlw
, in the PadWidths
transform. This will show up in emitted output if you run a low FIRRTL IR emitter with optimizations turned on, e.g.,:
firrtl -i Foo.fir -X verilog -E low -E verilog
This operand should (at least) be parsed by the FIRRTL CIRCT dialect for the same reason that we also parse cmem
and smem
---we should strive for interoperability with the lone extant FIRRTL compiler implementation.
Now that an RTL module exists, we can lower FIRRTL modules to RTL modules.
Since the lower-to-rtl pass runs on a FIRRTL module, would it make sense to change the lower-to-rtl pass to run on a FIRRTL circuit, make RTL modules, lower the FIRRTL stuff into them, and then remove the FIRRTL modules? Or is there some better way of doing this? I'd like to leave as much of the existing pass in place as possible.
The LLVM coding standards forbid <iostream>
. Please remove it. Also, please consider adoption of llvm::raw_ostream
for the stream IO in general, it is smaller code size and more efficient.
The FIRRTL dialect has things like firrtl.lt
but there are no corresponding RTL dialect ops for them. The ops should be added and lowerings implemented.
Note that the FIRRTL ops encode the sign of the operation into the types of the operands. We should use MLIR/LLVM style comparisons that embed the signedness into the operation name themselves, e.g. like std.cmpi.
Currently we're using AnyType
for primitive and other expression ops, e.g.:
let arguments = (ins AnyType:$input, I32Attr:$index);
let results = (outs AnyType:$result);
It would be much better to use IntType
where appropriate. This should eliminate some defensive checking in various places. This is blocked on getting rid of flip type operands though.
There's currently a small 'hack' to enable simulation with the handshake runner when code from the standard dialect has passed a pointer. In the current implementation, the pointer argument is converted to an internal memory, which is then intended to be initialized before simulation (although this doesn't actually happen yet). This is somewhat unintuitive since the memory conceptually lies outside the handshake design.
Fixing this properly probably requires better design for how the handshake dialect (And graph regions in general) are embedded inside SSACFG regions (e.g. the standard dialect). It would make more sense if a testable interface was provided by the standard to handshake conversion, which could then be easily interacted with by sequential code (like the handshake runner) or by an MLIR testbench. Today this complexity is built into the runner.
The wire op has an (optional) name attribute. It currently prints like this:
%42 = rtl.wire { name = "foo" } : i8
It would be much nicer to print it as this:
%42 = rtl.wire { name = "foo" } : i8
This should be possible just by changing the definition of WireOp in the .td file to:
def WireOp : RTLOp<"wire"> {
...
let assemblyFormat = [{
$name attr-dict `:` type($result)
}];
}
However, it looks like ODS isn't handling the name attribute correctly -- by omitting it when not present. Instead, ODS is generating an asmprinter that prints:
%12 = rtl.wire <<NULL ATTRIBUTE>> : i4
in the missing case, which is bad. ODS should be enhanced to reason about optional attributes just like it handles optional other parts of the operation grammer.
The following line is missing
; CHECK-NEXT: endmodule
After:
The test is passing. False-positive.
The FIRRTL dialect has things like firrtl.xorr
but there are no corresponding RTL dialect ops for them. The ops should be added and lowerings implemented.
Some time ago I have designed FIRRTL parser https://github.com/chipsalliance/tree-sitter-firrtl
using tree-sitter parser generator https://tree-sitter.github.io
it generates C/C++ parser
it passes all FIRRTL tests that I know of
if you are interested to reuse
The RTL dialect and verilog printer support an rtl.concat
operation.
The LowerToRTL.cpp pass should lower
firrtl.shl(x, 4)
==> {x, 4'h0}
(see the comment below)firrtl.cat
#6firrtl.cvt
(see the comment below)The current implementation of the handshake runner implicitly has one buffer place per operation. This is sufficient for models that are generated from the standard-to-handshake conversion, but may not be sufficient
for more general models.
Probably the right path forward is to implement a multi-threaded executable, either in a similar style to the handshake-runner, or through some lowering path (e.g. async dialect -> LLVM + coroutines -> binary) with dynamically growing fifos. Note that a bound can still be useful to detect improperly constructed designs.
Currently outputs to rtl.module
are represented as function/bb arguments that are connected to. We should move to a mode where they are more "result like". For example, instead of:
rtl.module @C(%a: i1 {rtl.direction = "in"},
%b: i1 {rtl.direction = "out"}) {
%0 = rtl.shl %a, %a : i1
rtl.connect %b, %0 : i1
}
we should have something like:
rtl.module @C(%a: i1 {rtl.direction = "in"}) -> "b": i1 {
%0 = rtl.shl %a, %a : i1
return %0 : i1
}
Given this, we can drop the rtl.direction
attribute, changing it to something like an rtl.inout
nullary attribute to represent the inout
case.
Currently, they support only SignlessIntegers of any size. They should support any type. Reasoning about what are valid SV types should be an issue for the lowering to the SV dialect, IMO.
https://mlir.llvm.org/docs/Canonicalization
and(x) -> x
-- noop [#38]and(..., 0) -> 0
-- annulment [#38]and(..., '1) -> and(...)
-- identity [#40]and(..., x, x) -> and(..., x)
-- idempotent [#85]and(..., c1, c2) -> and(..., c3)
where c3 = c1 & c2
-- constant folding [#91]and(x, and(...)) -> and(x, ...)
-- flatten [#114]and(..., x, not(x)) -> and(..., 0)
-- complementor(x) -> x
-- noop [#38]or(..., '1) -> '1
-- annulment [#38]or(..., 0) -> or(...)
-- identity [#40]or(..., x, x) -> or(..., x)
-- idempotent [#114]or(..., c1, c2) -> or(..., c3)
where c3 = c1 | c2
-- constant folding [#120]or(x, or(...)) -> or(x, ...)
-- flatten [#121]or(..., x, not(x)) -> or(..., '1)
-- complementxor(x) -> x
-- noop [#38]xor(..., 0) -> xor(...)
-- identity [#40]xor(..., '1) -> not(xor(...))
xor(..., x, x) -> xor(..., 0)
-- idempotent? [#114]xor(..., c1, c2) -> xor(..., c3)
where c3 = c1 ^ c2
-- constant folding [#120]xor(x, xor(...)) -> xor(x, ...)
-- flatten [#121]xor(..., x, not(x)) -> xor(..., '1)
add(x) -> x
-- noop [#38]add(..., 0) -> add(...)
-- identity [#40]add(..., x, x) -> add(..., shl(x, 1))
[#154]add(..., c1, c2) -> add(..., c3)
where c3 = c1 + c2
-- constant folding [#120]add(x, add(...)) -> add(x, ...)
-- flatten [#121]It doesn't appear that circt is under the llvm license---i.e. the GPLv2 exception.
There are a bunch of invariants spelled out in the FIRRTL spec that aren't enforced by the MLIR verifier. For example, it is invalid for the dest of a firrtl.connect to be smaller than the source of it. These should be enforced by the firrtl dialect so compiler bugs are caught closer to the source.
To finish lowering wire from the FIRRTL dialect to the RTL dialect, the RTL dialect must support vectors of wires.
While FIRRTL seems to have only vectors of wires, Verilog allows n-dimensional wires, which can be either packed or unpacked. The packed vs unpacked distinction has a huge impact on simulator performance. From my discussions with Verilog folks, here are some examples of wires:
wire a; // single wire, e.g. firrtl.wire : !firrtl.uint<1>
wire [7:0] b; // bitvector, e.g. firrtl.wire : !firrtl.uint<8> (I think)
wire [0:0] c; // bitvector length one, not equivalent to a.
wire [0:0] d [0:7] // unpacked, technically 2D
wire [0:0] [0:7] d // packed, technically 2D
wire [2:0] [0:7] e // packed, 2D
wire [2:0] [0:7] e [3:0] // unpacked?, 3D
q = b[5]; // slice one bit of bitvctor b
r = b[5:5] // vector slice of bitvector b
// more examples from http://www.verilogpro.com/verilog-arrays-plain-simple/
reg [31:0] x [127:0]; // 128-element array of 32-bit wide reg
wire[15:0] y[7:0], z[7:0]; // 2 arrays of 16-bit wide wires indexed from 7 to 0
reg [ 7:0] mema [255:0]; // 256-entry memory mema of 8-bit registers
reg arrayb [ 7:0][255:0]; // two-dimensional array of one bit registers
I'm not sure how fine grained these distinctions should be in the RTL dialect, but it might make sense to start with vectors and eventually support tensors of wires.
Additionally, bitvectors can be of length 0, and can also be of length unknown.
The firrtl dialect does some fancy things with SSA names: it pulls the 'name' attribute into the result of the operation, and correctly round trips this through the parser. The RTL dialect should do something similar, but limited to rtl.wire
specifically. Instead of printing:
%42 = rtl.wire { name = "foo" } : i8
We should print:
%foo = rtl.wire : i8
The real payoff of this is in the connects and other things that use the wire as an operand.
The code that does this is the FIRRTLOpAsmDialectInterface
stuff in lib/Dialect/FIRRTL/Dialect.cpp, it should be straight-forward to adapt and simplify it for RTL dialect.
The PrimOps currently are allowed to take and return flip types. This is really bad (and doesn't match the scala firrtl) because it means we have to do things like this in OpFolds.cpp:
input.getType().cast<FIRRTLType>().getPassiveType().cast<IntType>();
This is gross, but is also super buggy because it is very common to forget to strip the flips.
We should change this to verify that the operands and results are passive or IntType
specifically by encoding this into ODS correctly. We will also need to change the FIRParser to insert a cast from "flip inttype" to "inttype" as an explicit operation where needed.
standard to handshake currently assumes that all memory accesses are dependent. This should make use of alias analysis to identify accesses to different memories and treat them as independent.
Signed integer parameters will cause the FIRRTL dialect parser to fail.
Consider the following FIRRTL snippet:
circuit Foo:
extmodule Bar:
parameter A = -1
module Foo:
inst bar of Bar
When run through the Scala FIRRTL compiler, you get the following Verilog:
module Foo(
);
Bar #(.A(-1)) bar (
);
endmodule
When run through firtool
you get:
firtool Foo.fir
# Foo.fir:3:19: error: expected '.' in floating point literal
# parameter A = -1
# ^
This also may motivate a change in the underlying data format of integer parameters. Clearly, something like -1
needs to be set as a signed integer. However, should unsigned parameters continue to be stored as signless types, should they be unsigned, or should they be signed?
When building with cmake .. -DMLIR_DIR=llvm/build/lib/cmake/mlir -DLLVM_DIR=llvm/build/lib/cmake/llvm -DLLVM_ENABLE_ASSERTIONS=ON -DCMAKE_BUILD_TYPE=DEBUG -DLLVM_EXTERNAL_LIT=
pwd/../llvm/build//bin/llvm-lit
I get the following error:
[ 32%] Built target mlir-headers
Scanning dependencies of target obj.MLIRHandshakeOps
[ 35%] Building CXX object lib/Dialect/Handshake/CMakeFiles/obj.MLIRHandshakeOps.dir/HandshakeOps.cpp.o
In file included from /home/tobi/circt/lib/Dialect/Handshake/HandshakeOps.cpp:18:
/home/tobi/circt/include/circt/Dialect/Handshake/HandshakeOps.h:51:10: fatal error: circt/Dialect/Handshake/HandshakeInterfaces.h.inc: No such file or directory
51 | #include "circt/Dialect/Handshake/HandshakeInterfaces.h.inc"
| ^~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
compilation terminated.
make[2]: *** [lib/Dialect/Handshake/CMakeFiles/obj.MLIRHandshakeOps.dir/build.make:63: lib/Dialect/Handshake/CMakeFiles/obj.MLIRHandshakeOps.dir/HandshakeOps.cpp.o] Error 1
make[1]: *** [CMakeFiles/Makefile2:1278: lib/Dialect/Handshake/CMakeFiles/obj.MLIRHandshakeOps.dir/all] Error 2
make: *** [Makefile:130: all] Error 2
Any examples?
Thanks.
HLS often uses arbitrary precision arithmetic. In this model, most operations (e.g. add, multiply) have wider results than their input, in order to represent all the possible result values without overflow. Explicit Truncation operations are necessary in order to limit the growth of values. The rules are roughly:
bitwidth(a + b) = max(bitwidth(a), bitwidth(b)) + 1
bitwidth(a - b) = max(bitwidth(a), bitwidth(b)) + 1
bitwidth(a * b) = bitwidth(a) + bitwidth(b)
bitwidth(a / b) = bitwidth(a)
Most other operations are relatively straightforward. Note that division is often not exact, because only integer results are possible. A bitwidth-inference pass which propagates bitwidths from inputs through such operations, converting standard operations into arbitrary precision operations with explicit truncation would be interesting as well.
The input type required by AsAsyncResetPrimOp
is too restrictive. The FIRRTL dialect restricts this to UInt
, SInt
, or Clock
types, but the Scala FIRRTL compiler allows any FIRRTL type.
As one example, the following should not error:
circuit Foo :
module Foo :
input in : AsyncReset
output out : AsyncReset
out <= asAsyncReset(in)
Currently, this does:
firtool AsyncReset.fir
# AsyncReset.fir:5:12: error: invalid input types for 'asAsyncReset': '!firrtl.asyncreset'
# out <= asAsyncReset(in)
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.