mirror of https://github.com/llvm/circt.git
[RTG] Add MemoryBlock type and decl op (#8356)
This commit is contained in:
parent
025bb22361
commit
391ee7d4e9
|
@ -98,6 +98,16 @@ MLIR_CAPI_EXPORTED bool rtgTypeIsAArray(MlirType type);
|
|||
/// Returns the element type of the RTG array.
|
||||
MLIR_CAPI_EXPORTED MlirType rtgArrayTypeGetElementType(MlirType type);
|
||||
|
||||
/// If the type is an RTG memory block.
|
||||
MLIR_CAPI_EXPORTED bool rtgTypeIsAMemoryBlock(MlirType type);
|
||||
|
||||
/// Creates an RTG memory block type in the context.
|
||||
MLIR_CAPI_EXPORTED MlirType rtgMemoryBlockTypeGet(MlirContext ctx,
|
||||
uint32_t addressWidth);
|
||||
|
||||
/// Returns the address with of an RTG memory block type.
|
||||
MLIR_CAPI_EXPORTED uint32_t rtgMemoryBlockTypeGetAddressWidth(MlirType type);
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Attribute API.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -789,3 +789,31 @@ def IntToImmediateOp : RTGISAOp<"int_to_immediate", [Pure]> {
|
|||
|
||||
let assemblyFormat = "$input `:` qualified(type($result)) attr-dict";
|
||||
}
|
||||
|
||||
//===- ISA Memory Block Operations ----------------------------------------===//
|
||||
|
||||
def MemoryBlockDeclareOp : RTGISAOp<"memory_block_declare", [
|
||||
HasParent<"rtg::TargetOp">,
|
||||
]> {
|
||||
let summary = "declare a memory block with the provided properties";
|
||||
let description = [{
|
||||
This operation declares a memory block to be allocated with the provided
|
||||
properties. It is only allowed to declare new memory blocks in the
|
||||
`rtg.target` operations and must be passed as argument to the `rtg.test`.
|
||||
This is because the available memory blocks are specified by the hardware
|
||||
design. This specification is fixed from the start and thus a test should
|
||||
not be able to declare new memory blocks on-the-fly. However, tests are
|
||||
allowed to allocate memory regions from these memory blocks.
|
||||
|
||||
The 'baseAddress' attribute specifies the first memory address (lowest
|
||||
address representing a valid access to the memory) and the 'endAddress'
|
||||
represents the last address (highest address that is valid to access the
|
||||
memory).
|
||||
}];
|
||||
|
||||
let arguments = (ins APIntAttr:$baseAddress, APIntAttr:$endAddress);
|
||||
let results = (outs MemoryBlockType:$result);
|
||||
|
||||
let hasCustomAssemblyFormat = 1;
|
||||
let hasVerifier = 1;
|
||||
}
|
||||
|
|
|
@ -152,11 +152,11 @@ def ArrayType : RTGTypeDef<"Array"> {
|
|||
// Types for ISA targets
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
class RTGISATypeDef<string name, list<Trait> traits = []>
|
||||
: RTGTypeDef<name, traits> { let mnemonic = "isa." # !tolower(name); }
|
||||
class RTGISATypeDef<string name, string mnemo, list<Trait> traits = []>
|
||||
: RTGTypeDef<name, traits> { let mnemonic = "isa." # mnemo; }
|
||||
|
||||
|
||||
def LabelType : RTGISATypeDef<"Label"> {
|
||||
def LabelType : RTGISATypeDef<"Label", "label"> {
|
||||
let summary = "a reference to a label";
|
||||
let description = [{
|
||||
This type represents a label. Payload dialects can add operations to cast
|
||||
|
@ -167,7 +167,7 @@ def LabelType : RTGISATypeDef<"Label"> {
|
|||
let assemblyFormat = "";
|
||||
}
|
||||
|
||||
def ImmediateType : RTGISATypeDef<"Immediate"> {
|
||||
def ImmediateType : RTGISATypeDef<"Immediate", "immediate"> {
|
||||
let summary = "an ISA immediate";
|
||||
let description = [{
|
||||
This type represents immediates of arbitrary but fixed bit-width.
|
||||
|
@ -181,8 +181,24 @@ def ImmediateType : RTGISATypeDef<"Immediate"> {
|
|||
|
||||
class ImmediateOfWidth<int width> : Type<
|
||||
And<[CPred<"llvm::isa<::circt::rtg::ImmediateType>($_self)">,
|
||||
CPred<"llvm::cast<::circt::rtg::ImmediateType>($_self).getWidth() == " # width>]>,
|
||||
CPred<"llvm::cast<::circt::rtg::ImmediateType>($_self).getWidth() == " #
|
||||
width>]>,
|
||||
"a " # width # "-bit immediate">,
|
||||
BuildableType<"::circt::rtg::ImmediateType::get($_builder.getContext(), " # width # ")">;
|
||||
BuildableType<"::circt::rtg::ImmediateType::get($_builder.getContext(), " #
|
||||
width # ")">;
|
||||
|
||||
def MemoryBlockType : RTGISATypeDef<"MemoryBlock", "memory_block"> {
|
||||
let summary = "handle to a memory block";
|
||||
let description = [{
|
||||
A memory block is representing a continuous region in a memory map with a
|
||||
fixed size and base address. It can refer to actual memory or a memory
|
||||
mapped device.
|
||||
|
||||
It is assumed that there is only a single address space.
|
||||
}];
|
||||
|
||||
let parameters = (ins "uint32_t":$addressWidth);
|
||||
let assemblyFormat = "`<` $addressWidth `>`";
|
||||
}
|
||||
|
||||
#endif // CIRCT_DIALECT_RTG_IR_RTGTYPES_TD
|
||||
|
|
|
@ -59,6 +59,8 @@ public:
|
|||
TupleCreateOp, TupleExtractOp,
|
||||
// Immediates
|
||||
IntToImmediateOp,
|
||||
// Memory Blocks
|
||||
MemoryBlockDeclareOp,
|
||||
// Misc ops
|
||||
CommentOp>([&](auto expr) -> ResultType {
|
||||
return thisCast->visitOp(expr, args...);
|
||||
|
@ -131,6 +133,7 @@ public:
|
|||
HANDLE(FixedRegisterOp, Unhandled);
|
||||
HANDLE(VirtualRegisterOp, Unhandled);
|
||||
HANDLE(IntToImmediateOp, Unhandled);
|
||||
HANDLE(MemoryBlockDeclareOp, Unhandled);
|
||||
#undef HANDLE
|
||||
};
|
||||
|
||||
|
|
|
@ -219,6 +219,12 @@ with Context() as ctx, Location.unknown():
|
|||
# CHECK: #rtg.isa.immediate<32, 42>
|
||||
print(immediate_attr)
|
||||
|
||||
memory_block_type = rtg.MemoryBlockType.get(32)
|
||||
# CHECK: width=32
|
||||
print(f"width={memory_block_type.address_width}")
|
||||
# CHECK: !rtg.isa.memory_block<32>
|
||||
print(memory_block_type)
|
||||
|
||||
with Context() as ctx, Location.unknown():
|
||||
circt.register_dialects(ctx)
|
||||
indexTy = IndexType.get()
|
||||
|
|
|
@ -133,6 +133,17 @@ void circt::python::populateDialectRTGSubmodule(nb::module_ &m) {
|
|||
return rtgImmediateTypeGetWidth(self);
|
||||
});
|
||||
|
||||
mlir_type_subclass(m, "MemoryBlockType", rtgTypeIsAMemoryBlock)
|
||||
.def_classmethod(
|
||||
"get",
|
||||
[](nb::object cls, uint32_t addressWidth, MlirContext ctxt) {
|
||||
return cls(rtgMemoryBlockTypeGet(ctxt, addressWidth));
|
||||
},
|
||||
nb::arg("self"), nb::arg("address_width"), nb::arg("ctxt") = nullptr)
|
||||
.def_property_readonly("address_width", [](MlirType self) {
|
||||
return rtgMemoryBlockTypeGetAddressWidth(self);
|
||||
});
|
||||
|
||||
//===--------------------------------------------------------------------===//
|
||||
// Attributes
|
||||
//===--------------------------------------------------------------------===//
|
||||
|
|
|
@ -144,6 +144,21 @@ uint32_t rtgImmediateTypeGetWidth(MlirType type) {
|
|||
return cast<ImmediateType>(unwrap(type)).getWidth();
|
||||
}
|
||||
|
||||
// MemoryBlockType
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
bool rtgTypeIsAMemoryBlock(MlirType type) {
|
||||
return isa<MemoryBlockType>(unwrap(type));
|
||||
}
|
||||
|
||||
MlirType rtgMemoryBlockTypeGet(MlirContext ctxt, uint32_t addressWidth) {
|
||||
return wrap(MemoryBlockType::get(unwrap(ctxt), addressWidth));
|
||||
}
|
||||
|
||||
uint32_t rtgMemoryBlockTypeGetAddressWidth(MlirType type) {
|
||||
return cast<MemoryBlockType>(unwrap(type)).getAddressWidth();
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// Attribute API.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -644,6 +644,93 @@ void ArrayCreateOp::print(OpAsmPrinter &p) {
|
|||
p.printOptionalAttrDict((*this)->getAttrs(), {});
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// MemoryBlockDeclareOp
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
||||
LogicalResult MemoryBlockDeclareOp::verify() {
|
||||
if (getBaseAddress().getBitWidth() != getType().getAddressWidth())
|
||||
return emitOpError(
|
||||
"base address width must match memory block address width");
|
||||
|
||||
if (getEndAddress().getBitWidth() != getType().getAddressWidth())
|
||||
return emitOpError(
|
||||
"end address width must match memory block address width");
|
||||
|
||||
if (getBaseAddress().ugt(getEndAddress()))
|
||||
return emitOpError(
|
||||
"base address must be smaller than or equal to the end address");
|
||||
|
||||
return success();
|
||||
}
|
||||
|
||||
ParseResult MemoryBlockDeclareOp::parse(OpAsmParser &parser,
|
||||
OperationState &result) {
|
||||
SmallVector<OpAsmParser::UnresolvedOperand> operands;
|
||||
MemoryBlockType memoryBlockType;
|
||||
APInt start, end;
|
||||
|
||||
if (parser.parseLSquare())
|
||||
return failure();
|
||||
|
||||
auto startLoc = parser.getCurrentLocation();
|
||||
if (parser.parseInteger(start))
|
||||
return failure();
|
||||
|
||||
if (parser.parseMinus())
|
||||
return failure();
|
||||
|
||||
auto endLoc = parser.getCurrentLocation();
|
||||
if (parser.parseInteger(end) || parser.parseRSquare() ||
|
||||
parser.parseColonType(memoryBlockType) ||
|
||||
parser.parseOptionalAttrDict(result.attributes))
|
||||
return failure();
|
||||
|
||||
auto width = memoryBlockType.getAddressWidth();
|
||||
auto adjustAPInt = [&](APInt value, llvm::SMLoc loc) -> FailureOr<APInt> {
|
||||
if (value.getBitWidth() > width) {
|
||||
if (!value.isIntN(width))
|
||||
return parser.emitError(
|
||||
loc,
|
||||
"address out of range for memory block with address width ")
|
||||
<< width;
|
||||
|
||||
return value.trunc(width);
|
||||
}
|
||||
|
||||
if (value.getBitWidth() < width)
|
||||
return value.zext(width);
|
||||
|
||||
return value;
|
||||
};
|
||||
|
||||
auto startRes = adjustAPInt(start, startLoc);
|
||||
auto endRes = adjustAPInt(end, endLoc);
|
||||
if (failed(startRes) || failed(endRes))
|
||||
return failure();
|
||||
|
||||
auto intType = IntegerType::get(result.getContext(), width);
|
||||
result.addAttribute(getBaseAddressAttrName(result.name),
|
||||
IntegerAttr::get(intType, *startRes));
|
||||
result.addAttribute(getEndAddressAttrName(result.name),
|
||||
IntegerAttr::get(intType, *endRes));
|
||||
|
||||
result.addTypes(memoryBlockType);
|
||||
return success();
|
||||
}
|
||||
|
||||
void MemoryBlockDeclareOp::print(OpAsmPrinter &p) {
|
||||
SmallVector<char> str;
|
||||
getBaseAddress().toString(str, 16, false, false, false);
|
||||
p << " [0x" << str;
|
||||
p << " - 0x";
|
||||
str.clear();
|
||||
getEndAddress().toString(str, 16, false, false, false);
|
||||
p << str << "] : " << getType();
|
||||
p.printOptionalAttrDict((*this)->getAttrs(),
|
||||
{getBaseAddressAttrName(), getEndAddressAttrName()});
|
||||
}
|
||||
|
||||
//===----------------------------------------------------------------------===//
|
||||
// TableGen generated logic.
|
||||
//===----------------------------------------------------------------------===//
|
||||
|
|
|
@ -95,6 +95,7 @@ struct SetStorage;
|
|||
struct VirtualRegisterStorage;
|
||||
struct UniqueLabelStorage;
|
||||
struct TupleStorage;
|
||||
struct MemoryBlockStorage;
|
||||
|
||||
/// Simple wrapper around a 'StringAttr' such that we know to materialize it as
|
||||
/// a label declaration instead of calling the builtin dialect constant
|
||||
|
@ -113,7 +114,8 @@ using ElaboratorValue =
|
|||
std::variant<TypedAttr, BagStorage *, bool, size_t, SequenceStorage *,
|
||||
RandomizedSequenceStorage *, InterleavedSequenceStorage *,
|
||||
SetStorage *, VirtualRegisterStorage *, UniqueLabelStorage *,
|
||||
LabelValue, ArrayStorage *, TupleStorage *>;
|
||||
LabelValue, ArrayStorage *, TupleStorage *,
|
||||
MemoryBlockStorage *>;
|
||||
|
||||
// NOLINTNEXTLINE(readability-identifier-naming)
|
||||
llvm::hash_code hash_value(const LabelValue &val) {
|
||||
|
@ -403,6 +405,20 @@ struct TupleStorage {
|
|||
const SmallVector<ElaboratorValue> values;
|
||||
};
|
||||
|
||||
/// Storage object for '!rtg.isa.memoryblock`-typed values.
|
||||
struct MemoryBlockStorage {
|
||||
MemoryBlockStorage(const APInt &baseAddress, const APInt &endAddress)
|
||||
: baseAddress(baseAddress), endAddress(endAddress) {}
|
||||
|
||||
// The base address of the memory. The width of the APInt also represents the
|
||||
// address width of the memory. This is an APInt to support memories of
|
||||
// >64-bit machines.
|
||||
const APInt baseAddress;
|
||||
|
||||
// The last address of the memory.
|
||||
const APInt endAddress;
|
||||
};
|
||||
|
||||
/// An 'Internalizer' object internalizes storages and takes ownership of them.
|
||||
/// When the initializer object is destroyed, all owned storages are also
|
||||
/// deallocated and thus must not be accessed anymore.
|
||||
|
@ -563,6 +579,13 @@ static void print(const TupleStorage *val, llvm::raw_ostream &os) {
|
|||
os << ")>";
|
||||
}
|
||||
|
||||
static void print(const MemoryBlockStorage *val, llvm::raw_ostream &os) {
|
||||
os << "<memory-block {"
|
||||
<< ", address-width=" << val->baseAddress.getBitWidth()
|
||||
<< ", base-address=" << val->baseAddress
|
||||
<< ", end-address=" << val->endAddress << "}>";
|
||||
}
|
||||
|
||||
static llvm::raw_ostream &operator<<(llvm::raw_ostream &os,
|
||||
const ElaboratorValue &value) {
|
||||
std::visit([&](auto val) { print(val, os); }, value);
|
||||
|
@ -1458,6 +1481,12 @@ public:
|
|||
|
||||
FailureOr<DeletionKind> visitOp(CommentOp op) { return DeletionKind::Keep; }
|
||||
|
||||
FailureOr<DeletionKind> visitOp(MemoryBlockDeclareOp op) {
|
||||
state[op.getResult()] = sharedState.internalizer.create<MemoryBlockStorage>(
|
||||
op.getEndAddress(), op.getBaseAddress());
|
||||
return DeletionKind::Delete;
|
||||
}
|
||||
|
||||
FailureOr<DeletionKind> visitOp(scf::IfOp op) {
|
||||
bool cond = get<bool>(op.getCondition());
|
||||
auto &toElaborate = cond ? op.getThenRegion() : op.getElseRegion();
|
||||
|
|
|
@ -175,6 +175,20 @@ static void testImmediate(MlirContext ctx) {
|
|||
rtgImmediateAttrGetValue(immediateAttr));
|
||||
}
|
||||
|
||||
static void testMemoryBlock(MlirContext ctx) {
|
||||
MlirType memoryBlockTy = rtgMemoryBlockTypeGet(ctx, 32);
|
||||
|
||||
// CHECK: is_memory_block
|
||||
fprintf(stderr, rtgTypeIsAMemoryBlock(memoryBlockTy)
|
||||
? "is_memory_block\n"
|
||||
: "isnot_memory_block\n");
|
||||
// CHECK: !rtg.isa.memory_block<32>
|
||||
mlirTypeDump(memoryBlockTy);
|
||||
// CHECK: address_width=32
|
||||
fprintf(stderr, "address_width=%u\n",
|
||||
rtgMemoryBlockTypeGetAddressWidth(memoryBlockTy));
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
MlirContext ctx = mlirContextCreate();
|
||||
mlirDialectHandleLoadDialect(mlirGetDialectHandle__rtg__(), ctx);
|
||||
|
@ -191,6 +205,7 @@ int main(int argc, char **argv) {
|
|||
testLabelVisibilityAttr(ctx);
|
||||
testDefaultContextAttr(ctx);
|
||||
testImmediate(ctx);
|
||||
testMemoryBlock(ctx); // Add the new test
|
||||
|
||||
mlirContextDestroy(ctx);
|
||||
|
||||
|
|
|
@ -191,3 +191,11 @@ rtg.test @tuples() {
|
|||
%0 = rtg.tuple_create %idx0, %true : index, i1
|
||||
%1 = rtg.tuple_extract %0 at 1 : tuple<index, i1>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @memoryBlocks : !rtg.dict<mem_block: !rtg.isa.memory_block<32>>
|
||||
rtg.target @memoryBlocks : !rtg.dict<mem_block: !rtg.isa.memory_block<32>> {
|
||||
// CHECK: rtg.isa.memory_block_declare [0x0 - 0x8] : !rtg.isa.memory_block<32>
|
||||
%0 = rtg.isa.memory_block_declare [0x0 - 0x8] : !rtg.isa.memory_block<32>
|
||||
|
||||
rtg.yield %0 : !rtg.isa.memory_block<32>
|
||||
}
|
||||
|
|
|
@ -226,3 +226,31 @@ rtg.test @tupleExtractOOB(tup = %tup : tuple<index, i1>) {
|
|||
// expected-error @below {{index (2) must be smaller than number of elements in tuple (2)}}
|
||||
rtg.tuple_extract %tup at 2 : tuple<index, i1>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
rtg.target @memoryBlockAddressDoesNotFit : !rtg.dict<> {
|
||||
// expected-error @below {{address out of range for memory block with address width 2}}
|
||||
rtg.isa.memory_block_declare [0x0 - 0x8] : !rtg.isa.memory_block<2>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
rtg.target @memoryBlockBaseAddrWidthMismatch : !rtg.dict<> {
|
||||
// expected-error @below {{base address width must match memory block address width}}
|
||||
"rtg.isa.memory_block_declare"() <{baseAddress=0x0 : i32, endAddress=0x8 : i64}> : () -> !rtg.isa.memory_block<64>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
rtg.target @memoryBlockEndAddrWidthMismatch : !rtg.dict<> {
|
||||
// expected-error @below {{end address width must match memory block address width}}
|
||||
"rtg.isa.memory_block_declare"() <{baseAddress=0x0 : i64, endAddress=0x8 : i32}> : () -> !rtg.isa.memory_block<64>
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
rtg.target @memoryBlockBaseAddressLargerThanEndAddress : !rtg.dict<> {
|
||||
// expected-error @below {{base address must be smaller than or equal to the end address}}
|
||||
rtg.isa.memory_block_declare [0x9 - 0x8] : !rtg.isa.memory_block<64>
|
||||
}
|
||||
|
|
|
@ -645,6 +645,16 @@ rtg.test @comments() {
|
|||
rtg.comment "this is a comment"
|
||||
}
|
||||
|
||||
rtg.target @memoryBlocks : !rtg.dict<mem_block: !rtg.isa.memory_block<32>> {
|
||||
%0 = rtg.isa.memory_block_declare [0x0 - 0x8] : !rtg.isa.memory_block<32>
|
||||
rtg.yield %0 : !rtg.isa.memory_block<32>
|
||||
}
|
||||
|
||||
// CHECK-LABEL: @memoryBlockTest_memoryBlocks
|
||||
rtg.test @memoryBlockTest(mem_block = %arg0: !rtg.isa.memory_block<32>) {
|
||||
// CHECK-NEXT: }
|
||||
}
|
||||
|
||||
// -----
|
||||
|
||||
rtg.test @nestedRegionsNotSupported() {
|
||||
|
|
Loading…
Reference in New Issue