-
Notifications
You must be signed in to change notification settings - Fork 38
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
Conversation
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. |
I was able to get the combinational loop out, it was a bit more complicated than splitting the signals. Whenever we go into a Ideally I would like for the memory to be enabled whenever |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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) { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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 = { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
There was a problem hiding this 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.
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 aCombinMemoryTestHarness
that is structurally the same as theNonCombinMemoryTestHarness
.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 aCombinMemoryUnitTest
andNonCombinMemoryUnitTest
(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 aCombinMemoryUnitTest
orNonCombinMemoryUnitTest
individually if needed. This might be out of scope, but it should be a small change to the code.