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

Add non-combinational CPU model #87

Open
wants to merge 39 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
6dd5567
Merge pull request #3 from jlpteaching/master
Sep 17, 2019
6bddf9a
Update .gitignore to ignore test files
Sep 17, 2019
0b1dc3b
Create non-combin CPU model and a hazard w/ mem-stall module
Sep 17, 2019
8f9ef81
Add ready signal to stable interface
Sep 17, 2019
d752844
Add tester functionality for new memory types
Sep 17, 2019
a0bcf8b
Correct memory test to observe new interface signals
Sep 20, 2019
d26ffab
Fix R-type and I-type instructions
Sep 26, 2019
756432f
Fix memory instructions
Sep 26, 2019
fcd1b77
Fix combin memory to observe ready/good signals
Oct 2, 2019
2201ebf
Commit changes for review and identification of jump bug
Oct 4, 2019
704ef5c
Update .gitignore to ignore test files
Sep 17, 2019
c93af9c
Create non-combin CPU model and a hazard w/ mem-stall module
Sep 17, 2019
6ef5431
Add ready signal to stable interface
Sep 17, 2019
a48682b
Add tester functionality for new memory types
Sep 17, 2019
97cbb31
Correct memory test to observe new interface signals
Sep 20, 2019
0a3ce55
Fix R-type and I-type instructions
Sep 26, 2019
8a3b3ad
Fix memory instructions
Sep 26, 2019
f6665ed
Fix combin memory to observe ready/good signals
Oct 2, 2019
a610275
Commit changes for review and identification of jump bug
Oct 4, 2019
b0d1b15
Move non-combin CPU into 'dinocpu.pipelined' package
Nov 23, 2019
d058956
Fast forward master branch on non-combin-cpu branch
Nov 23, 2019
1f2aa00
Fix an import statement in CPU configuration
Nov 23, 2019
eccd4b8
Merge master commits into non-combin-cpu
Dec 22, 2019
f1a91a3
Copy over regular pipelined CPU over non combinational, for new model…
Dec 22, 2019
f14b973
Update mem stall hazard detector with new stall logic
Dec 27, 2019
bbc8b4e
Fix non-combinational CPU module name
Dec 28, 2019
92c4beb
Fix hazard unit compilation errors and IO signal names
Jan 1, 2020
bfa12e6
Fix R-type and I-type instructions in new non-combin CPU
Jan 1, 2020
b6cb493
Fix non-multi-cycle memory instructions
Jan 3, 2020
66219b2
Update jelf to 0.4.1
Jan 2, 2020
cdd3bb4
Improve the single stepper
powerjg Jan 3, 2020
c739ce3
Remove debugging from Chisel in favor of singlestep
powerjg Jan 3, 2020
12f3b10
Remove leftover debug prints in non-combin CPU
Jan 3, 2020
e274f2b
Fix sw memory instructions
Jan 11, 2020
2c44599
Fix branch and jumps
Jan 14, 2020
eabd6a2
Merge branch 'master' into non-combin-cpu
Jan 14, 2020
4a989d6
Correct incorrect merge in CPUTesterDriver
Jan 15, 2020
2e53b89
Amend load hazard bubbling, fixing ld forwarding.
Jan 18, 2020
018fecd
Fix branching and jump instructions
Jan 18, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -349,3 +349,9 @@ simulator_run_dir
# For containers
*.sif
.treadle_repl_history

# For test binary files, since we need to force CWD to be the root of the dinocpu directory instead
# of test_run_dir.
*.hex
*Top.datastore.snapshot.json
*Top.lo.fir
103 changes: 103 additions & 0 deletions src/main/scala/components/hazardmemstall.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
// This file contains the hazard detection unit

package dinocpu

import chisel3._

