Merge LLHD project into CIRCT (#14)

* Merge LLHD project into CIRCT

* Split LLHDOps.td into multiple smaller files

* move LLHDToLLVM init definition and prune includes

* Format tablegen files with 2 space indent, 80 col width; move out trait helper function

* Move implementation logic from LLHDOps.h to cpp file

* Empty lines for breathing space; nicer operation separators

* Move simulator to Dialect/LLHD/Simulator

* move `State.h` and `signal-runtime-wrappers.h` to lib directory

* pass ModuleOp by value

* make getters const, return ModuleOp by value

* Use isa, cast, dyn_cast appropriately

* wrap struct in anon namespace; make helpers static

* [cmake] Fold into LINK_LIBS

* fix for loops

* replace floating point with `divideCeil`

* prune redundant includes

* make llhd-sim helpers static

* remove StandardToLLVM pass registration

* move verilog printer to cpp file, add global function as public API

* Move transformation pass base classes and registration to lib, add file header boilerplate

* Few improvements

* Return diagnostics directly
* isa instead of kindof
* Improve walks
* etc.

* add 'using namespace llvm;' again

* Always pass a location when creating new ops

* Improve cmake files

  * remove unnecessary `LLVMSupport` links.
  * add `PUBLIC` where missing.
  * move LLVMCore under `LINK_COMPONENTS`.

* Add file headers and improve simulator comments

* Some LLHDToLLVM improvements

* Fix walks.
* Use `std::array` instead of `SmallVector` when resize is not needed.
* Fix a potential sefgault by passing an ArrayRef with no data owner.
* Remove some unnecessary steps.
* Remove some unnecessary const strings.

* Add new LowerToLLVMOptions argument

The new argument was added in 10643c9ad85bf072816bd271239281ec50a52e31.

* Add missing file header boilerplate and newline

* Improve for-loop

* use static instead of anonymous namespace for functions

* fit to 80 columns, cast instead of dyn_cast

* Changes for LLVM update

* use llvm format instead of std::stringstream and iomanip

Co-authored-by: rodonisi <simon@rodoni.ch>
This commit is contained in:
maerhart 2020-07-02 20:04:33 +02:00 committed by GitHub
parent b742968411
commit 8a82a81806
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
88 changed files with 8011 additions and 6 deletions

View File

@ -1,3 +1,2 @@
add_subdirectory(Conversion)
add_subdirectory(Dialect)

View File

@ -1,2 +1,2 @@
add_subdirectory(LLHDToLLVM)

View File

@ -0,0 +1,5 @@
set(LLVM_TARGET_DEFINITIONS Passes.td)
mlir_tablegen(Passes.h.inc -gen-pass-decls)
add_public_tablegen_target(MLIRLLHDConversionPassIncGen)
add_mlir_doc(Passes -gen-pass-doc LLHDToLLVM Passes/)

View File

@ -0,0 +1,34 @@
//===- LLHDToLLVM.h - LLHD to LLVM pass entry point -------------*- C++ -*-===//
//
// This header file defines prototypes that expose the LLHDToLLVM pass
// constructors.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_CONVERSION_LLHDTOLLVM_LLHDTOLLVM_H
#define CIRCT_CONVERSION_LLHDTOLLVM_LLHDTOLLVM_H
#include <memory>
namespace mlir {
class ModuleOp;
class LLVMTypeConverter;
class OwningRewritePatternList;
template <typename T>
class OperationPass;
namespace llhd {
/// Get the LLHD to LLVM conversion patterns.
void populateLLHDToLLVMConversionPatterns(LLVMTypeConverter &converter,
OwningRewritePatternList &patterns);
/// Create an LLHD to LLVM conversion pass.
std::unique_ptr<OperationPass<ModuleOp>> createConvertLLHDToLLVMPass();
void initLLHDToLLVMPass();
} // namespace llhd
} // namespace mlir
#endif // CIRCT_CONVERSION_LLHDTOLLVM_LLHDTOLLVM_H

View File

@ -0,0 +1,22 @@
//===-- Passes.td - LLHD to LLVM pass definition file ------*- tablegen -*-===//
//
// This file contains definitions for the LLHD to LLVM conversion pass.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_CONVERSION_LLHDTOLLVM_PASSES
#define CIRCT_CONVERSION_LLHDTOLLVM_PASSES
include "mlir/Pass/PassBase.td"
def ConvertLLHDToLLVM : Pass<"convert-llhd-to-llvm", "ModuleOp"> {
let summary = "Convert LLHD to LLVM";
// TODO: add description
let description = [{
TODO
}];
let constructor = "mlir::llhd::createConvertLLHDToLLVMPass()";
}
#endif // CIRCT_CONVERSION_LLHDTOLLVM_PASSES

View File

@ -1,3 +1,4 @@
add_subdirectory(FIRRTL)
add_subdirectory(Handshake)
add_subdirectory(LLHD)
add_subdirectory(RTL)

View File

@ -0,0 +1,2 @@
add_subdirectory(IR)
add_subdirectory(Transforms)

View File

@ -0,0 +1,61 @@
//===- ArithmeticOps.td - LLHD arithmetic operations -------*- tablegen -*-===//
//
// This describes the additional arithmetic MLIR ops for LLHD.
//
//===----------------------------------------------------------------------===//
def LLHD_NegOp : LLHD_ArithmeticOrBitwiseOp<"neg", []> {
let summary = "Negate a value.";
let description = [{
The operand and result always have the same type. The type has to be a
signless integer of any width. Although, only signless integers are
allowed, this instruction applies two's complement negation of the
integer, basically treating it as a signed integer.
Syntax:
```
neg-op ::= ssa-id `=` `llhd.neg` ssa-value attr-dict `:` type
```
Example:
```mlir
%0 = llhd.const 42 : i32
%1 = llhd.neg %0 : i32
```
}];
let arguments = (ins AnySignlessInteger:$value);
let hasFolder = 1;
}
def LLHD_SModOp : LLHD_ArithmeticOrBitwiseOp<"smod", []> {
let summary = "Signed modulo.";
let description = [{
This instruction computes the signed modulo of two signless integers of
any width, treating the leading bit as sign. The operand and result
types always have to be the same.
To calculate the signed remainder of two integers, use `remi_signed`
from the standard dialect.
Syntax:
```
smod-op ::= ssa-id `=` `llhd.smod` ssa-lhs `,` ssa-rhs attr-dict `:` type
```
Examples:
```mlir
%0 = llhd.const 9 : i4
%1 = llhd.const 4 : i4
%2 = llhd.smod %0, %1 : i4
```
}];
let arguments = (ins AnySignlessInteger:$lhs, AnySignlessInteger:$rhs);
let hasFolder = 1;
}

View File

@ -0,0 +1,276 @@
//===- BitwiseOps.td - LLHD bitwise operations -------------*- tablegen -*-===//
//
// This describes the bitwise MLIR ops for LLHD.
//
//===----------------------------------------------------------------------===//
def LLHD_NotOp : LLHD_ArithmeticOrBitwiseOp<"not", []> {
let summary = "Bitwise NOT";
let description = [{
Takes an integer of any width or a nine-valued-logic (IEEE 1164) value
of any width as input. Flips each bit of a value. The result always has
the exact same type.
Syntax:
```
not-op ::= ssa-id `=` `llhd.not` ssa-value attr-dict `:` type
```
Examples:
```mlir
%0 = llhd.const 0 : i32
%1 = llhd.not %0 : i32
```
Truth Table for integers:
| `not` | 0 | 1 |
|:-----:|:---:|:---:|
| | 1 | 0 |
Truth Table for nine-valued logic:
| `not` | U | X | 0 | 1 | Z | W | L | H | - |
|:-----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| | U | X | 1 | 0 | X | X | 1 | 0 | X |
}];
let arguments = (ins AnySignlessInteger:$value);
let hasFolder = 1;
}
def LLHD_AndOp : LLHD_ArithmeticOrBitwiseOp<"and", [Commutative]> {
let summary = "Bitwise AND";
let description = [{
Takes two integers of the same width or two nine-valued-logic (IEEE 1164)
values of the same width as input. Calculates the bitwise AND. The
result is always of the exact same type as the two inputs.
Syntax:
```
and-op ::= ssa-id `=` `llhd.and` ssa-lhs `,` ssa-rhs attr-dict `:` type
```
Examples:
```mlir
%0 = llhd.const 0 : i32
%1 = llhd.and %0, %0 : i32
```
Truth Table for integers:
| `and` | 0 | 1 |
|:-----:|:---:|:---:|
| 0 | 0 | 0 |
| 1 | 0 | 1 |
Truth Table for nine-valued logic:
| `and` | U | X | 0 | 1 | Z | W | L | H | - |
|:-----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| U | U | U | 0 | U | U | U | 0 | U | U |
| X | U | X | 0 | X | X | X | 0 | X | X |
| 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| 1 | U | X | 0 | 1 | X | X | 0 | 1 | X |
| Z | U | X | 0 | X | X | X | 0 | X | X |
| W | U | X | 0 | X | X | X | 0 | X | X |
| L | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 | 0 |
| H | U | X | 0 | 1 | X | X | 0 | 1 | X |
| - | U | X | 0 | X | X | X | 0 | X | X |
}];
let arguments = (ins AnySignlessInteger:$lhs, AnySignlessInteger:$rhs);
let hasFolder = 1;
}
def LLHD_OrOp : LLHD_ArithmeticOrBitwiseOp<"or", [Commutative]> {
let summary = "Bitwise OR";
let description = [{
Takes two integers of the same width or two nine-valued-logic (IEEE 1164)
values of the same width as input. Calculates the bitwise OR. The
result is always of the exact same type as the two inputs.
Syntax:
```
or-op ::= ssa-id `=` `llhd.or` ssa-lhs `,` ssa-rhs attr-dict `:` type
```
Examples:
```mlir
%0 = llhd.const 0 : i32
%1 = llhd.or %0, %0 : i32
```
Truth Table for integers:
| `or` | 0 | 1 |
|:-----:|:---:|:---:|
| 0 | 0 | 1 |
| 1 | 1 | 1 |
Truth Table for nine-valued logic:
| `or` | U | X | 0 | 1 | Z | W | L | H | - |
|:-----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| U | U | U | U | 1 | U | U | U | 1 | U |
| X | U | X | X | 1 | X | X | X | 1 | X |
| 0 | U | X | 0 | 1 | X | X | 0 | 1 | X |
| 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| Z | U | X | X | 1 | X | X | X | 1 | X |
| W | U | X | X | 1 | X | X | X | 1 | X |
| L | U | X | 0 | 1 | X | X | 0 | 1 | X |
| H | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 | 1 |
| - | U | X | X | 1 | X | X | X | 1 | X |
}];
let arguments = (ins AnySignlessInteger:$lhs, AnySignlessInteger:$rhs);
let hasFolder = 1;
}
def LLHD_XorOp : LLHD_ArithmeticOrBitwiseOp<"xor", [Commutative]> {
let summary = "Bitwise XOR";
let description = [{
Takes two integers of the same width or two nine-valued-logic (IEEE 1164)
values of the same width as input. Calculates the bitwise XOR. The
result is always of the exact same type as the two inputs.
Syntax:
```
xor-op ::= ssa-id `=` `llhd.xor` ssa-lhs `,` ssa-rhs attr-dict `:` type
```
Examples:
```mlir
%0 = llhd.const 0 : i32
%1 = llhd.xor %0, %0 : i32
```
Truth Table for integers:
| `xor` | 0 | 1 |
|:-----:|:---:|:---:|
| 0 | 0 | 1 |
| 1 | 1 | 0 |
Truth Table for nine-valued logic:
| `xor` | U | X | 0 | 1 | Z | W | L | H | - |
|:-----:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|:---:|
| U | U | U | U | U | U | U | U | U | U |
| X | U | X | X | X | X | X | X | X | X |
| 0 | U | X | 0 | 1 | X | X | 0 | 1 | X |
| 1 | U | X | 1 | 0 | X | X | 1 | 0 | X |
| Z | U | X | X | X | X | X | X | X | X |
| W | U | X | X | X | X | X | X | X | X |
| L | U | X | 0 | 1 | X | X | 0 | 1 | X |
| H | U | X | 1 | 0 | X | X | 1 | 0 | X |
| - | U | X | X | X | X | X | X | X | X |
}];
let arguments = (ins AnySignlessInteger:$lhs, AnySignlessInteger:$rhs);
let hasFolder = 1;
}
def LLHD_ShlOp : LLHD_Op<"shl", [NoSideEffect]> {
let summary = "Shifts a value to the left by a given amount.";
let description = [{
The type of the base value and the hidden value must be the same, but
may differ in the number of bits or elements. The result always has the
same type (including width) of the base value.
The instruction is transparent to signals and pointers. For example,
passing a signal as argument will shift the underlying value and return
a signal to the shifted value.
Allowed (underlying) types are signless integers, nine-valued-logic values
and arrays. The shift amount has to be a signless integer. A shift amount
bigger than the number of bits or elements of the hidden value is undefined.
The hidden value is uncovered by non-zero shift amounts. E.g. consider
the four bit values `base = 0xf`, `hidden = 0xc` shifted by an amount of
three result in `0xe`.
Syntax:
```
shl-op ::= ssa-id `=`
`llhd.shl` ssa-base `,` ssa-hidden `,` ssa-amount attr-dict `:`
`(` base-type `,` hidden-type `,` amount-type `)` `->` result-type
```
Examples:
```mlir
%0 = llhd.shl %base, %hidden, %amount : (i4, i2, i2) -> i4
```
}];
// TODO: adjust type T and Th to include arrays and pointers
let arguments = (ins AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$base,
AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$hidden,
AnySignlessInteger:$amount);
let results = (outs AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$result);
let assemblyFormat = [{
operands attr-dict `:` functional-type(operands, results)
}];
let verifier = [{ return ::verify(*this); }];
let hasFolder = 1;
}
def LLHD_ShrOp : LLHD_Op<"shr", [NoSideEffect]> {
let summary = "Shifts a value to the right by a given amount.";
let description = [{
The type of the base value and the hidden value must be the same, but
may differ in the number of bits or elements. The result always has the
same type (including width) of the base value.
The instruction is transparent to signals and pointers. For example,
passing a signal as argument will shift the underlying value and return
a signal to the shifted value.
Allowed (underlying) types are signless integers, nine-valued-logic values
and arrays. The shift amount has to be a signless integer. A shift amount
bigger than the number of bits or elements of the hidden value is undefined.
The hidden value is uncovered by non-zero shift amounts. E.g. consider
the four bit values `base = 0xf`, `hidden = 0xc` shifted by an amount of
three result in `0x9`.
Syntax:
```
shr-op ::= ssa-id `=`
`llhd.shr` ssa-base `,` ssa-hidden `,` ssa-amount attr-dict `:`
`(` base-type `,` hidden-type `,` amount-type `)` `->` result-type
```
Examples:
```mlir
%0 = llhd.shr %base, %hidden, %amount : (i4, i2, i2) -> i4
```
}];
// TODO: adjust type T and Th to include arrays and pointers
let arguments = (ins AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$base,
AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$hidden,
AnySignlessInteger:$amount);
let results = (outs AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType]>:$result);
let assemblyFormat = [{
operands attr-dict `:` functional-type(operands, results)
}];
let verifier = [{ return ::verify(*this); }];
let hasFolder = 1;
}

View File

@ -0,0 +1,7 @@
add_mlir_dialect(LLHD llhd)
add_mlir_doc(LLHD -gen-op-doc llhd Dialect/)
set(LLVM_TARGET_DEFINITIONS LLHD.td)
mlir_tablegen(LLHDEnums.h.inc -gen-enum-decls)
mlir_tablegen(LLHDEnums.cpp.inc -gen-enum-defs)
add_public_tablegen_target(MLIRLLHDEnumsIncGen)

View File

@ -0,0 +1,126 @@
//===- ExtractOps.td - LLHD extract operations -------------*- tablegen -*-===//
//
// This describes the MLIR ops for field and slice extractions.
//
//===----------------------------------------------------------------------===//
def LLHD_ExtsOp : LLHD_Op<"exts", [
NoSideEffect,
PredOpTrait<
"'start' + size of the slice have to be smaller or equal to the 'target' "
"size",
CPred<"$start.cast<IntegerAttr>().getInt() + this->getSliceSize() <= "
"this->getTargetSize()">>,
SameTypeArbitraryWidth<
"'target' and 'result' have to be both either signless integers, signals "
"or vectors with the same element type",
"$target", "$result">
]> {
let summary = "Extract a slice of consecutive elements.";
let description = [{
The `llhd.exts` operation allows access to a slice of the `$target`
operand. The `$start` attribute defines the index of the first element.
The return type is the same as `$target` but with the width of the
specified result type.
If `%target` is a signal, a new subsignal aliasing the slice will be
returned.
Example:
```mlir
%0 = llhd.const 123 : i32
%1 = llhd.exts %0, 0 : i32 -> i2
%2 = llhd.sig %0 : i32
%3 = llhd.exts %2, 0 : !llhd.sig<i32> -> !llhd.sig<i5>
```
}];
let arguments = (ins
AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType, AnyVector]>: $target,
IndexAttr: $start);
let results = (outs
AnyTypeOf<[AnySignlessInteger, LLHD_AnySigType, AnyVector]>: $result);
let assemblyFormat = [{
$target `,` $start attr-dict `:` type($target) `->` type($result)
}];
let extraClassDeclaration = [{
unsigned getSliceSize() {
Type sliceType = result().getType();
if (auto vec = sliceType.dyn_cast<VectorType>())
return vec.getNumElements();
if (auto sig = sliceType.dyn_cast<llhd::SigType>()) {
Type ty = sig.getUnderlyingType();
if (auto vec = ty.dyn_cast<VectorType>())
return vec.getNumElements();
if (auto tup = ty.dyn_cast<TupleType>())
return tup.size();
return ty.getIntOrFloatBitWidth();
}
return sliceType.getIntOrFloatBitWidth();
}
unsigned getTargetSize() {
Type targetType = target().getType();
if (auto vec = targetType.dyn_cast<VectorType>())
return vec.getNumElements();
if (auto sig = targetType.dyn_cast<llhd::SigType>()) {
Type ty = sig.getUnderlyingType();
if (auto vec = ty.dyn_cast<VectorType>())
return vec.getNumElements();
if (auto tup = ty.dyn_cast<TupleType>())
return tup.size();
return ty.getIntOrFloatBitWidth();
}
return targetType.getIntOrFloatBitWidth();
}
}];
}
def LLHD_DextsOp : LLHD_Op<"dexts", [
NoSideEffect,
SameTypeArbitraryWidth<
"'target' and 'result' types have to match apart from their width",
"$target", "$result">,
PredOpTrait<
"the result width cannot be larger than the target operand width",
CPred<"this->getTargetWidth() >= this->getSliceWidth()">>
]> {
let summary = "Dynamically extract a slice of consecutive elements";
let description = [{
The `llhd.dexts` operation allows to dynamically access a slice of the
`$target` operand, starting at the index given by the `$start` operand.
The resulting slice length is defined by the result type.
The `$target` operand kind has to match the result kind.
If `$target` is a vector, only the number of elements can change, while
the element type has to remain the same.
Example:
```mlir
%0 = llhd.const 0x0f0 : i12
%1 = llhd.const 4 : i3
%3 = llhd.dexts %0, %1 : (i12, i3) -> i4 // %3: 0xf
```
}];
let arguments = (ins
AnyTypeOf<[AnySignlessInteger, AnyVector, LLHD_AnySigType]>: $target,
AnySignlessInteger: $start);
let results = (outs
AnyTypeOf<[AnySignlessInteger, AnyVector, LLHD_AnySigType]>: $result);
let assemblyFormat = [{
operands attr-dict `:` functional-type(operands, results)
}];
let extraClassDeclaration = [{
unsigned getSliceWidth();
unsigned getTargetWidth();
}];
}

View File

@ -0,0 +1,125 @@
//===- InsertOps.td - LLHD insert operations ---------------*- tablegen -*-===//
//
// This describes the MLIR ops for field and slice insertions.
//
//===----------------------------------------------------------------------===//
def LLHD_InssOp : LLHD_Op<"inss", [
NoSideEffect,
AllTypesMatch<["target", "result"]>,
SameTypeArbitraryWidth<
"'target' and 'slice' have to be both either signless integers or vectors"
" with the same element type",
"$target", "$slice">,
PredOpTrait<
"'start' + size of the 'slice' have to be smaller or equal to the "
"'target' size",
CPred<
"$start.cast<IntegerAttr>().getInt() + this->getSliceSize() <= "
"this->getTargetSize()">>
]> {
let summary = "Insert a slice of consecutive elements.";
let description = [{
The `llhd.inss` operation allows insertion of a slice represented by the
`$slice` operand into the `$target` operand. The `$start` attribute
defines the index of the first element. The return type is the same as
`$target`. Note that the `$target` is not changed, but a new value with
the slice inserted is returned.
Example:
```mlir
%itarget = llhd.const 123 : i32
%islice = llhd.const 2 : i2
%0 = llhd.inss %itarget, %islice, 0 : i32, i2
%vtarget = constant dense<[1,2,3]> : vector<3xi32>
%vslice = constant dense<[4,5]> : vector<2xi32>
%1 = llhd.inss %vtarget, %vslice, 0 : vector<3xi32>, vector<2xi32>
```
}];
let arguments = (ins AnyTypeOf<[AnySignlessInteger, AnyVector]>: $target,
AnyTypeOf<[AnySignlessInteger, AnyVector]>: $slice,
IndexAttr: $start);
let results = (outs AnyTypeOf<[AnySignlessInteger, AnyVector]>: $result);
let assemblyFormat = [{
$target `,` $slice `,` $start attr-dict `:` type($target) `,` type($slice)
}];
let extraClassDeclaration = [{
unsigned getSliceSize() {
Type sliceType = slice().getType();
if (auto vec = sliceType.dyn_cast<VectorType>())
return vec.getNumElements();
return sliceType.getIntOrFloatBitWidth();
}
unsigned getTargetSize() {
Type targetType = target().getType();
if (auto vec = targetType.dyn_cast<VectorType>())
return vec.getNumElements();
return targetType.getIntOrFloatBitWidth();
}
}];
}
def LLHD_InsfOp : LLHD_Op<"insf", [
NoSideEffect,
AllTypesMatch<["target", "result"]>,
PredOpTrait<"'index' has to be smaller than the 'target' size",
CPred<"$index.cast<IntegerAttr>().getInt() < this->getTargetSize()">>,
TypesMatchWith<"'element' type has to match type at 'index' of 'target'",
"target", "element",
"this->getElementTypeAtIndex($index.cast<IntegerAttr>().getInt())">
]> {
let summary = "Insert an element into a vector or tuple.";
let description = [{
The `llhd.insf` operation allows insertion of an element represented by
the `$element` operand into the `$target` operand. The `$index`
attribute defines the index where to insert the element. The return type
is the same as `$target`. Note that the `$target` is not changed, but a
new value with the element inserted is returned.
Example:
```mlir
%target = constant dense<[1,2,3]> : vector<3xi8>
%element = llhd.const 2 : i8
%0 = llhd.insf %target, %element, 0 : vector<3xi8>, i8
%tuptarget = llhd.tuple %element, %target : tuple<i8, vector<3xi8>
%newelement = llhd.const 4 : i8
%1 = llhd.insf %tuptarget, %newelement, 0 : tuple<i8, vector<3xi8>>, i8
```
}];
let arguments = (ins AnyTypeOf<[AnyVector, AnyTuple]>: $target,
AnyType: $element,
IndexAttr: $index);
let results = (outs AnyTypeOf<[AnyVector, AnyTuple]>: $result);
let assemblyFormat = [{
$target `,` $element `,` $index attr-dict `:`
type($target) `,` type($element)
}];
let extraClassDeclaration = [{
int64_t getTargetSize() {
Type targetType = target().getType();
if (auto vec = targetType.dyn_cast<VectorType>())
return vec.getNumElements();
return targetType.cast<TupleType>().size();
}
Type getElementTypeAtIndex(int64_t index) {
Type targetType = target().getType();
if (auto vec = targetType.dyn_cast<VectorType>())
return vec.getElementType();
return targetType.cast<TupleType>().getTypes()[index];
}
}];
}

View File

@ -0,0 +1,114 @@
//===- LLHD.td - LLHD dialect definition -------------------*- tablegen -*-===//
//
// This is the top level file for the LLHD dialect.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_IR_LLHD
#define CIRCT_DIALECT_LLHD_IR_LLHD
include "mlir/IR/OpBase.td"
include "mlir/Interfaces/SideEffectInterfaces.td"
include "mlir/Interfaces/ControlFlowInterfaces.td"
include "mlir/Interfaces/CallInterfaces.td"
include "mlir/IR/SymbolInterfaces.td"
//===----------------------------------------------------------------------===//
// LLHD dialect definition
//===----------------------------------------------------------------------===//
def LLHD_Dialect : Dialect {
let name = "llhd";
let description = [{
A low-level hardware description dialect in MLIR.
}];
let cppNamespace = "llhd";
let hasConstantMaterializer = 1;
}
//===----------------------------------------------------------------------===//
// LLHD type definitions
//===----------------------------------------------------------------------===//
// LLHD Time Type
def LLHD_TimeType : Type<CPred<"$_self.isa<TimeType>()">, "LLHD time type">,
BuildableType<"TimeType::get($_builder.getContext())">;
// LLHD sig type
class LLHD_SigType<list<Type> allowedTypes>
: ContainerType<AnyTypeOf<allowedTypes>, CPred<"$_self.isa<SigType>()">,
"$_self.cast<SigType>().getUnderlyingType()", "LLHD sig type">;
def LLHD_AnySigUnderlyingType : AnyTypeOf<[AnySignlessInteger, LLHD_TimeType]>;
def LLHD_AnySigType : LLHD_SigType<[LLHD_AnySigUnderlyingType]>;
//===----------------------------------------------------------------------===//
// LLDH attribute definitions
//===----------------------------------------------------------------------===//
// LLHD time attr
def LLHD_TimeAttr
: Attr<CPred<"$_self.isa<TimeAttr>()">, "LLHD time attribute"> {
let storageType= [{ TimeAttr }];
let returnType = [{ llvm::ArrayRef<unsigned> }];
let valueType = LLHD_TimeType;
}
//===----------------------------------------------------------------------===//
// LLHD op definition
//===----------------------------------------------------------------------===//
// Base class for all LLHD ops.
class LLHD_Op<string mnemonic, list<OpTrait> traits = []>
: Op<LLHD_Dialect, mnemonic, traits> {
// For each LLHD op, the following static functions need to be defined in
// LLHDOps.cpp:
//
// * static ParseResult parse<op-c++-class-name>(OpAsmParser &parser,
// OperationState &state);
// * static void print(OpAsmPrinter &p, <op-c++-class-name> op)
let parser = [{ return ::parse$cppClass(parser, result); }];
let printer = [{ ::print(p, *this); }];
}
class LLHD_ArithmeticOrBitwiseOp<string mnemonic, list<OpTrait> traits = []>
: Op<LLHD_Dialect, mnemonic,
!listconcat(traits, [NoSideEffect, SameOperandsAndResultType])> {
let results = (outs AnySignlessInteger);
let parser = [{
return impl::parseOneResultSameOperandTypeOp(parser, result);
}];
let printer = [{
impl::printOneResultOp(this->getOperation(), p);
}];
}
//===----------------------------------------------------------------------===//
// LLHD trait definitions
//===----------------------------------------------------------------------===//
class SameTypeArbitraryWidth<string desc, string lhs, string rhs>
: PredOpTrait<desc, CPred<"sameKindArbitraryWidth(" # lhs # ".getType(),"
# rhs # ".getType())">>;
//===----------------------------------------------------------------------===//
// LLHD Operations
//===----------------------------------------------------------------------===//
include "ValueOps.td"
include "ArithmeticOps.td"
include "BitwiseOps.td"
include "SignalOps.td"
include "ExtractOps.td"
include "InsertOps.td"
include "StructureOps.td"
#endif // CIRCT_DIALECT_LLHD_IR_LLHD

View File

@ -0,0 +1,136 @@
//===- LLHDDialect.h - Declare LLHD dialect operations ----------*- C++ -*-===//
//
// This file declares an MLIR dialect for the LLHD IR.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_H
#define CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_H
#include "mlir/IR/Dialect.h"
#include "mlir/IR/Function.h"
namespace mlir {
namespace llhd {
namespace detail {
struct SigTypeStorage;
struct TimeAttrStorage;
} // namespace detail
class LLHDDialect : public Dialect {
public:
explicit LLHDDialect(MLIRContext *context);
/// Returns the prefix used in the textual IR to refer to LLHD operations
static StringRef getDialectNamespace() { return "llhd"; }
/// Parses a type registered to this dialect
Type parseType(DialectAsmParser &parser) const override;
/// Print a type registered to this dialect
void printType(Type type, DialectAsmPrinter &printer) const override;
/// Parse an attribute regustered to this dialect
Attribute parseAttribute(DialectAsmParser &parser, Type type) const override;
/// Print an attribute registered to this dialect
void printAttribute(Attribute attr,
DialectAsmPrinter &printer) const override;
Operation *materializeConstant(OpBuilder &builder, Attribute value, Type type,
Location loc) override;
};
//===----------------------------------------------------------------------===//
// LLHD Types
//===----------------------------------------------------------------------===//
namespace LLHDTypes {
enum Kinds {
Sig = mlir::Type::FIRST_PRIVATE_EXPERIMENTAL_0_TYPE,
Time,
};
} // namespace LLHDTypes
class SigType
: public mlir::Type::TypeBase<SigType, mlir::Type, detail::SigTypeStorage> {
public:
using Base::Base;
/// Return whether the given kind is of type Sig
static bool kindof(unsigned kind) { return kind == LLHDTypes::Sig; }
/// Get a new instance of llhd sig type
static SigType get(mlir::Type underlyingType);
/// The underlying type of the sig type
Type getUnderlyingType();
/// Get the keyword for the signal type
static llvm::StringRef getKeyword() { return "sig"; }
};
class TimeType : public Type::TypeBase<TimeType, Type, DefaultTypeStorage> {
public:
using Base::Base;
/// Return whether the given kind is of type Time
static bool kindof(unsigned kind) { return kind == LLHDTypes::Time; }
/// Get a new instance of type Time
static TimeType get(MLIRContext *context);
/// Get the keyword for the time type
static llvm::StringRef getKeyword() { return "time"; }
};
//===----------------------------------------------------------------------===//
// LLHD Attributes
//===----------------------------------------------------------------------===//
namespace LLHDAttrs {
enum Kinds {
Time = mlir::Attribute::FIRST_PRIVATE_EXPERIMENTAL_0_ATTR,
};
} // namespace LLHDAttrs
class TimeAttr
: public Attribute::AttrBase<TimeAttr, Attribute, detail::TimeAttrStorage> {
public:
using Base::Base;
using ValueType = llvm::ArrayRef<unsigned>;
/// Returns whether the passed argument is of kind Time.
static bool kindof(unsigned kind) { return kind == LLHDAttrs::Time; }
/// Get a new instance of Time attribute.
static TimeAttr get(Type type, llvm::ArrayRef<unsigned> timeValues,
llvm::StringRef timeUnit);
/// Verify construction invariants of a new time attribute.
static LogicalResult
verifyConstructionInvariants(Location loc, Type type,
llvm::ArrayRef<unsigned> timeValues,
llvm::StringRef timeUnit);
/// Get the time values stored in the attribute.
llvm::ArrayRef<unsigned> getValue() const;
/// Get the real time value of the attribute.
unsigned getTime() const;
/// Get the delta step value of the attribute.
unsigned getDelta() const;
/// Get the epsilon value of the attribute.
unsigned getEps() const;
/// Get the real time unit used by the attribute.
llvm::StringRef getTimeUnit() const;
/// Get the keyword of the time attribute
static llvm::StringRef getKeyword() { return "time"; }
};
} // namespace llhd
} // namespace mlir
#endif // CIRCT_DIALECT_LLHD_IR_LLHDDIALECT_H

View File

