Skip to content

[FIRRTL][CreateSiFiveMetadata] Add memory read-under-write behavior to emitted metadata #8604

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: main
Choose a base branch
from

Conversation

fzi-hielscher
Copy link
Contributor

This PR adds information on the read under write behavior of memories, as specified by the FIRRTL/CHIRRTL IR, to the emitted memory metadata records. This includes the textual repl-seq-mem-file, the seq_mem.json, and the object model IR. It should allow external tools to generate memories with the appropriate behavior.

  • In the JSON file, there is a new string attribute ruw_behavior that can take one of the following values : Undefined, New, Old.
  • In the textual file, Undefined behavior is elided to keep the format unchanged for the common case. Otherwise ruw New or ruw Old is appended to the memory's line.
  • In the object model it is added to the MemorySchema as a string-typed subfield called ruwBehavior. I've never dealt with the object model before and I don't really know how enums should be handled here. So, please tell me if I should change my approach.

To make this information available at the point of metadata emission, I had to add the ruw attribute to FMemModuleOp. Previously, this attribute was just an I32EnumAttr. When adding it to the FMemModuleOp's attribute dictionary this would become a generic i32 typed attribute, which did not seem desirable to me. So I replaced the RUWAttr with a RUWBehaviorAttr dialect attribute to give it an enum specific serialization (ruw = #firrtl<ruwbehavior Undefined>). Unfortunately, this creates a fair amount of change noise. I can break it out as a separate PR if desired.

@@ -292,7 +292,7 @@ MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetParamDecl(MlirContext ctx,
MLIR_CAPI_EXPORTED MlirAttribute firrtlAttrGetNameKind(MlirContext ctx,
FIRRTLNameKind nameKind);

/// Creates a RUWAttr with the specified Read-Under-Write Behaviour.
/// Creates a RUWBehaviorAttr with the specified Read-Under-Write Behavior.
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologise to the British, but since the FIRRTL spec is written in AE I chose 'behavior'.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's usually the Canadians you have to watch out for...

DenseBoolArrayAttr:$portDirections, ArrayRefAttr:$portLocations,
ArrayRefAttr:$portAnnotations, ArrayRefAttr:$portSymbols,
ArrayRefAttr:$portNames, ArrayRefAttr:$portTypes,
DefaultValuedAttr<RUWBehaviorAttr, "firrtl::RUWBehavior::Undefined">:$ruw,
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Making RUWBehavior::Undefined the default reduces the impact of this change but might increase the chances of accidentally dropping specified behavior.

Comment on lines 571 to +577
seqMemConfStr = (StringRef(seqMemConfStr) + "name {{" + Twine(symId) +
"}} depth " + Twine(mem.getDepth()) + " width " +
Twine(width) + " ports " + portStr + maskGranStr + "\n")
Twine(width) + " ports " + portStr + maskGranStr +
(mem.getRuw() == RUWBehavior::Undefined
? ""
: " ruw " + stringifyRUWBehavior(mem.getRuw())) +
"\n")
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is getting a bit out of hand. When I'll also add the MemoryInitAttr to the metadata, I'll probably just append the string instead of concatenating twines.

@@ -206,7 +206,8 @@ LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
auto moduleOp = b.create<FMemModuleOp>(
mem.loc, moduleName, ports, mem.numReadPorts, mem.numWritePorts,
mem.numReadWritePorts, mem.dataWidth, mem.maskBits, mem.readLatency,
mem.writeLatency, mem.depth);
mem.writeLatency, mem.depth,
*symbolizeRUWBehavior(uint32_t(mem.readUnderWrite)));
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not a huge fan of this myself, but it is just the inverse of line 92: *seq::symbolizeRUW(unsigned(op.getRuw())),

@@ -116,13 +116,13 @@ circuit test:
; CHECK: FILE "metadata/tb_seq_mems.json"
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
; CHECK-SAME: "mask_granularity":8,"extra_ports":[],
; CHECK-SAME: "ruw_behavior": "Undefined","mask_granularity":8,"extra_ports":[],
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not really sure what this test is supposed to do. Does it just check that the metatdata is not emitted without the --repl-seq-mem option?
At the moment it should always expectedly fail as the formatting has not been updated.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure... @prithayan do you have any idea?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I've no recollection of why this was added. I'll get rid of it in another PR.

@fzi-hielscher fzi-hielscher marked this pull request as ready for review June 26, 2025 15:17
@prithayan
Copy link
Contributor

Thanks @fzi-hielscher for the PR, I'm also taking a look at it, will have the review by Monday.

Copy link
Member

@seldridge seldridge left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

I'll try to get a head start to figure out if this is going to break anything internally due to the format changes of some of these files. However, that is my problem.

@@ -314,7 +315,7 @@ struct ObjectModelIR {
extraPortsList.push_back(extraPortsObj);
}
auto extraPorts = builderOM.create<ListCreateOp>(
memorySchemaClass.getPortType(22), extraPortsList);
memorySchemaClass.getPortType(24), extraPortsList);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did this bump by +2 when others only bumped by +1?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Because it crashed after I initially just increased it by one. 😉

The builder for the ClassOp creates both an input and an output port for each field. So the extraPorts port is shifted by two positions.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, FIRRTL class, doesn't have the fields representation. It uses input and output ports to represent fields, hence the +2

@@ -206,7 +206,8 @@ LowerMemoryPass::emitMemoryModule(MemOp op, const FirMemory &mem,
auto moduleOp = b.create<FMemModuleOp>(
mem.loc, moduleName, ports, mem.numReadPorts, mem.numWritePorts,
mem.numReadWritePorts, mem.dataWidth, mem.maskBits, mem.readLatency,
mem.writeLatency, mem.depth);
mem.writeLatency, mem.depth,
*symbolizeRUWBehavior(uint32_t(mem.readUnderWrite)));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit?

Suggested change
*symbolizeRUWBehavior(uint32_t(mem.readUnderWrite)));
*symbolizeRUWBehavior(static_cast(mem.readUnderWrite)));

Comment on lines -155 to +156
; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %hierarchy_in: !om.list<!om.path>, %inDut_in: i1, %extraPorts_in: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, %preExtInstName_in: !om.list<!om.string>) -> (name: !om.string, depth: !om.integer, width: !om.integer, maskBits: !om.integer, readPorts: !om.integer, writePorts: !om.integer, readwritePorts: !om.integer, writeLatency: !om.integer, readLatency: !om.integer, hierarchy: !om.list<!om.path>, inDut: i1, extraPorts: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, preExtInstName: !om.list<!om.string>)
; MLIR_OUT: om.class.fields %name_in, %depth_in, %width_in, %maskBits_in, %readPorts_in, %writePorts_in, %readwritePorts_in, %writeLatency_in, %readLatency_in, %hierarchy_in, %inDut_in, %extraPorts_in, %preExtInstName_in : !om.string, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.list<!om.path>, i1, !om.list<!om.class.type<@ExtraPortsMemorySchema>>, !om.list<!om.string>
; MLIR_OUT: om.class @MemoryMetadata(%basepath: !om.basepath) -> (foo_m_ext_field: !om.class.type<@MemorySchema>, bar_m_ext_field: !om.class.type<@MemorySchema>, baz_m_ext_field: !om.class.type<@MemorySchema>)
; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %ruwBehavior_in: !om.string, %hierarchy_in: !om.list<!om.path>, %inDut_in: i1, %extraPorts_in: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, %preExtInstName_in: !om.list<!om.string>) -> (name: !om.string, depth: !om.integer, width: !om.integer, maskBits: !om.integer, readPorts: !om.integer, writePorts: !om.integer, readwritePorts: !om.integer, writeLatency: !om.integer, readLatency: !om.integer, ruwBehavior: !om.string, hierarchy: !om.list<!om.path>, inDut: i1, extraPorts: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, preExtInstName: !om.list<!om.string>)
; MLIR_OUT: om.class.fields %name_in, %depth_in, %width_in, %maskBits_in, %readPorts_in, %writePorts_in, %readwritePorts_in, %writeLatency_in, %readLatency_in, %ruwBehavior_in, %hierarchy_in, %inDut_in, %extraPorts_in, %preExtInstName_in : !om.string, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.string, !om.list<!om.path>, i1, !om.list<!om.class.type<@ExtraPortsMemorySchema>>, !om.list<!om.string>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ugh... this is not great to work with. Somebody (not you, likely me) needs to go in and convert all of this to *-SAME checks to make this more manageable.

Comment on lines +1124 to +1139
//------------------------------------------------------------------ (1) OM Info
// CHECK-LABEL: firrtl.class @MemoryMetadata({{.*$}}
// CHECK: %[[memoryObject:.+]] = firrtl.object @MemorySchema
// CHECK: %[[#a:]] = firrtl.string "Old"
// CHECK-NEXT: %[[#ruw:]] = firrtl.object.subfield %[[memoryObject]][ruwBehavior_in]
// CHECK-NEXT: firrtl.propassign %[[#ruw]], %[[#a]]

//-------------------------------------------------------------- (2) Memory JSON
// CHECK-LABEL: emit.file "metadata{{/|\\\\}}seq_mems.json"
// CHECK-NEXT: sv.verbatim "[
// CHECK: \22ruw_behavior\22: \22Old\22

//------------------------------------------------------- (3) Configuration File
// CHECK-LABEL: emit.file "mems.conf"
// CHECK-NEXT: sv.verbatim
// CHECK-SAME: ruw Old
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Very nice test organization. 💯

Comment on lines 70 to 73
{
"class":"sifive.enterprise.firrtl.MarkDUTAnnotation",
"target": "test.test"
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: is the MarkDUTAnnotation necessary?

Ditto for the second split test in this file.

@@ -116,13 +116,13 @@ circuit test:
; CHECK: FILE "metadata/tb_seq_mems.json"
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
; CHECK-SAME: "mask_granularity":8,"extra_ports":[],
; CHECK-SAME: "ruw_behavior": "Undefined","mask_granularity":8,"extra_ports":[],
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure... @prithayan do you have any idea?

@fzi-hielscher
Copy link
Contributor Author

Thanks, @seldridge. It's not a problem for me to delay this a bit more to make sure all known users are prepared. I've also lined up the PR for MemInitAttr on top of this one. So if something needs changing, ideally that should be supported too:
fzi-hielscher#1

The script we are using internally has been forked many years ago from the rocket-chip repo:
https://github.com/chipsalliance/rocket-chip/blob/master/scripts/vlsi_mem_gen
I do not know if this is actively being used anymore.

Copy link
Contributor

@prithayan prithayan left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good. Hopefully will "just work" with outer internal flow. :)

; MLIR_OUT: om.class.fields %name_in, %depth_in, %width_in, %maskBits_in, %readPorts_in, %writePorts_in, %readwritePorts_in, %writeLatency_in, %readLatency_in, %hierarchy_in, %inDut_in, %extraPorts_in, %preExtInstName_in : !om.string, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.list<!om.path>, i1, !om.list<!om.class.type<@ExtraPortsMemorySchema>>, !om.list<!om.string>
; MLIR_OUT: om.class @MemoryMetadata(%basepath: !om.basepath) -> (foo_m_ext_field: !om.class.type<@MemorySchema>, bar_m_ext_field: !om.class.type<@MemorySchema>, baz_m_ext_field: !om.class.type<@MemorySchema>)
; MLIR_OUT: om.class @MemorySchema(%basepath: !om.basepath, %name_in: !om.string, %depth_in: !om.integer, %width_in: !om.integer, %maskBits_in: !om.integer, %readPorts_in: !om.integer, %writePorts_in: !om.integer, %readwritePorts_in: !om.integer, %writeLatency_in: !om.integer, %readLatency_in: !om.integer, %ruwBehavior_in: !om.string, %hierarchy_in: !om.list<!om.path>, %inDut_in: i1, %extraPorts_in: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, %preExtInstName_in: !om.list<!om.string>) -> (name: !om.string, depth: !om.integer, width: !om.integer, maskBits: !om.integer, readPorts: !om.integer, writePorts: !om.integer, readwritePorts: !om.integer, writeLatency: !om.integer, readLatency: !om.integer, ruwBehavior: !om.string, hierarchy: !om.list<!om.path>, inDut: i1, extraPorts: !om.list<!om.class.type<@ExtraPortsMemorySchema>>, preExtInstName: !om.list<!om.string>)
; MLIR_OUT: om.class.fields %name_in, %depth_in, %width_in, %maskBits_in, %readPorts_in, %writePorts_in, %readwritePorts_in, %writeLatency_in, %readLatency_in, %ruwBehavior_in, %hierarchy_in, %inDut_in, %extraPorts_in, %preExtInstName_in : !om.string, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.integer, !om.string, !om.list<!om.path>, i1, !om.list<!om.class.type<@ExtraPortsMemorySchema>>, !om.list<!om.string>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Most likely I'm the culprit, I'll format this properly in another PR.

@@ -116,13 +116,13 @@ circuit test:
; CHECK: FILE "metadata/tb_seq_mems.json"
; CHECK: [{"module_name":"memory","depth":16,"width":8,"masked":"true",
; CHECK-SAME: "read":"true","write":"true","readwrite":"false",
; CHECK-SAME: "mask_granularity":8,"extra_ports":[],
; CHECK-SAME: "ruw_behavior": "Undefined","mask_granularity":8,"extra_ports":[],
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Unfortunately, I've no recollection of why this was added. I'll get rid of it in another PR.

@@ -314,7 +315,7 @@ struct ObjectModelIR {
extraPortsList.push_back(extraPortsObj);
}
auto extraPorts = builderOM.create<ListCreateOp>(
memorySchemaClass.getPortType(22), extraPortsList);
memorySchemaClass.getPortType(24), extraPortsList);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's right, FIRRTL class, doesn't have the fields representation. It uses input and output ports to represent fields, hence the +2

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants