mirror of https://github.com/llvm/circt.git
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:
parent
b742968411
commit
8a82a81806
|
@ -1,3 +1,2 @@
|
|||
add_subdirectory(Conversion)
|
||||
add_subdirectory(Dialect)
|
||||
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
|
||||
add_subdirectory(LLHDToLLVM)
|
||||
|
||||
|
|
|
@ -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/)
|
|
@ -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
|
|
@ -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
|
|
@ -1,3 +1,4 @@
|
|||
add_subdirectory(FIRRTL)
|
||||
add_subdirectory(Handshake)
|
||||
add_subdirectory(LLHD)
|
||||
add_subdirectory(RTL)
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
add_subdirectory(IR)
|
||||
add_subdirectory(Transforms)
|
|
@ -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;
|
||||
}
|
|
@ -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;
|
||||
}
|
|
@ -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)
|
|
@ -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();
|
||||
}];
|
||||
}
|
|
@ -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];
|
||||
}
|
||||
}];
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -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); }];
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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)";
|
||||
}
|
|
@ -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
|
|
@ -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/)
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
|
@ -2,3 +2,4 @@ add_subdirectory(Conversion)
|
|||
add_subdirectory(Dialect)
|
||||
add_subdirectory(FIRParser)
|
||||
add_subdirectory(EmitVerilog)
|
||||
add_subdirectory(Target)
|
||||
|
|
|
@ -1,2 +1,2 @@
|
|||
add_subdirectory(StandardToHandshake)
|
||||
|
||||
add_subdirectory(LLHDToLLVM)
|
||||
|
|
|
@ -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
|
@ -1,3 +1,4 @@
|
|||
add_subdirectory(FIRRTL)
|
||||
add_subdirectory(Handshake)
|
||||
add_subdirectory(LLHD)
|
||||
add_subdirectory(RTL)
|
||||
|
|
|
@ -0,0 +1,3 @@
|
|||
add_subdirectory(IR)
|
||||
add_subdirectory(Simulator)
|
||||
add_subdirectory(Transforms)
|
|
@ -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
|
||||
)
|
|
@ -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();
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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();
|
||||
});
|
||||
}
|
|
@ -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";
|
||||
}
|
|
@ -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
|
|
@ -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);
|
||||
}
|
||||
}
|
|
@ -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
|
|
@ -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
|
||||
)
|
|
@ -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>();
|
||||
}
|
|
@ -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
|
|
@ -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"
|
||||
}
|
|
@ -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>();
|
||||
}
|
|
@ -0,0 +1 @@
|
|||
add_subdirectory(Verilog)
|
|
@ -0,0 +1,9 @@
|
|||
add_mlir_library(MLIRLLHDTargetVerilog
|
||||
TranslateToVerilog.cpp
|
||||
|
||||
ADDITIONAL_HEADER_DIRS
|
||||
${PROJECT_SOURCE_DIR}/include/circt/Target/Verilog
|
||||
|
||||
LINK_LIBS PUBLIC
|
||||
MLIRLLHD
|
||||
)
|
|
@ -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);
|
||||
});
|
||||
}
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
||||
}
|
|
@ -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>) {}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
}
|
||||
|
|
@ -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>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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=()->()} : () -> ()
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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() -> () : () -> ()
|
||||
}
|
|
@ -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>)->()} : () -> ()
|
|
@ -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)
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
} : () -> ()
|
||||
}
|
|
@ -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
|
|
@ -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
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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>
|
||||
}
|
|
@ -57,6 +57,7 @@ tools = [
|
|||
'handshake-runner',
|
||||
'circt-opt',
|
||||
'circt-translate',
|
||||
'llhd-sim'
|
||||
]
|
||||
|
||||
llvm_config.add_tool_substitutions(tools, tool_dirs)
|
||||
|
|
|
@ -3,3 +3,4 @@ add_subdirectory(circt-opt)
|
|||
add_subdirectory(circt-translate)
|
||||
add_subdirectory(handshake-runner)
|
||||
add_subdirectory(firtool)
|
||||
add_subdirectory(llhd-sim)
|
||||
|
|
|
@ -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
|
||||
)
|
||||
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -15,4 +15,6 @@ target_link_libraries(circt-translate
|
|||
|
||||
CIRCTEmitVerilog
|
||||
CIRCTFIRParser
|
||||
MLIRLLHD
|
||||
MLIRLLHDTargetVerilog
|
||||
)
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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})
|
|
@ -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;
|
||||
}
|
Loading…
Reference in New Issue