@ -0,0 +1,26 @@
//===- LLHDOps.h - Declare LLHD dialect operations --------------*- C++ -*-===//
//
// This file declares the operation class for the LLHD IR.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_IR_LLHDOPS_H
#define CIRCT_DIALECT_LLHD_IR_LLHDOPS_H
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "circt/Dialect/LLHD/IR/LLHDEnums.h.inc"
#include "mlir/IR/StandardTypes.h"
#include "mlir/Interfaces/ControlFlowInterfaces.h"
#include "mlir/Interfaces/SideEffectInterfaces.h"
namespace mlir {
namespace llhd {
/// Retrieve the class declarations generated by TableGen
#define GET_OP_CLASSES
#include "circt/Dialect/LLHD/IR/LLHD.h.inc"
} // namespace llhd
} // namespace mlir
#endif // CIRCT_DIALECT_LLHD_IR_LLHDOPS_H

View File

@ -0,0 +1,255 @@
//===- SignalOps.td - LLHD signal operations ---------------*- tablegen -*-===//
//
// This describes the MLIR ops for LLHD signal creation and manipulation.
//
//===----------------------------------------------------------------------===//
def LLHD_SigOp : LLHD_Op<"sig", [
HasParent<"EntityOp">,
TypesMatchWith<
"type of 'init' and underlying type of 'signal' have to match.",
"init", "result", "SigType::get($_self)">
]> {
let summary = "Create a signal.";
let description = [{
The `llhd.sig` instruction introduces a new signal in the IR. The input
operand determines the initial value carried by the signal, while the
result type will always be a signal carrying the type of the init operand.
A signal defines a unique name within the entity it resides in. Signals
can only be allocated within entities.
Syntax:
```
sig-op ::= ssa-id `=` `llhd.sig` sig-name ssa-init attr-dict `:` init-type
```
Example:
```mlir
%init_i64 = llhd.const 123 : i64
%sig_i64 = llhd.sig "foo" %init_64 : i64
%init_i1 = llhd.const 1 : i1
%sig_i1 = llhd.sig "bar" %init_i1 : i1
```
The first `llhd.sig` instruction creates a new signal named "foo", carrying
an `i64` type with initial value of 123, while the second one creates a new
signal named "bar", carrying an `i1` type with initial value of 1.
}];
let arguments = (ins StrAttr: $name, AnySignlessInteger: $init);
let results = (outs LLHD_AnySigType: $result);
let assemblyFormat = "$name $init attr-dict `:` type($init)";
}
def LLHD_PrbOp : LLHD_Op<"prb", [
NoSideEffect,
TypesMatchWith<
"type of 'result' and underlying type of 'signal' have to match.",
"signal", "result", "$_self.cast<SigType>().getUnderlyingType()">
]> {
let summary = "Probe a signal.";
let description = [{
The `llhd.prb` instruction probes a signal and returns the value it
currently carries as a new SSA operand. The result type is always
the type carried by the signal.
Syntax:
```
prb-op ::= ssa-id `=` `llhd.prb` ssa-sig attr-dict `:` !llhd.sig<type>
```
Example:
```mlir
%const_i1 = llhd.const 1 : i1
%sig_i1 = llhd.sig %const_i1 : i1
%prbd = llhd.prb %sig_i1 : !llhd.sig<i1>
```
}];
let arguments = (ins LLHD_AnySigType: $signal);
let results = (outs AnySignlessInteger: $result);
let assemblyFormat = "$signal attr-dict `:` type($signal)";
}
def LLHD_DrvOp : LLHD_Op<"drv", [
TypesMatchWith<
"type of 'value' and underlying type of 'signal' have to match.",
"signal", "value", "$_self.cast<SigType>().getUnderlyingType()">
]> {
let summary = "Drive a value into a signal.";
let description = [{
The `llhd.drv` operation drives a new value onto a signal. A time
operand also has to be passed, which specifies the frequency at which
the drive will be performed. An optional enable value can be passed as
last argument. In this case the drive will only be performed if the
value is 1. In case no enable signal is passed the drive will always be
performed. This operation does not define any new SSA operands.
Syntax:
```
drv-op ::= `llhd.drv` ssa-signal `,` ssa-const `after` ssa-time
(`if` ssa-enable)? `:` !llhd.sig<const-type>
```
Example:
```mlir
%init = llhd.const 1 : i1
%en = llhd.const 0 : i1
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
%sig = llhd.sig %init : i1
%new = llhd.not %init : i1
llhd.drv %sig, %new after %time : !llhd.sig<i1>
llhd.drv %sig, %new after %time if %en : !llhd.sig<i1>
```
}];
let arguments = (ins LLHD_AnySigType: $signal,
AnySignlessInteger: $value,
LLHD_TimeType: $time,
Optional<I1>: $enable);
let assemblyFormat = [{
$signal `,` $value `after` $time ( `if` $enable^ )? attr-dict `:`
type($signal)
}];
}
def REG_MODE_LOW : I64EnumAttrCase<"low", 0>;
def REG_MODE_HIGH : I64EnumAttrCase<"high", 1>;
def REG_MODE_RISE : I64EnumAttrCase<"rise", 2>;
def REG_MODE_FALL : I64EnumAttrCase<"fall", 3>;
def REG_MODE_BOTH : I64EnumAttrCase<"both", 4>;
def LLHD_RegModeAttr : I64EnumAttr<"RegMode", "", [
REG_MODE_LOW, REG_MODE_HIGH, REG_MODE_RISE, REG_MODE_FALL, REG_MODE_BOTH
]> {
let cppNamespace = "::mlir::llhd";
}
def LLHD_RegModeArrayAttr
: TypedArrayAttrBase<LLHD_RegModeAttr, "reg mode array attribute"> {}
def LLHD_RegOp : LLHD_Op<"reg", [
HasParent<"EntityOp">,
AttrSizedOperandSegments
]> {
let summary = "Represents a storage element";
let description = [{
This instruction represents a storage element. It drives its output onto
the 'signal' value. An arbitrary amount of triggers can be added to the
storage element. However, at least one is required. They are quadruples
consisting of the new value to be stored if the trigger applies, the
mode and trigger value which specify when this trigger has to be applied
as well as a delay. Optionally, each triple may also have a gate
condition, in this case the trigger only applies if the gate is one. If
multiple triggers apply the left-most in the list takes precedence.
There are five modes available:
| Mode | Meaning |
|--------|-----------------------------------------------------------------|
| "low" | Storage element stores `value` while the `trigger` is low. Models active-low resets and low-transparent latches.
| "high" | Storage element stores `value` while the `trigger` is high. Models active-high resets and high-transparent latches.
| "rise" | Storage element stores `value` upon the rising edge of the `trigger`. Models rising-edge flip-flops.
| "fall" | Storage element stores `value` upon the falling edge of the `trigger`. Models falling-edge flip-flops.
| "both" | Storage element stores `value` upon the a rising or a falling edge of the `trigger`. Models dual-edge flip-flops.
This instruction may only be used in an LLHD entity.
Syntax:
```
reg-op ::= `llhd.reg` signal-ssa-value
( `,` `(` value-ssa-value `,` mode-string trigger-ssa-value `after`
delay-ssa-value ( `if` gate-ssa-value )? `:` value-type )+
attr-dict `:` signal-type
```
Examples:
A rising, falling, and dual-edge triggered flip-flop:
```mlir
llhd.reg %Q, (%D, "rise" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
llhd.reg %Q, (%D, "fall" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
llhd.reg %Q, (%D, "both" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
```
A rising-edge triggered flip-flop with active-low reset:
```mlir
llhd.reg %Q, (%init, "low" %RSTB after %T : !llhd.sig<i8>),
(%D, "rise" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
```
A rising-edge triggered enable flip-flop with active-low reset:
```mlir
llhd.reg %Q, (%init, "low" %RSTB after %T : !llhd.sig<i8>),
(%D, "rise" %CLK after %T if %EN : !llhd.sig<i8>) : !llhd.sig<i8>
```
A transparent-low and transparent-high latch:
```mlir
llhd.reg %Q, (%D, "low" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
llhd.reg %Q, (%D, "high" %CLK after %T : !llhd.sig<i8>) : !llhd.sig<i8>
```
An SR latch:
```mlir
%0 = llhd.const 0 : i1
%1 = llhd.const 1 : i1
llhd.reg %Q, (%0, "high" %R after %T : !llhd.sig<i1>),
(%1, "high" %S after %T : !llhd.sig<i1>) : !llhd.sig<i1>
```
}];
let arguments = (ins
LLHD_AnySigType: $signal,
LLHD_RegModeArrayAttr: $modes,
Variadic<AnyTypeOf<[LLHD_AnySigUnderlyingType, LLHD_AnySigType]>>: $values,
Variadic<I1>: $triggers,
Variadic<LLHD_TimeType>: $delays,
Variadic<I1>: $gates,
I64ArrayAttr: $gateMask);
let extraClassDeclaration = [{
static StringRef getModeAttrName() { return "modes"; }
static RegMode getRegModeByName(StringRef name) {
llvm::Optional<RegMode> optional = symbolizeRegMode(name);
assert(optional && "Invalid RegMode string.");
return optional.getValue();
}
bool hasGate(unsigned index) {
assert(index < gateMask().getValue().size() && "Index out of range.");
return gateMask().getValue()[index].cast<IntegerAttr>().getInt() != 0;
}
Value getGateAt(unsigned index) {
assert(index < gateMask().getValue().size() && "Index out of range.");
if (!hasGate(index)) return Value();
return
gates()[gateMask().getValue()[index].cast<IntegerAttr>().getInt()-1];
}
RegMode getRegModeAt(unsigned index) {
assert(index < modes().getValue().size() && "Index out of range.");
return (RegMode)modes().getValue()[index].cast<IntegerAttr>().getInt();
}
}];
let verifier = [{ return ::verify(*this); }];
}

View File

@ -0,0 +1,276 @@
//===- StructureOps.td - Process and Entity definitions ----*- tablegen -*-===//
//
// This describes the LLHD Process, Entity and control flow MLIR ops.
//
//===----------------------------------------------------------------------===//
def LLHD_EntityOp : LLHD_Op<"entity", [
Symbol,
FunctionLike,
IsolatedFromAbove,
SingleBlockImplicitTerminator<"TerminatorOp">,
DeclareOpInterfaceMethods<CallableOpInterface>
]> {
let summary = "Create an entity.";
let description = [{
The `llhd.entity` operation defines a new entity unit. An entity
represents the data-flow description of how a circuit's output values
change in reaction to changing input values.
An entity contains one region with a single block and an implicit
`TerminatorOp` terminator. Both the block name and terminator are
omitted in the custom syntax. No further blocks and control-flow are
legal inside an entity.
Syntax:
```
entity-op ::= `llhd.entity` entity-symbol `(` arg-list `)` `->`
`(` out-list `)` attr-dict entity-region
```
Example:
```mlir
llhd.entity @Foo () -> () {
%0 = llhd.const 0 : i1
%toggle = llhd.sig %0 : i1 -> !llhd.sig<i1>
%1 = llhd.prb %toggle : !llhd.sig<i1> -> i1
%2 = llhd.not %1 : i1
%dt = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %toggle, %2, %dt : !llhd.sig<i1>, i1, !llhd.time
}
```
}];
let arguments = (ins I64Attr: $ins);
let regions = (region SizedRegion<1>: $body);
let verifier = [{ return ::verify(*this); }];
let extraClassDeclaration = [{
friend class OpTrait::FunctionLike<EntityOp>;
// use FunctionLike traits's getBody method
using OpTrait::FunctionLike<EntityOp>::getBody;
/// Hooks for the input/output type enumeration in FunctionLike.
unsigned getNumFuncArguments() { return getType().getNumInputs(); }
unsigned getNumFuncResults() { return getType().getNumResults(); }
/// Hook for FunctionLike verifier.
LogicalResult verifyType();
/// Verifies the body of the function.
LogicalResult verifyBody();
}];
}
def LLHD_ProcOp : LLHD_Op<"proc", [
Symbol,
FunctionLike,
IsolatedFromAbove,
DeclareOpInterfaceMethods<CallableOpInterface>
]> {
let summary = "Create a process";
let description = [{
A `llhd.proc` represents control-flow in a timed fashion. It allows a
procedural description of how a circuit's output signals change in
reaction to changing input signals. It has a region with arbitrarily
many basic blocks. The first block is the entry block and cannot be
targeted by the terminators. It uses `llhd.wait` as a terminator to add
timed control-flow. Immediate control-flow with `br` or `cond_br` is
also possible. Every process must either contain an infinite loop or
terminate with the `llhd.halt` terminator.
How does a process compare to functions and entities?
| Unit | Paradigm | Timing | Models |
|----------|--------------|-----------|--------------------------------|
| Function | control-flow | immediate | Computation in zero time |
| Process | control-flow | timed | Behavioral circuit description |
| Entity | data-flow | timed | Structural circuit description |
Syntax:
```
proc-op ::= `llhd.proc` proc-symbol `(` ssa-input-list `)` `->`
`(` ssa-output-list `)` attr-dict `{` proc-region `}`
```
Examples:
```mlir
llhd.proc @example(%in0 : !llhd.sig<i64>, %in1 : !llhd.sig<i1>) ->
(%out2 : !llhd.sig<i1>) {
br ^bb1
^bb1:
llhd.halt
}
```
}];
let arguments = (ins I64Attr: $ins);
let regions = (region AnyRegion: $body);
let extraClassDeclaration = [{
friend class OpTrait::FunctionLike<ProcOp>;
/// Hooks for the input/output type enumeration in FunctionLike.
unsigned getNumFuncArguments() { return getType().getNumInputs(); }
unsigned getNumFuncResults() { return getType().getNumResults(); }
/// Hook for FunctionLike verifier.
LogicalResult verifyType();
/// Verifies the body of the function.
LogicalResult verifyBody();
}];
let verifier = [{ return ::verify(*this); }];
}
//===----------------------------------------------------------------------===//
//=== Process and Entity Instanciation
//===----------------------------------------------------------------------===//
def LLHD_InstOp : LLHD_Op<"inst", [
CallOpInterface,
HasParent<"EntityOp">,
AttrSizedOperandSegments
]> {
let summary = "Instantiates a process or entity.";
let description = [{
Instantiates a process or entity and thus allows to build hierarchies.
Can only be used within an entity. An instance defines a unique name
within the entity it resides in.
Syntax:
```
inst-op ::= `llhd.inst` inst-name symbol-name `(` ssa-input-list `)` `->`
`(` ssa-output-list `)` attr-dict `:`
functional-type(ssa-input-list, ssa-output-list)
```
Example:
```mlir
llhd.inst "foo" @empty() -> () : () -> ()
llhd.inst "bar" @proc_symbol() -> (%out0) : () -> !llhd.sig<i32>
llhd.inst "baz" @entity_symbol(%in0, %in1) -> (%out0, %out1) :
(!llhd.sig<i32>, !llhd.sig<i16>) -> (!llhd.sig<i8>, !llhd.sig<i4>)
```
}];
let arguments = (ins StrAttr:$name,
FlatSymbolRefAttr:$callee,
Variadic<LLHD_AnySigType>:$inputs,
Variadic<LLHD_AnySigType>:$outputs);
let assemblyFormat = [{
$name $callee `(` $inputs `)` `->` `(` $outputs `)` attr-dict `:`
functional-type($inputs, $outputs)
}];
let extraClassDeclaration = [{
StringRef getCallee() { return callee(); }
FunctionType getCalleeType();
/// Get the argument operands to the called function.
operand_range getArgOperands() {
return {arg_operand_begin(), arg_operand_end()};
}
operand_iterator arg_operand_begin() { return operand_begin(); }
operand_iterator arg_operand_end() { return operand_end(); }
/// Return the callee of this operation.
CallInterfaceCallable getCallableForCallee() {
return getAttrOfType<SymbolRefAttr>("callee");
}
}];
let verifier = [{ return ::verify(*this); }];
}
//===----------------------------------------------------------------------===//
//=== Control Flow Operations
//===----------------------------------------------------------------------===//
def LLHD_TerminatorOp : LLHD_Op<"terminator", [
Terminator,
HasParent<"EntityOp">
]> {
let summary = "Dummy terminator";
let description = [{
The `"llhd.terminator"` op is a dummy terminator for an `EntityOp` unit.
It provides no further meaning other than ensuring correct termination
of an entitiy's region. This operation provides no custom syntax and
should never explicitly appear in LLHD's custom syntax.
}];
let parser = ?;
let printer = ?;
}
def LLHD_WaitOp : LLHD_Op<"wait", [
Terminator,
AttrSizedOperandSegments,
HasParent<"ProcOp">,
DeclareOpInterfaceMethods<BranchOpInterface>
]> {
let summary = "Suspends execution of a process.";
let description = [{
The `wait` instruction suspends execution of a process until any of the
observed signals change or a fixed time interval has passed. Execution
resumes at the specified basic block with the passed arguments.
* This is a terminator instruction.
* This instruction is only allowed in processes (`llhd.proc`).
Example:
```mlir
llhd.wait ^bb1
llhd.wait for %time, ^bb1(%time : !llhd.time)
llhd.wait (%0, %1 : !llhd.sig<i64>, !llhd.sig<i1>), ^bb1(%1 : !llhd.sig<i1>)
llhd.wait for %time, (%0, %1 : !llhd.sig<i64>, !llhd.sig<i1>),
^bb1(%1, %0 : !llhd.sig<i1>, !llhd.sig<i64>)
```
}];
let arguments = (ins Variadic<LLHD_AnySigType>:$obs,
Optional<LLHD_TimeType>:$time,
Variadic<AnyType>:$destOps);
let successors = (successor AnySuccessor:$dest);
let assemblyFormat = [{
(`for` $time^ `,`)? (`(`$obs^ `:` type($obs)`)` `,`)?
$dest (`(` $destOps^ `:` type($destOps) `)`)? attr-dict
}];
}
def LLHD_HaltOp : LLHD_Op<"halt", [Terminator, HasParent<"ProcOp">]> {
let summary = "Terminates execution of a process.";
let description = [{
The `halt` instruction terminates execution of a process. All processes
must halt eventually or consist of an infinite loop.
* This is a terminator instruction
* This instruction is only allowed in processes (`llhd.proc`).
Syntax:
```
halt-op ::= `llhd.halt`
```
Example:
```mlir
llhd.halt
```
}];
let assemblyFormat = "attr-dict";
}

View File

@ -0,0 +1,97 @@
//===- ValueOps.td - LLHD value operations -----------------*- tablegen -*-===//
//
// This describes the MLIR ops for LLHD value creation.
//
//===----------------------------------------------------------------------===//
def LLHD_ConstOp : LLHD_Op<"const", [ConstantLike, NoSideEffect]> {
let summary = "Introduce a new constant.";
let description = [{
The `llhd.const` instruction introduces a new constant value as an
SSA-operator.
Legal types are integers and time. Note: Signals
are not legal to define using `llhd.const`, use the `llhd.sig`
instruction for that.
Syntax:
```
const-op ::= ssa-id `=`
`llhd.const` attribute-value attr-dict `:` result-type
```
Example:
```mlir
%0 = llhd.const 1 : i64
%1 = llhd.const #llhd.time<1ns, 2d, 3d> : !llhd.time
```
}];
let arguments = (ins AnyAttr: $value);
let results = (outs AnyTypeOf<[AnySignlessInteger, LLHD_TimeType]>: $out);
let hasFolder = 1;
}
def LLHD_VecOp : LLHD_Op<"vec", [
NoSideEffect,
TypesMatchWith<
"types and number of 'values' have to match the length and type of the "
"'result' vector",
"result", "values",
"std::vector<Type>($_self.cast<ShapedType>().getNumElements(), "
"$_self.cast<ShapedType>().getElementType())">
]> {
let summary = "Create a vector from a list of values.";
let description = [{
The `llhd.vec` operation allows to create a vector from a list of
SSA-values. This allows for more flexibility compared to only using
`std.constant` and the vector dialect operations.
Example:
```mlir
%c1 = llhd.const 1 : i32
%c2 = llhd.const 2 : i32
%c3 = llhd.const 3 : i32
%vec = llhd.vec %c1, %c2, %c3 : vector<3xi32>
%elem = vector.extractelement %vec[%c1 : i32] : vector<3xi32>
```
}];
let arguments = (ins Variadic<AnyType>: $values);
let results = (outs AnyVector: $result);
let assemblyFormat = "$values attr-dict `:` type($result)";
}
def LLHD_TupleOp : LLHD_Op<"tuple", [
NoSideEffect,
TypesMatchWith<
"types of 'values' have to match the type of the corresponding 'result' "
"tuple element",
"result", "values",
"$_self.cast<TupleType>().getTypes()">
]> {
let summary = "Create a tuple from a list of values.";
let description = [{
The `llhd.tuple` operation creates a tuple from a list of SSA-values.
Example:
```mlir
%c1 = llhd.const 1 : i32
%c2 = llhd.const 2 : i2
%sig = llhd.sig "sig_name" %c1 : i32
%vec = constant dense<[1, 2]> : vector<2xi32>
%tuple = llhd.tuple %c1, %c2, %vec, %sig :
tuple<i32, i2, vector<2xi32>, !llhd.sig<i32>>
```
}];
let arguments = (ins Variadic<AnyType>: $values);
let results = (outs AnyTuple: $result);
let assemblyFormat = "$values attr-dict `:` type($result)";
}

View File

@ -0,0 +1,67 @@
//===- Engine.h - LLHD simulaton engine -------------------------*- C++ -*-===//
//
// This file defines the main Engine class of the LLHD simulator.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_SIMULATOR_ENGINE_H
#define CIRCT_DIALECT_LLHD_SIMULATOR_ENGINE_H
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "mlir/IR/Module.h"
namespace mlir {
class ExecutionEngine;
} // namespace mlir
namespace mlir {
namespace llhd {
namespace sim {
struct State;
struct Instance;
class Engine {
public:
/// Initialize an LLHD simulation engine. This initializes the state, as well
/// as the mlir::ExecutionEngine with the given module.
Engine(llvm::raw_ostream &out, ModuleOp module, MLIRContext &context,
std::string root);
/// Default destructor
~Engine();
/// Run simulation up to n steps. Pass n=0 to run indefinitely.
int simulate(int n);
/// Build the instance layout of the design.
void buildLayout(ModuleOp module);
/// Get the MLIR module.
const ModuleOp getModule() const { return module; }
/// Get the simulation state.
const State *getState() const { return state.get(); }
/// Dump the instance layout stored in the State.
void dumpStateLayout();
/// Dump the instances each signal triggers.
void dumpStateSignalTriggers();
private:
void walkEntity(EntityOp entity, Instance &child);
llvm::raw_ostream &out;
std::string root;
std::unique_ptr<State> state;
std::unique_ptr<ExecutionEngine> engine;
ModuleOp module;
};
} // namespace sim
} // namespace llhd
} // namespace mlir
#endif // CIRCT_DIALECT_LLHD_SIMULATOR_ENGINE_H

View File

@ -0,0 +1,5 @@
set(LLVM_TARGET_DEFINITIONS Passes.td)
mlir_tablegen(Passes.h.inc -gen-pass-decls)
add_public_tablegen_target(MLIRLLHDTransformsIncGen)
add_mlir_doc(Passes -gen-pass-doc Transformations Passes/)

View File

@ -0,0 +1,31 @@
//===- Passes.h - LLHD pass entry points ------------------------*- C++ -*-===//
//
// This header file defines prototypes that expose pass constructors.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES_H
#define CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES_H
#include <memory>
namespace mlir {
class ModuleOp;
template <typename T>
class OperationPass;
} // namespace mlir
namespace mlir {
namespace llhd {
std::unique_ptr<OperationPass<ModuleOp>> createProcessLoweringPass();
std::unique_ptr<OperationPass<ModuleOp>> createFunctionEliminationPass();
/// Register the LLHD Transformation passes.
void initLLHDTransformationPasses();
} // namespace llhd
} // namespace mlir
#endif // CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES_H

View File

@ -0,0 +1,36 @@
//===-- Passes.td - LLHD pass definition file --------------*- tablegen -*-===//
//
// This file contains definitions for passes that work on the LLHD dialect.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES
#define CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES
include "mlir/Pass/PassBase.td"
def ProcessLowering : Pass<"llhd-process-lowering", "ModuleOp"> {
let summary = "Lowers LLHD Processes to Entities.";
let description = [{
TODO
}];
let constructor = "mlir::llhd::createProcessLoweringPass()";
}
def FunctionElimination : Pass<"llhd-function-elimination", "ModuleOp"> {
let summary = "Deletes all functions.";
let description = [{
Deletes all functions in the module. In case there is still a function
call in an entity or process, it fails.
This pass is intended as a post-inlining pass to check if all functions
could be successfully inlined and remove the inlined functions. This
is necessary because Structural LLHD does not allow functions. Fails in
the case that there is still a function call left in a `llhd.proc` or
`llhd.entity`.
}];
let constructor = "mlir::llhd::createFunctionEliminationPass()";
}
#endif // CIRCT_DIALECT_LLHD_TRANSFORMS_PASSES

View File

@ -0,0 +1,28 @@
//===- TranslateToVerilog.h - Verilog Printer -------------------*- C++ -*-===//
//
// Defines the interface to the LLHD to Verilog Printer.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_TARGET_VERILOG_TRANSLATETOVERILOG_H
#define CIRCT_TARGET_VERILOG_TRANSLATETOVERILOG_H
namespace llvm {
class raw_ostream;
} // namespace llvm
namespace mlir {
struct LogicalResult;
class ModuleOp;
namespace llhd {
mlir::LogicalResult printVerilog(mlir::ModuleOp module, llvm::raw_ostream &os);
void registerToVerilogTranslation();
} // namespace llhd
} // namespace mlir
#endif // CIRCT_TARGET_VERILOG_TRANSLATETOVERILOG_H

View File

@ -2,3 +2,4 @@ add_subdirectory(Conversion)
add_subdirectory(Dialect)
add_subdirectory(FIRParser)
add_subdirectory(EmitVerilog)
add_subdirectory(Target)

View File

@ -1,2 +1,2 @@
add_subdirectory(StandardToHandshake)
add_subdirectory(LLHDToLLVM)

View File

@ -0,0 +1,16 @@
add_mlir_conversion_library(MLIRLLHDToLLVM
LLHDToLLVM.cpp
DEPENDS
MLIRLLHDConversionPassIncGen
LINK_COMPONENTS
Core
LINK_LIBS PUBLIC
MLIRLLHD
MLIRLLVMIR
MLIRStandardToLLVM
MLIRVector
MLIRTransforms
)

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
add_subdirectory(FIRRTL)
add_subdirectory(Handshake)
add_subdirectory(LLHD)
add_subdirectory(RTL)

View File

@ -0,0 +1,3 @@
add_subdirectory(IR)
add_subdirectory(Simulator)
add_subdirectory(Transforms)

View File

@ -0,0 +1,18 @@
add_mlir_dialect_library(MLIRLLHD
LLHDDialect.cpp
LLHDOps.cpp
ADDITIONAL_HEADER_DIRS
${PROJECT_SOURCE_DIR}/include/Dialect/LLHD
DEPENDS
MLIRLLHDEnumsIncGen
MLIRLLHDIncGen
LINK_LIBS PUBLIC
MLIREDSC
MLIRIR
MLIRSideEffectInterfaces
MLIRControlFlowInterfaces
MLIRCallInterfaces
)

View File

@ -0,0 +1,360 @@
//===- LLHDDialect.cpp - Implement the LLHD dialect -----------------------===//
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "mlir/IR/Builders.h"
#include "mlir/IR/DialectImplementation.h"
#include "mlir/Transforms/InliningUtils.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/StringRef.h"
using namespace mlir;
using namespace mlir::llhd;
//===----------------------------------------------------------------------===//
// LLHDDialect Interfaces
//===----------------------------------------------------------------------===//
namespace {
/// This class defines the interface for handling inlining with LLHD operations.
struct LLHDInlinerInterface : public DialectInlinerInterface {
using DialectInlinerInterface::DialectInlinerInterface;
//===--------------------------------------------------------------------===//
// Analysis Hooks
//===--------------------------------------------------------------------===//
/// All operations within LLHD can be inlined.
bool isLegalToInline(Operation *, Region *,
BlockAndValueMapping &) const final {
return true;
}
bool isLegalToInline(Region *, Region *src,
BlockAndValueMapping &) const final {
// Don't inline processes and entities
return !isa<llhd::ProcOp>(src->getParentOp()) &&
!isa<llhd::EntityOp>(src->getParentOp());
}
};
} // end anonymous namespace
//===----------------------------------------------------------------------===//
// LLHD Dialect
//===----------------------------------------------------------------------===//
LLHDDialect::LLHDDialect(mlir::MLIRContext *context)
: Dialect(getDialectNamespace(), context) {
addTypes<SigType, TimeType>();
addAttributes<TimeAttr>();
addOperations<
#define GET_OP_LIST
#include "circt/Dialect/LLHD/IR/LLHD.cpp.inc"
>();
addInterfaces<LLHDInlinerInterface>();
}
Operation *LLHDDialect::materializeConstant(OpBuilder &builder, Attribute value,
Type type, Location loc) {
return builder.create<llhd::ConstOp>(loc, type, value);
}
//===----------------------------------------------------------------------===//
// Type parsing
//===----------------------------------------------------------------------===//
/// Parse a signal type.
/// Syntax: sig ::= !llhd.sig<type>
static Type parseSigType(DialectAsmParser &parser) {
Type underlyingType;
if (parser.parseLess())
return Type();
llvm::SMLoc loc = parser.getCurrentLocation();
if (parser.parseType(underlyingType)) {
parser.emitError(loc, "No signal type found. Signal needs an underlying "
"type.");
return nullptr;
}
if (parser.parseGreater())
return Type();
return SigType::get(underlyingType);
}
Type LLHDDialect::parseType(DialectAsmParser &parser) const {
llvm::StringRef typeKeyword;
// parse the type keyword first
if (parser.parseKeyword(&typeKeyword))
return Type();
if (typeKeyword == SigType::getKeyword()) {
return parseSigType(parser);
}
if (typeKeyword == TimeType::getKeyword())
return TimeType::get(getContext());
return Type();
}
//===----------------------------------------------------------------------===//
// Type printing
//===----------------------------------------------------------------------===//
/// Print a signal type with custom syntax:
/// type ::= !sig.type<underlying-type>
static void printSigType(SigType sig, DialectAsmPrinter &printer) {
printer << sig.getKeyword() << "<" << sig.getUnderlyingType() << ">";
}
void LLHDDialect::printType(Type type, DialectAsmPrinter &printer) const {
if (SigType sig = type.dyn_cast<SigType>()) {
printSigType(sig, printer);
} else if (TimeType time = type.dyn_cast<TimeType>()) {
printer << time.getKeyword();
}
}
//===----------------------------------------------------------------------===//
// Attribute parsing
//===----------------------------------------------------------------------===//
/// Parse a time attribute with the custom syntax:
/// time ::= #llhd.time<time time_unit, delta d, epsilon e>
static Attribute parseTimeAttribute(DialectAsmParser &parser, Type type) {
if (parser.parseLess())
return Attribute();
// values to parse
llvm::SmallVector<unsigned, 3> values;
llvm::StringRef timeUnit;
unsigned time = 0;
unsigned delta = 0;
unsigned eps = 0;
// parse the time value
if (parser.parseInteger(time) || parser.parseKeyword(&timeUnit))
return {};
values.push_back(time);
// parse the delta step value
if (parser.parseComma() || parser.parseInteger(delta) ||
parser.parseKeyword("d"))
return {};
values.push_back(delta);
// parse the epsilon value
if (parser.parseComma() || parser.parseInteger(eps) ||
parser.parseKeyword("e") || parser.parseGreater())
return Attribute();
values.push_back(eps);
// return a new instance of time attribute
return TimeAttr::get(type, values, timeUnit);
}
/// Parse an LLHD attribute
Attribute LLHDDialect::parseAttribute(DialectAsmParser &parser,
Type type) const {
llvm::StringRef attrKeyword;
// parse keyword first
if (parser.parseKeyword(&attrKeyword))
return Attribute();
if (attrKeyword == TimeAttr::getKeyword()) {
return parseTimeAttribute(parser, type);
}
return Attribute();
}
//===----------------------------------------------------------------------===//
// Attribute printing
//===----------------------------------------------------------------------===//
/// Print an LLHD time attribute.
static void printTimeAttribute(TimeAttr attr, DialectAsmPrinter &printer) {
printer << attr.getKeyword() << "<";
printer << attr.getTime() << attr.getTimeUnit() << ", ";
printer << attr.getDelta() << "d, ";
printer << attr.getEps() << "e>";
}
void LLHDDialect::printAttribute(Attribute attr,
DialectAsmPrinter &printer) const {
if (TimeAttr time = attr.dyn_cast<TimeAttr>()) {
printTimeAttribute(time, printer);
}
}
namespace mlir {
namespace llhd {
namespace detail {
//===----------------------------------------------------------------------===//
// Type storage
//===----------------------------------------------------------------------===//
// Sig Type Storage
/// Storage struct implementation for LLHD's sig type. The sig type only
/// contains one underlying llhd type.
struct SigTypeStorage : public mlir::TypeStorage {
using KeyTy = mlir::Type;
/// construcor for sig type's storage.
/// Takes the underlying type as the only argument
SigTypeStorage(mlir::Type underlyingType) : underlyingType(underlyingType) {}
/// compare sig type instances on the underlying type
bool operator==(const KeyTy &key) const { return key == getUnderlyingType(); }
/// return the KeyTy for sig type
static KeyTy getKey(mlir::Type underlyingType) {
return KeyTy(underlyingType);
}
/// construction method for creating a new instance of the sig type
/// storage
static SigTypeStorage *construct(mlir::TypeStorageAllocator &allocator,
const KeyTy &key) {
return new (allocator.allocate<SigTypeStorage>()) SigTypeStorage(key);
}
/// get the underlying type
mlir::Type getUnderlyingType() const { return underlyingType; }
private:
mlir::Type underlyingType;
};
//===----------------------------------------------------------------------===//
// Attribute storage
//===----------------------------------------------------------------------===//
struct TimeAttrStorage : public mlir::AttributeStorage {
public:
// use the ArrayRef containign the timing attributes for uniquing
using KeyTy = std::tuple<Type, llvm::ArrayRef<unsigned>, llvm::StringRef>;
/// Construct a time attribute storage
TimeAttrStorage(Type type, llvm::ArrayRef<unsigned> timeValues,
llvm::StringRef timeUnit)
: AttributeStorage(type), timeValues(timeValues), timeUnit(timeUnit) {}
/// Compare two istances of the time attribute. Hashing and equality are done
/// only on the time values and time unit. The time type is implicitly always
/// equal.
bool operator==(const KeyTy &key) const {
return (std::get<1>(key) == timeValues && std::get<2>(key) == timeUnit);
}
/// Generate hash key for uniquing.
static unsigned hashKey(const KeyTy &key) {
auto vals = std::get<1>(key);
auto unit = std::get<2>(key);
return llvm::hash_combine(vals, unit);
}
/// Construction method for llhd's time attribute
static TimeAttrStorage *construct(mlir::AttributeStorageAllocator &allocator,
const KeyTy &key) {
auto keyValues = std::get<1>(key);
auto values = allocator.copyInto(keyValues);
auto keyUnit = std::get<2>(key);
auto unit = allocator.copyInto(keyUnit);
return new (allocator.allocate<TimeAttrStorage>())
TimeAttrStorage(std::get<0>(key), values, unit);
}
llvm::ArrayRef<unsigned> getValue() const { return timeValues; }
unsigned getTime() { return timeValues[0]; }
unsigned getDelta() { return timeValues[1]; }
unsigned getEps() { return timeValues[2]; }
llvm::StringRef getTimeUnit() { return timeUnit; }
private:
llvm::ArrayRef<unsigned> timeValues;
llvm::StringRef timeUnit;
};
} // namespace detail
} // namespace llhd
} // namespace mlir
//===----------------------------------------------------------------------===//
// LLHD Types
//===----------------------------------------------------------------------===//
// Sig Type
SigType SigType::get(mlir::Type underlyingType) {
return Base::get(underlyingType.getContext(), LLHDTypes::Sig, underlyingType);
};
mlir::Type SigType::getUnderlyingType() {
return getImpl()->getUnderlyingType();
}
// Time Type
TimeType TimeType::get(MLIRContext *context) {
return Base::get(context, LLHDTypes::Time);
}
//===----------------------------------------------------------------------===//
// LLHD Attribtues
//===----------------------------------------------------------------------===//
// Time Attribute
TimeAttr TimeAttr::get(Type type, llvm::ArrayRef<unsigned> timeValues,
llvm::StringRef timeUnit) {
return Base::get(type.getContext(), LLHDAttrs::Time, type, timeValues,
timeUnit);
}
LogicalResult
TimeAttr::verifyConstructionInvariants(Location loc, Type type,
llvm::ArrayRef<unsigned> timeValues,
llvm::StringRef timeUnit) {
// Check the attribute type is of TimeType.
if (!type.isa<TimeType>())
return emitError(loc) << "Time attribute type has to be TimeType, but got "
<< type;
// Check the time unit is a legal SI unit
std::vector<std::string> legalUnits{"ys", "zs", "as", "fs", "ps",
"ns", "us", "ms", "s"};
if (std::find(legalUnits.begin(), legalUnits.end(), timeUnit) ==
legalUnits.end())
return emitError(loc) << "Illegal time unit.";
// Check there are exactly 3 time values
if (timeValues.size() != 3)
return emitError(loc) << "Got a wrong number of time values. Expected "
"exactly 3, but got "
<< timeValues.size();
// Check the time values are positive or zero integers.
if (timeValues[0] < 0 || timeValues[1] < 0 || timeValues[2] < 0)
return emitError(loc) << "Received a negative time value.";
return success();
}
llvm::ArrayRef<unsigned> TimeAttr::getValue() const {
return getImpl()->getValue();
}
unsigned TimeAttr::getTime() const { return getImpl()->getTime(); }
unsigned TimeAttr::getDelta() const { return getImpl()->getDelta(); }
unsigned TimeAttr::getEps() const { return getImpl()->getEps(); }
llvm::StringRef TimeAttr::getTimeUnit() const {
return getImpl()->getTimeUnit();
}

View File

@ -0,0 +1,969 @@
//===- LLHDOps.cpp - Implement the LLHD operations ------------------------===//
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "mlir/Dialect/CommonFolders.h"
#include "mlir/IR/Attributes.h"
#include "mlir/IR/Matchers.h"
#include "mlir/IR/Module.h"
#include "mlir/IR/OpImplementation.h"
#include "mlir/IR/PatternMatch.h"
#include "mlir/IR/Region.h"
#include "mlir/IR/StandardTypes.h"
#include "mlir/IR/Types.h"
#include "mlir/IR/Value.h"
#include "mlir/Support/LogicalResult.h"
#include "llvm/ADT/ArrayRef.h"
#include "llvm/ADT/SmallVector.h"
#include "llvm/ADT/StringMap.h"
using namespace mlir;
template <class AttrElementT,
class ElementValueT = typename AttrElementT::ValueType,
class CalculationT = function_ref<ElementValueT(ElementValueT)>>
static Attribute constFoldUnaryOp(ArrayRef<Attribute> operands,
const CalculationT &calculate) {
assert(operands.size() == 1 && "unary op takes one operand");
if (!operands[0])
return {};
if (auto val = operands[0].dyn_cast<AttrElementT>()) {
return AttrElementT::get(val.getType(), calculate(val.getValue()));
} else if (auto val = operands[0].dyn_cast<SplatElementsAttr>()) {
// Operand is a splat so we can avoid expanding the value out and
// just fold based on the splat value.
auto elementResult = calculate(val.getSplatValue<ElementValueT>());
return DenseElementsAttr::get(val.getType(), elementResult);
}
if (auto val = operands[0].dyn_cast<ElementsAttr>()) {
// Operand is ElementsAttr-derived; perform an element-wise fold by
// expanding the values.
auto valIt = val.getValues<ElementValueT>().begin();
SmallVector<ElementValueT, 4> elementResults;
elementResults.reserve(val.getNumElements());
for (size_t i = 0, e = val.getNumElements(); i < e; ++i, ++valIt)
elementResults.push_back(calculate(*valIt));
return DenseElementsAttr::get(val.getType(), elementResults);
}
return {};
}
template <class AttrElementT,
class ElementValueT = typename AttrElementT::ValueType,
class CalculationT = function_ref<
ElementValueT(ElementValueT, ElementValueT, ElementValueT)>>
static Attribute constFoldTernaryOp(ArrayRef<Attribute> operands,
const CalculationT &calculate) {
assert(operands.size() == 3 && "ternary op takes three operands");
if (!operands[0] || !operands[1] || !operands[2])
return {};
if (operands[0].getType() != operands[1].getType())
return {};
if (operands[0].getType() != operands[2].getType())
return {};
if (operands[0].isa<AttrElementT>() && operands[1].isa<AttrElementT>() &&
operands[2].isa<AttrElementT>()) {
auto fst = operands[0].cast<AttrElementT>();
auto snd = operands[1].cast<AttrElementT>();
auto trd = operands[2].cast<AttrElementT>();
return AttrElementT::get(
fst.getType(),
calculate(fst.getValue(), snd.getValue(), trd.getValue()));
}
if (operands[0].isa<SplatElementsAttr>() &&
operands[1].isa<SplatElementsAttr>() &&
operands[2].isa<SplatElementsAttr>()) {
// Operands are splats so we can avoid expanding the values out and
// just fold based on the splat value.
auto fst = operands[0].cast<SplatElementsAttr>();
auto snd = operands[1].cast<SplatElementsAttr>();
auto trd = operands[2].cast<SplatElementsAttr>();
auto elementResult = calculate(fst.getSplatValue<ElementValueT>(),
snd.getSplatValue<ElementValueT>(),
trd.getSplatValue<ElementValueT>());
return DenseElementsAttr::get(fst.getType(), elementResult);
}
if (operands[0].isa<ElementsAttr>() && operands[1].isa<ElementsAttr>() &&
operands[2].isa<ElementsAttr>()) {
// Operands are ElementsAttr-derived; perform an element-wise fold by
// expanding the values.
auto fst = operands[0].cast<ElementsAttr>();
auto snd = operands[1].cast<ElementsAttr>();
auto trd = operands[2].cast<ElementsAttr>();
auto fstIt = fst.getValues<ElementValueT>().begin();
auto sndIt = snd.getValues<ElementValueT>().begin();
auto trdIt = trd.getValues<ElementValueT>().begin();
SmallVector<ElementValueT, 4> elementResults;
elementResults.reserve(fst.getNumElements());
for (size_t i = 0, e = fst.getNumElements(); i < e;
++i, ++fstIt, ++sndIt, ++trdIt)
elementResults.push_back(calculate(*fstIt, *sndIt, *trdIt));
return DenseElementsAttr::get(fst.getType(), elementResults);
}
return {};
}
namespace {
struct constant_int_all_ones_matcher {
bool match(Operation *op) {
APInt value;
return mlir::detail::constant_int_op_binder(&value).match(op) &&
value.isAllOnesValue();
}
};
} // anonymous namespace
//===---------------------------------------------------------------------===//
// LLHD Trait Helper Functions
//===---------------------------------------------------------------------===//
static bool sameKindArbitraryWidth(Type lhsType, Type rhsType) {
return (lhsType.getKind() == rhsType.getKind()) &&
(!lhsType.isa<ShapedType>() ||
(lhsType.cast<ShapedType>().getElementType() ==
rhsType.cast<ShapedType>().getElementType()));
}
//===---------------------------------------------------------------------===//
// LLHD Operations
//===---------------------------------------------------------------------===//
//===----------------------------------------------------------------------===//
// ConstOp
//===----------------------------------------------------------------------===//
static ParseResult parseConstOp(OpAsmParser &parser, OperationState &result) {
Attribute val;
Type type;
if (parser.parseAttribute(val, "value", result.attributes) ||
parser.parseOptionalAttrDict(result.attributes))
return failure();
// parse the type for attributes that do not print the type by default
if (parser.parseOptionalColon().value ||
!parser.parseOptionalType(type).hasValue())
type = val.getType();
return parser.addTypeToList(val.getType(), result.types);
}
static void print(OpAsmPrinter &printer, llhd::ConstOp op) {
printer << op.getOperationName() << " ";
// The custom time attribute is not printing the attribute type by default for
// some reason. Work around by printing the attribute without type, explicitly
// followed by the operation type
printer.printAttributeWithoutType(op.valueAttr());
printer.printOptionalAttrDict(op.getAttrs(), {"value"});
printer << " : " << op.getType();
}
OpFoldResult llhd::ConstOp::fold(ArrayRef<Attribute> operands) {
assert(operands.empty() && "const has no operands");
return value();
}
//===----------------------------------------------------------------------===//
// DextsOp
//===----------------------------------------------------------------------===//
unsigned llhd::DextsOp::getSliceWidth() {
auto resultTy = result().getType();
if (resultTy.isSignlessInteger()) {
return resultTy.getIntOrFloatBitWidth();
} else if (auto sigRes = resultTy.dyn_cast<llhd::SigType>()) {
return sigRes.getUnderlyingType().getIntOrFloatBitWidth();
} else if (auto vecRes = resultTy.dyn_cast<VectorType>()) {
return vecRes.getNumElements();
}
return 0;
}
unsigned llhd::DextsOp::getTargetWidth() {
auto targetTy = target().getType();
if (targetTy.isSignlessInteger()) {
return targetTy.getIntOrFloatBitWidth();
} else if (auto sigRes = targetTy.dyn_cast<llhd::SigType>()) {
return sigRes.getUnderlyingType().getIntOrFloatBitWidth();
} else if (auto vecRes = targetTy.dyn_cast<VectorType>()) {
return vecRes.getNumElements();
}
return 0;
}
//===----------------------------------------------------------------------===//
// NegOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::NegOp::fold(ArrayRef<Attribute> operands) {
return constFoldUnaryOp<IntegerAttr>(operands, [](APInt a) { return -a; });
}
//===----------------------------------------------------------------------===//
// SModOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::SModOp::fold(ArrayRef<Attribute> operands) {
/// llhd.smod(x, 1) -> 0
if (matchPattern(rhs(), m_One()))
return Builder(getContext()).getZeroAttr(getType());
/// llhd.smod(0, x) -> 0
if (matchPattern(lhs(), m_Zero()))
return Builder(getContext()).getZeroAttr(getType());
/// llhs.smod(x,x) -> 0
if (lhs() == rhs())
return Builder(getContext()).getZeroAttr(getType());
return constFoldBinaryOp<IntegerAttr>(operands, [](APInt lhs, APInt rhs) {
APInt result = lhs.srem(rhs);
if ((lhs.isNegative() && rhs.isNonNegative()) ||
(lhs.isNonNegative() && rhs.isNegative())) {
result += rhs;
}
return result;
});
}
//===----------------------------------------------------------------------===//
// NotOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::NotOp::fold(ArrayRef<Attribute> operands) {
return constFoldUnaryOp<IntegerAttr>(operands, [](APInt a) { return ~a; });
}
//===----------------------------------------------------------------------===//
// AndOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::AndOp::fold(ArrayRef<Attribute> operands) {
/// llhd.and(x, 0) -> 0
if (matchPattern(rhs(), m_Zero()))
return rhs();
/// llhd.and(x, all_bits_set) -> x
if (matchPattern(rhs(), constant_int_all_ones_matcher()))
return lhs();
// llhd.and(x, x) -> x
if (rhs() == lhs())
return rhs();
return constFoldBinaryOp<IntegerAttr>(operands,
[](APInt a, APInt b) { return a & b; });
}
//===----------------------------------------------------------------------===//
// OrOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::OrOp::fold(ArrayRef<Attribute> operands) {
/// llhd.or(x, 0) -> x
if (matchPattern(rhs(), m_Zero()))
return lhs();
/// llhd.or(x, all_bits_set) -> all_bits_set
if (matchPattern(rhs(), constant_int_all_ones_matcher()))
return rhs();
// llhd.or(x, x) -> x
if (rhs() == lhs())
return rhs();
return constFoldBinaryOp<IntegerAttr>(operands,
[](APInt a, APInt b) { return a | b; });
}
//===----------------------------------------------------------------------===//
// XorOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::XorOp::fold(ArrayRef<Attribute> operands) {
/// llhd.xor(x, 0) -> x
if (matchPattern(rhs(), m_Zero()))
return lhs();
/// llhs.xor(x,x) -> 0
if (lhs() == rhs())
return Builder(getContext()).getZeroAttr(getType());
return constFoldBinaryOp<IntegerAttr>(operands,
[](APInt a, APInt b) { return a ^ b; });
}
//===----------------------------------------------------------------------===//
// ShlOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::ShlOp::fold(ArrayRef<Attribute> operands) {
/// llhd.shl(base, hidden, 0) -> base
if (matchPattern(amount(), m_Zero()))
return base();
return constFoldTernaryOp<IntegerAttr>(
operands, [](APInt base, APInt hidden, APInt amt) {
base <<= amt;
base += hidden.getHiBits(amt.getZExtValue());
return base;
});
}
static LogicalResult verify(llhd::ShlOp op) {
if (op.base().getType() != op.result().getType()) {
return op.emitError(
"The output of the Shl operation is required to have the "
"same type as the base value (first operand), (")
<< op.base().getType() << " vs. " << op.result().getType() << ")";
}
// TODO: verify that T and Th only differ in the number of bits or elements
return success();
}
//===----------------------------------------------------------------------===//
// ShrOp
//===----------------------------------------------------------------------===//
OpFoldResult llhd::ShrOp::fold(ArrayRef<Attribute> operands) {
/// llhd.shl(base, hidden, 0) -> base
if (matchPattern(amount(), m_Zero()))
return base();
return constFoldTernaryOp<IntegerAttr>(
operands, [](APInt base, APInt hidden, APInt amt) {
base = base.getHiBits(base.getBitWidth() - amt.getZExtValue());
hidden = hidden.getLoBits(amt.getZExtValue());
hidden <<= base.getBitWidth() - amt.getZExtValue();
return base + hidden;
});
}
static LogicalResult verify(llhd::ShrOp op) {
if (op.base().getType() != op.result().getType()) {
return op.emitError(
"The output of the Shr operation is required to have the "
"same type as the base value (first operand), (")
<< op.base().getType() << " vs. " << op.result().getType() << ")";
}
// TODO: verify that T and Th only differ in the number of bits or elements
return success();
}
//===----------------------------------------------------------------------===//
// WaitOp
//===----------------------------------------------------------------------===//
// Implement this operation for the BranchOpInterface
Optional<MutableOperandRange>
llhd::WaitOp::getMutableSuccessorOperands(unsigned index) {
assert(index == 0 && "invalid successor index");
return destOpsMutable();
}
//===----------------------------------------------------------------------===//
// EntityOp
//===----------------------------------------------------------------------===//
/// Parse an argument list of an entity operation.
/// The argument list and argument types are returned in args and argTypes
/// respectively.
static ParseResult
parseArgumentList(OpAsmParser &parser,
SmallVectorImpl<OpAsmParser::OperandType> &args,
SmallVectorImpl<Type> &argTypes) {
if (parser.parseLParen())
return failure();
do {
OpAsmParser::OperandType argument;
Type argType;
if (succeeded(parser.parseOptionalRegionArgument(argument))) {
if (!argument.name.empty() && succeeded(parser.parseColonType(argType))) {
args.push_back(argument);
argTypes.push_back(argType);
}
}
} while (succeeded(parser.parseOptionalComma()));
if (parser.parseRParen())
return failure();
return success();
}
/// parse an entity signature with syntax:
/// (%arg0 : T0, %arg1 : T1, <...>) -> (%out0 : T0, %out1 : T1, <...>)
static ParseResult
parseEntitySignature(OpAsmParser &parser, OperationState &result,
SmallVectorImpl<OpAsmParser::OperandType> &args,
SmallVectorImpl<Type> &argTypes) {
if (parseArgumentList(parser, args, argTypes))
return failure();
// create the integer attribute with the number of inputs.
IntegerAttr insAttr = parser.getBuilder().getI64IntegerAttr(args.size());
result.addAttribute("ins", insAttr);
if (parser.parseArrow() || parseArgumentList(parser, args, argTypes))
return failure();
return success();
}
static ParseResult parseEntityOp(OpAsmParser &parser, OperationState &result) {
StringAttr entityName;
SmallVector<OpAsmParser::OperandType, 4> args;
SmallVector<Type, 4> argTypes;
if (parser.parseSymbolName(entityName, SymbolTable::getSymbolAttrName(),
result.attributes))
return failure();
parseEntitySignature(parser, result, args, argTypes);
if (parser.parseOptionalAttrDictWithKeyword(result.attributes))
return failure();
auto type = parser.getBuilder().getFunctionType(argTypes, llvm::None);
result.addAttribute(mlir::llhd::EntityOp::getTypeAttrName(),
TypeAttr::get(type));
auto *body = result.addRegion();
parser.parseRegion(*body, args, argTypes);
llhd::EntityOp::ensureTerminator(*body, parser.getBuilder(), result.location);
return success();
}
static void printArgumentList(OpAsmPrinter &printer,
std::vector<BlockArgument> args) {
printer << "(";
llvm::interleaveComma(args, printer, [&](BlockArgument arg) {
printer << arg << " : " << arg.getType();
});
printer << ")";
}
static void print(OpAsmPrinter &printer, llhd::EntityOp op) {
std::vector<BlockArgument> ins, outs;
uint64_t n_ins = op.insAttr().getInt();
for (uint64_t i = 0; i < op.body().front().getArguments().size(); ++i) {
// no furter verification for the attribute type is required, already
// handled by verify.
if (i < n_ins) {
ins.push_back(op.body().front().getArguments()[i]);
} else {
outs.push_back(op.body().front().getArguments()[i]);
}
}
auto entityName =
op.getAttrOfType<StringAttr>(SymbolTable::getSymbolAttrName()).getValue();
printer << op.getOperationName() << " ";
printer.printSymbolName(entityName);
printer << " ";
printArgumentList(printer, ins);
printer << " -> ";
printArgumentList(printer, outs);
printer.printOptionalAttrDictWithKeyword(
op.getAttrs(),
/*elidedAttrs =*/{SymbolTable::getSymbolAttrName(),
llhd::EntityOp::getTypeAttrName(), "ins"});
printer.printRegion(op.body(), false, false);
}
static LogicalResult verify(llhd::EntityOp op) {
uint64_t numArgs = op.getNumArguments();
uint64_t nIns = op.insAttr().getInt();
// check that there is at most one flag for each argument
if (numArgs < nIns) {
return op.emitError(
"Cannot have more inputs than arguments, expected at most ")
<< numArgs << " but got: " << nIns;
}
return success();
}
LogicalResult mlir::llhd::EntityOp::verifyType() {
// Fail if function returns any values. An entity's outputs are specially
// marked arguments.
if (getNumResults() > 0)
return emitOpError("an entity cannot have return types.");
// Check that all operands are of signal type
for (int i = 0, e = getNumFuncArguments(); i < e; ++i) {
if (!getArgument(i).getType().isa<llhd::SigType>()) {
return emitOpError("usage of invalid argument type. Got ")
<< getArgument(i).getType() << ", expected LLHD signal type";
}
}
return success();
}
LogicalResult mlir::llhd::EntityOp::verifyBody() {
// Body must not be empty.
if (isExternal())
return emitOpError("defining external entity with the entity instruction "
"is not allowed, use the intended instruction instead.");
// check signal names are unique
llvm::StringMap<bool> sigMap;
llvm::StringMap<bool> instMap;
auto walkResult = walk([&sigMap, &instMap](Operation *op) -> WalkResult {
if (auto sigOp = dyn_cast<SigOp>(op)) {
if (sigMap[sigOp.name()]) {
return sigOp.emitError("Redefinition of signal named '")
<< sigOp.name() << "'!";
}
sigMap.insert_or_assign(sigOp.name(), true);
} else if (auto instOp = dyn_cast<InstOp>(op)) {
if (instMap[instOp.name()]) {
return instOp.emitError("Redefinition of instance named '")
<< instOp.name() << "'!";
}
instMap.insert_or_assign(instOp.name(), true);
}
return WalkResult::advance();
});
return failure(walkResult.wasInterrupted());
}
Region *llhd::EntityOp::getCallableRegion() {
return isExternal() ? nullptr : &getBody();
}
ArrayRef<Type> llhd::EntityOp::getCallableResults() {
return getType().getResults();
}
//===----------------------------------------------------------------------===//
// ProcOp
//===----------------------------------------------------------------------===//
LogicalResult mlir::llhd::ProcOp::verifyType() {
// Fail if function returns more than zero values. This is because the
// outputs of a process are specially marked arguments.
if (getNumResults() > 0) {
return emitOpError(
"process has more than zero return types, this is not allowed");
}
// Check that all operands are of signal type
for (int i = 0, e = getNumFuncArguments(); i < e; ++i) {
if (!getArgument(i).getType().isa<llhd::SigType>()) {
return emitOpError("usage of invalid argument type, was ")
<< getArgument(i).getType() << ", expected LLHD signal type";
}
}
return success();
}
LogicalResult mlir::llhd::ProcOp::verifyBody() {
// Body must not be empty, this indicates an external process. We use
// another instruction to reference external processes.
if (isExternal()) {
return emitOpError("defining external processes with the proc instruction "
"is not allowed, use the intended instruction instead.");
}
return success();
}
static LogicalResult verify(llhd::ProcOp op) {
// Check that the ins attribute is smaller or equal the number of
// arguments
uint64_t numArgs = op.getNumArguments();
uint64_t numIns = op.insAttr().getInt();
if (numArgs < numIns) {
return op.emitOpError(
"Cannot have more inputs than arguments, expected at most ")
<< numArgs << ", got " << numIns;
}
return success();
}
static ParseResult
parseProcArgumentList(OpAsmParser &parser, SmallVectorImpl<Type> &argTypes,
SmallVectorImpl<OpAsmParser::OperandType> &argNames) {
if (parser.parseLParen())
return failure();
// The argument list either has to consistently have ssa-id's followed by
// types, or just be a type list. It isn't ok to sometimes have SSA ID's
// and sometimes not.
auto parseArgument = [&]() -> ParseResult {
llvm::SMLoc loc = parser.getCurrentLocation();
// Parse argument name if present.
OpAsmParser::OperandType argument;
Type argumentType;
if (succeeded(parser.parseOptionalRegionArgument(argument)) &&
!argument.name.empty()) {
// Reject this if the preceding argument was missing a name.
if (argNames.empty() && !argTypes.empty())
return parser.emitError(loc, "expected type instead of SSA identifier");
argNames.push_back(argument);
if (parser.parseColonType(argumentType))
return failure();
} else if (!argNames.empty()) {
// Reject this if the preceding argument had a name.
return parser.emitError(loc, "expected SSA identifier");
} else if (parser.parseType(argumentType)) {
return failure();
}
// Add the argument type.
argTypes.push_back(argumentType);
return success();
};
// Parse the function arguments.
if (failed(parser.parseOptionalRParen())) {
do {
unsigned numTypedArguments = argTypes.size();
if (parseArgument())
return failure();
llvm::SMLoc loc = parser.getCurrentLocation();
if (argTypes.size() == numTypedArguments &&
succeeded(parser.parseOptionalComma()))
return parser.emitError(loc, "variadic arguments are not allowed");
} while (succeeded(parser.parseOptionalComma()));
parser.parseRParen();
}
return success();
}
static ParseResult parseProcOp(OpAsmParser &parser, OperationState &result) {
StringAttr procName;
SmallVector<OpAsmParser::OperandType, 8> argNames;
SmallVector<Type, 8> argTypes;
Builder &builder = parser.getBuilder();
if (parser.parseSymbolName(procName, SymbolTable::getSymbolAttrName(),
result.attributes))
return failure();
if (parseProcArgumentList(parser, argTypes, argNames))
return failure();
result.addAttribute("ins", builder.getI64IntegerAttr(argTypes.size()));
if (parser.parseArrow())
return failure();
if (parseProcArgumentList(parser, argTypes, argNames))
return failure();
auto type = builder.getFunctionType(argTypes, llvm::None);
result.addAttribute(mlir::llhd::ProcOp::getTypeAttrName(),
TypeAttr::get(type));
auto *body = result.addRegion();
parser.parseRegion(*body, argNames,
argNames.empty() ? ArrayRef<Type>() : argTypes);
return success();
}
/// Print the signature of the `proc` unit. Assumes that it passed the
/// verification.
static void printProcArguments(OpAsmPrinter &p, Operation *op,
ArrayRef<Type> types, uint64_t numIns) {
Region &body = op->getRegion(0);
auto printList = [&](unsigned i, unsigned max) -> void {
for (; i < max; ++i) {
p << body.front().getArgument(i) << " : " << types[i];
p.printOptionalAttrDict(::mlir::impl::getArgAttrs(op, i));
if (i < max - 1)
p << ", ";
}
};
p << '(';
printList(0, numIns);
p << ") -> (";
printList(numIns, types.size());
p << ')';
}
static void print(OpAsmPrinter &printer, llhd::ProcOp op) {
FunctionType type = op.getType();
printer << op.getOperationName() << ' ';
printer.printSymbolName(op.getName());
printProcArguments(printer, op.getOperation(), type.getInputs(),
op.insAttr().getInt());
printer.printRegion(op.body(), false, true);
}
Region *llhd::ProcOp::getCallableRegion() {
return isExternal() ? nullptr : &getBody();
}
ArrayRef<Type> llhd::ProcOp::getCallableResults() {
return getType().getResults();
}
//===----------------------------------------------------------------------===//
// InstOp
//===----------------------------------------------------------------------===//
static LogicalResult verify(llhd::InstOp op) {
// Check that the callee attribute was specified.
auto calleeAttr = op.getAttrOfType<FlatSymbolRefAttr>("callee");
if (!calleeAttr)
return op.emitOpError("requires a 'callee' symbol reference attribute");
auto proc = op.getParentOfType<ModuleOp>().lookupSymbol<llhd::ProcOp>(
calleeAttr.getValue());
auto entity = op.getParentOfType<ModuleOp>().lookupSymbol<llhd::EntityOp>(
calleeAttr.getValue());
// Verify that the input and output types match the callee.
if (proc) {
auto type = proc.getType();
if (proc.ins() != op.inputs().size())
return op.emitOpError(
"incorrect number of inputs for proc instantiation");
if (type.getNumInputs() != op.getNumOperands())
return op.emitOpError(
"incorrect number of outputs for proc instantiation");
for (size_t i = 0, e = type.getNumInputs(); i != e; ++i)
if (op.getOperand(i).getType() != type.getInput(i))
return op.emitOpError("operand type mismatch");
return success();
}
if (entity) {
auto type = entity.getType();
if (entity.ins() != op.inputs().size())
return op.emitOpError(
"incorrect number of inputs for entity instantiation");
if (type.getNumInputs() != op.getNumOperands())
return op.emitOpError(
"incorrect number of outputs for entity instantiation");
for (size_t i = 0, e = type.getNumInputs(); i != e; ++i)
if (op.getOperand(i).getType() != type.getInput(i))
return op.emitOpError("operand type mismatch");
return success();
}
return op.emitOpError() << "'" << calleeAttr.getValue()
<< "' does not reference a valid proc or entity";
}
FunctionType llhd::InstOp::getCalleeType() {
SmallVector<Type, 8> argTypes(getOperandTypes());
return FunctionType::get(argTypes, ArrayRef<Type>(), getContext());
}
//===----------------------------------------------------------------------===//
// RegOp
//===----------------------------------------------------------------------===//
static ParseResult parseRegOp(OpAsmParser &parser, OperationState &result) {
OpAsmParser::OperandType signal;
Type signalType;
SmallVector<OpAsmParser::OperandType, 8> valueOperands;
SmallVector<OpAsmParser::OperandType, 8> triggerOperands;
SmallVector<OpAsmParser::OperandType, 8> delayOperands;
SmallVector<OpAsmParser::OperandType, 8> gateOperands;
SmallVector<Type, 8> valueTypes;
llvm::SmallVector<int64_t, 8> modesArray;
llvm::SmallVector<int64_t, 8> gateMask;
int64_t gateCount = 0;
if (parser.parseOperand(signal))
return failure();
while (succeeded(parser.parseOptionalComma())) {
OpAsmParser::OperandType value;
OpAsmParser::OperandType trigger;
OpAsmParser::OperandType delay;
OpAsmParser::OperandType gate;
Type valueType;
StringAttr modeAttr;
NamedAttrList attrStorage;
if (parser.parseLParen())
return failure();
if (parser.parseOperand(value) || parser.parseComma())
return failure();
if (parser.parseAttribute(modeAttr, parser.getBuilder().getNoneType(),
"modes", attrStorage))
return failure();
auto attrOptional = llhd::symbolizeRegMode(modeAttr.getValue());
if (!attrOptional)
return parser.emitError(parser.getCurrentLocation(),
"invalid string attribute");
modesArray.push_back(static_cast<int64_t>(attrOptional.getValue()));
if (parser.parseOperand(trigger))
return failure();
if (parser.parseKeyword("after") || parser.parseOperand(delay))
return failure();
if (succeeded(parser.parseOptionalKeyword("if"))) {
gateMask.push_back(++gateCount);
if (parser.parseOperand(gate))
return failure();
gateOperands.push_back(gate);
} else {
gateMask.push_back(0);
}
if (parser.parseColon() || parser.parseType(valueType) ||
parser.parseRParen())
return failure();
valueOperands.push_back(value);
triggerOperands.push_back(trigger);
delayOperands.push_back(delay);
valueTypes.push_back(valueType);
}
if (parser.parseOptionalAttrDict(result.attributes) || parser.parseColon() ||
parser.parseType(signalType))
return failure();
if (parser.resolveOperand(signal, signalType, result.operands))
return failure();
if (parser.resolveOperands(valueOperands, valueTypes,
parser.getCurrentLocation(), result.operands))
return failure();
for (auto operand : triggerOperands)
if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(),
result.operands))
return failure();
for (auto operand : delayOperands)
if (parser.resolveOperand(
operand, llhd::TimeType::get(parser.getBuilder().getContext()),
result.operands))
return failure();
for (auto operand : gateOperands)
if (parser.resolveOperand(operand, parser.getBuilder().getI1Type(),
result.operands))
return failure();
result.addAttribute("gateMask",
parser.getBuilder().getI64ArrayAttr(gateMask));
result.addAttribute("modes", parser.getBuilder().getI64ArrayAttr(modesArray));
llvm::SmallVector<int32_t, 5> operandSizes;
operandSizes.push_back(1);
operandSizes.push_back(valueOperands.size());
operandSizes.push_back(triggerOperands.size());
operandSizes.push_back(delayOperands.size());
operandSizes.push_back(gateOperands.size());
result.addAttribute("operand_segment_sizes",
parser.getBuilder().getI32VectorAttr(operandSizes));
return success();
}
static void print(OpAsmPrinter &printer, llhd::RegOp op) {
printer << op.getOperationName() << " " << op.signal();
for (size_t i = 0, e = op.values().size(); i < e; ++i) {
Optional<llhd::RegMode> mode = llhd::symbolizeRegMode(
op.modes().getValue()[i].cast<IntegerAttr>().getInt());
if (!mode) {
op.emitError("invalid RegMode");
return;
}
printer << ", (" << op.values()[i] << ", \""
<< llhd::stringifyRegMode(mode.getValue()) << "\" "
<< op.triggers()[i] << " after " << op.delays()[i];
if (op.hasGate(i))
printer << " if " << op.getGateAt(i);
printer << " : " << op.values()[i].getType() << ")";
}
printer.printOptionalAttrDict(op.getAttrs(),
{"modes", "gateMask", "operand_segment_sizes"});
printer << " : " << op.signal().getType();
}
static LogicalResult verify(llhd::RegOp op) {
// At least one trigger has to be present
if (op.triggers().size() < 1)
return op.emitError("At least one trigger quadruple has to be present.");
// Values variadic operand must have the same size as the triggers variadic
if (op.values().size() != op.triggers().size())
return op.emitOpError("Number of 'values' is not equal to the number of "
"'triggers', got ")
<< op.values().size() << " modes, but " << op.triggers().size()
<< " triggers!";
// Delay variadic operand must have the same size as the triggers variadic
if (op.delays().size() != op.triggers().size())
return op.emitOpError("Number of 'delays' is not equal to the number of "
"'triggers', got ")
<< op.delays().size() << " modes, but " << op.triggers().size()
<< " triggers!";
// Array Attribute of RegModes must have the same number of elements as the
// variadics
if (op.modes().size() != op.triggers().size())
return op.emitOpError("Number of 'modes' is not equal to the number of "
"'triggers', got ")
<< op.modes().size() << " modes, but " << op.triggers().size()
<< " triggers!";
// Array Attribute 'gateMask' must have the same number of elements as the
// triggers and values variadics
if (op.gateMask().size() != op.triggers().size())
return op.emitOpError("Size of 'gateMask' is not equal to the size of "
"'triggers', got ")
<< op.gateMask().size() << " modes, but " << op.triggers().size()
<< " triggers!";
// Number of non-zero elements in 'gateMask' has to be the same as the size
// of the gates variadic, also each number from 1 to size-1 has to occur
// only once and in increasing order
unsigned counter = 0;
unsigned prevElement = 0;
for (Attribute maskElem : op.gateMask().getValue()) {
int64_t val = maskElem.cast<IntegerAttr>().getInt();
if (val < 0)
return op.emitError("Element in 'gateMask' must not be negative!");
if (val == 0)
continue;
if (val != ++prevElement)
return op.emitError(
"'gateMask' has to contain every number from 1 to the "
"number of gates minus one exactly once in increasing order "
"(may have zeros in-between).");
counter++;
}
if (op.gates().size() != counter)
return op.emitError("The number of non-zero elements in 'gateMask' and the "
"size of the 'gates' variadic have to match.");
// Each value must be either the same type as the 'signal' or the underlying
// type of the 'signal'
for (auto val : op.values()) {
if (val.getType() != op.signal().getType() &&
val.getType() !=
op.signal().getType().cast<llhd::SigType>().getUnderlyingType()) {
return op.emitOpError(
"type of each 'value' has to be either the same as the "
"type of 'signal' or the underlying type of 'signal'");
}
}
return success();
}
#include "circt/Dialect/LLHD/IR/LLHDEnums.cpp.inc"
namespace mlir {
namespace llhd {
#define GET_OP_CLASSES
#include "circt/Dialect/LLHD/IR/LLHD.cpp.inc"
} // namespace llhd
} // namespace mlir

View File

@ -0,0 +1,27 @@
set(LLVM_OPTIONAL_SOURCES
State.cpp
Engine.cpp
signals-runtime-wrappers.cpp
)
add_mlir_library(CIRCTLLHDSimState
State.cpp
)
add_mlir_library(circt-llhd-signals-runtime-wrappers SHARED
signals-runtime-wrappers.cpp
LINK_LIBS PUBLIC
CIRCTLLHDSimState
)
add_mlir_library(CIRCTLLHDSimEngine
Engine.cpp
LINK_LIBS PUBLIC
MLIRLLHD
MLIRLLHDToLLVM
CIRCTLLHDSimState
circt-llhd-signals-runtime-wrappers
MLIRExecutionEngine
)

View File

@ -0,0 +1,305 @@
//===- Engine.cpp - Simulator Engine class implementation -------*- C++ -*-===//
//
// This file implements the Engine class.
//
//===----------------------------------------------------------------------===//
#include "State.h"
#include "circt/Conversion/LLHDToLLVM/LLHDToLLVM.h"
#include "circt/Dialect/LLHD/Simulator/Engine.h"
#include "mlir/ExecutionEngine/ExecutionEngine.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Transforms/DialectConversion.h"
#include "llvm/Support/TargetSelect.h"
using namespace mlir;
using namespace llhd::sim;
Engine::Engine(llvm::raw_ostream &out, ModuleOp module, MLIRContext &context,
std::string root)
: out(out), root(root) {
state = std::make_unique<State>();
buildLayout(module);
auto rootEntity = module.lookupSymbol<EntityOp>(root);
// Insert explicit instantiation of the design root.
OpBuilder insertInst =
OpBuilder::atBlockTerminator(&rootEntity.getBody().getBlocks().front());
insertInst.create<InstOp>(rootEntity.getBlocks().front().back().getLoc(),
llvm::None, root, root, ArrayRef<Value>(),
ArrayRef<Value>());
// Add the 0-time event.
state->queue.push(Slot(Time()));
mlir::PassManager pm(&context);
pm.addPass(llhd::createConvertLLHDToLLVMPass());
if (failed(pm.run(module))) {
llvm::errs() << "failed to convert module to LLVM";
exit(EXIT_FAILURE);
}
this->module = module;
llvm::InitializeNativeTarget();
llvm::InitializeNativeTargetAsmPrinter();
auto maybeEngine = mlir::ExecutionEngine::create(this->module);
assert(maybeEngine && "failed to create JIT");
engine = std::move(*maybeEngine);
}
Engine::~Engine() = default;
void Engine::dumpStateLayout() { state->dumpLayout(); }
void Engine::dumpStateSignalTriggers() { state->dumpSignalTriggers(); }
int Engine::simulate(int n) {
assert(engine && "engine not found");
assert(state && "state not found");
SmallVector<void *, 1> arg({&state});
// Initialize tbe simulation state.
auto invocationResult = engine->invoke("llhd_init", arg);
if (invocationResult) {
llvm::errs() << "Failed invocation of llhd_init: " << invocationResult;
return -1;
}
// Dump the signals' initial values.
for (int i = 0; i < state->nSigs; ++i) {
state->dumpSignal(out, i);
}
int i = 0;
// Keep track of the instances that need to wakeup.
std::vector<std::string> wakeupQueue;
// All instances are run in the first cycle.
for (auto k : state->instances.keys())
wakeupQueue.push_back(k.str());
while (!state->queue.empty()) {
if (n > 0 && i >= n) {
break;
}
auto pop = state->popQueue();
// Update the simulation time.
assert(state->time < pop.time || pop.time.time == 0);
state->time = pop.time;
// Apply the signal changes and dump the signals that actually changed
// value.
for (auto change : pop.changes) {
// Get a buffer to apply the changes on.
Signal *curr = &(state->signals[change.first]);
APInt buff(
curr->size * 8,
ArrayRef<uint64_t>(reinterpret_cast<uint64_t *>(curr->detail.value),
curr->size));
// Apply all the changes to the buffer, in order of execution.
for (auto drive : change.second) {
if (drive.second.getBitWidth() < buff.getBitWidth())
buff.insertBits(drive.second, drive.first);
else
buff = drive.second;
}
// Skip if the updated signal value is equal to the initial value.
if (std::memcmp(curr->detail.value, buff.getRawData(), curr->size) == 0)
continue;
// Apply the signal update.
std::memcpy(curr->detail.value, buff.getRawData(),
state->signals[change.first].size);
// Trigger all sensitive instances.
// The owner is always triggered.
wakeupQueue.push_back(state->signals[change.first].owner);
// Add sensitive instances.
for (auto inst : state->signals[change.first].triggers) {
// Skip if the process is not currently sensible to the signal.
if (!state->instances[inst].isEntity) {
auto &sensList = state->instances[inst].sensitivityList;
auto it = std::find(sensList.begin(), sensList.end(), change.first);
if (sensList.end() != it &&
state->instances[inst].procState->senses[it - sensList.begin()] ==
0)
continue;
}
wakeupQueue.push_back(inst);
}
// Dump the updated signal.
state->dumpSignal(out, change.first);
}
// TODO: don't wakeup a process instances if already woken up by an observed
// signal.
// Add scheduled process resumes to the wakeup queue.
for (auto inst : pop.scheduled) {
wakeupQueue.push_back(inst);
}
// Clear temporary subsignals.
state->signals.erase(state->signals.begin() + state->nSigs,
state->signals.end());
// Run the instances present in the wakeup queue.
for (auto inst : wakeupQueue) {
auto name = state->instances[inst].unit;
auto sigTable = state->instances[inst].signalTable.data();
auto sensitivityList = state->instances[inst].sensitivityList;
auto outputList = state->instances[inst].outputs;
// Combine inputs and outputs in one argument table.
sensitivityList.insert(sensitivityList.end(), outputList.begin(),
outputList.end());
auto argTable = sensitivityList.data();
// Gather the instance arguments for unit invocation.
SmallVector<void *, 3> args;
if (state->instances[inst].isEntity)
args.assign({&state, &sigTable, &argTable});
else {
args.assign({&state, &state->instances[inst].procState, &argTable});
}
// Run the unit.
auto invocationResult = engine->invoke(name, args);
if (invocationResult) {
llvm::errs() << "Failed invocation of " << root << ": "
<< invocationResult;
return -1;
}
}
// Clear wakeup queue.
wakeupQueue.clear();
i++;
}
llvm::errs() << "Finished after " << i << " steps.\n";
return 0;
}
void Engine::buildLayout(ModuleOp module) {
// Start from the root entity.
auto rootEntity = module.lookupSymbol<EntityOp>(root);
assert(rootEntity && "root entity not found!");
// Build root instance, the parent and instance names are the same for the
// root.
Instance rootInst(root, root);
rootInst.unit = root;
// Recursively walk the units starting at root.
walkEntity(rootEntity, rootInst);
// The root is always an instance.
rootInst.isEntity = true;
// Store the root instance.
state->instances[rootInst.name] = rootInst;
// Add triggers and outputs to all signals.
for (auto &inst : state->instances) {
for (auto trigger : inst.getValue().sensitivityList) {
state->signals[trigger].triggers.push_back(inst.getKey().str());
}
for (auto out : inst.getValue().outputs) {
state->signals[out].outOf.push_back(inst.getKey().str());
}
}
state->nSigs = state->signals.size();
}
void Engine::walkEntity(EntityOp entity, Instance &child) {
entity.walk([&](Operation *op) -> WalkResult {
assert(op);
// Add a signal to the signal table.
if (auto sig = dyn_cast<SigOp>(op)) {
int index = state->addSignal(sig.name().str(), child.name);
child.signalTable.push_back(index);
}
// Build (recursive) instance layout.
if (auto inst = dyn_cast<InstOp>(op)) {
// Skip self-recursion.
if (inst.callee() == child.name)
return WalkResult::advance();
if (auto e =
op->getParentOfType<ModuleOp>().lookupSymbol(inst.callee())) {
Instance newChild(inst.name().str(), child.name);
newChild.unit = inst.callee().str();
// Gather sensitivity list.
for (auto arg : inst.inputs()) {
// Check if the argument comes from a parent's argument.
if (auto a = arg.dyn_cast<BlockArgument>()) {
unsigned int argInd = a.getArgNumber();
// The argument comes either from one of the parent's inputs, or one
// of the parent's outputs.
if (argInd < newChild.sensitivityList.size())
newChild.sensitivityList.push_back(child.sensitivityList[argInd]);
else
newChild.sensitivityList.push_back(
child.outputs[argInd - newChild.sensitivityList.size()]);
} else if (auto sig = dyn_cast<SigOp>(arg.getDefiningOp())) {
// Otherwise has to come from a sigop, search through the intantce's
// signal table.
for (auto s : child.signalTable) {
if (state->signals[s].name == sig.name() &&
state->signals[s].owner == child.name) {
newChild.sensitivityList.push_back(s);
break;
}
}
}
}
// Gather outputs list.
for (auto out : inst.outputs()) {
// Check if it comes from an argument.
if (auto a = out.dyn_cast<BlockArgument>()) {
unsigned int argInd = a.getArgNumber();
// The argument comes either from one of the parent's inputs or one
// of the parent's outputs.
if (argInd < newChild.sensitivityList.size())
newChild.outputs.push_back(child.sensitivityList[argInd]);
else
newChild.outputs.push_back(
child.outputs[argInd - newChild.sensitivityList.size()]);
} else if (auto sig = dyn_cast<SigOp>(out.getDefiningOp())) {
// Search through the signal table.
for (auto s : child.signalTable) {
if (state->signals[s].name == sig.name() &&
state->signals[s].owner == child.name) {
newChild.outputs.push_back(s);
break;
}
}
}
}
// Recursively walk a new entity, otherwise it is a process and cannot
// define new signals or instances.
if (auto ent = dyn_cast<EntityOp>(e)) {
newChild.isEntity = true;
walkEntity(ent, newChild);
} else {
newChild.isEntity = false;
}
// Store the created instance.
state->instances[newChild.name] = newChild;
}
}
return WalkResult::advance();
});
}

View File

@ -0,0 +1,257 @@
//===- State.cpp - LLHD simulator state -------------------------*- C++ -*-===//
//
// This file implements the constructs used to keep track of the simulation
// state in the LLHD simulator.
//
//===----------------------------------------------------------------------===//
#include "State.h"
#include "llvm/Support/Format.h"
#include "llvm/Support/raw_ostream.h"
#include <string>
using namespace llvm;
using namespace mlir;
using namespace llhd::sim;
//===----------------------------------------------------------------------===//
// Time
//===----------------------------------------------------------------------===//
bool Time::operator<(const Time &rhs) const {
if (time < rhs.time)
return true;
if (time == rhs.time && delta < rhs.delta)
return true;
if (time == rhs.time && delta == rhs.delta && eps < rhs.eps)
return true;
return false;
}
bool Time::operator==(const Time &rhs) const {
return (time == rhs.time && delta == rhs.delta && eps == rhs.eps);
}
Time Time::operator+(const Time &rhs) const {
return Time(time + rhs.time, delta + rhs.delta, eps + rhs.eps);
}
bool Time::isZero() { return (time == 0 && delta == 0 && eps == 0); }
std::string Time::dump() {
std::string ret;
raw_string_ostream ss(ret);
ss << time << "ns " << delta << "d " << eps << "e";
return ss.str();
}
//===----------------------------------------------------------------------===//
// Signal
//===----------------------------------------------------------------------===//
Signal::Signal(std::string name, std::string owner)
: name(name), owner(owner), size(0), detail({nullptr, 0}) {}
Signal::Signal(std::string name, std::string owner, uint8_t *value,
uint64_t size)
: name(name), owner(owner), size(size), detail({value, 0}) {}
Signal::Signal(int origin, uint8_t *value, uint64_t size, uint64_t offset)
: origin(origin), size(size), detail({value, offset}) {}
bool Signal::operator==(const Signal &rhs) const {
if (owner != rhs.owner || name != rhs.name || size != rhs.size)
return false;
for (uint64_t i = 0; i < size; ++i) {
if (detail.value[i] != rhs.detail.value[i])
return false;
}
return true;
}
bool Signal::operator<(const Signal &rhs) const {
if (owner < rhs.owner)
return true;
if (owner == rhs.owner && name < rhs.name)
return true;
return false;
}
std::string Signal::dump() {
std::string ret;
raw_string_ostream ss(ret);
ss << "0x";
for (int i = size - 1; i >= 0; --i) {
ss << format_hex_no_prefix(static_cast<int>(detail.value[i]), 2);
}
return ss.str();
}
//===----------------------------------------------------------------------===//
// Slot
//===----------------------------------------------------------------------===//
bool Slot::operator<(const Slot &rhs) const { return time < rhs.time; }
bool Slot::operator>(const Slot &rhs) const { return rhs.time < time; }
void Slot::insertChange(int index, int bitOffset, APInt &bytes) {
changes[index].push_back(std::make_pair(bitOffset, bytes));
}
void Slot::insertChange(std::string inst) { scheduled.push_back(inst); }
//===----------------------------------------------------------------------===//
// UpdateQueue
//===----------------------------------------------------------------------===//
void UpdateQueue::insertOrUpdate(Time time, int index, int bitOffset,
APInt &bytes) {
for (size_t i = 0, e = c.size(); i < e; ++i) {
if (time == c[i].time) {
c[i].insertChange(index, bitOffset, bytes);
return;
}
}
Slot newSlot(time);
newSlot.insertChange(index, bitOffset, bytes);
push(newSlot);
}
void UpdateQueue::insertOrUpdate(Time time, std::string inst) {
for (size_t i = 0, e = c.size(); i < e; ++i) {
if (time == c[i].time) {
c[i].insertChange(inst);
return;
}
}
Slot newSlot(time);
newSlot.insertChange(inst);
push(newSlot);
}
//===----------------------------------------------------------------------===//
// State
//===----------------------------------------------------------------------===//
State::~State() {
for (int i = 0; i < nSigs; ++i)
if (signals[i].detail.value)
std::free(signals[i].detail.value);
for (auto &entry : instances) {
auto inst = entry.getValue();
if (!inst.isEntity && inst.procState) {
std::free(inst.procState->inst);
std::free(inst.procState->senses);
std::free(inst.procState);
}
}
}
Slot State::popQueue() {
assert(!queue.empty() && "the event queue is empty");
Slot pop = queue.top();
queue.pop();
return pop;
}
void State::pushQueue(Time t, int index, int bitOffset, APInt &bytes) {
Time newTime = time + t;
queue.insertOrUpdate(newTime, index, bitOffset, bytes);
}
void State::pushQueue(Time t, std::string inst) {
Time newTime = time + t;
queue.insertOrUpdate(newTime, inst);
}
int State::addSignal(std::string name, std::string owner) {
signals.push_back(Signal(name, owner));
return signals.size() - 1;
}
void State::addProcPtr(std::string name, ProcState *procStatePtr) {
instances[name].procState = procStatePtr;
// Copy string to owner name ptr.
name.copy(instances[name].procState->inst, name.size());
// Ensure the string is null-terminated.
instances[name].procState->inst[name.size()] = '\0';
}
int State::addSignalData(int index, std::string owner, uint8_t *value,
uint64_t size) {
int globalIdx = instances[owner].signalTable[index];
signals[globalIdx].detail.value = value;
signals[globalIdx].size = size;
return globalIdx;
}
void State::dumpSignal(llvm::raw_ostream &out, int index) {
auto &sig = signals[index];
out << time.dump() << " " << sig.owner << "/" << sig.name << " "
<< sig.dump() << "\n";
for (auto inst : sig.triggers) {
std::string curr = inst, path = inst;
do {
curr = instances[curr].parent;
path = curr + "/" + path;
} while (instances[curr].name != sig.owner);
out << time.dump() << " " << path << "/" << sig.name << " " << sig.dump()
<< "\n";
}
for (auto inst : sig.outOf) {
std::string path = inst;
std::string curr = inst;
do {
curr = instances[curr].parent;
path = curr + "/" + path;
} while (instances[curr].name != sig.owner);
out << time.dump() << " " << path << "/" << sig.name << " " << sig.dump()
<< "\n";
}
}
void State::dumpLayout() {
llvm::errs() << "::------------------- Layout -------------------::\n";
for (auto &inst : instances) {
llvm::errs() << inst.getKey().str() << ":\n";
llvm::errs() << "---parent: " << inst.getValue().parent << "\n";
llvm::errs() << "---isEntity: " << inst.getValue().isEntity << "\n";
llvm::errs() << "---signal table: ";
for (auto sig : inst.getValue().signalTable) {
llvm::errs() << sig << " ";
}
llvm::errs() << "\n";
llvm::errs() << "---sensitivity list: ";
for (auto in : inst.getValue().sensitivityList) {
llvm::errs() << in << " ";
}
llvm::errs() << "\n";
llvm::errs() << "---output list: ";
for (auto out : inst.getValue().outputs) {
llvm::errs() << out << " ";
}
llvm::errs() << "\n";
}
llvm::errs() << "::----------------------------------------------::\n";
}
void State::dumpSignalTriggers() {
llvm::errs() << "::------------- Signal information -------------::\n";
for (size_t i = 0, e = signals.size(); i < e; ++i) {
llvm::errs() << signals[i].owner << "/" << signals[i].name
<< " triggers: " << signals[i].owner << " ";
for (auto trig : signals[i].triggers) {
llvm::errs() << trig << " ";
}
llvm::errs() << "\n";
llvm::errs() << signals[i].owner << "/" << signals[i].name
<< " is output of: " << signals[i].owner << " ";
for (auto out : signals[i].outOf) {
llvm::errs() << out << " ";
}
llvm::errs() << "\n";
}
llvm::errs() << "::----------------------------------------------::\n";
}

View File

@ -0,0 +1,217 @@
//===- State.h - Simulation state definition --------------------*- C++ -*-===//
//
// Defines structures used to keep track of the simulation state in the LLHD
// simulator.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_SIMULATOR_STATE_H
#define CIRCT_DIALECT_LLHD_SIMULATOR_STATE_H
#include "llvm/ADT/APInt.h"
#include "llvm/ADT/StringMap.h"
#include <map>
#include <queue>
namespace mlir {
namespace llhd {
namespace sim {
/// The simulator's internal representation of time.
struct Time {
/// Empty (zero) time constructor. All the time values are defaulted to 0.
Time() = default;
/// Construct with given time values.
Time(unsigned time, unsigned delta, unsigned eps)
: time(time), delta(delta), eps(eps) {}
/// Compare the time values in order of time, delta, eps.
bool operator<(const Time &rhs) const;
/// Return true if all the time values are equal.
bool operator==(const Time &rhs) const;
/// Add two time values.
Time operator+(const Time &rhs) const;
/// Return true if the time represents zero-time.
bool isZero();
/// Get the stored time in a printable format.
std::string dump();
unsigned time;
unsigned delta;
unsigned eps;
private:
};
/// Detail structure that can be easily accessed by the lowered code.
struct SignalDetail {
uint8_t *value;
uint64_t offset;
};
/// The simulator's internal representation of a signal.
struct Signal {
/// Construct an "empty" signal.
Signal(std::string name, std::string owner);
/// Construct a signal with the given name, owner and initial value.
Signal(std::string name, std::string owner, uint8_t *value, uint64_t size);
/// Construct a subsignal of the signal at origin un the global signal table.
Signal(int origin, uint8_t *value, uint64_t size, uint64_t offset);
/// Default signal destructor.
~Signal() = default;
/// Returns true if the signals match in name, owner, size and value.
bool operator==(const Signal &rhs) const;
/// Returns true if the owner name is lexically smaller than rhs's owner, or
/// the name is lexically smaller than rhs's name, in case they share the same
/// owner.
bool operator<(const Signal &rhs) const;
/// Return the signal value in dumpable format: "0x<value>".
std::string dump();
std::string name;
std::string owner;
// The list of instances this signal triggers.
std::vector<std::string> triggers;
// The list of instances this signal is an output of.
std::vector<std::string> outOf;
int origin = -1;
uint64_t size;
SignalDetail detail;
};
/// The simulator's internal representation of one queue slot.
struct Slot {
/// Construct a new slot.
Slot(Time time) : time(time) {}
/// Returns true if the slot's time is smaller than the compared slot's time.
bool operator<(const Slot &rhs) const;
/// Returns true if the slot's time is greater than the compared slot's time.
bool operator>(const Slot &rhs) const;
/// Insert a change.
void insertChange(int index, int bitOffset, llvm::APInt &bytes);
/// Insert a scheduled process wakeup.
void insertChange(std::string inst);
// Map structure: <signal-index, vec<(offset, new-value)>>.
std::map<int, std::vector<std::pair<int, llvm::APInt>>> changes;
// Processes with scheduled wakeup.
std::vector<std::string> scheduled;
Time time;
};
/// This is equivalent to and std::priorityQueue<Slot> ordered using the greater
/// operator, which adds an insertion method to add changes to a slot.
class UpdateQueue
: public std::priority_queue<Slot, std::vector<Slot>, std::greater<Slot>> {
public:
/// Check wheter a slot for the given time already exists. If that's the case,
/// add the new change to it, else create a new slot and push it to the queue.
void insertOrUpdate(Time time, int index, int bitOffset, llvm::APInt &bytes);
/// Check wheter a slot for the given time already exists. If that's the case,
/// add the scheduled wakeup to it, else create a new slot and push it to the
/// queue.
void insertOrUpdate(Time time, std::string inst);
};
/// State structure for process persistence across suspension.
struct ProcState {
char *inst;
int resume;
bool *senses;
uint8_t *resumeState;
};
/// The simulator internal representation of an instance.
struct Instance {
Instance() = default;
Instance(std::string name, std::string parent)
: name(name), parent(parent), procState(nullptr) {}
// The instance name.
std::string name;
// The instance parent's name.
std::string parent;
// The instance's base unit.
std::string unit;
bool isEntity;
// The signals the unit defines.
std::vector<int> signalTable;
// The input list.
std::vector<int> sensitivityList;
// The output list.
std::vector<int> outputs;
ProcState *procState;
};
/// The simulator's state. It contains the current simulation time, signal
/// values and the event queue.
struct State {
/// Construct a new empty (at 0 time) state.
State() = default;
/// State destructor, ensures all malloc'd regions stored in the state are
/// correctly free'd.
~State();
/// Pop the head of the queue and update the simulation time.
Slot popQueue();
/// Push a new event in the event queue.
void pushQueue(Time time, int index, int bitOffset, llvm::APInt &bytes);
/// Push a new scheduled wakeup event in the event queue.
void pushQueue(Time time, std::string inst);
/// Get the signal at position i in the signal list.
Signal getSignal(int index);
/// Add a new signal to the state. Returns the index of the new signal.
int addSignal(std::string name, std::string owner);
int addSignalData(int index, std::string owner, uint8_t *value,
uint64_t size);
/// Add a pointer to the process persistence state to a process instance.
void addProcPtr(std::string name, ProcState *procStatePtr);
/// Dump a signal to the out stream. One entry is added for every instance
/// the signal appears in.
void dumpSignal(llvm::raw_ostream &out, int index);
/// Dump the instance layout. Used for testing purposes.
void dumpLayout();
/// Dump the instances each signal triggers. Used for testing purposes.
void dumpSignalTriggers();
Time time;
std::string root;
int nSigs;
llvm::StringMap<Instance> instances;
std::vector<Signal> signals;
UpdateQueue queue;
};
} // namespace sim
} // namespace llhd
} // namespace mlir
#endif // CIRCT_DIALECT_LLHD_SIMULATOR_STATE_H

View File

@ -0,0 +1,77 @@
//===- signals-runtime-wrappers.cpp - Runtime library implementation ------===//
//
// This file implements the runtime library used in LLHD simulation.
//
//===----------------------------------------------------------------------===//
#include "signals-runtime-wrappers.h"
#include "llvm/ADT/ArrayRef.h"
using namespace llvm;
using namespace mlir::llhd::sim;
//===----------------------------------------------------------------------===//
// Runtime interface
//===----------------------------------------------------------------------===//
int alloc_signal(State *state, int index, char *owner, uint8_t *value,
int64_t size) {
assert(state && "alloc_signal: state not found");
std::string sOwner(owner);
return state->addSignalData(index, sOwner, value, size);
}
void alloc_proc(State *state, char *owner, ProcState *procState) {
assert(state && "alloc_proc: state not found");
std::string sOwner(owner);
state->addProcPtr(sOwner, procState);
}
SignalDetail *probe_signal(State *state, int index) {
assert(state && "probe_signal: state not found");
auto &sig = state->signals[index];
return &sig.detail;
}
void drive_signal(State *state, int index, uint8_t *value, uint64_t width,
int time, int delta, int eps) {
assert(state && "drive_signal: state not found");
APInt drive(width, ArrayRef<uint64_t>(reinterpret_cast<uint64_t *>(value),
state->signals[index].size));
Time sTime(time, delta, eps);
// Track back origin signal.
int originIdx = index;
while (state->signals[originIdx].origin >= 0) {
originIdx = state->signals[originIdx].origin;
}
int bitOffset = (state->signals[index].detail.value -
state->signals[originIdx].detail.value) *
8 +
state->signals[index].detail.offset;
// Spawn a new event.
state->pushQueue(sTime, originIdx, bitOffset, drive);
}
int add_subsignal(mlir::llhd::sim::State *state, int origin, uint8_t *ptr,
uint64_t len, uint64_t offset) {
int size = llvm::divideCeil(len + offset, 8);
state->signals.push_back(Signal(origin, ptr, size, offset));
return (state->signals.size() - 1);
}
void llhd_suspend(State *state, ProcState *procState, int time, int delta,
int eps) {
std::string instS(procState->inst);
// Add a new scheduled wake up if a time is specified.
if (time || delta || eps) {
Time sTime(time, delta, eps);
state->pushQueue(sTime, instS);
}
}

View File

@ -0,0 +1,45 @@
//===- signals-runtime-wrappers.h - Simulation runtime library --*- C++ -*-===//
//
// Defines the runtime library used in LLHD simulation.
//
//===----------------------------------------------------------------------===//
#ifndef CIRCT_DIALECT_LLHD_SIMULATOR_SIGNALS_RUNTIME_WRAPPERS_H
#define CIRCT_DIALECT_LLHD_SIMULATOR_SIGNALS_RUNTIME_WRAPPERS_H
#include "State.h"
extern "C" {
//===----------------------------------------------------------------------===//
// Runtime interfaces
//===----------------------------------------------------------------------===//
/// Allocate a new signal. The index of the new signal in the state's list of
/// signals is returned.
int alloc_signal(mlir::llhd::sim::State *state, int index, char *owner,
uint8_t *value, int64_t size);
/// Add allocated constructs to a process instance.
void alloc_proc(mlir::llhd::sim::State *state, char *owner,
mlir::llhd::sim::ProcState *procState);
/// Gather information of the signal at index.
mlir::llhd::sim::SignalDetail *probe_signal(mlir::llhd::sim::State *state,
int index);
/// Drive a value onto a signal.
void drive_signal(mlir::llhd::sim::State *state, int index, uint8_t *value,
uint64_t width, int time, int delta, int eps);
/// Add a temporary subsignal to the global signal table.
int add_subsignal(mlir::llhd::sim::State *state, int origin, uint8_t *ptr,
uint64_t len, uint64_t offset);
/// Suspend a process.
void llhd_suspend(mlir::llhd::sim::State *state,
mlir::llhd::sim::ProcState *procState, int time, int delta,
int eps);
}
#endif // CIRCT_DIALECT_LLHD_SIMULATOR_SIGNALS_RUNTIME_WRAPPERS_H

View File

@ -0,0 +1,15 @@
add_mlir_dialect_library(MLIRLLHDTransforms
PassRegistration.cpp
ProcessLoweringPass.cpp
FunctionEliminationPass.cpp
DEPENDS
MLIRLLHDTransformsIncGen
LINK_LIBS PUBLIC
MLIREDSC
MLIRIR
MLIRStandardOps
MLIRLLHD
MLIRTransformUtils
)

View File

@ -0,0 +1,48 @@
//===- FunctionEliminationPass.cpp - Implement Function Elimination Pass --===//
//
// Implement pass to check that all functions got inlined and delete them.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "circt/Dialect/LLHD/Transforms/Passes.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Visitors.h"
using namespace mlir;
namespace {
struct FunctionEliminationPass
: public llhd::FunctionEliminationBase<FunctionEliminationPass> {
void runOnOperation() override;
};
void FunctionEliminationPass::runOnOperation() {
ModuleOp module = getOperation();
WalkResult result = module.walk([](CallOp op) -> WalkResult {
if (isa<llhd::ProcOp>(op.getParentOp()) ||
isa<llhd::EntityOp>(op.getParentOp())) {
return emitError(
op.getLoc(),
"Not all functions are inlined, there is at least "
"one function call left within a llhd.proc or llhd.entity.");
}
WalkResult::advance();
});
if (result.wasInterrupted()) {
signalPassFailure();
return;
}
module.walk([](FuncOp op) { op.erase(); });
}
} // namespace
std::unique_ptr<OperationPass<ModuleOp>>
mlir::llhd::createFunctionEliminationPass() {
return std::make_unique<FunctionEliminationPass>();
}

View File

@ -0,0 +1,19 @@
//===- PassDetails.h - LLHD pass class details ------------------*- C++ -*-===//
//
//===----------------------------------------------------------------------===//
#ifndef DIALECT_LLHD_TRANSFORMS_PASSDETAILS_H
#define DIALECT_LLHD_TRANSFORMS_PASSDETAILS_H
#include "mlir/Pass/Pass.h"
namespace mlir {
namespace llhd {
#define GEN_PASS_CLASSES
#include "circt/Dialect/LLHD/Transforms/Passes.h.inc"
} // namespace llhd
} // namespace mlir
#endif // DIALECT_LLHD_TRANSFORMS_PASSDETAILS_H

View File

@ -0,0 +1,11 @@
//===- PassRegistration.cpp - Register LLHD transformation passes ---------===//
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/LLHD/Transforms/Passes.h"
#include "mlir/Pass/Pass.h"
void mlir::llhd::initLLHDTransformationPasses() {
#define GEN_PASS_REGISTRATION
#include "circt/Dialect/LLHD/Transforms/Passes.h.inc"
}

View File

@ -0,0 +1,132 @@
//===- ProcessLoweringPass.cpp - Implement Process Lowering Pass ----------===//
//
// Implement Pass to transform combinatorial processes to entities.
//
//===----------------------------------------------------------------------===//
#include "PassDetails.h"
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "circt/Dialect/LLHD/Transforms/Passes.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Visitors.h"
using namespace mlir;
namespace {
struct ProcessLoweringPass
: public llhd::ProcessLoweringBase<ProcessLoweringPass> {
void runOnOperation() override;
};
void ProcessLoweringPass::runOnOperation() {
ModuleOp module = getOperation();
WalkResult result = module.walk([](llhd::ProcOp op) -> WalkResult {
// Check invariants
size_t numBlocks = op.body().getBlocks().size();
if (numBlocks == 1) {
if (!isa<llhd::HaltOp>(op.body().back().getTerminator())) {
return op.emitOpError("Process-lowering: Entry block is required to be "
"terminated by a HaltOp from the LLHD dialect.");
}
} else if (numBlocks == 2) {
Block &first = op.body().front();
Block &last = op.body().back();
if (last.getArguments().size() != 0) {
return op.emitOpError(
"Process-lowering: The second block (containing the "
"llhd.wait) is not allowed to have arguments.");
}
if (!isa<BranchOp>(first.getTerminator())) {
return op.emitOpError(
"Process-lowering: The first block has to be terminated "
"by a BranchOp from the standard dialect.");
}
if (auto wait = dyn_cast<llhd::WaitOp>(last.getTerminator())) {
// No optional time argument is allowed
if (wait.time()) {
return wait.emitOpError(
"Process-lowering: llhd.wait terminators with optional time "
"argument cannot be lowered to structural LLHD.");
}
// Every probed signal has to occur in the observed signals list in
// the wait instruction
WalkResult result = op.walk([&wait](llhd::PrbOp prbOp) -> WalkResult {
if (!llvm::is_contained(wait.obs(), prbOp.signal())) {
return wait.emitOpError(
"Process-lowering: The wait terminator is required to have "
"all probed signals as arguments!");
}
return WalkResult::advance();
});
if (result.wasInterrupted()) {
return result;
}
} else {
return op.emitOpError(
"Process-lowering: The second block must be terminated by "
"a WaitOp from the LLHD dialect.");
}
} else {
return op.emitOpError(
"Process-lowering only supports processes with either one basic "
"block terminated by a llhd.halt operation or two basic blocks where "
"the first one contains a std.br terminator and the second one "
"is terminated by a llhd.wait operation.");
}
OpBuilder builder(op);
// Replace proc with entity
llhd::EntityOp entity =
builder.create<llhd::EntityOp>(op.getLoc(), op.ins());
// Set the symbol name of the entity to the same as the process (as the
// process gets deleted anyways).
entity.setName(op.getName());
// Move all blocks from the process to the entity, the process does not have
// a region afterwards.
entity.body().takeBody(op.body());
entity.setAttr("type", op.getAttr("type"));
// In the case that wait is used to suspend the process, we need to merge
// the two blocks as we needed the second block to have a target for wait
// (the entry block cannot be targeted).
if (entity.body().getBlocks().size() == 2) {
Block &first = entity.body().front();
Block &second = entity.body().back();
// Delete the BranchOp operation in the entry block
first.getTerminator()->dropAllReferences();
first.getTerminator()->erase();
// Move operations of second block in entry block.
first.getOperations().splice(first.end(), second.getOperations());
// Drop all references to the second block and delete it.
second.dropAllReferences();
second.dropAllDefinedValueUses();
second.erase();
}
// Delete the process as it is now replaced by an entity.
op.getOperation()->dropAllReferences();
op.getOperation()->dropAllDefinedValueUses();
op.getOperation()->erase();
// Replace the llhd.halt or llhd.wait with the implicit entity terminator
builder.setInsertionPointToEnd(&entity.body().front());
Operation *terminator = entity.body().front().getTerminator();
builder.create<llhd::TerminatorOp>(terminator->getLoc());
terminator->dropAllReferences();
terminator->dropAllUses();
terminator->erase();
return WalkResult::advance();
});
if (result.wasInterrupted())
signalPassFailure();
}
} // namespace
std::unique_ptr<OperationPass<ModuleOp>>
mlir::llhd::createProcessLoweringPass() {
return std::make_unique<ProcessLoweringPass>();
}

View File

@ -0,0 +1 @@
add_subdirectory(Verilog)

View File

@ -0,0 +1,9 @@
add_mlir_library(MLIRLLHDTargetVerilog
TranslateToVerilog.cpp
ADDITIONAL_HEADER_DIRS
${PROJECT_SOURCE_DIR}/include/circt/Target/Verilog
LINK_LIBS PUBLIC
MLIRLLHD
)

View File

@ -0,0 +1,422 @@
//===- TranslateToVerilog.cpp - Verilog Printer ---------------------------===//
//
// This is the main LLHD to Verilog Printer implementation.
//
//===----------------------------------------------------------------------===//
#include "circt/Target/Verilog/TranslateToVerilog.h"
#include "circt/Dialect/LLHD/IR/LLHDOps.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/Module.h"
#include "mlir/IR/Visitors.h"
#include "mlir/Support/LogicalResult.h"
#include "mlir/Translation.h"
#include "llvm/ADT/Twine.h"
#include "llvm/Support/FormattedStream.h"
using namespace mlir;
namespace {
class VerilogPrinter {
public:
VerilogPrinter(llvm::formatted_raw_ostream &output) : out(output) {}
LogicalResult printModule(ModuleOp op);
LogicalResult printOperation(Operation *op, unsigned indentAmount = 0);
private:
LogicalResult printType(Type type);
LogicalResult printUnaryOp(Operation *op, StringRef opSymbol,
unsigned indentAmount = 0);
LogicalResult printBinaryOp(Operation *op, StringRef opSymbol,
unsigned indentAmount = 0);
LogicalResult printSignedBinaryOp(Operation *op, StringRef opSymbol,
unsigned indentAmount = 0);
/// Prints a SSA value. In case no mapping to a name exists yet, a new one is
/// added.
Twine getVariableName(Value value);
Twine getNewInstantiationName() {
return Twine("inst_") + Twine(instCount++);
}
/// Adds an alias for an existing SSA value. In case doesn't exist, it just
/// adds the alias as a new value.
void addAliasVariable(Value alias, Value existing);
unsigned instCount = 0;
llvm::formatted_raw_ostream &out;
unsigned nextValueNum = 0;
DenseMap<Value, unsigned> mapValueToName;
DenseMap<Value, unsigned> timeValueMap;
};
LogicalResult VerilogPrinter::printModule(ModuleOp module) {
WalkResult result = module.walk([this](llhd::EntityOp entity) -> WalkResult {
// An EntityOp always has a single block
Block &entryBlock = entity.body().front();
// Print the module signature
out << "module _" << entity.getName();
if (!entryBlock.args_empty()) {
out << "(";
for (unsigned int i = 0, e = entryBlock.getNumArguments(); i < e; ++i) {
out << (i > 0 ? ", " : "")
<< (i < entity.ins().getZExtValue() ? "input " : "output ");
printType(entryBlock.getArgument(i).getType());
out << " " << getVariableName(entryBlock.getArgument(i));
}
out << ")";
}
out << ";\n";
// Print the operations within the entity
for (auto iter = entryBlock.begin();
iter != entryBlock.end() && !dyn_cast<llhd::TerminatorOp>(iter);
++iter) {
if (failed(printOperation(&(*iter), 4))) {
return emitError(iter->getLoc(), "Operation not supported!");
}
}
out << "endmodule\n";
// Reset variable name counter as variables only have to be unique within a
// module
nextValueNum = 0;
return WalkResult::advance();
});
// if printing of a single operation failed, fail the whole translation
return failure(result.wasInterrupted());
}
LogicalResult VerilogPrinter::printBinaryOp(Operation *inst, StringRef opSymbol,
unsigned indentAmount) {
// Check that the operation is indeed a binary operation
if (inst->getNumOperands() != 2) {
return emitError(inst->getLoc(),
"This operation does not have two operands!");
}
if (inst->getNumResults() != 1) {
return emitError(inst->getLoc(),
"This operation does not have one result!");
}
// Print the operation
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = "
<< getVariableName(inst->getOperand(0)) << " " << opSymbol << " "
<< getVariableName(inst->getOperand(1)) << ";\n";
return success();
}
LogicalResult VerilogPrinter::printSignedBinaryOp(Operation *inst,
StringRef opSymbol,
unsigned indentAmount) {
// Note: the wire is not declared as signed, because if you have the result
// of two signed operation as an input to an unsigned operation, it would
// perform the signed version (sometimes this is not a problem because it's
// the same, but e.g. for modulo it would make a difference). Alternatively
// we could use $unsigned at every operation where it makes a difference,
// but that would look a lot uglier, we could also track which variable is
// signed and which unsigned and only do the conversion when necessary, but
// that is more effort. Also, because we have the explicit $signed at every
// signed operation, this isn't a problem for further signed operations.
//
// Check that the operation is indeed a binary operation
if (inst->getNumOperands() != 2) {
return emitError(inst->getLoc(),
"This operation does not have two operands!");
}
if (inst->getNumResults() != 1) {
return emitError(inst->getLoc(),
"This operation does not have one result!");
}
// Print the operation
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = $signed("
<< getVariableName(inst->getOperand(0)) << ") " << opSymbol << " $signed("
<< getVariableName(inst->getOperand(1)) << ");\n";
return success();
}
LogicalResult VerilogPrinter::printUnaryOp(Operation *inst, StringRef opSymbol,
unsigned indentAmount) {
// Check that the operation is indeed a unary operation
if (inst->getNumOperands() != 1) {
return emitError(inst->getLoc(),
"This operation does not have exactly one operand!");
}
if (inst->getNumResults() != 1) {
return emitError(inst->getLoc(),
"This operation does not have one result!");
}
// Print the operation
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = " << opSymbol
<< getVariableName(inst->getOperand(0)) << ";\n";
return success();
}
LogicalResult VerilogPrinter::printOperation(Operation *inst,
unsigned indentAmount) {
if (auto op = dyn_cast<llhd::ConstOp>(inst)) {
if (IntegerAttr intAttr = op.value().dyn_cast<IntegerAttr>()) {
// Integer constant
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = "
<< op.getResult().getType().getIntOrFloatBitWidth() << "'d"
<< intAttr.getValue().getZExtValue() << ";\n";
return success();
}
if (llhd::TimeAttr timeAttr = op.value().dyn_cast<llhd::TimeAttr>()) {
// Time Constant
if (timeAttr.getTime() == 0 && timeAttr.getDelta() != 1) {
return emitError(
op.getLoc(),
"Not possible to translate a time attribute with 0 real "
"time and non-1 delta.");
}
// Track time value for future use
timeValueMap.insert(
std::make_pair(inst->getResult(0), timeAttr.getTime()));
return success();
}
return failure();
}
if (auto op = dyn_cast<llhd::SigOp>(inst)) {
out.PadToColumn(indentAmount);
out << "var ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = "
<< getVariableName(op.init()) << ";\n";
return success();
}
if (auto op = dyn_cast<llhd::PrbOp>(inst)) {
// Prb is a nop, it just defines an alias for an already existing wire
addAliasVariable(inst->getResult(0), op.signal());
return success();
}
if (auto op = dyn_cast<llhd::DrvOp>(inst)) {
out.PadToColumn(indentAmount);
out << "assign " << getVariableName(op.signal()) << " = #("
<< timeValueMap.lookup(op.time()) << "ns) ";
if (op.enable()) {
out << getVariableName(op.enable()) << " ? "
<< getVariableName(op.value()) << " : "
<< getVariableName(op.signal()) << ";\n";
} else {
out << getVariableName(op.value()) << ";\n";
}
return success();
}
if (auto op = dyn_cast<llhd::AndOp>(inst)) {
return printBinaryOp(inst, "&", indentAmount);
}
if (auto op = dyn_cast<llhd::OrOp>(inst)) {
return printBinaryOp(inst, "|", indentAmount);
}
if (auto op = dyn_cast<llhd::XorOp>(inst)) {
return printBinaryOp(inst, "^", indentAmount);
}
if (auto op = dyn_cast<llhd::NotOp>(inst)) {
return printUnaryOp(inst, "~", indentAmount);
}
if (auto op = dyn_cast<llhd::ShlOp>(inst)) {
unsigned baseWidth = inst->getOperand(0).getType().getIntOrFloatBitWidth();
unsigned hiddenWidth =
inst->getOperand(1).getType().getIntOrFloatBitWidth();
unsigned combinedWidth = baseWidth + hiddenWidth;
out.PadToColumn(indentAmount);
out << "wire [" << (combinedWidth - 1) << ":0] "
<< getVariableName(inst->getResult(0)) << "tmp0 = {"
<< getVariableName(inst->getOperand(0)) << ", "
<< getVariableName(inst->getOperand(1)) << "};\n";
out.PadToColumn(indentAmount);
out << "wire [" << (combinedWidth - 1) << ":0] "
<< getVariableName(inst->getResult(0))
<< "tmp1 = " << getVariableName(inst->getResult(0)) << "tmp0 << "
<< getVariableName(inst->getOperand(2)) << ";\n";
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = "
<< getVariableName(inst->getResult(0)) << "tmp1[" << (combinedWidth - 1)
<< ':' << hiddenWidth << "];\n";
return success();
}
if (auto op = dyn_cast<llhd::ShrOp>(inst)) {
unsigned baseWidth = inst->getOperand(0).getType().getIntOrFloatBitWidth();
unsigned hiddenWidth =
inst->getOperand(1).getType().getIntOrFloatBitWidth();
unsigned combinedWidth = baseWidth + hiddenWidth;
out.PadToColumn(indentAmount);
out << "wire [" << (combinedWidth - 1) << ":0] "
<< getVariableName(inst->getResult(0)) << "tmp0 = {"
<< getVariableName(inst->getOperand(1)) << ", "
<< getVariableName(inst->getOperand(0)) << "};\n";
out.PadToColumn(indentAmount);
out << "wire [" << (combinedWidth - 1) << ":0] "
<< getVariableName(inst->getResult(0))
<< "tmp1 = " << getVariableName(inst->getResult(0)) << "tmp0 << "
<< getVariableName(inst->getOperand(2)) << ";\n";
out.PadToColumn(indentAmount);
out << "wire ";
if (failed(printType(inst->getResult(0).getType())))
return failure();
out << " " << getVariableName(inst->getResult(0)) << " = "
<< getVariableName(inst->getResult(0)) << "tmp1[" << (baseWidth - 1)
<< ":0];\n";
return success();
}
if (auto op = dyn_cast<llhd::NegOp>(inst)) {
return printUnaryOp(inst, "-", indentAmount);
}
if (auto op = dyn_cast<AddIOp>(inst)) {
return printBinaryOp(inst, "+", indentAmount);
}
if (auto op = dyn_cast<SubIOp>(inst)) {
return printBinaryOp(inst, "-", indentAmount);
}
if (auto op = dyn_cast<MulIOp>(inst)) {
return printBinaryOp(inst, "*", indentAmount);
}
if (auto op = dyn_cast<UnsignedDivIOp>(inst)) {
return printBinaryOp(inst, "/", indentAmount);
}
if (auto op = dyn_cast<SignedDivIOp>(inst)) {
return printSignedBinaryOp(inst, "/", indentAmount);
}
if (auto op = dyn_cast<UnsignedRemIOp>(inst)) {
// % in Verilog is the remainder in LLHD semantics
return printBinaryOp(inst, "%", indentAmount);
}
if (auto op = dyn_cast<SignedRemIOp>(inst)) {
// % in Verilog is the remainder in LLHD semantics
return printSignedBinaryOp(inst, "%", indentAmount);
}
if (auto op = dyn_cast<llhd::SModOp>(inst)) {
return emitError(op.getLoc(),
"Signed modulo operation is not yet supported!");
}
if (auto op = dyn_cast<CmpIOp>(inst)) {
switch (op.getPredicate()) {
case mlir::CmpIPredicate::eq:
return printBinaryOp(inst, "==", indentAmount);
case mlir::CmpIPredicate::ne:
return printBinaryOp(inst, "!=", indentAmount);
case mlir::CmpIPredicate::sge:
return printSignedBinaryOp(inst, ">=", indentAmount);
case mlir::CmpIPredicate::sgt:
return printSignedBinaryOp(inst, ">", indentAmount);
case mlir::CmpIPredicate::sle:
return printSignedBinaryOp(inst, "<=", indentAmount);
case mlir::CmpIPredicate::slt:
return printSignedBinaryOp(inst, "<", indentAmount);
case mlir::CmpIPredicate::uge:
return printBinaryOp(inst, ">=", indentAmount);
case mlir::CmpIPredicate::ugt:
return printBinaryOp(inst, ">", indentAmount);
case mlir::CmpIPredicate::ule:
return printBinaryOp(inst, "<=", indentAmount);
case mlir::CmpIPredicate::ult:
return printBinaryOp(inst, "<", indentAmount);
}
return failure();
}
if (auto op = dyn_cast<llhd::InstOp>(inst)) {
out.PadToColumn(indentAmount);
out << "_" << op.callee() << " " << getNewInstantiationName();
if (op.inputs().size() > 0 || op.outputs().size() > 0)
out << " (";
unsigned counter = 0;
for (Value arg : op.inputs()) {
if (counter++ > 0)
out << ", ";
out << getVariableName(arg);
}
for (Value arg : op.outputs()) {
if (counter++ > 0)
out << ", ";
out << getVariableName(arg);
}
if (op.inputs().size() > 0 || op.outputs().size() > 0)
out << ")";
out << ";\n";
return success();
}
// TODO: insert structural operations here
return failure();
}
LogicalResult VerilogPrinter::printType(Type type) {
if (type.isIntOrFloat()) {
unsigned int width = type.getIntOrFloatBitWidth();
if (width != 1)
out << '[' << (width - 1) << ":0]";
return success();
}
if (llhd::SigType sig = type.dyn_cast<llhd::SigType>()) {
return printType(sig.getUnderlyingType());
}
return failure();
}
Twine VerilogPrinter::getVariableName(Value value) {
if (!mapValueToName.count(value)) {
mapValueToName.insert(std::make_pair(value, nextValueNum));
nextValueNum++;
}
return Twine("_") + Twine(mapValueToName.lookup(value));
}
void VerilogPrinter::addAliasVariable(Value alias, Value existing) {
if (!mapValueToName.count(existing)) {
mapValueToName.insert(std::make_pair(alias, nextValueNum));
nextValueNum++;
} else {
mapValueToName.insert(
std::make_pair(alias, mapValueToName.lookup(existing)));
}
}
} // anonymous namespace
LogicalResult mlir::llhd::printVerilog(ModuleOp module, raw_ostream &os) {
llvm::formatted_raw_ostream out(os);
VerilogPrinter printer(out);
return printer.printModule(module);
}
void mlir::llhd::registerToVerilogTranslation() {
TranslateFromMLIRRegistration registration(
"llhd-to-verilog", [](ModuleOp module, raw_ostream &output) {
return printVerilog(module, output);
});
}

View File

@ -11,6 +11,7 @@ set(CIRCT_TEST_DEPENDS
circt-translate
handshake-runner
firtool
llhd-sim
)
add_lit_testsuite(check-circt "Running the CIRCT regression tests"

View File

@ -0,0 +1,102 @@
//RUN: circt-opt %s --convert-llhd-to-llvm | FileCheck %s
// CHECK-LABEL: convert_bitwise_i1
// CHECK-SAME: %[[LHS:.*]]: !llvm.i1,
// CHECK-SAME: %[[RHS:.*]]: !llvm.i1
func @convert_bitwise_i1(%lhs : i1, %rhs : i1) {
// CHECK-NEXT: %[[MASK:.*]] = llvm.mlir.constant(true) : !llvm.i1
// CHECK-NEXT: %{{.*}} = llvm.xor %[[LHS]], %[[MASK]] : !llvm.i1
%0 = llhd.not %lhs : i1
// CHECK-NEXT: %{{.*}} = llvm.and %[[LHS]], %[[RHS]] : !llvm.i1
%1 = llhd.and %lhs, %rhs : i1
// CHECK-NEXT: %{{.*}} = llvm.or %[[LHS]], %[[RHS]] : !llvm.i1
%2 = llhd.or %lhs, %rhs : i1
// CHECK-NEXT: %{{.*}} = llvm.xor %[[LHS]], %[[RHS]] : !llvm.i1
%3 = llhd.xor %lhs, %rhs : i1
return
}
// CHECK-LABEL: convert_bitwise_i32
// CHECK-SAME: %[[LHS:.*]]: !llvm.i32,
// CHECK-SAME: %[[RHS:.*]]: !llvm.i32
func @convert_bitwise_i32(%lhs : i32, %rhs : i32) {
// CHECK-NEXT: %[[MASK:.*]] = llvm.mlir.constant(-1 : i32) : !llvm.i32
// CHECK-NEXT: %{{.*}} = llvm.xor %[[LHS]], %[[MASK]] : !llvm.i32
llhd.not %lhs : i32
// CHECK-NEXT: %{{.*}} = llvm.and %[[LHS]], %[[RHS]] : !llvm.i32
llhd.and %lhs, %rhs : i32
// CHECK-NEXT: %{{.*}} = llvm.or %[[LHS]], %[[RHS]] : !llvm.i32
llhd.or %lhs, %rhs : i32
// CHECK-NEXT: %{{.*}} = llvm.xor %[[LHS]], %[[RHS]] : !llvm.i32
llhd.xor %lhs, %rhs : i32
return
}
// CHECK-LABEL: convert_shl_i5_i2_i2
// CHECK-SAME: %[[BASE:.*]]: !llvm.i5,
// CHECK-SAME: %[[HIDDEN:.*]]: !llvm.i2,
// CHECK-SAME: %[[AMOUNT:.*]]: !llvm.i2
func @convert_shl_i5_i2_i2(%base : i5, %hidden : i2, %amount : i2) {
// CHECK-NEXT: %[[ZEXTB:.*]] = llvm.zext %[[BASE]] : !llvm.i5 to !llvm.i7
// CHECK-NEXT: %[[ZEXTH:.*]] = llvm.zext %[[HIDDEN]] : !llvm.i2 to !llvm.i7
// CHECK-NEXT: %[[ZEXTA:.*]] = llvm.zext %[[AMOUNT]] : !llvm.i2 to !llvm.i7
// CHECK-NEXT: %[[HDNW:.*]] = llvm.mlir.constant(2 : i7) : !llvm.i7
// CHECK-NEXT: %[[SHB:.*]] = llvm.shl %[[ZEXTB]], %[[HDNW]] : !llvm.i7
// CHECK-NEXT: %[[COMB:.*]] = llvm.or %[[SHB]], %[[ZEXTH]] : !llvm.i7
// CHECK-NEXT: %[[SA:.*]] = llvm.sub %[[HDNW]], %[[ZEXTA]] : !llvm.i
// CHECK-NEXT: %[[SH:.*]] = llvm.lshr %[[COMB]], %[[SA]] : !llvm.i7
// CHECK-NEXT: %{{.*}} = llvm.trunc %[[SH]] : !llvm.i7 to !llvm.i5
%0 = llhd.shl %base, %hidden, %amount : (i5, i2, i2) -> i5
return
}
// CHECK-LABEL: convert_shr_i5_i2_i2
// CHECK-SAME: %[[BASE:.*]]: !llvm.i5,
// CHECK-SAME: %[[HIDDEN:.*]]: !llvm.i2,
// CHECK-SAME: %[[AMOUNT:.*]]: !llvm.i2
func @convert_shr_i5_i2_i2(%base : i5, %hidden : i2, %amount : i2) {
// CHECK-NEXT: %[[ZEXTB:.*]] = llvm.zext %[[BASE]] : !llvm.i5 to !llvm.i7
// CHECK-NEXT: %[[ZEXTH:.*]] = llvm.zext %[[HIDDEN]] : !llvm.i2 to !llvm.i7
// CHECK-NEXT: %[[ZEXTA:.*]] = llvm.zext %[[AMOUNT]] : !llvm.i2 to !llvm.i7
// CHECK-NEXT: %[[BASEW:.*]] = llvm.mlir.constant(5 : i7) : !llvm.i7
// CHECK-NEXT: %[[SHH:.*]] = llvm.shl %[[ZEXTH]], %[[BASEW]] : !llvm.i7
// CHECK-NEXT: %[[COMB:.*]] = llvm.or %[[SHH]], %[[ZEXTB]] : !llvm.i7
// CHECK-NEXT: %[[SH:.*]] = llvm.lshr %[[COMB]], %[[ZEXTA]] : !llvm.i7
// CHECK-NEXT: %{{.*}} = llvm.trunc %[[SH]] : !llvm.i7 to !llvm.i5
%0 = llhd.shr %base, %hidden, %amount : (i5, i2, i2) -> i5
return
}
// CHECK-LABEL: @convert_shr_sig
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">,
// CHECK-SAME: %[[SIGTAB:.*]]: !llvm<"i32*">,
// CHECK-SAME: %[[ARGTAB:.*]]: !llvm<"i32*">
llhd.entity @convert_shr_sig (%sI32 : !llhd.sig<i32>) -> () {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[AMNT0:.*]] = llvm.mlir.constant(8 : i32) : !llvm.i32
%0 = llhd.const 8 : i32
// CHECK-NEXT: %[[CALL0:.*]] = llvm.call @probe_signal(%[[STATE]], %[[L0]]) : (!llvm<"i8*">, !llvm.i32) -> !llvm<"{ i8*, i64 }*">
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C0]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i8**">
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C1]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i64*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i8**">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"i64*">
// CHECK-NEXT: %[[ZEXT0:.*]] = llvm.zext %[[AMNT0]] : !llvm.i32 to !llvm.i64
// CHECK-NEXT: %[[IDX1:.*]] = llvm.add %[[L2]], %[[ZEXT0]] : !llvm.i64
// CHECK-NEXT: %[[PTRTOINT0:.*]] = llvm.ptrtoint %[[L1]] : !llvm<"i8*"> to !llvm.i64
// CHECK-NEXT: %[[C2:.*]] = llvm.mlir.constant(8 : i64) : !llvm.i64
// CHECK-NEXT: %[[PTROFFSET0:.*]] = llvm.udiv %[[IDX1]], %[[C2]] : !llvm.i64
// CHECK-NEXT: %[[ADD0:.*]] = llvm.add %[[PTRTOINT0]], %[[PTROFFSET0]] : !llvm.i64
// CHECK-NEXT: %[[INTTTOPTR0:.*]] = llvm.inttoptr %[[ADD0]] : !llvm.i64 to !llvm<"i8*">
// CHECK-NEXT: %[[BYTEOFFSET0:.*]] = llvm.urem %[[IDX1]], %[[C2]] : !llvm.i64
// CHECK-NEXT: %[[LEN0:.*]] = llvm.mlir.constant(32 : i64) : !llvm.i64
// CHECK-NEXT: %{{.*}} = llvm.call @add_subsignal(%[[STATE]], %[[L0]], %[[INTTTOPTR0]], %[[LEN0]], %[[BYTEOFFSET0]]) : (!llvm<"i8*">, !llvm.i32, !llvm<"i8*">, !llvm.i64, !llvm.i64) -> !llvm.i32
%1 = llhd.shr %sI32, %sI32, %0 : (!llhd.sig<i32>, !llhd.sig<i32>, i32) -> !llhd.sig<i32>
}

View File

@ -0,0 +1,33 @@
//RUN: circt-opt %s --convert-llhd-to-llvm --split-input-file | FileCheck %s
// CHECK: llvm.func @convert_empty(%{{.*}}: !llvm<"i8*">, %{{.*}}: !llvm<"i32*">, %{{.*}}: !llvm<"i32*">) {
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.entity @convert_empty () -> () {}
// CHECK: llvm.func @convert_one_input(%{{.*}}: !llvm<"i8*">, %{{.*}}: !llvm<"i32*">, %[[ARGTABLE:.*]]: !llvm<"i32*">) {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %{{.*}} = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.entity @convert_one_input (%in0 : !llhd.sig<i1>) -> () {}
// CHECK: llvm.func @convert_one_output(%{{.*}}: !llvm<"i8*">, %{{.*}}: !llvm<"i32*">, %[[ARGTABLE:.*]]: !llvm<"i32*">) {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %{{.*}} = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.entity @convert_one_output () -> (%out0 : !llhd.sig<i1>) {}
// CHECK: llvm.func @convert_input_and_output(%{{.*}}: !llvm<"i8*">, %{{.*}}: !llvm<"i32*">, %[[ARGTABLE:.*]]: !llvm<"i32*">) {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %{{.*}} = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[IDX1:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[IDX1]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %{{.*}} = llvm.load %[[GEP1]] : !llvm<"i32*">
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.entity @convert_input_and_output (%in0 : !llhd.sig<i1>) -> (%out0 : !llhd.sig<i1>) {}

View File

@ -0,0 +1,135 @@
//RUN: circt-opt %s --convert-llhd-to-llvm --split-input-file | FileCheck %s
// CHECK-LABEL: @dummy_i1
func @dummy_i1 (%0 : i1) {
return
}
// CHECK-LABEL: @dummy_i32
func @dummy_i32 (%0 : i32) {
return
}
// CHECK-LABEL: @convert_persistent_value
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">
// CHECK-SAME: %[[PROCSTATE:.*]]: !llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">
// CHECK-SAME: %[[ARGTABLE:.*]]: !llvm<"i32*">
// CHECK-NEXT: %[[GIND1:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[GIND1]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[GIND2:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[GIND2]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i32*">
// CHECK-NEXT: %[[GIND3:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND4:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND3]], %[[GIND4]]] : (!llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">, !llvm.i32, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"i32*">
// CHECK-NEXT: llvm.br ^[[BB0:.*]]
// CHECK-NEXT: ^[[BB0]]:
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[CMP0:.*]] = llvm.icmp "eq" %[[L2]], %[[C0]] : !llvm.i32
// CHECK-NEXT: llvm.cond_br %[[CMP0]], ^[[BB1:.*]], ^[[BB2:.*]]
// CHECK-NEXT: ^[[BB1]]:
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(false) : !llvm.i1
// CHECK-NEXT: %[[GIND5:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND6:.*]] = llvm.mlir.constant(3 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND7:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP3:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND5]], %[[GIND6]], %[[GIND7]]] : (!llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: llvm.store %[[C1]], %[[GEP3]] : !llvm<"i1*">
// CHECK-NEXT: %[[C2:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND8:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND9:.*]] = llvm.mlir.constant(3 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND10:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP4:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND8]], %[[GIND9]], %[[GIND10]]] : (!llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: llvm.store %[[C2]], %[[GEP4]] : !llvm<"i32*">
// CHECK-NEXT: llvm.br ^[[BB3:.*]]
// CHECK-NEXT: ^[[BB3]]:
// CHECK-NEXT: %[[GIND11:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND12:.*]] = llvm.mlir.constant(3 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND13:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP5:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND11]], %[[GIND12]], %[[GIND13]]] : (!llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L3:.*]] = llvm.load %[[GEP5]] : !llvm<"i32*">
// CHECK-NEXT: %[[GIND14:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND15:.*]] = llvm.mlir.constant(3 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND16:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP6:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND14]], %[[GIND15]], %[[GIND16]]] : (!llvm<"{ i8*, i32, [0 x i1]*, { i1, i32 } }*">, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: %[[L4:.*]] = llvm.load %[[GEP6]] : !llvm<"i1*">
// CHECK-NEXT: llvm.call @dummy_i1(%[[L4]]) : (!llvm.i1) -> ()
// CHECK-NEXT: llvm.call @dummy_i32(%[[L3]]) : (!llvm.i32) -> ()
// CHECK-NEXT: llvm.br ^[[BB3]]
// CHECK-NEXT: ^[[BB2]]:
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.proc @convert_persistent_value () -> (%out0 : !llhd.sig<i1>, %out1 : !llhd.sig<i32>) {
br ^entry
^entry:
%0 = llhd.const 0 : i1
%1 = llhd.const 0 : i32
br ^resume
^resume:
%t = llhd.const #llhd.time<0ns, 0d, 1e> : !llhd.time
call @dummy_i1(%0) : (i1) -> ()
call @dummy_i32(%1) : (i32) -> ()
br ^resume
}
// CHECK-LABEL: @convert_resume
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">
// CHECK-SAME: %[[PROCSTATE:.*]]: !llvm<"{ i8*, i32, [1 x i1]*, {} }*">
// CHECK-SAME: %[[ARGTABLE:.*]]: !llvm<"i32*">
// CHECK-NEXT: %[[GIND1:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTABLE]][%[[GIND1]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[GIND2:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND3:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND2]], %[[GIND3]]] : (!llvm<"{ i8*, i32, [1 x i1]*, {} }*">, !llvm.i32, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i32*">
// CHECK-NEXT: llvm.br ^[[BB0:.*]]
// CHECK-NEXT: ^[[BB0]]:
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[CMP0:.*]] = llvm.icmp "eq" %[[L1]], %[[C0]] : !llvm.i32
// CHECK-NEXT: llvm.cond_br %[[CMP0]], ^[[BB1:.*]], ^[[BB2:.*]]
// CHECK-NEXT: ^[[BB2]]:
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[CMP1:.*]] = llvm.icmp "eq" %[[L1]], %[[C1]] : !llvm.i32
// CHECK-NEXT: llvm.cond_br %[[CMP1]], ^[[BB3:.*]], ^[[BB4:.*]]
// CHECK-NEXT: ^[[BB3]]:
// CHECK-NEXT: %[[GIND4:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND5:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND6:.*]] = llvm.mlir.constant(2 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND4]], %[[GIND6]]] : (!llvm<"{ i8*, i32, [1 x i1]*, {} }*">, !llvm.i32, !llvm.i32) -> !llvm<"[1 x i1]**">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"[1 x i1]**">
// CHECK-NEXT: %[[GIND7:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C2:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i1
// CHECK-NEXT: %[[GEP3:.*]] = llvm.getelementptr %[[L2]][%[[GIND4]], %[[GIND7]]] : (!llvm<"[1 x i1]*">, !llvm.i32, !llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: llvm.store %[[C2]], %[[GEP3]] : !llvm<"i1*">
// CHECK-NEXT: %[[T0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[T1:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[T2:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[BC0:.*]] = llvm.bitcast %[[PROCSTATE]] : !llvm<"{ i8*, i32, [1 x i1]*, {} }*"> to !llvm<"i8*">
// CHECK-NEXT: %[[C3:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP4:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND4]], %[[GIND5]]] : (!llvm<"{ i8*, i32, [1 x i1]*, {} }*">, !llvm.i32, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: llvm.store %[[C3]], %[[GEP4]] : !llvm<"i32*">
// CHECK-NEXT: %{{.*}} = llvm.call @llhd_suspend(%[[STATE]], %[[BC0]], %[[T0]], %[[T1]], %[[T2]]) : (!llvm<"i8*">, !llvm<"i8*">, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm.void
// CHECK-NEXT: llvm.return
// CHECK-NEXT: ^[[BB1]]:
// CHECK-NEXT: %[[GIND8:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GIND9:.*]] = llvm.mlir.constant(2 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP5:.*]] = llvm.getelementptr %[[PROCSTATE]][%[[GIND8]], %[[GIND9]]] : (!llvm<"{ i8*, i32, [1 x i1]*, {} }*">, !llvm.i32, !llvm.i32) -> !llvm<"[1 x i1]**">
// CHECK-NEXT: %[[L3:.*]] = llvm.load %[[GEP5]] : !llvm<"[1 x i1]**">
// CHECK-NEXT: %[[GIND10:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C4:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i1
// CHECK-NEXT: %[[GEP6:.*]] = llvm.getelementptr %[[L3]][%[[GIND8]], %[[GIND10]]] : (!llvm<"[1 x i1]*">, !llvm.i32, !llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: llvm.store %[[C4]], %[[GEP6]] : !llvm<"i1*">
// CHECK-NEXT: llvm.return
// CHECK-NEXT: ^[[BB4]]:
// CHECK-NEXT: llvm.return
// CHECK-NEXT: }
llhd.proc @convert_resume (%in0 : !llhd.sig<i1>) -> () {
br ^entry
^entry:
%t = llhd.const #llhd.time<0ns, 0d, 1e> : !llhd.time
llhd.wait for %t, ^resume
^resume:
llhd.halt
}

View File

@ -0,0 +1,61 @@
//RUN: circt-opt %s --convert-llhd-to-llvm | FileCheck %s
// CHECK-LABEL: @convert_sig
// CHECK-SAME: %{{.*}}: !llvm<"i8*">,
// CHECK-SAME: %[[SIGTAB:.*]]: !llvm<"i32*">,
// CHECK-SAME: %{{.*}}: !llvm<"i32*">
llhd.entity @convert_sig () -> () {
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(false) : !llvm.i1
%init = llhd.const 0 : i1
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[SIGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
%s0 = llhd.sig "sig0" %init : i1
}
// CHECK-LABEL: @convert_prb
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">,
// CHECK-SAME:{{.*}}: !llvm<"i32*">,
// CHECK-SAME: %[[ARGTAB:.*]]: !llvm<"i32*">
llhd.entity @convert_prb (%sI1 : !llhd.sig<i1>) -> () {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[CALL0:.*]] = llvm.call @probe_signal(%[[STATE]], %[[L0]]) : (!llvm<"i8*">, !llvm.i32) -> !llvm<"{ i8*, i64 }*">
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C0]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i8**">
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C1]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i64*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i8**">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"i64*">
// CHECK-NEXT: %[[BC0:.*]] = llvm.bitcast %[[L1]] : !llvm<"i8*"> to !llvm<"i16*">
// CHECK-NEXT: %[[L3:.*]] = llvm.load %[[BC0]] : !llvm<"i16*">
// CHECK-NEXT: %[[TRUNC0:.*]] = llvm.trunc %[[L2]] : !llvm.i64 to !llvm.i16
// CHECK-NEXT: %[[SHR:.*]] = llvm.lshr %[[L3]], %[[TRUNC0]] : !llvm.i16
// CHECK-NEXT: %{{.*}} = llvm.trunc %[[SHR]] : !llvm.i16 to !llvm.i1
%p0 = llhd.prb %sI1 : !llhd.sig<i1>
}
// CHECK-LABEL: @convert_drv
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">,
// CHECK-SAME:{{.*}}: !llvm<"i32*">,
// CHECK-SAME: %[[ARGTAB:.*]]: !llvm<"i32*">
llhd.entity @convert_drv (%sI1 : !llhd.sig<i1>) -> () {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(false) : !llvm.i1
%cI1 = llhd.const 0 : i1
// CHECK-NEXT: %[[WIDTH0:.*]] = llvm.mlir.constant(1 : i64) : !llvm.i64
// CHECK-NEXT: %[[S0:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[A0:.*]] = llvm.alloca %[[S0]] x !llvm.i1 {alignment = 4 : i64} : (!llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: llvm.store %[[C0]], %[[A0]] : !llvm<"i1*">
// CHECK-NEXT: %[[BC0:.*]] = llvm.bitcast %[[A0]] : !llvm<"i1*"> to !llvm<"i8*">
// CHECK-NEXT: %[[T0:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[T1:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[T2:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %{{.*}} = llvm.call @drive_signal(%[[STATE]], %[[L0]], %[[BC0]], %[[WIDTH0]], %[[T0]], %[[T1]], %[[T2]]) : (!llvm<"i8*">, !llvm.i32, !llvm<"i8*">, !llvm.i64, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm.void
%t = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %sI1, %cI1 after %t : !llhd.sig<i1>
}

View File

@ -0,0 +1,40 @@
//RUN: circt-opt %s --convert-llhd-to-llvm | FileCheck %s
// CHECK: llvm.func @Foo(%[[STATE:.*]]: !llvm<"i8*">, %[[SIGTAB:.*]]: !llvm<"i32*">, %[[ARGTAB:.*]]: !llvm<"i32*">) {
// CHECK-NEXT: %{{.*}} = llvm.mlir.constant(false) : !llvm.i1
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[SIGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[CALL0:.*]] = llvm.call @probe_signal(%[[STATE]], %[[L0]]) : (!llvm<"i8*">, !llvm.i32) -> !llvm<"{ i8*, i64 }*">
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C0]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i8**">
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C1]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i64*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i8**">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"i64*">
// CHECK-NEXT: %[[BC0:.*]] = llvm.bitcast %[[L1]] : !llvm<"i8*"> to !llvm<"i16*">
// CHECK-NEXT: %[[L3:.*]] = llvm.load %[[BC0]] : !llvm<"i16*">
// CHECK-NEXT: %[[TRUNC0:.*]] = llvm.trunc %[[L2]] : !llvm.i64 to !llvm.i16
// CHECK-NEXT: %[[SHR:.*]] = llvm.lshr %[[L3]], %[[TRUNC0]] : !llvm.i16
// CHECK-NEXT: %[[TRUNC1:.*]] = llvm.trunc %[[SHR]] : !llvm.i16 to !llvm.i1
// CHECK-NEXT: %[[C3:.*]] = llvm.mlir.constant(true) : !llvm.i1
// CHECK-NEXT: %[[X1:.*]] = llvm.xor %[[TRUNC1]], %[[C3]] : !llvm.i1
// CHECK-NEXT: %[[SIZE0:.*]] = llvm.mlir.constant(1 : i64) : !llvm.i64
// CHECK-NEXT: %[[ARRSIZE0:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[ALLOCA0:.*]] = llvm.alloca %[[ARRSIZE0]] x !llvm.i1 {alignment = 4 : i64} : (!llvm.i32) -> !llvm<"i1*">
// CHECK-NEXT: llvm.store %[[X1]], %[[ALLOCA0]] : !llvm<"i1*">
// CHECK-NEXT: %[[BC2:.*]] = llvm.bitcast %[[ALLOCA0]] : !llvm<"i1*"> to !llvm<"i8*">
// CHECK-NEXT: %[[TIME:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[DELTA:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[EPS:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[CALL3:.*]] = llvm.call @drive_signal(%[[STATE]], %[[L0]], %[[BC2]], %[[SIZE0:.*]], %[[TIME]], %[[DELTA]], %[[EPS]]) : (!llvm<"i8*">, !llvm.i32, !llvm<"i8*">, !llvm.i64, !llvm.i32, !llvm.i32, !llvm.i32) -> !llvm.void
// CHECK-NEXT: llvm.return
llhd.entity @Foo () -> () {
%0 = llhd.const 0 : i1
%toggle = llhd.sig "toggle" %0 : i1
%1 = llhd.prb %toggle : !llhd.sig<i1>
%2 = llhd.not %1 : i1
%dt = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %toggle, %2 after %dt : !llhd.sig<i1>
}

View File

@ -0,0 +1,66 @@
//RUN: circt-opt %s --convert-llhd-to-llvm | FileCheck %s
// CHECK-LABEL: @convert_const
llvm.func @convert_const() {
// CHECK-NEXT: %{{.*}} = llvm.mlir.constant(true) : !llvm.i1
%0 = llhd.const 1 : i1
// CHECK-NEXT %{{.*}} = llvm.mlir.constant(0 : i32) : !llvm.i32
%1 = llhd.const 0 : i32
// this gets erased
%2 = llhd.const #llhd.time<0ns, 0d, 0e> : !llhd.time
// CHECK-NEXT %{{.*}} = llvm.mlir.constant(123 : i64) : !llvm.i64
%3 = llhd.const 123 : i64
llvm.return
}
// CHECK-LABEL: @convert_exts_int
// CHECK-SAME: %[[CI32:.*]]: !llvm.i32
// CHECK-SAME: %[[CI100:.*]]: !llvm.i100
func @convert_exts_int(%cI32 : i32, %cI100 : i100) {
// CHECK-NEXT: %[[CIND0:.*]] = llvm.mlir.constant(0 : index) : !llvm.i64
// CHECK-NEXT: %[[CIND1:.*]] = llvm.mlir.constant(10 : index) : !llvm.i64
// CHECK-NEXT: %[[ADJUST0:.*]] = llvm.trunc %[[CIND0]] : !llvm.i64 to !llvm.i32
// CHECK-NEXT: %[[SHR0:.*]] = llvm.lshr %[[CI32]], %[[ADJUST0]] : !llvm.i32
// CHECK-NEXT: %{{.*}} = llvm.trunc %[[SHR0]] : !llvm.i32 to !llvm.i10
%0 = llhd.exts %cI32, 0 : i32 -> i10
// CHECK-NEXT: %[[CIND2:.*]] = llvm.mlir.constant(0 : index) : !llvm.i64
// CHECK-NEXT: %[[CIND3:.*]] = llvm.mlir.constant(10 : index) : !llvm.i64
// CHECK-NEXT: %[[ADJUST1:.*]] = llvm.zext %[[CIND2]] : !llvm.i64 to !llvm.i100
// CHECK-NEXT: %[[SHR1:.*]] = llvm.lshr %[[CI100]], %[[ADJUST1]] : !llvm.i100
// CHECK-NEXT: %{{.*}} = llvm.trunc %[[SHR1]] : !llvm.i100 to !llvm.i10
%2 = llhd.exts %cI100, 0 : i100 -> i10
return
}
// CHECK-LABEL: @convert_exts_sig
// CHECK-SAME: %[[STATE:.*]]: !llvm<"i8*">,
// CHECK-SAME: %[[SIGTAB:.*]]: !llvm<"i32*">,
// CHECK-SAME: %[[ARGTAB:.*]]: !llvm<"i32*">
llhd.entity @convert_exts_sig (%sI32 : !llhd.sig<i32>) -> () {
// CHECK-NEXT: %[[IDX0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP0:.*]] = llvm.getelementptr %[[ARGTAB]][%[[IDX0]]] : (!llvm<"i32*">, !llvm.i32) -> !llvm<"i32*">
// CHECK-NEXT: %[[L0:.*]] = llvm.load %[[GEP0]] : !llvm<"i32*">
// CHECK-NEXT: %[[IDX1:.*]] = llvm.mlir.constant(0 : index) : !llvm.i64
// CHECK-NEXT: %[[LEN0:.*]] = llvm.mlir.constant(10 : index) : !llvm.i64
// CHECK-NEXT: %[[CALL0:.*]] = llvm.call @probe_signal(%[[STATE]], %[[L0]]) : (!llvm<"i8*">, !llvm.i32) -> !llvm<"{ i8*, i64 }*">
// CHECK-NEXT: %[[C0:.*]] = llvm.mlir.constant(0 : i32) : !llvm.i32
// CHECK-NEXT: %[[C1:.*]] = llvm.mlir.constant(1 : i32) : !llvm.i32
// CHECK-NEXT: %[[GEP1:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C0]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i8**">
// CHECK-NEXT: %[[GEP2:.*]] = llvm.getelementptr %[[CALL0]][%[[C0]], %[[C1]]] : (!llvm<"{ i8*, i64 }*">, !llvm.i32, !llvm.i32) -> !llvm<"i64*">
// CHECK-NEXT: %[[L1:.*]] = llvm.load %[[GEP1]] : !llvm<"i8**">
// CHECK-NEXT: %[[L2:.*]] = llvm.load %[[GEP2]] : !llvm<"i64*">
// CHECK-NEXT: %[[IDX2:.*]] = llvm.add %[[L2]], %[[IDX1]] : !llvm.i64
// CHECK-NEXT: %[[PTRTOINT0:.*]] = llvm.ptrtoint %[[L1]] : !llvm<"i8*"> to !llvm.i64
// CHECK-NEXT: %[[C2:.*]] = llvm.mlir.constant(8 : i64) : !llvm.i64
// CHECK-NEXT: %[[PTROFFSET0:.*]] = llvm.udiv %[[IDX2]], %[[C2]] : !llvm.i64
// CHECK-NEXT: %[[ADD0:.*]] = llvm.add %[[PTRTOINT0]], %[[PTROFFSET0]] : !llvm.i64
// CHECK-NEXT: %[[INTTTOPTR0:.*]] = llvm.inttoptr %[[ADD0]] : !llvm.i64 to !llvm<"i8*">
// CHECK-NEXT: %[[BYTEOFFSET0:.*]] = llvm.urem %[[IDX2]], %[[C2]] : !llvm.i64
// CHECK-NEXT: %{{.*}} = llvm.call @add_subsignal(%[[STATE]], %[[L0]], %[[INTTTOPTR0]], %[[LEN0]], %[[BYTEOFFSET0]]) : (!llvm<"i8*">, !llvm.i32, !llvm<"i8*">, !llvm.i64, !llvm.i64) -> !llvm.i32
%0 = llhd.exts %sI32, 0 : !llhd.sig<i32> -> !llhd.sig<i10>
}

View File

@ -0,0 +1,12 @@
//RUN: circt-opt %s | circt-opt | FileCheck %s
// CHECK: func @check_arithmetic(%[[A:.*]]: i64, %[[B:.*]]: i64) {
func @check_arithmetic(%a : i64, %b : i64) -> () {
// CHECK-NEXT: %{{.*}} = llhd.neg %[[A]] : i64
%0 = "llhd.neg"(%a) : (i64) -> i64
// CHECK-NEXT: %{{.*}} = llhd.smod %[[A]], %[[B]] : i64
%2 = "llhd.smod"(%a, %b) : (i64, i64) -> i64
return
}

View File

@ -0,0 +1,30 @@
//RUN: circt-opt %s | circt-opt | FileCheck %s
// CHECK: func @check_bitwise(%[[A:.*]]: i64, %[[B:.*]]: i64, %[[C:.*]]: i8, %[[SIG1:.*]]: !llhd.sig<i32>, %[[SIG2:.*]]: !llhd.sig<i4>) {
func @check_bitwise(%a : i64, %b : i64, %c : i8, %sig1 : !llhd.sig<i32>, %sig2 : !llhd.sig<i4>) -> () {
// CHECK-NEXT: %{{.*}} = llhd.not %[[A]] : i64
%0 = "llhd.not"(%a) : (i64) -> i64
// CHECK-NEXT: %{{.*}} = llhd.and %[[A]], %[[B]] : i64
%1 = "llhd.and"(%a, %b) : (i64, i64) -> i64
// CHECK-NEXT: %{{.*}} = llhd.or %[[A]], %[[B]] : i64
%2 = "llhd.or"(%a, %b) : (i64, i64) -> i64
// CHECK-NEXT: %{{.*}} = llhd.xor %[[A]], %[[B]] : i64
%3 = "llhd.xor"(%a, %b) : (i64, i64) -> i64
// CHECK-NEXT: %{{.*}} = llhd.shl %[[A]], %[[B]], %[[C]] : (i64, i64, i8) -> i64
%4 = "llhd.shl"(%a, %b, %c) : (i64, i64, i8) -> i64
// CHECK-NEXT: %{{.*}} = llhd.shl %[[SIG1]], %[[SIG2]], %[[C]] : (!llhd.sig<i32>, !llhd.sig<i4>, i8) -> !llhd.sig<i32>
%5 = "llhd.shl"(%sig1, %sig2, %c) : (!llhd.sig<i32>, !llhd.sig<i4>, i8) -> !llhd.sig<i32>
// CHECK-NEXT: %{{.*}} = llhd.shr %[[A]], %[[B]], %[[C]] : (i64, i64, i8) -> i64
%6 = "llhd.shr"(%a, %b, %c) : (i64, i64, i8) -> i64
// CHECK-NEXT: %{{.*}} = llhd.shr %[[SIG1]], %[[SIG2]], %[[C]] : (!llhd.sig<i32>, !llhd.sig<i4>, i8) -> !llhd.sig<i32>
%7 = "llhd.shr"(%sig1, %sig2, %c) : (!llhd.sig<i32>, !llhd.sig<i4>, i8) -> !llhd.sig<i32>
return
}

View File

@ -0,0 +1,9 @@
//RUN: circt-opt %s | circt-opt | FileCheck %s
func @lap() {
// CHECK: %{{.*}} = llhd.const 5 : i64
%0 = "llhd.const"() {value = 5 : i64} : () -> i64
// CHECK-NEXT: %{{.*}} = llhd.const #llhd.time<1ns, 2d, 3e> : !llhd.time
%1 = "llhd.const"() {value = #llhd.time<1ns, 2d, 3e> : !llhd.time} : () -> !llhd.time
return
}

View File

@ -0,0 +1,38 @@
// RUN: circt-opt %s | circt-opt | FileCheck %s
// check inputs and outputs, usage
// CHECK: llhd.entity @foo (%[[ARG0:.*]] : !llhd.sig<i64>, %[[ARG1:.*]] : !llhd.sig<i64>) -> (%[[OUT0:.*]] : !llhd.sig<i64>) {
"llhd.entity"() ({
^body(%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i64>, %out0 : !llhd.sig<i64>):
// CHECK-NEXT: %[[C0:.*]] = llhd.const 1
%0 = llhd.const 1 : i64
// CHECK-NEXT: %[[P0:.*]] = llhd.prb %[[ARG0]]
%1 = llhd.prb %arg0 : !llhd.sig<i64>
"llhd.terminator"() {} : () -> ()
// CHECK-NEXT: }
}) {sym_name="foo", ins=2, type=(!llhd.sig<i64>, !llhd.sig<i64>, !llhd.sig<i64>)->()} : () -> ()
// check 0 inputs, empty body
// CHECK-NEXT: llhd.entity @bar () -> (%{{.*}} : !llhd.sig<i64>) {
"llhd.entity"() ({
^body(%0 : !llhd.sig<i64>):
"llhd.terminator"() {} : () -> ()
// CHECK-NEXT: }
}) {sym_name="bar", ins=0, type=(!llhd.sig<i64>)->()} : () -> ()
// check 0 outputs, empty body
// CHECK-NEXT: llhd.entity @baz (%{{.*}} : !llhd.sig<i64>) -> () {
"llhd.entity"() ({
^body(%arg0 : !llhd.sig<i64>):
"llhd.terminator"() {} : () -> ()
// CHECK-NEXT: }
}) {sym_name="baz", ins=1, type=(!llhd.sig<i64>)->()} : () -> ()
//check 0 arguments, empty body
// CHECK-NEXT: llhd.entity @out_of_names () -> () {
"llhd.entity"() ({
^body:
"llhd.terminator"() {} : () -> ()
// CHECK-NEXT : }
}) {sym_name="out_of_names", ins=0, type=()->()} : () -> ()

View File

@ -0,0 +1,121 @@
// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s
// CHECK-LABEL: @exts_integers
// CHECK-SAME: %[[CI1:.*]]: i1
// CHECK-SAME: %[[CI32:.*]]: i32
func @exts_integers(%cI1 : i1, %cI32 : i32) {
// CHECK-NEXT: %{{.*}} = llhd.exts %[[CI1]], 0 : i1 -> i1
%0 = llhd.exts %cI1, 0 : i1 -> i1
// CHECK-NEXT: %{{.*}} = llhd.exts %[[CI32]], 0 : i32 -> i5
%1 = llhd.exts %cI32, 0 : i32 -> i5
return
}
// CHECK-LABEL: @exts_signals
// CHECK-SAME: %[[SI1:.*]]: !llhd.sig<i1>
// CHECK-SAME: %[[SI32:.*]]: !llhd.sig<i32>
func @exts_signals (%sI1 : !llhd.sig<i1>, %sI32 : !llhd.sig<i32>) -> () {
// CHECK-NEXT: %{{.*}} = llhd.exts %[[SI1]], 0 : !llhd.sig<i1> -> !llhd.sig<i1>
%0 = llhd.exts %sI1, 0 : !llhd.sig<i1> -> !llhd.sig<i1>
// CHECK-NEXT: %{{.*}} = llhd.exts %[[SI32]], 0 : !llhd.sig<i32> -> !llhd.sig<i5>
%1 = llhd.exts %sI32, 0 : !llhd.sig<i32> -> !llhd.sig<i5>
return
}
// CHECK-LABEL: @dexts_integers
// CHECK-SAME: %[[CI1:.*]]: i1,
// CHECK-SAME: %[[CI32:.*]]: i32,
// CHECK-SAME: %[[IND0:.*]]: i5,
// CHECK-SAME: %[[IND1:.*]]: i10
func @dexts_integers(%cI1 : i1, %cI32 : i32, %i0 : i5, %i1 : i10) {
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[CI1]], %[[IND0]] : (i1, i5) -> i1
%0 = llhd.dexts %cI1, %i0 : (i1, i5) -> i1
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[CI32]], %[[IND1]] : (i32, i10) -> i15
%1 = llhd.dexts %cI32, %i1 : (i32, i10) -> i15
return
}
// CHECK-LABEL: @dexts_signals
// CHECK-SAME: %[[SI1:.*]]: !llhd.sig<i1>,
// CHECK-SAME: %[[SI32:.*]]: !llhd.sig<i32>,
// CHECK-SAME: %[[IND0:.*]]: i5,
// CHECK-SAME: %[[IND1:.*]]: i10
func @dexts_signals (%sI1 : !llhd.sig<i1>, %sI32 : !llhd.sig<i32>, %i0 : i5, %i1 : i10) {
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[SI1]], %[[IND0]] : (!llhd.sig<i1>, i5) -> !llhd.sig<i1>
%0 = llhd.dexts %sI1, %i0 : (!llhd.sig<i1>, i5) -> !llhd.sig<i1>
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[SI32]], %[[IND1]] : (!llhd.sig<i32>, i10) -> !llhd.sig<i5>
%1 = llhd.dexts %sI32, %i1 : (!llhd.sig<i32>, i10) -> !llhd.sig<i5>
return
}
// CHECK-LABEL: @dexts_vec
// CHECK-SAME: %[[V1:.*]]: vector<1xi1>,
// CHECK-SAME: %[[V10:.*]]: vector<10xi1>,
// CHECK-SAME: %[[IND0:.*]]: i5,
// CHECK-SAME: %[[IND1:.*]]: i10
func @dexts_vec(%v1 : vector<1xi1>, %v10 : vector<10xi1>, %i0 : i5, %i1 : i10) {
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[V1]], %[[IND0]] : (vector<1xi1>, i5) -> vector<1xi1>
%0 = llhd.dexts %v1, %i0 : (vector<1xi1>, i5) -> vector<1xi1>
// CHECK-NEXT: %{{.*}} = llhd.dexts %[[V10]], %[[IND1]] : (vector<10xi1>, i10) -> vector<5xi1>
%1 = llhd.dexts %v10, %i1 : (vector<10xi1>, i10) -> vector<5xi1>
return
}
// -----
func @illegal_int_to_sig(%c : i32) {
// expected-error @+1 {{failed to verify that 'target' and 'result' have to be both either signless integers, signals or vectors with the same element type}}
%0 = llhd.exts %c, 0 : i32 -> !llhd.sig<i10>
return
}
// -----
func @illegal_sig_to_int(%s : !llhd.sig<i32>) {
// expected-error @+1 {{failed to verify that 'target' and 'result' have to be both either signless integers, signals or vectors with the same element type}}
%0 = llhd.exts %s, 0 : !llhd.sig<i32> -> i10
return
}
// -----
func @illegal_out_too_big(%c : i32) {
// expected-error @+1 {{failed to verify that 'start' + size of the slice have to be smaller or equal to the 'target' size}}
%0 = llhd.exts %c, 0 : i32 -> i40
return
}
// -----
func @dexts_illegal_conversion(%s : !llhd.sig<i32>, %i : i1) {
// expected-error @+1 {{'llhd.dexts' op failed to verify that 'target' and 'result' types have to match apart from their width}}
%0 = llhd.dexts %s, %i : (!llhd.sig<i32>, i1) -> i10
return
}
// -----
func @dexts_illegal_out_too_wide(%c : i32, %i : i1) {
// expected-error @+1 {{'llhd.dexts' op failed to verify that the result width cannot be larger than the target operand width}}
%0 = llhd.dexts %c, %i : (i32, i1) -> i40
return
}
// -----
func @dexts_illegal_vec_element_conversion(%c : vector<1xi1>, %i : i1) {
// expected-error @+1 {{'llhd.dexts' op failed to verify that 'target' and 'result' types have to match apart from their width}}
%0 = llhd.dexts %c, %i : (vector<1xi1>, i1) -> vector<1xi10>
return
}

View File

@ -0,0 +1,115 @@
// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s
// CHECK-LABEL: @inss_integers
// CHECK-SAME: %[[CI1:.*]]: i1
// CHECK-SAME: %[[CI32:.*]]: i32
func @inss_integers(%cI1 : i1, %cI32 : i32) {
// CHECK-NEXT: %{{.*}} = llhd.inss %[[CI1]], %[[CI1]], 0 : i1, i1
%0 = llhd.inss %cI1, %cI1, 0 : i1, i1
// CHECK-NEXT: %{{.*}} = llhd.inss %[[CI32]], %[[CI1]], 31 : i32, i1
%1 = llhd.inss %cI32, %cI1, 31 : i32, i1
return
}
// CHECK-LABEL: @inss_vectors
// CHECK-SAME: %[[VEC2:.*]]: vector<2xi1>
// CHECK-SAME: %[[VEC5:.*]]: vector<5xi1>
func @inss_vectors(%vec2 : vector<2xi1>, %vec5 : vector<5xi1>) -> () {
// CHECK-NEXT: %{{.*}} = llhd.inss %[[VEC5]], %[[VEC2]], 3 : vector<5xi1>, vector<2xi1>
%0 = llhd.inss %vec5, %vec2, 3 : vector<5xi1>, vector<2xi1>
// CHECK-NEXT: %{{.*}} = llhd.inss %[[VEC2]], %[[VEC2]], 0 : vector<2xi1>, vector<2xi1>
%1 = llhd.inss %vec2, %vec2, 0 : vector<2xi1>, vector<2xi1>
return
}
// CHECK-LABEL: @insf_tuples
// CHECK-SAME: %[[TUP:.*]]: tuple<i1, i8>,
// CHECK-SAME: %[[I1:.*]]: i1,
// CHECK-SAME: %[[I8:.*]]: i8
func @insf_tuples(%tup : tuple<i1, i8>, %i1 : i1, %i8 : i8) {
// CHECK-NEXT: %{{.*}} = llhd.insf %[[TUP]], %[[I1]], 0 : tuple<i1, i8>, i1
%0 = llhd.insf %tup, %i1, 0 : tuple<i1, i8>, i1
// CHECK-NEXT: %{{.*}} = llhd.insf %[[TUP]], %[[I8]], 1 : tuple<i1, i8>, i8
%1 = llhd.insf %tup, %i8, 1 : tuple<i1, i8>, i8
return
}
// CHECK-LABEL: @insf_vectors
// CHECK-SAME: %[[V1:.*]]: vector<4xi1>,
// CHECK-SAME: %[[V8:.*]]: vector<4xi8>,
// CHECK-SAME: %[[I1:.*]]: i1,
// CHECK-SAME: %[[I8:.*]]: i8
func @insf_vectors(%v1 : vector<4xi1>, %v8 : vector<4xi8>, %i1 : i1, %i8 : i8) {
// CHECK-NEXT: %{{.*}} = llhd.insf %[[V1]], %[[I1]], 0 : vector<4xi1>, i1
%0 = llhd.insf %v1, %i1, 0 : vector<4xi1>, i1
// CHECK-NEXT: %{{.*}} = llhd.insf %[[V8]], %[[I8]], 2 : vector<4xi8>, i8
%1 = llhd.insf %v8, %i8, 2 : vector<4xi8>, i8
return
}
// -----
func @illegal_kind(%c : i32, %vec : vector<2xi32>) {
// expected-error @+1 {{failed to verify that 'target' and 'slice' have to be both either signless integers or vectors with the same element type}}
%0 = llhd.inss %vec, %c, 0 : vector<2xi32>, i32
return
}
// -----
func @illegal_elemental_type(%slice : vector<1xi1>, %vec : vector<2xi32>) {
// expected-error @+1 {{failed to verify that 'target' and 'slice' have to be both either signless integers or vectors with the same element type}}
%0 = llhd.inss %vec, %slice, 0 : vector<2xi32>, vector<1xi1>
return
}
// -----
func @inss_illegal_start_index_int(%slice : i16, %c : i32) {
// expected-error @+1 {{failed to verify that 'start' + size of the 'slice' have to be smaller or equal to the 'target' size}}
%0 = llhd.inss %c, %slice, 20 : i32, i16
return
}
// -----
func @inss_illegal_start_index_vector(%slice : vector<2xi1>, %vec : vector<3xi1>) {
// expected-error @+1 {{failed to verify that 'start' + size of the 'slice' have to be smaller or equal to the 'target' size}}
%0 = llhd.inss %vec, %slice, 2 : vector<3xi1>, vector<2xi1>
return
}
// -----
func @insf_index_out_of_bounds(%e : i1, %vec : vector<3xi1>) {
// expected-error @+1 {{failed to verify that 'index' has to be smaller than the 'target' size}}
%0 = llhd.insf %vec, %e, 3 : vector<3xi1>, i1
return
}
// -----
func @insf_type_mismatch_vector(%e : i2, %vec : vector<3xi1>) {
// expected-error @+1 {{failed to verify that 'element' type has to match type at 'index' of 'target'}}
%0 = llhd.insf %vec, %e, 0 : vector<3xi1>, i2
return
}
// -----
func @insf_type_mismatch_tuple(%e : i2, %tup : tuple<i2, i1, i2>) {
// expected-error @+1 {{failed to verify that 'element' type has to match type at 'index' of 'target'}}
%0 = llhd.insf %tup, %e, 1 : tuple<i2, i1, i2>, i2
return
}

View File

@ -0,0 +1,99 @@
//RUN: circt-opt %s -split-input-file -verify-diagnostics | circt-opt | FileCheck %s
// Testing Objectives:
// * inst can only be used in entities
// * inst must always refer to a valid proc or entity (match symbol name, input and output operands)
// * syntax: no inputs and outputs, one input zero outputs, zero inputs one output, multiple inputs and outputs
// * check that number of inputs and number of outputs are verified separately
// CHECK-LABEL: @empty_entity
llhd.entity @empty_entity() -> () {}
// CHECK-LABEL: @one_input_entity
llhd.entity @one_input_entity(%arg : !llhd.sig<i32>) -> () {}
// CHECK-LABEL: @one_output_entity
llhd.entity @one_output_entity() -> (%arg : !llhd.sig<i32>) {}
// CHECK-LABEL: @entity
llhd.entity @entity(%arg0 : !llhd.sig<i32>, %arg1 : !llhd.sig<i16>) -> (%out0 : !llhd.sig<i8>, %out1 : !llhd.sig<i4>) {}
// CHECK-LABEL: @empty_proc
llhd.proc @empty_proc() -> () {
llhd.halt
}
// CHECK-LABEL: @one_input_proc
llhd.proc @one_input_proc(%arg : !llhd.sig<i32>) -> () {
llhd.halt
}
// CHECK-LABEL: @one_output_proc
llhd.proc @one_output_proc() -> (%arg : !llhd.sig<i32>) {
llhd.halt
}
// CHECK-LABEL: @proc
llhd.proc @proc(%arg0 : !llhd.sig<i32>, %arg1 : !llhd.sig<i16>) -> (%out0 : !llhd.sig<i8>, %out1 : !llhd.sig<i4>) {
llhd.halt
}
// CHECK: llhd.entity @caller (%[[ARG0:.*]] : !llhd.sig<i32>, %[[ARG1:.*]] : !llhd.sig<i16>) -> (%[[OUT0:.*]] : !llhd.sig<i8>, %[[OUT1:.*]] : !llhd.sig<i4>) {
llhd.entity @caller(%arg0 : !llhd.sig<i32>, %arg1 : !llhd.sig<i16>) -> (%out0 : !llhd.sig<i8>, %out1 : !llhd.sig<i4>) {
// CHECK-NEXT: llhd.inst "empty_entity" @empty_entity() -> () : () -> ()
"llhd.inst"() {callee=@empty_entity, operand_segment_sizes=dense<[0,0]> : vector<2xi32>, name="empty_entity"} : () -> ()
// CHECK-NEXT: llhd.inst "empty_proc" @empty_proc() -> () : () -> ()
"llhd.inst"() {callee=@empty_proc, operand_segment_sizes=dense<[0,0]> : vector<2xi32>, name="empty_proc"} : () -> ()
// CHECK-NEXT: llhd.inst "one_in_entity" @one_input_entity(%[[ARG0]]) -> () : (!llhd.sig<i32>) -> ()
"llhd.inst"(%arg0) {callee=@one_input_entity, operand_segment_sizes=dense<[1,0]> : vector<2xi32>, name="one_in_entity"} : (!llhd.sig<i32>) -> ()
// CHECK-NEXT: llhd.inst "one_in_proc" @one_input_proc(%[[ARG0]]) -> () : (!llhd.sig<i32>) -> ()
"llhd.inst"(%arg0) {callee=@one_input_proc, operand_segment_sizes=dense<[1,0]> : vector<2xi32>, name="one_in_proc"} : (!llhd.sig<i32>) -> ()
// CHECK-NEXT: llhd.inst "one_out_entity" @one_output_entity() -> (%[[ARG0]]) : () -> !llhd.sig<i32>
"llhd.inst"(%arg0) {callee=@one_output_entity, operand_segment_sizes=dense<[0,1]> : vector<2xi32>, name="one_out_entity"} : (!llhd.sig<i32>) -> ()
// CHECK-NEXT: llhd.inst "one_out_proc" @one_output_proc() -> (%[[ARG0]]) : () -> !llhd.sig<i32>
"llhd.inst"(%arg0) {callee=@one_output_proc, operand_segment_sizes=dense<[0,1]> : vector<2xi32>, name="one_out_proc"} : (!llhd.sig<i32>) -> ()
// CHECK-NEXT: llhd.inst "entity" @entity(%[[ARG0]], %[[ARG1]]) -> (%[[OUT0]], %[[OUT1]]) : (!llhd.sig<i32>, !llhd.sig<i16>) -> (!llhd.sig<i8>, !llhd.sig<i4>)
"llhd.inst"(%arg0, %arg1, %out0, %out1) {callee=@entity, operand_segment_sizes=dense<[2,2]> : vector<2xi32>, name="entity"} : (!llhd.sig<i32>, !llhd.sig<i16>, !llhd.sig<i8>, !llhd.sig<i4>) -> ()
// CHECK-NEXT: llhd.inst "proc" @proc(%[[ARG0]], %[[ARG1]]) -> (%[[OUT0]], %[[OUT1]]) : (!llhd.sig<i32>, !llhd.sig<i16>) -> (!llhd.sig<i8>, !llhd.sig<i4>)
"llhd.inst"(%arg0, %arg1, %out0, %out1) {callee=@proc, operand_segment_sizes=dense<[2,2]> : vector<2xi32>, name="proc"} : (!llhd.sig<i32>, !llhd.sig<i16>, !llhd.sig<i8>, !llhd.sig<i4>) -> ()
// CHECK-NEXT: }
}
// -----
llhd.proc @empty_proc() -> () {
llhd.halt
}
llhd.proc @fail() -> () {
// expected-error @+1 {{expects parent op 'llhd.entity'}}
llhd.inst "empty" @empty_proc() -> () : () -> ()
llhd.halt
}
// -----
llhd.entity @operand_count_mismatch(%arg : !llhd.sig<i32>) -> () {}
llhd.entity @caller(%arg : !llhd.sig<i32>) -> () {
// expected-error @+1 {{incorrect number of inputs for entity instantiation}}
llhd.inst "mismatch" @operand_count_mismatch() -> (%arg) : () -> (!llhd.sig<i32>)
}
// -----
llhd.entity @caller() -> () {
// expected-error @+1 {{does not reference a valid proc or entity}}
llhd.inst "does_not_exist" @does_not_exist() -> () : () -> ()
}
// -----
llhd.entity @empty() -> () {}
llhd.entity @test_uniqueness() -> () {
llhd.inst "inst" @empty() -> () : () -> ()
// expected-error @+1 {{Redefinition of instance named 'inst'!}}
llhd.inst "inst" @empty() -> () : () -> ()
}

View File

@ -0,0 +1,36 @@
// RUN: circt-opt %s | circt-opt | FileCheck %s
// no inputs and outputs
// CHECK: llhd.proc @empty() -> () {
"llhd.proc"() ({
// CHECK: llhd.halt
// CHECK-NEXT: }
"llhd.halt"() {} : () -> ()
}) {sym_name="empty", ins=0, type=()->()} : () -> ()
// two inputs, one output
// CHECK-NEXT: llhd.proc @inputandoutput(%{{.*}} : !llhd.sig<i64>, %{{.*}} : !llhd.sig<i64>) -> (%{{.*}} : !llhd.sig<i64>) {
"llhd.proc"() ({
// CHECK-NEXT: llhd.halt
// CHECK-NEXT: }
^body(%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i64>, %out0 : !llhd.sig<i64>):
"llhd.halt"() {} : () -> ()
}) {sym_name="inputandoutput", ins=2, type=(!llhd.sig<i64>, !llhd.sig<i64>, !llhd.sig<i64>)->()} : () -> ()
// zero inputs, one output
// CHECK-NEXT: llhd.proc @output() -> (%{{.*}} : !llhd.sig<i64>) {
"llhd.proc"() ({
// CHECK-NEXT: llhd.halt
// CHECK-NEXT: }
^body(%0 : !llhd.sig<i64>):
"llhd.halt"() {} : () -> ()
}) {sym_name="output", ins=0, type=(!llhd.sig<i64>)->()} : () -> ()
// one input, zero outputs
// CHECK-NEXT: llhd.proc @input(%{{.*}} : !llhd.sig<i64>) -> () {
"llhd.proc"() ({
// CHECK-NEXT: llhd.halt
// CHECK-NEXT: }
^body(%arg0 : !llhd.sig<i64>):
"llhd.halt"() {} : () -> ()
}) {sym_name="input", ins=1, type=(!llhd.sig<i64>)->()} : () -> ()

View File

@ -0,0 +1,23 @@
// RUN: circt-opt %s -split-input-file -verify-diagnostics | circt-opt | FileCheck %s
// CHECK-LABEL: @check_reg
// CHECK-SAME: %[[IN64:.*]] : !llhd.sig<i64>
llhd.entity @check_reg (%in64 : !llhd.sig<i64>) -> () {
// CHECK: %[[C1:.*]] = llhd.const
%c1 = llhd.const 0 : i1
// CHECK-NEXT: %[[C64:.*]] = llhd.const
%c64 = llhd.const 0 : i64
// CHECK-NEXT: %[[TIME:.*]] = llhd.const
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
// one trigger with optional gate
// CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64) : !llhd.sig<i64>
"llhd.reg"(%in64, %c64, %c1, %time, %c1) {modes=[0], gateMask=[1], operand_segment_sizes=dense<[1,1,1,1,1]> : vector<5xi32>} : (!llhd.sig<i64>, i64, i1, !llhd.time, i1) -> ()
// two triggers with optional gates
// CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] if %[[C1]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !llhd.sig<i64>) : !llhd.sig<i64>
"llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1, %c1) {modes=[0,1], gateMask=[1,2], operand_segment_sizes=dense<[1,2,2,2,2]> : vector<5xi32>} : (!llhd.sig<i64>, i64, !llhd.sig<i64>, i1, i1, !llhd.time, !llhd.time, i1, i1) -> ()
// two triggers with only one optional gate
// CHECK-NEXT: llhd.reg %[[IN64]], (%[[C64]], "low" %[[C1]] after %[[TIME]] : i64), (%[[IN64]], "high" %[[C1]] after %[[TIME]] if %[[C1]] : !llhd.sig<i64>) : !llhd.sig<i64>
"llhd.reg"(%in64, %c64, %in64, %c1, %c1, %time, %time, %c1) {modes=[0,1], gateMask=[0,1], operand_segment_sizes=dense<[1,2,2,2,1]> : vector<5xi32>} : (!llhd.sig<i64>, i64, !llhd.sig<i64>, i1, i1, !llhd.time, !llhd.time, i1) -> ()
}
// TODO: add verification tests (expected-error tests)

View File

@ -0,0 +1,74 @@
// RUN: circt-opt %s -split-input-file -verify-diagnostics | circt-opt | FileCheck %s
llhd.entity @check_sig_inst () -> () {
// CHECK: %[[CI1:.*]] = llhd.const
%cI1 = llhd.const 0 : i1
// CHECK-NEXT: %{{.*}} = llhd.sig "sigI1" %[[CI1]] : i1
%sigI1 = "llhd.sig"(%cI1) {name = "sigI1"} : (i1) -> !llhd.sig<i1>
// CHECK-NEXT: %[[CI64:.*]] = llhd.const
%cI64 = llhd.const 0 : i64
// CHECK-NEXT: %{{.*}} = llhd.sig "sigI64" %[[CI64]] : i64
%sigI64 = "llhd.sig"(%cI64) {name = "sigI64"} : (i64) -> !llhd.sig<i64>
}
// CHECK-LABEL: check_prb
// CHECK-SAME: %[[SI1:.*]]: !llhd.sig<i1>
// CHECK-SAME: %[[SI64:.*]]: !llhd.sig<i64>
func @check_prb(%sigI1 : !llhd.sig<i1>, %sigI64 : !llhd.sig<i64>) {
// CHECK: %{{.*}} = llhd.prb %[[SI1]] : !llhd.sig<i1>
%0 = "llhd.prb"(%sigI1) {} : (!llhd.sig<i1>) -> i1
// CHECK-NEXT: %{{.*}} = llhd.prb %[[SI64]] : !llhd.sig<i64>
%1 = "llhd.prb"(%sigI64) {} : (!llhd.sig<i64>) -> i64
return
}
// CHECK-LABEL: check_drv
// CHECK-SAME: %[[SI1:.*]]: !llhd.sig<i1>
// CHECK-SAME: %[[SI64:.*]]: !llhd.sig<i64>
// CHECK-SAME: %[[CI1:.*]]: i1
// CHECK-SAME: %[[CI64:.*]]: i64
// CHECK-SAME: %[[TIME:.*]]: !llhd.time
func @check_drv(%sigI1 : !llhd.sig<i1>, %sigI64 : !llhd.sig<i64>, %cI1 : i1, %cI64 : i64, %t : !llhd.time) {
// CHECK-NEXT: llhd.drv %[[SI1]], %[[CI1]] after %[[TIME]] : !llhd.sig<i1>
"llhd.drv"(%sigI1, %cI1, %t) {} : (!llhd.sig<i1>, i1, !llhd.time) -> ()
// CHECK-NEXT: llhd.drv %[[SI64]], %[[CI64]] after %[[TIME]] : !llhd.sig<i64>
"llhd.drv" (%sigI64, %cI64, %t) {} : (!llhd.sig<i64>, i64, !llhd.time) -> ()
// CHECK-NEXT: llhd.drv %[[SI64]], %[[CI64]] after %[[TIME]] if %[[CI1]] : !llhd.sig<i64>
"llhd.drv" (%sigI64, %cI64, %t, %cI1) {} : (!llhd.sig<i64>, i64, !llhd.time, i1) -> ()
return
}
// -----
// expected-error @+3 {{failed to verify that type of 'init' and underlying type of 'signal' have to match.}}
llhd.entity @check_illegal_sig () -> () {
%cI1 = llhd.const 0 : i1
%sig1 = "llhd.sig"(%cI1) {name="foo"} : (i1) -> !llhd.sig<i32>
}
// -----
// expected-error @+2 {{failed to verify that type of 'result' and underlying type of 'signal' have to match.}}
llhd.entity @check_illegal_prb (%sig : !llhd.sig<i1>) -> () {
%prb = "llhd.prb"(%sig) {} : (!llhd.sig<i1>) -> i32
}
// -----
// expected-error @+4 {{failed to verify that type of 'value' and underlying type of 'signal' have to match.}}
llhd.entity @check_illegal_drv (%sig : !llhd.sig<i1>) -> () {
%c = llhd.const 0 : i32
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
"llhd.drv"(%sig, %c, %time) {} : (!llhd.sig<i1>, i32, !llhd.time) -> ()
}
// -----
// expected-error @+4 {{Redefinition of signal named 'sigI1'!}}
llhd.entity @check_unique_sig_names () -> () {
%cI1 = llhd.const 0 : i1
%sig1 = llhd.sig "sigI1" %cI1 : i1
%sig2 = llhd.sig "sigI1" %cI1 : i1
}

View File

@ -0,0 +1,22 @@
// RUN: circt-opt %s -allow-unregistered-dialect | FileCheck %s
func @test_time_type() {
// CHECK: %[[CONST:.*]] = "time_result"() : () -> !llhd.time
%0 = "time_result"() : () -> !llhd.time
// CHECK-NEXT: "time_const_arg"(%[[CONST]]) : (!llhd.time) -> ()
"time_const_arg"(%0) : (!llhd.time) -> ()
return
}
func @test_time_attr() {
"time_attr"() {
// CHECK: time0 = #llhd.time<1ns, 0d, 0e>
time0 = #llhd.time<1ns, 0d, 0e> : !llhd.time,
// CHECK-SAME: time1 = #llhd.time<1ns, 2d, 0e>
time1 = #llhd.time<1ns, 2d, 0e> : !llhd.time,
// CHECK-SAME: time2 = #llhd.time<1ns, 2d, 3e>
time2 = #llhd.time<1ns, 2d, 3e> : !llhd.time,
// CHECK-SAME: time3 = #llhd.time<1ns, 0d, 3e>
time3 = #llhd.time<1ns, 0d, 3e> : !llhd.time
} : () -> ()
}

View File

@ -0,0 +1,22 @@
// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s
// CHECK-LABEL: @check_tuple
// CHECK-SAME: %[[C1:.*]]: i1
// CHECK-SAME: %[[C2:.*]]: i2
// CHECK-SAME: %[[C3:.*]]: i3
// CHECK-SAME: %[[VEC:.*]]: vector<3xi32>
// CHECK-SAME: %[[TUP:.*]]: tuple<i8, i32, i16>
func @check_tuple(%c1 : i1, %c2 : i2, %c3 : i3, %vec : vector<3xi32>, %tup : tuple<i8, i32, i16>) {
// CHECK-NEXT: %{{.*}} = llhd.tuple : tuple<>
%0 = llhd.tuple : tuple<>
// CHECK-NEXT: %{{.*}} = llhd.tuple %[[C1]] : tuple<i1>
%1 = llhd.tuple %c1 : tuple<i1>
// CHECK-NEXT: %{{.*}} = llhd.tuple %[[C1]], %[[C2]], %[[C3]] : tuple<i1, i2, i3>
%2 = llhd.tuple %c1, %c2, %c3 : tuple<i1, i2, i3>
// CHECK-NEXT: %{{.*}} = llhd.tuple %[[C1]], %[[VEC]], %[[TUP]] : tuple<i1, vector<3xi32>, tuple<i8, i32, i16>
%3 = llhd.tuple %c1, %vec, %tup : tuple<i1, vector<3xi32>, tuple<i8, i32, i16>>
return
}
// TODO: add verification tests

View File

@ -0,0 +1,15 @@
// RUN: circt-opt %s -mlir-print-op-generic -split-input-file -verify-diagnostics | circt-opt | circt-opt | FileCheck %s
// CHECK-LABEL: @check_vec
// CHECK-SAME: %[[C1:.*]]: i1, %[[C2:.*]]: i1, %[[C3:.*]]: i1
// CHECK-SAME: %[[C4:.*]]: i32, %[[C5:.*]]: i32, %[[C6:.*]]: i32
func @check_vec(%c1 : i1, %c2 : i1, %c3 : i1, %c4 : i32, %c5 : i32, %c6 : i32) {
// CHECK-NEXT: %{{.*}} = llhd.vec %[[C1]], %[[C2]], %[[C3]] : vector<3xi1>
%0 = llhd.vec %c1, %c2, %c3 : vector<3xi1>
// CHECK-NEXT: %{{.*}} = llhd.vec %[[C4]], %[[C5]], %[[C6]] : vector<3xi32>
%1 = llhd.vec %c4, %c5, %c6 : vector<3xi32>
return
}
// TODO: add more tests

View File

@ -0,0 +1,47 @@
// RUN: circt-opt %s | circt-opt | FileCheck %s
// Test Overview:
// * 0 observed signals, no time, successor without arguments
// * 0 observed signals, with time, sucessor with arguments
// * 2 observed signals, no time, successor with arguments
// * 2 observed signals, with time, successor with arguments
// CHECK-LABEL: @check_wait_0
llhd.proc @check_wait_0 () -> () {
// CHECK: llhd.wait ^[[BB:.*]]
"llhd.wait"() [^bb1] {operand_segment_sizes=dense<[0,0,0]> : vector<3xi32>} : () -> ()
// CHECK-NEXT: ^[[BB]]
^bb1:
llhd.halt
}
// CHECK-LABEL: @check_wait_1
llhd.proc @check_wait_1 () -> () {
// CHECK-NEXT: %[[TIME:.*]] = llhd.const
%time = llhd.const #llhd.time<0ns, 0d, 0e> : !llhd.time
// CHECK-NEXT: llhd.wait for %[[TIME]], ^[[BB:.*]](%[[TIME]] : !llhd.time)
"llhd.wait"(%time, %time) [^bb1] {operand_segment_sizes=dense<[0,1,1]> : vector<3xi32>} : (!llhd.time, !llhd.time) -> ()
// CHECK-NEXT: ^[[BB]](%[[T:.*]]: !llhd.time):
^bb1(%t: !llhd.time):
llhd.halt
}
// CHECK: llhd.proc @check_wait_2(%[[ARG0:.*]] : !llhd.sig<i64>, %[[ARG1:.*]] : !llhd.sig<i1>) -> () {
llhd.proc @check_wait_2 (%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i1>) -> () {
// CHECK-NEXT: llhd.wait (%[[ARG0]], %[[ARG1]] : !llhd.sig<i64>, !llhd.sig<i1>), ^[[BB:.*]](%[[ARG1]] : !llhd.sig<i1>)
"llhd.wait"(%arg0, %arg1, %arg1) [^bb1] {operand_segment_sizes=dense<[2,0,1]> : vector<3xi32>} : (!llhd.sig<i64>, !llhd.sig<i1>, !llhd.sig<i1>) -> ()
// CHECK: ^[[BB]](%[[A:.*]]: !llhd.sig<i1>):
^bb1(%a: !llhd.sig<i1>):
llhd.halt
}
// CHECK: llhd.proc @check_wait_3(%[[ARG0:.*]] : !llhd.sig<i64>, %[[ARG1:.*]] : !llhd.sig<i1>) -> () {
llhd.proc @check_wait_3 (%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i1>) -> () {
// CHECK-NEXT: %[[TIME:.*]] = llhd.const
%time = llhd.const #llhd.time<0ns, 0d, 0e> : !llhd.time
// CHECK-NEXT: llhd.wait for %[[TIME]], (%[[ARG0]], %[[ARG1]] : !llhd.sig<i64>, !llhd.sig<i1>), ^[[BB:.*]](%[[ARG1]], %[[ARG0]] : !llhd.sig<i1>, !llhd.sig<i64>)
"llhd.wait"(%arg0, %arg1, %time, %arg1, %arg0) [^bb1] {operand_segment_sizes=dense<[2,1,2]> : vector<3xi32>} : (!llhd.sig<i64>, !llhd.sig<i1>, !llhd.time, !llhd.sig<i1>, !llhd.sig<i64>) -> ()
// CHECK: ^[[BB]](%[[A:.*]]: !llhd.sig<i1>, %[[B:.*]]: !llhd.sig<i64>):
^bb1(%a: !llhd.sig<i1>, %b: !llhd.sig<i64>):
llhd.halt
}

View File

@ -0,0 +1,165 @@
// RUN: circt-opt %s -canonicalize | FileCheck %s
// This test checks the canonicalization of the LLHD arithmetic and bitwise
// operations. For each operation it checks:
// * changing the order of the operands of commutative operations s.t. the
// constant one is on the right
// * folding patterns
// * result of the folding if all operands are constant
// Furthermore it checks the hoisting of llhd constants to the entry block
// CHECK-LABEL: @check_neg
func @check_neg() -> i32 {
// CHECK-NEXT: %[[C0:.*]] = llhd.const -5 : i32
%0 = llhd.const 5 : i32
%2 = llhd.neg %0 : i32
// CHECK-NEXT: return %[[C0]] : i32
return %2 : i32
}
// CHECK-LABEL: @check_smod
// CHECK-SAME: %[[IN:.*]]: i32
func @check_smod(%in : i32) -> (i32, i32, i32, i32, i32, i32, i32) {
// CHECK-NEXT: %[[FOUR:.*]] = llhd.const 4 : i32
// CHECK-NEXT: %[[ONE:.*]] = llhd.const 1 : i32
// CHECK-NEXT: %[[NEGONE:.*]] = llhd.const -1 : i32
// CHECK-NEXT: %[[NEGFOUR:.*]] = llhd.const -4 : i32
// CHECK-NEXT: %[[ZERO:.*]] = llhd.const 0 : i32
%0 = llhd.const 5 : i32
%1 = llhd.const -5 : i32
%2 = llhd.const 9 : i32
%3 = llhd.const -9 : i32
%4 = llhd.const 1 : i32
%5 = llhd.const 0 : i32
// check correct calculation of smod if lhs and rhs are positive
%6 = llhd.smod %2, %0 : i32
// check correct calculation of smod if lhs is negative and rhs is positive
%7 = llhd.smod %3, %0 : i32
// check correct calculation of smod if lhs is positive and rhs is negative
%8 = llhd.smod %2, %1 : i32
// check correct calculation of smod if lhs and rhs are negative
%9 = llhd.smod %3, %1 : i32
// check correct pattern replacement if rhs is one
%10 = llhd.smod %in, %4 : i32
// check correct pattern replacement if lhs is zero
%11 = llhd.smod %5, %in : i32
// check correct pattern replacement if lhs and rhs are the same register
%12 = llhd.smod %in, %in : i32
// CHECK-NEXT: return %[[FOUR]], %[[ONE]], %[[NEGONE]], %[[NEGFOUR]], %[[ZERO]], %[[ZERO]], %[[ZERO]] : i32, i32, i32, i32, i32, i32, i32
return %6, %7, %8, %9, %10, %11, %12 : i32, i32, i32, i32, i32, i32, i32
}
// CHECK-LABEL: @check_not
func @check_not() -> i32 {
// CHECK-NEXT: %[[C0:.*]] = llhd.const -2 : i32
%0 = llhd.const 1 : i32
%2 = llhd.not %0 : i32
// CHECK-NEXT: return %[[C0]] : i32
return %2 : i32
}
// CHECK-LABEL: @check_and
// CHECK-SAME: %[[IN:.*]]: i32
func @check_and(%in : i32) -> (i32, i32, i32, i32) {
// CHECK-NEXT: %[[ZERO:.*]] = llhd.const 0 : i32
// CHECK-NEXT: %[[TWO:.*]] = llhd.const 2 : i32
%0 = llhd.const 2 : i32
%1 = llhd.const -1 : i32
%2 = llhd.const 0 : i32
// check correct calculation of smod if lhs and rhs are positive
%3 = llhd.and %1, %0 : i32
// check correct pattern replacement if one operand is zero
%4 = llhd.and %2, %in : i32
// check correct pattern replacement if one operand is all ones
%5 = llhd.and %1, %in : i32
// check correct pattern replacement if lhs and rhs are the same register
%6 = llhd.and %in, %in : i32
// CHECK-NEXT: return %[[TWO]], %[[ZERO]], %[[IN]], %[[IN]] : i32, i32, i32, i32
return %3, %4, %5, %6 : i32, i32, i32, i32
}
// CHECK-LABEL: @check_or
// CHECK-SAME: %[[IN:.*]]: i32
func @check_or(%in : i32) -> (i32, i32, i32, i32) {
// CHECK-NEXT: %[[NEGONE:.*]] = llhd.const -1 : i32
// CHECK-NEXT: %[[TWO:.*]] = llhd.const 2 : i32
%0 = llhd.const 2 : i32
%1 = llhd.const -1 : i32
%2 = llhd.const 0 : i32
// check correct calculation of smod if lhs and rhs are positive
%3 = llhd.or %2, %0 : i32
// check corre2t pattern replacement if one operand is zero
%4 = llhd.or %2, %in : i32
// check correct pattern replacement if one operand is all ones
%5 = llhd.or %1, %in : i32
// check correct pattern replacement if lhs and rhs are the same register
%6 = llhd.or %in, %in : i32
// CHECK-NEXT: return %[[TWO]], %[[IN]], %[[NEGONE]], %[[IN]] : i32, i32, i32, i32
return %3, %4, %5, %6 : i32, i32, i32, i32
}
// CHECK-LABEL: @check_xor
// CHECK-SAME: %[[IN:.*]]: i32
func @check_xor(%in : i32) -> (i32, i32, i32) {
// CHECK-NEXT: %[[TWO:.*]] = llhd.const 2 : i32
// CHECK-NEXT: %[[ZERO:.*]] = llhd.const 0 : i32
%0 = llhd.const 2 : i32
%2 = llhd.const 0 : i32
// check correct calculation of smod if lhs and rhs are positive
%3 = llhd.xor %2, %0 : i32
// check correct pattern replacement if one operand is zero
%4 = llhd.xor %2, %in : i32
// check correct pattern replacement if lhs and rhs are the same register
%6 = llhd.xor %in, %in : i32
// CHECK-NEXT: return %[[TWO]], %[[IN]], %[[ZERO]] : i32, i32, i32
return %3, %4, %6 : i32, i32, i32
}
// CHECK-LABEL: @check_shl
// CHECK-SAME: %[[BASE:.*]]: i4
// CHECK-SAME: %[[HIDDEN:.*]]: i8
func @check_shl(%base : i4, %hidden : i8) -> (i4, i4) {
// CHECK-NEXT: %[[NEGSEVEN:.*]] = llhd.const -7 : i4
%0 = llhd.const 2 : i4
%1 = llhd.const 4 : i4
%2 = llhd.const 0 : i4
// check correct calculation of smod if lhs and rhs are positive
%3 = llhd.shl %0, %1, %0 : (i4, i4, i4) -> i4
// check correct pattern replacement if amount is constant zero
%4 = llhd.shl %base, %hidden, %2 : (i4, i8, i4) -> i4
// CHECK-NEXT: return %[[NEGSEVEN]], %[[BASE]] : i4, i4
return %3, %4 : i4, i4
}
// CHECK-LABEL: @check_shr
// CHECK-SAME: %[[BASE:.*]]: i4
// CHECK-SAME: %[[HIDDEN:.*]]: i8
func @check_shr(%base : i4, %hidden : i8) -> (i4, i4) {
// CHECK-NEXT: %[[NEGSEVEN:.*]] = llhd.const -7 : i4
%0 = llhd.const 2 : i4
%1 = llhd.const 4 : i4
%2 = llhd.const 0 : i4
// check correct calculation of smod if lhs and rhs are positive
%3 = llhd.shr %1, %0, %0 : (i4, i4, i4) -> i4
// check correct pattern replacement if amount is constant zero
%4 = llhd.shr %base, %hidden, %2 : (i4, i8, i4) -> i4
// CHECK-NEXT: return %[[NEGSEVEN]], %[[BASE]] : i4, i4
return %3, %4 : i4, i4
}
// CHECK-LABEL: @const_hoisting
// CHECK-SAME: %[[SIG:.*]]: !llhd.sig<i32>
func @const_hoisting(%sig : !llhd.sig<i32>) {
// CHECK-NEXT: %[[C0:.*]] = llhd.const -1 : i32
// CHECK-NEXT: %[[TIME:.*]] = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
// CHECK-NEXT: br ^[[BB:.*]]
br ^bb1
// CHECK-NEXT: ^[[BB]]
^bb1:
%0 = llhd.const -1 : i32
%1 = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
// CHECK-NEXT: llhd.drv %[[SIG]], %[[C0]] after %[[TIME]] : !llhd.sig<i32>
llhd.drv %sig, %0 after %1 : !llhd.sig<i32>
br ^bb1
}

View File

@ -0,0 +1,110 @@
// RUN: circt-opt %s -llhd-process-lowering -split-input-file -verify-diagnostics | FileCheck %s
// no inputs and outputs
// CHECK: llhd.entity @empty () -> () {
llhd.proc @empty() -> () {
// CHECK-NEXT: }
llhd.halt
}
// check that input and output signals are transferred correctly
// CHECK-NEXT: llhd.entity @inputAndOutput (%{{.*}} : !llhd.sig<i64>, %{{.*}} : !llhd.sig<i1>) -> (%{{.*}} : !llhd.sig<i1>) {
llhd.proc @inputAndOutput(%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i1>) -> (%arg2 : !llhd.sig<i1>) {
// CHECK-NEXT: }
llhd.halt
}
// check wait suspended process
// CHECK-NEXT: llhd.entity @simpleWait () -> () {
llhd.proc @simpleWait() -> () {
// CHECK-NEXT: }
br ^bb1
^bb1:
llhd.wait ^bb1
}
// Check wait with observing probed signals
// CHECK-NEXT: llhd.entity @prbAndWait (%{{.*}} : !llhd.sig<i64>) -> () {
llhd.proc @prbAndWait(%arg0 : !llhd.sig<i64>) -> () {
// CHECK-NEXT: %{{.*}} = llhd.prb
// CHECK-NEXT: }
br ^bb1
^bb1:
%0 = llhd.prb %arg0 : !llhd.sig<i64>
llhd.wait (%arg0 : !llhd.sig<i64>), ^bb1
}
// Check wait with observing probed signals
// CHECK-NEXT: llhd.entity @prbAndWaitMoreObserved (%{{.*}} : !llhd.sig<i64>, %{{.*}} : !llhd.sig<i64>) -> () {
llhd.proc @prbAndWaitMoreObserved(%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i64>) -> () {
// CHECK-NEXT: %{{.*}} = llhd.prb
// CHECK-NEXT: }
br ^bb1
^bb1:
%0 = llhd.prb %arg0 : !llhd.sig<i64>
llhd.wait (%arg0, %arg1 : !llhd.sig<i64>, !llhd.sig<i64>), ^bb1
}
// -----
// Check wait with observing probed signals
llhd.proc @prbAndWaitNotObserved(%arg0 : !llhd.sig<i64>) -> () {
br ^bb1
^bb1:
%0 = llhd.prb %arg0 : !llhd.sig<i64>
// expected-error @+1 {{Process-lowering: The wait terminator is required to have all probed signals as arguments!}}
llhd.wait ^bb1
}
// -----
// Check that block arguments for the second block are not allowed.
// expected-error @+1 {{Process-lowering: The second block (containing the llhd.wait) is not allowed to have arguments.}}
llhd.proc @blockArgumentsNotAllowed(%arg0 : !llhd.sig<i64>) -> () {
br ^bb1(%arg0 : !llhd.sig<i64>)
^bb1(%a : !llhd.sig<i64>):
llhd.wait ^bb1(%a : !llhd.sig<i64>)
}
// -----
// Check that the entry block is terminated by a br terminator.
// expected-error @+1 {{Process-lowering: The first block has to be terminated by a BranchOp from the standard dialect.}}
llhd.proc @entryBlockMustHaveBrTerminator() -> () {
llhd.wait ^bb1
^bb1:
llhd.wait ^bb1
}
// -----
// Check that there is no optional time operand in the wait terminator.
llhd.proc @noOptionalTime() -> () {
br ^bb1
^bb1:
%time = llhd.const #llhd.time<0ns, 0d, 0e> : !llhd.time
// expected-error @+1 {{Process-lowering: llhd.wait terminators with optional time argument cannot be lowered to structural LLHD.}}
llhd.wait for %time, ^bb1
}
// -----
// Check that if there are two blocks, the second one is terminated by a wait terminator.
// expected-error @+1 {{Process-lowering: The second block must be terminated by a WaitOp from the LLHD dialect.}}
llhd.proc @secondBlockTerminatedByWait() -> () {
br ^bb1
^bb1:
llhd.halt
}
// -----
// Check that there are not more than two blocks.
// expected-error @+1 {{Process-lowering only supports processes with either one basic block terminated by a llhd.halt operation or two basic blocks where the first one contains a std.br terminator and the second one is terminated by a llhd.wait operation.}}
llhd.proc @moreThanTwoBlocksNotAllowed() -> () {
br ^bb1
^bb1:
br ^bb2
^bb2:
llhd.wait ^bb1
}

View File

@ -0,0 +1,57 @@
// RUN: circt-opt %s -inline="disable-simplify" -llhd-function-elimination | FileCheck %s
// This test checks the presence of inlining into entities and processes
// and their general structure after inlining. It also checks that the functions
// are deleted by the elimination pass.
// Note: Only functions which can be reduced to one basic block can be inlined
// into entities.
// CHECK-NOT: func
func @simple() -> i32 {
%0 = llhd.const 5 : i32
return %0 : i32
}
// CHECK-NOT: func
func @complex(%flag : i1) -> i32 {
cond_br %flag, ^bb1, ^bb2
^bb1:
%0 = llhd.const 5 : i32
return %0 : i32
^bb2:
%1 = llhd.const 7 : i32
return %1 : i32
}
// CHECK-LABEL: @check_entity_inline
llhd.entity @check_entity_inline() -> (%out : !llhd.sig<i32>) {
// CHECK-NEXT: %{{.*}} = llhd.const
// CHECK-NEXT: %{{.*}} = llhd.const
// CHECK-NEXT: llhd.drv
// CHECK-NEXT: }
%1 = call @simple() : () -> i32
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %out, %1 after %time : !llhd.sig<i32>
}
// CHECK-LABEL: @check_proc_inline
llhd.proc @check_proc_inline(%arg : !llhd.sig<i1>) -> (%out : !llhd.sig<i32>) {
// CHECK-NEXT: %[[PRB:.*]] = llhd.prb
// CHECK-NEXT: cond_br %[[PRB]], ^[[BB1:.*]], ^[[BB2:.*]]
// CHECK-NEXT: ^[[BB1]]:
// CHECK-NEXT: %[[C0:.*]] = llhd.const
// CHECK-NEXT: br ^[[BB3:.*]](%[[C0]] : i32)
// CHECK-NEXT: ^[[BB2]]:
// CHECK-NEXT: %[[C1:.*]] = llhd.const
// CHECK-NEXT: br ^[[BB3]](%[[C1]] : i32)
// CHECK-NEXT: ^[[BB3]](%[[A:.*]]: i32):
// CHECK-NEXT: %[[C2:.*]] = llhd.const
// CHECK-NEXT: llhd.drv %{{.*}}, %[[A]] after %[[C2]] : !llhd.sig<i32>
// CHECK-NEXT: llhd.halt
// CHECK-NEXT: }
%0 = llhd.prb %arg : !llhd.sig<i1>
%1 = call @complex(%0) : (i1) -> i32
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %out, %1 after %time : !llhd.sig<i32>
llhd.halt
}

View File

@ -0,0 +1,14 @@
// RUN: llhd-sim %s | FileCheck %s
// CHECK: 0ns 0d 0e root/bool 0x01
// CHECK-NEXT: 0ns 0d 0e root/fair 0xff00
// CHECK-NEXT: 0ns 0d 0e root/ginormous 0x000000000000000000000008727f6369aaf83ca15026747af8c7f196ce3f0ad2
llhd.entity @root () -> () {
%small = llhd.const 1 : i1
%r = llhd.const 0xff00 : i16
%b = llhd.const 12345678901234567890123456789012345678901234567890 : i256
%1 = llhd.sig "bool" %small : i1
%2 = llhd.sig "fair" %r : i16
%3 = llhd.sig "ginormous" %b : i256
}

View File

@ -0,0 +1,27 @@
// RUN: llhd-sim %s | FileCheck %s
// CHECK: 0ns 0d 0e root/twoBytes 0x12345678
// CHECK-NEXT: 0ns 0d 0e root/spanBytes 0xffffffff
// CHECK-NEXT: 0ns 0d 0e root/sameByte 0xffffffff
// CHECK-NEXT: 1ns 0d 0e root/twoBytes 0x1234ffff
// CHECK-NEXT: 1ns 0d 0e root/spanBytes 0xfffff00f
// CHECK-NEXT: 1ns 0d 0e root/sameByte 0xfffffffc
llhd.entity @root () -> () {
%0 = llhd.const 0x12345678 : i32
%1 = llhd.const 0xffffffff : i32
%s0 = llhd.sig "twoBytes" %0 : i32
%s1 = llhd.sig "spanBytes" %1 : i32
%s2 = llhd.sig "sameByte" %1 : i32
%c0 = llhd.const 0xffff : i16
%c1 = llhd.const 0 : i8
%c2 = llhd.const 0 : i1
%t = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
%e0 = llhd.exts %s0, 0 : !llhd.sig<i32> -> !llhd.sig<i16>
%e1 = llhd.exts %s1, 4 : !llhd.sig<i32> -> !llhd.sig<i8>
%e2 = llhd.exts %s2, 0 : !llhd.sig<i32> -> !llhd.sig<i1>
%e3 = llhd.exts %s2, 1 : !llhd.sig<i32> -> !llhd.sig<i1>
llhd.drv %e0, %c0 after %t : !llhd.sig<i16>
llhd.drv %e1, %c1 after %t : !llhd.sig<i8>
llhd.drv %e2, %c2 after %t : !llhd.sig<i1>
llhd.drv %e3, %c2 after %t : !llhd.sig<i1>
}

View File

@ -0,0 +1,24 @@
// RUN: llhd-sim %s | FileCheck %s
// CHECK: 0ns 0d 0e root/toggle 0x01
// CHECK-NEXT: 0ns 0d 0e root/proc/toggle 0x01
// CHECK-NEXT: 1ns 0d 1e root/toggle 0x00
// CHECK-NEXT: 1ns 0d 1e root/proc/toggle 0x00
llhd.entity @root () -> () {
%0 = llhd.const 1 : i1
%1 = llhd.sig "toggle" %0 : i1
llhd.inst "proc" @p () -> (%1) : () -> (!llhd.sig<i1>)
}
llhd.proc @p () -> (%a : !llhd.sig<i1>) {
br ^wait
^wait:
%1 = llhd.prb %a : !llhd.sig<i1>
%0 = llhd.not %1 : i1
%wt = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.wait for %wt, ^drive
^drive:
%dt = llhd.const #llhd.time<0ns, 0d, 1e> : !llhd.time
llhd.drv %a, %0 after %dt : !llhd.sig<i1>
llhd.halt
}

View File

@ -0,0 +1,33 @@
// RUN: llhd-sim %s | FileCheck %s
// CHECK: 0ns 0d 0e root/shr 0x08
// CHECK-NEXT: 0ns 0d 0e root/shl 0x01
// CHECK-NEXT: 1ns 0d 0e root/shr 0x0c
// CHECK-NEXT: 1ns 0d 0e root/shl 0x02
// CHECK-NEXT: 2ns 0d 0e root/shr 0x0e
// CHECK-NEXT: 2ns 0d 0e root/shl 0x04
// CHECK-NEXT: 3ns 0d 0e root/shr 0x0f
// CHECK-NEXT: 3ns 0d 0e root/shl 0x08
// CHECK-NEXT: 4ns 0d 0e root/shl 0x00
llhd.entity @root () -> () {
%time = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
%init = llhd.const 8 : i4
%hidden = llhd.const 1 : i2
%amnt = llhd.const 1 : i1
%sig = llhd.sig "shr" %init : i4
%prbd = llhd.prb %sig : !llhd.sig<i4>
%shr = llhd.shr %prbd, %hidden, %amnt : (i4, i2, i1) -> i4
llhd.drv %sig, %shr after %time : !llhd.sig<i4>
%init1 = llhd.const 1 : i4
%hidden1 = llhd.const 0 : i1
%amnt1 = llhd.const 1 : i1
%sig1 = llhd.sig "shl" %init1 : i4
%prbd1 = llhd.prb %sig1 : !llhd.sig<i4>
%shl = llhd.shl %prbd1, %hidden1, %amnt1 : (i4, i1, i1) -> i4
llhd.drv %sig1, %shl after %time : !llhd.sig<i4>
}

View File

@ -0,0 +1,20 @@
// RUN: llhd-sim %s -n 10 -r Foo | FileCheck %s
// CHECK: 0ns 0d 0e Foo/toggle 0x00
// CHECK-NEXT: 1ns 0d 0e Foo/toggle 0x01
// CHECK-NEXT: 2ns 0d 0e Foo/toggle 0x00
// CHECK-NEXT: 3ns 0d 0e Foo/toggle 0x01
// CHECK-NEXT: 4ns 0d 0e Foo/toggle 0x00
// CHECK-NEXT: 5ns 0d 0e Foo/toggle 0x01
// CHECK-NEXT: 6ns 0d 0e Foo/toggle 0x00
// CHECK-NEXT: 7ns 0d 0e Foo/toggle 0x01
// CHECK-NEXT: 8ns 0d 0e Foo/toggle 0x00
// CHECK-NEXT: 9ns 0d 0e Foo/toggle 0x01
llhd.entity @Foo () -> () {
%0 = llhd.const 0 : i1
%toggle = llhd.sig "toggle" %0 : i1
%1 = llhd.prb %toggle : !llhd.sig<i1>
%2 = llhd.not %1 : i1
%dt = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
llhd.drv %toggle, %2 after %dt : !llhd.sig<i1>
}

View File

@ -0,0 +1,23 @@
//RUN: circt-translate --llhd-to-verilog %s | FileCheck %s
// CHECK-LABEL: _check_arithmetic
llhd.entity @check_arithmetic() -> () {
// CHECK-NEXT: wire [63:0] _[[A:.*]] = 64'd42;
%a = llhd.const 42 : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = -_[[A]];
%0 = llhd.neg %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] + _[[A]];
%1 = addi %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] - _[[A]];
%2 = subi %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] * _[[A]];
%3 = muli %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] / _[[A]];
%4 = divi_unsigned %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = $signed(_[[A]]) / $signed(_[[A]]);
%5 = divi_signed %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] % _[[A]];
%6 = remi_unsigned %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = $signed(_[[A]]) % $signed(_[[A]]);
%7 = remi_signed %a, %a : i64
}

View File

@ -0,0 +1,29 @@
//RUN: circt-translate --llhd-to-verilog %s | FileCheck %s
// CHECK-LABEL: _check_bitwise
llhd.entity @check_bitwise() -> () {
// CHECK-NEXT: wire [63:0] _[[A:.*]] = 64'd42;
%a = llhd.const 42 : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = ~_[[A]];
%0 = llhd.not %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] & _[[A]];
%1 = llhd.and %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] | _[[A]];
%2 = llhd.or %a, %a : i64
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[A]] ^ _[[A]];
%3 = llhd.xor %a, %a : i64
// CHECK-NEXT: wire [4:0] _[[HIDDEN:.*]] = 5'd0;
%hidden = llhd.const 0 : i5
// CHECK-NEXT: wire [1:0] _[[AMT:.*]] = 2'd3;
%amt = llhd.const 3 : i2
// CHECK-NEXT: wire [68:0] _[[TMP0:.*]] = {_[[A]], _[[HIDDEN]]};
// CHECK-NEXT: wire [68:0] _[[TMP1:.*]] = _[[TMP0]] << _[[AMT]];
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[TMP1]][68:5];
%4 = llhd.shl %a, %hidden, %amt : (i64, i5, i2) -> i64
// CHECK-NEXT: wire [68:0] _[[TMP0:.*]] = {_[[HIDDEN]], _[[A]]};
// CHECK-NEXT: wire [68:0] _[[TMP1:.*]] = _[[TMP0]] << _[[AMT]];
// CHECK-NEXT: wire [63:0] _{{.*}} = _[[TMP1]][63:0];
%5 = llhd.shr %a, %hidden, %amt : (i64, i5, i2) -> i64
}

View File

@ -0,0 +1,34 @@
//RUN: circt-translate --llhd-to-verilog %s | FileCheck %s
// CHECK: module _empty;
llhd.entity @empty () -> () {
// CHECK-NEXT: endmodule
}
// CHECK-NEXT: module _onlyinput(input [63:0] _{{.*}});
llhd.entity @onlyinput (%arg0 : !llhd.sig<i64>) -> () {
// CHECK-NEXT: endmodule
}
// CHECK-NEXT: module _onlyoutput(output [63:0] _{{.*}});
llhd.entity @onlyoutput () -> (%out0 : !llhd.sig<i64>) {
// CHECK-NEXT: endmodule
}
// CHECK-NEXT: module _inputandoutput(input [63:0] _{{.*}}, input [31:0] _{{.*}}, output [7:0] _{{.*}}, output [15:0] _{{.*}});
llhd.entity @inputandoutput (%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i32>) -> (%out0 : !llhd.sig<i8>, %out1 : !llhd.sig<i16>) {
// CHECK-NEXT: endmodule
}
// CHECK-NEXT: module _check_inst(input [63:0] _[[ARG0:.*]], input [31:0] _[[ARG1:.*]], output [7:0] _[[OUT0:.*]], output [15:0] _[[OUT1:.*]]);
llhd.entity @check_inst (%arg0 : !llhd.sig<i64>, %arg1 : !llhd.sig<i32>) -> (%out0 : !llhd.sig<i8>, %out1 : !llhd.sig<i16>) {
// CHECK-NEXT: _empty inst_[[INST0:.*]];
llhd.inst "empty" @empty () -> () : () -> ()
// CHECK-NEXT: _onlyinput inst_[[INST1:.*]] (_[[ARG0]]);
llhd.inst "input" @onlyinput (%arg0) -> () : (!llhd.sig<i64>) -> ()
// CHECK-NEXT: _onlyoutput inst_[[INST2:.*]] (_[[ARG0]]);
llhd.inst "output" @onlyoutput () -> (%arg0) : () -> (!llhd.sig<i64>)
// CHECK-NEXT: _inputandoutput inst_[[INST3:.*]] (_[[ARG0]], _[[ARG1]], _[[OUT0]], _[[OUT1]]);
llhd.inst "in-out" @inputandoutput (%arg0, %arg1) -> (%out0, %out1) : (!llhd.sig<i64>, !llhd.sig<i32>) -> (!llhd.sig<i8>, !llhd.sig<i16>)
// CHECK-NEXT: endmodule
}

View File

@ -0,0 +1,27 @@
//RUN: circt-translate --llhd-to-verilog %s | FileCheck %s
// CHECK-LABEL: _check_relations
llhd.entity @check_relations() -> () {
// CHECK-NEXT: wire [63:0] _[[A:.*]] = 64'd42;
%a = llhd.const 42 : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] == _[[A]];
%1 = cmpi "eq", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] != _[[A]];
%2 = cmpi "ne", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] >= _[[A]];
%3 = cmpi "uge", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] > _[[A]];
%4 = cmpi "ugt", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] <= _[[A]];
%5 = cmpi "ule", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = _[[A]] < _[[A]];
%6 = cmpi "ult", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = $signed(_[[A]]) >= $signed(_[[A]]);
%7 = cmpi "sge", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = $signed(_[[A]]) > $signed(_[[A]]);
%8 = cmpi "sgt", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = $signed(_[[A]]) <= $signed(_[[A]]);
%9 = cmpi "sle", %a, %a : i64
// CHECK-NEXT: wire _{{.*}} = $signed(_[[A]]) < $signed(_[[A]]);
%10 = cmpi "slt", %a, %a : i64
}

View File

@ -0,0 +1,33 @@
//RUN: circt-translate --llhd-to-verilog -split-input-file -verify-diagnostics %s | FileCheck %s
// CHECK-LABEL: _check_sig
llhd.entity @check_sig () -> () {
// CHECK-NEXT: wire _[[A:.*]] = 1'd1;
%0 = llhd.const 1 : i1
// CHECK-NEXT: wire [63:0] _[[B:.*]] = 64'd256;
%1 = llhd.const 256 : i64
%2 = llhd.const #llhd.time<1ns, 0d, 0e> : !llhd.time
// CHECK-NEXT: var _[[C:.*]] = _[[A]];
%3 = llhd.sig "sigI1" %0 : i1
// CHECK-NEXT: var [63:0] _{{.*}} = _[[B]];
%4 = llhd.sig "sigI64" %1 : i64
%5 = llhd.prb %3 : !llhd.sig<i1>
// CHECK-NEXT: assign _[[C]] = #(1ns) _[[A]];
llhd.drv %3, %0 after %2 : !llhd.sig<i1>
%6 = llhd.const #llhd.time<0ns, 1d, 0e> : !llhd.time
// CHECK-NEXT: assign _[[C]] = #(0ns) _[[A]];
llhd.drv %3, %0 after %6 : !llhd.sig<i1>
// CHECK-NEXT: assign _[[C]] = #(0ns) _[[A]] ? _[[A]] : _[[C]];
llhd.drv %3, %0 after %6 if %0 : !llhd.sig<i1>
}
// -----
llhd.entity @check_invalid_drv_time () -> () {
%0 = llhd.const 1 : i1
%1 = llhd.sig "sigI1" %0 : i1
// expected-error @+2 {{Not possible to translate a time attribute with 0 real time and non-1 delta.}}
// expected-error @+1 {{Operation not supported!}}
%2 = llhd.const #llhd.time<0ns, 0d, 0e> : !llhd.time
llhd.drv %1, %0 after %2 : !llhd.sig<i1>
}

View File

@ -57,6 +57,7 @@ tools = [
'handshake-runner',
'circt-opt',
'circt-translate',
'llhd-sim'
]
llvm_config.add_tool_substitutions(tools, tool_dirs)

View File

@ -3,3 +3,4 @@ add_subdirectory(circt-opt)
add_subdirectory(circt-translate)
add_subdirectory(handshake-runner)
add_subdirectory(firtool)
add_subdirectory(llhd-sim)

View File

@ -12,6 +12,9 @@ target_link_libraries(circt-opt
MLIRHandshakeOps
MLIRRTL
MLIRStandardToHandshake
MLIRLLHD
MLIRLLHDTransforms
MLIRLLHDToLLVM
MLIRParser
MLIRSupport
@ -19,5 +22,6 @@ target_link_libraries(circt-opt
MLIROptLib
MLIRStandardOps
MLIRTransforms
MLIRLLVMIR
)

View File

@ -5,11 +5,16 @@
//
//===----------------------------------------------------------------------===//
#include "circt/Dialect/FIRRTL/Dialect.h"
#include "circt/Dialect/RTL/Dialect.h"
#include "circt/Dialect/Handshake/HandshakeOps.h"
#include "circt/Conversion/LLHDToLLVM/LLHDToLLVM.h"
#include "circt/Conversion/StandardToHandshake/StandardToHandshake.h"
#include "circt/Dialect/FIRRTL/Dialect.h"
#include "circt/Dialect/Handshake/HandshakeOps.h"
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "circt/Dialect/LLHD/Transforms/Passes.h"
#include "circt/Dialect/RTL/Dialect.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/AsmState.h"
#include "mlir/Pass/Pass.h"
#include "mlir/Pass/PassManager.h"
#include "mlir/Pass/PassRegistry.h"
@ -62,16 +67,21 @@ int main(int argc, char **argv) {
// Register MLIR stuff
registerDialect<StandardOpsDialect>();
registerDialect<LLVM::LLVMDialect>();
// Register the standard passes we want.
#define GEN_PASS_REGISTRATION_Canonicalizer
#define GEN_PASS_REGISTRATION_CSE
#define GEN_PASS_REGISTRATION_Inliner
#include "mlir/Transforms/Passes.h.inc"
// Register any pass manager command line options.
registerMLIRContextCLOptions();
registerPassManagerCLOptions();
// Register printer command line options.
registerAsmPrinterCLOptions();
// Register our dialects.
registerDialect<firrtl::FIRRTLDialect>();
firrtl::registerFIRRTLPasses();
@ -81,6 +91,11 @@ int main(int argc, char **argv) {
registerDialect<rtl::RTLDialect>();
registerDialect<llhd::LLHDDialect>();
llhd::initLLHDTransformationPasses();
llhd::initLLHDToLLVMPass();
PassPipelineCLParser passPipeline("", "Compiler passes to run");
// Parse pass names in main to ensure static initialization completed.

View File

@ -15,4 +15,6 @@ target_link_libraries(circt-translate
CIRCTEmitVerilog
CIRCTFIRParser
MLIRLLHD
MLIRLLHDTargetVerilog
)

View File

@ -7,9 +7,11 @@
//===----------------------------------------------------------------------===//
#include "circt/Dialect/FIRRTL/Dialect.h"
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "circt/Dialect/RTL/Dialect.h"
#include "circt/EmitVerilog.h"
#include "circt/FIRParser.h"
#include "circt/Target/Verilog/TranslateToVerilog.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/IR/AsmState.h"
#include "mlir/IR/Diagnostics.h"
@ -58,6 +60,10 @@ int main(int argc, char **argv) {
registerFIRParserTranslation();
registerVerilogEmitterTranslation();
// LLHD
registerDialect<llhd::LLHDDialect>();
llhd::registerToVerilogTranslation();
llvm::InitLLVM y(argc, argv);
// Add flags for all the registered translations.

View File

@ -0,0 +1,16 @@
get_property(dialect_libs GLOBAL PROPERTY MLIR_DIALECT_LIBS)
get_property(conversion_libs GLOBAL PROPERTY MLIR_CONVERSION_LIBS)
set(LIBS
${dialect_libs}
${conversion_libs}
MLIRLLHD
MLIRLLHDToLLVM
CIRCTLLHDSimEngine
)
add_llvm_executable(llhd-sim
llhd-sim.cpp)
llvm_update_compile_flags(llhd-sim)
target_link_libraries(llhd-sim PRIVATE ${LIBS})

132
tools/llhd-sim/llhd-sim.cpp Normal file
View File

@ -0,0 +1,132 @@
//===- llhd-sim.cpp - LLHD simulator tool -----------------------*- C++ -*-===//
//
// This file implements a command line tool to run LLHD simulation.
//
//===----------------------------------------------------------------------===//
#include "circt/Conversion/LLHDToLLVM/LLHDToLLVM.h"
#include "circt/Dialect/LLHD/IR/LLHDDialect.h"
#include "circt/Dialect/LLHD/Simulator/Engine.h"
#include "mlir/Dialect/LLVMIR/LLVMDialect.h"
#include "mlir/Dialect/StandardOps/IR/Ops.h"
#include "mlir/Parser.h"
#include "mlir/Support/FileUtilities.h"
#include "mlir/Target/LLVMIR.h"
#include "llvm/Support/CommandLine.h"
#include "llvm/Support/InitLLVM.h"
#include "llvm/Support/ToolOutputFile.h"
using namespace llvm;
using namespace mlir;
static cl::opt<std::string>
inputFilename(cl::Positional, cl::desc("<input-file>"), cl::init("-"));
static cl::opt<std::string> outputFilename("o", cl::desc("Output filename"),
cl::value_desc("filename"),
cl::init("-"));
static cl::opt<int> nSteps("n", cl::desc("Set the maximum number of steps"),
cl::value_desc("max-steps"));
static cl::opt<bool>
dumpLLVMDialect("dump-llvm-dialect",
cl::desc("Dump the LLVM IR dialect module"));
static cl::opt<bool> dumpLLVMIR("dump-llvm-ir",
cl::desc("Dump the LLVM IR module"));
static cl::opt<bool> dumpMLIR("dump-mlir",
cl::desc("Dump the original MLIR module"));
static cl::opt<bool> dumpLayout("dump-layout",
cl::desc("Dump the gathered instance layout"));
static cl::opt<std::string> root(
"root",
cl::desc("Specify the name of the entity to use as root of the design"),
cl::value_desc("root_name"), cl::init("root"));
static cl::alias rootA("r", cl::desc("Alias for -root"), cl::aliasopt(root));
static int parseMLIR(MLIRContext &context, OwningModuleRef &module) {
module = parseSourceFile(inputFilename, &context);
if (!module)
return 1;
return 0;
}
static int dumpLLVM(ModuleOp module, MLIRContext &context) {
if (dumpLLVMDialect) {
module.dump();
llvm::errs() << "\n";
return 0;
}
// Translate the module, that contains the LLVM dialect, to LLVM IR.
auto llvmModule = mlir::translateModuleToLLVMIR(module);
if (!llvmModule) {
llvm::errs() << "Failed to emit LLVM IR\n";
return -1;
}
llvm::errs() << *llvmModule << "\n";
return 0;
}
int main(int argc, char **argv) {
registerDialect<llhd::LLHDDialect>();
registerDialect<LLVM::LLVMDialect>();
registerDialect<StandardOpsDialect>();
llhd::initLLHDToLLVMPass();
InitLLVM y(argc, argv);
cl::ParseCommandLineOptions(argc, argv, "LLHD simulator\n");
// Set up the input and output files.
std::string errorMessage;
auto file = openInputFile(inputFilename, &errorMessage);
if (!file) {
llvm::errs() << errorMessage << "\n";
return 1;
}
auto output = openOutputFile(outputFilename, &errorMessage);
if (!output) {
llvm::errs() << errorMessage << "\n";
exit(1);
}
// Parse the input file.
MLIRContext context;
OwningModuleRef module;
if (parseMLIR(context, module))
return 1;
if (dumpMLIR) {
module->dump();
llvm::errs() << "\n";
return 0;
}
llhd::sim::Engine engine(output->os(), *module, context, root);
if (dumpLLVMDialect || dumpLLVMIR) {
return dumpLLVM(engine.getModule(), context);
}
if (dumpLayout) {
engine.dumpStateLayout();
engine.dumpStateSignalTriggers();
return 0;
}
engine.simulate(nSteps);
output->keep();
return 0;
}