[RTG] Add MemoryBlock type and decl op (#8356)

This commit is contained in:
Martin Erhart 2025-05-13 10:30:23 +01:00 committed by GitHub
parent 025bb22361
commit 391ee7d4e9
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
13 changed files with 273 additions and 7 deletions

View File

@ -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.
//===----------------------------------------------------------------------===//

View File

@ -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;
}

View File

@ -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

View File

@ -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
};

View File

@ -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()

View File

@ -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
//===--------------------------------------------------------------------===//

View File

@ -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.
//===----------------------------------------------------------------------===//

View File

@ -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.
//===----------------------------------------------------------------------===//

View File

@ -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();

View File

@ -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);

View File

@ -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>
}

View File

@ -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>
}

View File

@ -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() {