Skip to content
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

Implement read-write memory accessors for SyncReadMem #3190

Merged
merged 16 commits into from
Apr 25, 2023

Conversation

jared-barocsi
Copy link
Contributor

@jared-barocsi jared-barocsi commented Apr 17, 2023

Implements SyncReadMem.readWrite(address, writeData, enabled, isWrite) as a way to explicitly instantiate a readwrite port for a memory. This addresses the problem of unintentionally creating multiple read or write ports through multiple calls of SyncReadMem.read() and/or SyncReadMem.write(). Additionally, implements clocked and masked versions of readWrite for parity with the existing read and write functions.

Compare:

val myMem = SyncReadMem(100, UInt(32.W))
val readValue = myMem.read(someAddress)
when (timeToWrite)
  myMem.write(someAddress, valueToWrite)
when (timeToWrite) 
  myMem.write(someAddress, valueToWrite)

which instantiates a 1R2W memory, as opposed to

val myMem = SyncReadMem(100, UInt(32.W))
val rwPort = myMem.readWrite(someAddress, writeData, portEnabled, timeToWrite)

val readValue = rwPort
when (timeToWrite)
  writeData = valueToWrite
when (timeToWrite)
  writeData = differentValueToWrite

which instantiates a 1RW memory.

Contributor Checklist

  • Did you add Scaladoc to every public function/method?
  • Did you add at least one test demonstrating the PR?
  • Did you delete any extraneous printlns/debugging code?
  • Did you specify the type of improvement?
  • Did you add appropriate documentation in docs/src?
  • Did you request a desired merge strategy?
  • Did you add text to be included in the Release Notes for this change?

Type of Improvement

  • Feature (or new API)

Desired Merge Strategy

squash and merge

Release Notes

SyncReadMem.readWrite(address, writeData, enabled, isWrite) explicitly generates a read-write port that supports both read and write access to the memory.

Reviewer Checklist (only modified by reviewer)

  • Did you add the appropriate labels? (Select the most appropriate one based on the "Type of Improvement")
  • Did you mark the proper milestone (Bug fix: 3.5.x or 3.6.x depending on impact, API modification or big change: 5.0.0)?
  • Did you review?
  • Did you check whether all relevant Contributor checkboxes have been checked?
  • Did you do one of the following when ready to merge:
    • Squash: You/ the contributor Enable auto-merge (squash), clean up the commit message, and label with Please Merge.
    • Merge: Ensure that contributor has cleaned up their commit history, then merge with Create a merge commit.

@jackkoenig jackkoenig added the Feature New feature, will be included in release notes label Apr 18, 2023
@mwachs5
Copy link
Contributor

mwachs5 commented Apr 18, 2023

Can we add this to the Memories explanations doc

@mwachs5
Copy link
Contributor

mwachs5 commented Apr 18, 2023

Or add a new cookbook "how to I make a 1readwrite port memory, how do I make a 1r1w port memory"?

* val mem = Module(new MyMemWrapper)
* mem.io := DontCare
*
* val rdata = Wire(UInt(2.W))
Copy link
Contributor

Choose a reason for hiding this comment

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

this code would not quite compile

Comment on lines 431 to 432
* val rdata = Wire(UInt(2.W))
* readData := DontCare
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
* val rdata = Wire(UInt(2.W))
* readData := DontCare
* val rdata = Wire(UInt(2.W))
* rdata := DontCare

* readData := DontCare
*
* // Read from the memory port
* when(doRead) {
Copy link
Contributor

Choose a reason for hiding this comment

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

what is doRead? an Input?

* }
* // Read data will show up on the next cycle
* when(timeToRead) {
* readData := mem.io.rdata
Copy link
Contributor

@mwachs5 mwachs5 Apr 19, 2023

Choose a reason for hiding this comment

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

this is a little confusing, the user is going to have to understand that the rddata is available the following cycle. Let's just omit this part

*
* val rdata = Wire(UInt(2.W))
* readData := DontCare
*
Copy link
Contributor

Choose a reason for hiding this comment

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

we should default mem.io.enable to false.B

@mwachs5
Copy link
Contributor

mwachs5 commented Apr 21, 2023

This LGTM. I am not sure if there are any issues on the CIRCT side in interpreting this. Can @jackkoenig @seldridge give a review?

@jared-barocsi jared-barocsi enabled auto-merge (squash) April 21, 2023 17:22
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.

A long missing API. Great work!

Minor nits throughout. Good to land whenever you're ready.

Comment on lines +473 to +474
val a = Wire(UInt())
a := DontCare
Copy link
Member

Choose a reason for hiding this comment

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

Using val _a indicates better that this is a temporary. Same for port. Basically, I would use _ for any hardware component created in a library/utility.

Copy link
Member

Choose a reason for hiding this comment

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

Nits/golf:

Suggested change
val a = Wire(UInt())
a := DontCare
val _a = WireDefault(chiselTypeOf(addr), DontCare)

Copy link
Member

Choose a reason for hiding this comment

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

Same comments for the masked version.

Copy link
Contributor

Choose a reason for hiding this comment

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

Is the declaration of a separate from the connection of addr to it required for the enable to infer correctly @jared-barocsi? Or why else is is split?

Also are we sure returning the port works? Such value capturing is often problematic for lexical scoping, what happens if someone uses the return value of this function? Perhaps we should be assigning the read data to a wire? The integration test is passing so that suggests the lexical scoping is okay or at least supported by firtool but I'd be curious to see what the FIRRTL actually looks like.

* }
* }}}
*
* @note this is only allowed if the memory's element data type is a Vec
Copy link
Member

Choose a reason for hiding this comment

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

At least in FIRRTL this is allowed so long as the type of the mask is the same type as the underlying data type with the leaf types as all 1-bit UInts. I.e., this is supported on any aggregate including bundles.

This is a relatively niche feature and pretty rare. I am fine to start this as only working for Vec (which makes sense).

@@ -188,6 +188,156 @@ private class TrueDualPortMemory(addrW: Int, dataW: Int) extends RawModule {
}
}

class MemReadWriteTester extends BasicTester {
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 a huge fan of big integration tests like this. However, this is entirely consistent with the rest of this existing file.

Can you open an issue to move all refactor this to move the integration tests into the integration test area and slim these down to just checking the CHIRRTL?

@jared-barocsi jared-barocsi merged commit 1e6f2b4 into main Apr 25, 2023
@jared-barocsi jared-barocsi deleted the mem-readwrite-port-apis branch April 25, 2023 03:45
@seldridge
Copy link
Member

I didn't notice you had auto-merge on. 😂 Any cleanup/feedback can happen post-merge. 😉

Comment on lines +228 to +229
enable := true.B;
isWrite := false.B;
Copy link
Contributor

Choose a reason for hiding this comment

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

Can you please remove all of the semicolons in this file?

@jackkoenig jackkoenig added this to the 3.6.x milestone Apr 25, 2023
@mergify mergify bot added the Backported This PR has been backported label Apr 25, 2023
mergify bot pushed a commit that referenced this pull request Apr 25, 2023
Adds new APIs to generate explicit read-write (rdwr) ports for a SyncReadMem, and all masked or clocked variants.
---------

Co-authored-by: Megan Wachs <megan@sifive.com>
(cherry picked from commit 1e6f2b4)
mergify bot pushed a commit that referenced this pull request Apr 25, 2023
Adds new APIs to generate explicit read-write (rdwr) ports for a SyncReadMem, and all masked or clocked variants.
---------

Co-authored-by: Megan Wachs <megan@sifive.com>
(cherry picked from commit 1e6f2b4)
mergify bot added a commit that referenced this pull request Apr 25, 2023
Adds new APIs to generate explicit read-write (rdwr) ports for a SyncReadMem, and all masked or clocked variants.
---------

Co-authored-by: Megan Wachs <megan@sifive.com>
(cherry picked from commit 1e6f2b4)

Co-authored-by: Jared Barocsi <82000041+jared-barocsi@users.noreply.github.com>
Comment on lines +558 to +574
)(
implicit evidence: T <:< Vec[_]
): T = masked_readWrite_impl(idx, writeData, mask, en, isWrite, clock, true)

private def masked_readWrite_impl(
addr: UInt,
data: T,
mask: Seq[Bool],
enable: Bool,
isWrite: Bool,
clock: Clock,
warn: Boolean
)(
implicit evidence: T <:< Vec[_]
): T = {
implicit val sourceInfo = UnlocatableSourceInfo
val a = Wire(UInt())
Copy link
Contributor

Choose a reason for hiding this comment

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

It would also be good to have a source locator here, since we're already taking an implicit argument we can just take the sourceInfo argument. Users won't be able to immediately call .apply on the return value, but due to the existing implicit argument, they already can't.

mergify bot added a commit that referenced this pull request Apr 26, 2023
#3213)

* Implement read-write memory accessors for SyncReadMem (#3190)

Adds new APIs to generate explicit read-write (rdwr) ports for a SyncReadMem, and all masked or clocked variants.
---------

Co-authored-by: Megan Wachs <megan@sifive.com>
(cherry picked from commit 1e6f2b4)

* Add CompileOptions to SyncReadMem.readWrite methods

---------

Co-authored-by: Jared Barocsi <82000041+jared-barocsi@users.noreply.github.com>
Co-authored-by: Jack Koenig <koenig@sifive.com>
Co-authored-by: mergify[bot] <37929162+mergify[bot]@users.noreply.github.com>
@sequencer sequencer mentioned this pull request Jun 20, 2023
14 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Backported This PR has been backported Feature New feature, will be included in release notes
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants