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 combinational modular memory #78

Merged
merged 13 commits into from
Jul 10, 2019

Conversation

jardhu
Copy link
Collaborator

@jardhu jardhu commented Jun 24, 2019

This PR creates a modular implementation of a combinational memory that should function exactly the same as the DualPortedMemory. It will interface with the pipeline in the exact same manner as the non-combinational memory, and I included a tester for the memory which utilizes a CombinMemoryTestHarness that is structurally the same as the NonCombinMemoryTestHarness.

I also moved all memory component files into their own directory and renamed modules from the "asynchronous"/"synchronous" naming scheme to "NonCombin"/"Combin" respectively.

There is one issue with the implementation though, and that is that moving the masking logic for writing data into the data memory port introduces a combinational loop. I'm trying to find an elegant way around this that avoids including data manipulation code in the backing memory.

One last thing I want to do is to change the MemoryUnitTest into a testing suite that sets up a CombinMemoryUnitTest and NonCombinMemoryUnitTest (and any other future memory tests), and runs them all. This is so we can test all memory components in one single request, or run a CombinMemoryUnitTest or NonCombinMemoryUnitTest individually if needed. This might be out of scope, but it should be a small change to the code.

@nganjehloo
Copy link
Contributor

nganjehloo commented Jun 25, 2019

For the combinational loop issue, I think having so many nest bundles for IO is causing this issue. I don't think there is a real loop based off the code I've read. (although i've been known to be wrong 99% of the time :P) You will have to split the signals in a more explicit manner so that they can be differentiated from each other more easily. I think the layers of bundles used to build a Request bundle is the cause of the issue. Also try to be explicit with what is an input and what is an output in your chisel. There is a way to force ignore the loop warning, but lets not do that just incase.

@jardhu
Copy link
Collaborator Author

jardhu commented Jun 30, 2019

I was able to get the combinational loop out, it was a bit more complicated than splitting the signals. Whenever we go into a when block with io.dmem.request.valid we can't use any of the request's other signals or else we end up introducing a combinational loop. So I basically pulled out the code that wires the memory out of the when loop. This fix relies on the fact that when the pipeline isn't supplying a request the wires in the memory/port buses are all set to DontCare, so the memory conveniently doesn't do anything when no memory operation is being done, but as I see it, this code would be fragile if we were to send unintended signals.

Ideally I would like for the memory to be enabled whenever io.dmem.request.valid is true just to make sure that the backing memory won't do anything when there is no valid request, but I'm unable to find some kind of enable feature in the Mem module. I think maybe I can force the memory's signals to be DontCare if io.dmem.response.valid is low.

Copy link
Contributor

@powerjg powerjg left a comment

Choose a reason for hiding this comment

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

This is looking great!

One thing that we need to do is write some documentation. This interface is getting complicated (which seems to be required, though). We need some pictures and a clear description of what are the interfaces and how students (and others) should interact with this code.

Things we need to document

  • What is a port? It's a "smart" component that connects the pipeline to the memory.
    • What "smarts" are in the port?
    • Why different kinds of ports?
    • What are the interfaces?
  • What are the different kinds of memories?
    • When should you use each one
    • How to configure the system for each one
  • What are the stable interfaces?
  • How can this be extended (e.g., what to modify to make a cache?)

Note: this documentation can be in another patch :).

/**
* Base class for all modular backing memories. Simply declares the IO and the memory file.
*/
class BaseDualPortedMemory(size: Int, memfile: String) extends Module {
Copy link
Contributor

Choose a reason for hiding this comment

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

Are these supposed to be abstract? I don't know exactly how to do this in scala, but maybe we should make it more explicit that these shouldn't be instantiated.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

I suppose so, but I observed that the BaseBranchPredictor module isn't abstract as well so I figured that maybe there was some intention for this. If this shouldn't be the case then I'll correct both the base memory modules and the base branch predictor accordingly.

Copy link
Contributor

Choose a reason for hiding this comment

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

Right... Let's do this in a separate patch. I'd like to see this merged sooner rather than later.

For the branch predictor... I was on a deadline and couldn't figure out how to make the class abstract, IIRC. ;)

class Request extends Bundle {
val address = UInt(32.W)
val writedata = UInt(32.W)
val operation = UInt(1.W)
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs to be two bits.

Is there a way to type it as a MemoryOperation since you have the enum? There should be a way to infer the width (in case we add another operation type).


// When the memory is outputting a valid instruction
io.good := io.response.valid
when (io.response.valid) {
Copy link
Contributor

Choose a reason for hiding this comment

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

This when is probably not necessary. The pipeline should ignore the io.instruction if !io.good


val data = Wire (UInt (32.W))
// Mask the portion of the existing data so it can be or'd with the writedata
when (io.maskmode === 0.U) {
Copy link
Contributor

Choose a reason for hiding this comment

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

I assume the plan is to split this logic out into a function in the BaseDMemPort in another patch since the code is replicated in NonCombinMemPort.

io.request.valid := true.B

when (io.memwrite) {
// We issue a ReadWrite to the backing memory.
Copy link
Contributor

Choose a reason for hiding this comment

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

This all makes sense to me.

* Base class for all modular backing memories. Simply declares the IO and the memory file.
*/
class BaseDualPortedMemory(size: Int, memfile: String) extends Module {
def wireMemPipe(portio: MemPortBusIO): Unit = {
Copy link
Contributor

Choose a reason for hiding this comment

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

This function is only used in the DualPortedCombinMemory, right? The NonCombin version has a different version (with a different signature) of this function. I think it would be better to move this into DualPortedCombinMemory

// Check that address is pointing to a valid location in memory
assert (request.address < size.U)
} .otherwise {
memAddress := DontCare
Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm... This is strange. I would have expected this to also introduce a combinational loop (or we still have the possibility that we're writing bogus data to memory).

Note: I tried just moving the "read path" and "write path" above into the when (io.dmem.request.valid) statement and it "just worked" for me. I don't see a combinational loop.

@powerjg powerjg mentioned this pull request Jul 9, 2019
9 tasks
Copy link
Contributor

@powerjg powerjg left a comment

Choose a reason for hiding this comment

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

I believe this is now good to go. There's a bit that needs to be cleaned up, but we can do that as the pipeline is updated.

@jardhu jardhu merged commit 9e9404a into jlpteaching:master Jul 10, 2019
@jardhu jardhu deleted the combinational-memory branch July 10, 2019 19:59
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.

None yet

3 participants