/**
* The hazard detection unit, modified for memory stalling
*
* Input: rs1, the first source register number
* Input: rs2, the first source register number
* Input: idex_memread, true if the instruction in the ID/EX register is going to read from memory
* Input: idex_rd, the register number of the destination register for the instruction in the ID/EX register
* Input: exmem_rd, the register number of the destination register for the instruction in the EX/MEM register
* Input: exmem_taken, if true, then we are using the nextpc in the EX/MEM register, *not* pc+4.
* Input: imem_good, high if instruction memory is idle and ready.
* Input: dmem_good, high if data memory is idle and ready.
*
* Output: pcwrite, the value to write to the pc. If 0, pc+4, if 1 the next_pc from the memory stage,
* if 2, then the last pc value (2 stalls the pipeline)
* Output: imem_disable, if true, prevent instruction memory from receiving requests
* Output: ifid_bubble, if true, we should insert a bubble in the IF/ID stage
* Output: ifid_disable, if true, explicitly prevent the IF/ID register from being written to
* Output: ifid_flush, if true, set the IF/ID register to 0
* Output: idex_bubble, if true, we should insert a bubble in the ID/EX stage
* Output: idex_disable, if true, explicitly prevent the ID/EX register from being written to
* Output: exmem_bubble, if true, we should insert a bubble in the EX/MEM stage
* Output: exmem_disable, if true, explicitly prevent the EX/MEM register from being written to
*
* For more information, see Section 4.7 and beginning of 4.8 of Patterson and Hennessy
* This follows the "Data hazards and stalls" section and the "Assume branch not taken" section
Copy link
Contributor

Choose a reason for hiding this comment

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

Is this true or copied from somewhere else?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

The new signals do not follow the pattern. Do you think I should move them beyond that note about the Patterson note and clarify that they are additionally added in?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'd say just drop that part of the comment if it doesn't apply anymore. It's fine to go beyond the textbook :)

*/
class HazardUnitMemStall extends Module {
val io = IO(new Bundle {
val rs1 = Input(UInt(5.W))
val rs2 = Input(UInt(5.W))
val idex_memread = Input(Bool())
val idex_rd = Input(UInt(5.W))
val exmem_memread = Input(Bool())
val exmem_rd = Input(UInt(5.W))
val exmem_taken = Input(Bool())
val imem_ready = Input(Bool())
val dmem_ready = Input(Bool())
val dmem_good = Input(Bool())

val pcwrite = Output(UInt(2.W))
val ifid_bubble = Output(Bool())
val ifid_disable = Output(Bool())
val ifid_flush = Output(Bool())
val idex_bubble = Output(Bool())
val idex_disable = Output(Bool())
val exmem_bubble = Output(Bool())
val exmem_disable = Output(Bool())
})

// default
io.pcwrite := 0.U
io.ifid_bubble := false.B
io.ifid_disable := false.B
io.ifid_flush := false.B
io.idex_bubble := false.B
io.idex_disable := false.B
io.exmem_bubble := false.B
io.exmem_disable := false.B

// Load to use hazard:
// Bubble ifid and idex when a load is in ex, or if it is in ex and the data memory
// hasn't responded with valid data yet (the read data must be present on dmem.io.readdata before we proceed)
when ((io.idex_memread && (io.idex_rd === io.rs1 || io.idex_rd === io.rs2)) ||
(io.exmem_memread || ! io.dmem_ready && (io.exmem_rd === io.rs1 || io.exmem_rd === io.rs2))) {
io.pcwrite := 2.U
io.ifid_bubble := true.B
io.idex_bubble := true.B
}

// imem stall:
// Freeze the PC to preserve the current PC for the entire stall period,
// so that on un-stall the next PC points to the next instruction
// Disable exmem so that re-execution of the same instruction doesn't have any
// residual effect (jal)
when (! io.imem_ready) {
io.pcwrite := 2.U
io.exmem_bubble := true.B
}

// dmem stall
// Freeze the PC to preserve the current PC for imem
// Disable writes for ID/EX
// Disable writes for EX/MEM
when (! io.dmem_ready) {
io.pcwrite := 2.U
io.idex_bubble := true.B
io.exmem_bubble := true.B
}

// branch flush
when (io.exmem_taken) {
io.pcwrite := 1.U // use the PC from mem stage
io.ifid_flush := true.B
io.idex_bubble := true.B
io.exmem_bubble := true.B
}
}
4 changes: 2 additions & 2 deletions src/main/scala/configuration.scala
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,7 @@ package dinocpu
import java.io.File
import dinocpu.components._
import dinocpu.memory._
import dinocpu.pipelined.PipelinedCPU
import dinocpu.pipelined.PipelinedCPUBP
import dinocpu.pipelined._

/**
* This class configures all of the dinocpus. It takes parameters for the type of CPU model
Expand Down Expand Up @@ -45,6 +44,7 @@ class CPUConfig
case "single-cycle" => new SingleCycleCPU
case "pipelined" => new PipelinedCPU
case "pipelined-bp" => new PipelinedCPUBP
case "pipelined-non-combin" => new PipelinedNonCombinCPU
case _ => throw new IllegalArgumentException("Must specify known CPU model")
}
}
Expand Down
2 changes: 2 additions & 0 deletions src/main/scala/memory/base-memory-components.scala
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,8 @@ abstract class BaseIMemPort extends Module {
// ports internally assigning values to the, means that these DontCares
// should be completely 'overwritten' when the CPU is elaborated
io.bus <> DontCare

io.pipeline.good := io.bus.response.valid
}

/**
Expand Down
7 changes: 5 additions & 2 deletions src/main/scala/memory/memory-combin-ports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@ import dinocpu.memory.MemoryOperation._
* The I/O for this module is defined in [[IMemPortIO]].
*/
class ICombinMemPort extends BaseIMemPort {
// Combinational memory is always ready
io.pipeline.ready := true.B

// When the pipeline is supplying a high valid signal
when (io.pipeline.valid) {
val request = Wire(new Request)
Expand All @@ -26,7 +29,6 @@ class ICombinMemPort extends BaseIMemPort {
}

// When the memory is outputting a valid instruction
io.pipeline.good := true.B
io.pipeline.instruction := io.bus.response.bits.data
}

Expand All @@ -36,7 +38,8 @@ class ICombinMemPort extends BaseIMemPort {
* The I/O for this module is defined in [[DMemPortIO]].
*/
class DCombinMemPort extends BaseDMemPort {
io.pipeline.good := true.B
// Combinational memory is always ready
io.pipeline.ready := true.B

when (io.pipeline.valid && (io.pipeline.memread || io.pipeline.memwrite)) {
// Check that we are not issuing a read and write at the same time
Expand Down
30 changes: 27 additions & 3 deletions src/main/scala/memory/memory-noncombin-ports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,26 @@ class OutstandingReq extends Bundle {

/**
* The instruction memory port. Since both the combinational and noncombinational instruction ports just issue
* read requests in the same way both ports have the same implementation
* read requests in the same way both ports share the same implementation
*
* The I/O for this module is defined in [[IMemPortIO]].
*/
class INonCombinMemPort extends ICombinMemPort {
io.pipeline.good := io.bus.response.valid
// Non-combinational memory can technically always accept requests since they are delayed through a pipe.
// But we want to be able to signal that the memory is holding a request, so a register is used to store
// whether a request passed through this memory port
val imemBusy = RegInit (false.B)

when (io.pipeline.valid) {
imemBusy := true.B
} .elsewhen (io.bus.response.valid) {
imemBusy := false.B
}

// Memory is ready when the backing memory responds with valid data, or the busy register is false.
// If io.bus.response.valid is true, then imemBusy must be false as the when statements above updates
// imemBusy on the next cycle.
io.pipeline.ready := (! imemBusy || io.bus.response.valid)
}

/**
Expand All @@ -31,6 +45,11 @@ class INonCombinMemPort extends ICombinMemPort {
* The I/O for this module is defined in [[DMemPortIO]].
*/
class DNonCombinMemPort extends BaseDMemPort {
// Non-combinational memory can technically always accept requests since they are delayed through a pipe.
// But we want to be able to signal that the memory is holding a request, so a register is used to store
// whether a request passed through this memory port
// In this case outstandingReq is adequate for this purpose, as outstandingReq.valid is true when this port is
// withholding either a read or write request

// A register to hold intermediate data (e.g., write data, mask mode) while the request
// is outstanding to memory.
Expand All @@ -46,7 +65,12 @@ class DNonCombinMemPort extends BaseDMemPort {
// Ready if either we don't have an outstanding request or the outstanding request is a read and
// it has been satisfied this cycle. Note: we cannot send a read until one cycle after the write has
// been sent.
val ready = !outstandingReq.valid || (io.bus.response.valid && (outstandingReq.valid && outstandingReq.bits.operation === MemoryOperation.Read))
val wasRequestARead = outstandingReq.valid && outstandingReq.bits.operation === MemoryOperation.Read

val ready = !outstandingReq.valid || (io.bus.response.valid && wasRequestARead)

io.pipeline.ready := ready

when (io.pipeline.valid && (io.pipeline.memread || io.pipeline.memwrite) && ready) {
// Check if we aren't issuing both a read and write at the same time
assert (! (io.pipeline.memread && io.pipeline.memwrite))
Expand Down
7 changes: 6 additions & 1 deletion src/main/scala/memory/memory-port-io.scala
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,17 @@ import chisel3._
*
* Input: address, the address of a piece of data in memory.
* Input: valid, true when the address specified is valid
* Output: good, true when memory is responding with a piece of data (used to un-stall the pipeline)
* Output: good, true when memory is responding with a piece of data (used to correctly write correct data to
* each stage's register)
* Output: ready, true when the memory is ready to accept another request (used to un-stall the pipeline)
*
*/
class MemPortIO extends Bundle {
// Pipeline <=> Port
val address = Input(UInt(32.W))
val valid = Input(Bool())
val good = Output(Bool())
val ready = Output(Bool())
}

/**
Expand All @@ -30,6 +33,7 @@ class MemPortIO extends Bundle {
* Input: valid, true when the address specified is valid
* Output: instruction, the requested instruction
* Output: good, true when memory is responding with a piece of data
* Output: ready, true when the memory is ready to accept another request
*/
class IMemPortIO extends MemPortIO {
val instruction = Output(UInt(32.W))
Expand All @@ -48,6 +52,7 @@ class IMemPortIO extends MemPortIO {
* Input: sext, true if we should sign extend the result
* Output: readdata, the data read and sign extended
* Output: good, true when memory is responding with a piece of data
* Output: ready, true when the memory is ready to accept another request
*/
class DMemPortIO extends MemPortIO {
// Pipeline <=> Port
Expand Down
Loading