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

Integrate combin memory into pipelined and single-cycle CPUs #82

Merged
merged 16 commits into from
Aug 8, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
11 changes: 11 additions & 0 deletions src/main/scala/base-cpu.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
// The base abstract CPU module which declares the CoreIO of a CPU
package dinocpu

import chisel3._

/**
* Base CPU module which all CPU models implement
*/
abstract class BaseCPU extends Module {
val io = IO(new CoreIO())
}
2 changes: 1 addition & 1 deletion src/main/scala/components/branchpred.scala
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ class BranchPredIO extends Bundle {
* Base class for all branch predictors. Simply declares the IO and has some
* simple functions for updating saturating counters
*/
class BaseBranchPredictor(val c: CPUConfig) extends Module {
abstract class BaseBranchPredictor(val c: CPUConfig) extends Module {
val io = IO(new BranchPredIO)

// Default value is weakly taken for each branch
Expand Down
4 changes: 2 additions & 2 deletions src/main/scala/components/coreio.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ package dinocpu
import chisel3._

class CoreIO extends Bundle {
jardhu marked this conversation as resolved.
Show resolved Hide resolved
val imem = Flipped(new IMemIO)
val dmem = Flipped(new DMemIO)
val imem = Flipped(new IMemPortIO)
val dmem = Flipped(new DMemPortIO)
}
41 changes: 26 additions & 15 deletions src/main/scala/components/memory/base-memory-components.scala
Original file line number Diff line number Diff line change
Expand Up @@ -8,40 +8,51 @@ import chisel3.util.experimental.loadMemoryFromFile
/**
* Base class for all modular backing memories. Simply declares the IO and the memory file.
*/
class BaseDualPortedMemory(size: Int, memfile: String) extends Module {
abstract class BaseDualPortedMemory(size: Int, memfile: String) extends Module {
def wireMemory (imem: BaseIMemPort, dmem: BaseDMemPort): Unit = {
// Connect memory imem IO to dmem accessor
this.io.imem.request <> imem.io.request
imem.io.response <> this.io.imem.response
this.io.imem.request <> imem.io.bus.request
imem.io.bus.response <> this.io.imem.response
// Connect memory dmem IO to dmem accessor
this.io.dmem.request <> dmem.io.request
dmem.io.response <> this.io.dmem.response
this.io.dmem.request <> dmem.io.bus.request
dmem.io.bus.response <> this.io.dmem.response
}

val io = IO(new Bundle {
val imem = new MemPortBusIO
val dmem = new MemPortBusIO
})
io <> DontCare
io.imem <> 0.U.asTypeOf (new MemPortBusIO)
io.dmem <> 0.U.asTypeOf (new MemPortBusIO)


val memory = Mem(math.ceil(size.toDouble/4).toInt, UInt(32.W))
val memory = Mem(math.ceil(size.toDouble/4).toInt, UInt(32.W))
loadMemoryFromFile(memory, memfile)
}

/**
* Base class for all instruction ports. Simply declares the IO.
*/
class BaseIMemPort extends Module {
val io = IO (new IMemPortIO)
io := DontCare
abstract class BaseIMemPort extends Module {
val io = IO (new Bundle {
val pipeline = new IMemPortIO
val bus = Flipped (new MemPortBusIO)
})

io.pipeline <> 0.U.asTypeOf (new IMemPortIO)
io.bus <> 0.U.asTypeOf (new MemPortBusIO)
}

/**
* Base class for all data ports. Simply declares the IO.
*/
class BaseDMemPort extends Module {
val io = IO (new DMemPortIO)
io := DontCare
io.good := io.response.valid
abstract class BaseDMemPort extends Module {
val io = IO (new Bundle {
val pipeline = new DMemPortIO
val bus = Flipped (new MemPortBusIO)
})

io.pipeline <> 0.U.asTypeOf (new DMemPortIO)
io.bus <> 0.U.asTypeOf (new MemPortBusIO)

io.pipeline.good := io.bus.response.valid
}
74 changes: 37 additions & 37 deletions src/main/scala/components/memory/memory-combin-ports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,21 @@ import dinocpu.MemoryOperation._
*/
class ICombinMemPort extends BaseIMemPort {
// When the pipeline is supplying a high valid signal
when (io.valid) {
when (io.pipeline.valid) {
val request = Wire(new Request)
request := DontCare
request.address := io.address
request.address := io.pipeline.address
request.operation := Read
request.writedata := 0.U

io.request.bits := request
io.request.valid := true.B
io.bus.request.bits := request
io.bus.request.valid := true.B
} .otherwise {
io.request.valid := false.B
io.bus.request.valid := false.B
}

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

/**
Expand All @@ -36,16 +36,16 @@ class ICombinMemPort extends BaseIMemPort {
* The I/O for this module is defined in [[DMemPortIO]].
*/
class DCombinMemPort extends BaseDMemPort {
io.good := io.response.valid && io.memread && !io.memwrite
io.pipeline.good := true.B

when (io.valid && (io.memread || io.memwrite)) {
when (io.pipeline.valid && (io.pipeline.memread || io.pipeline.memwrite)) {
// Check that we are not issuing a read and write at the same time
assert(!(io.memread && io.memwrite))
assert(!(io.pipeline.memread && io.pipeline.memwrite))

io.request.bits.address := io.address
io.request.valid := true.B
io.bus.request.bits.address := io.pipeline.address
io.bus.request.valid := true.B

when (io.memwrite) {
when (io.pipeline.memwrite) {
// We issue a ReadWrite to the backing memory.
// Basic run-down of the ReadWrite operation:
// - DCombinMemPort sends a ReadWrite at a specific address, **addr**.
Expand All @@ -54,65 +54,65 @@ class DCombinMemPort extends BaseDMemPort {
// is masked and sign extended, and sent down io.request.writedata
// - Backing memory receives the modified writedata and feeds it into the memory at **addr**.
// Since this is combinational logic, this should theoretically all resolve in one clock cycle with no issues
io.request.bits.operation := ReadWrite
io.bus.request.bits.operation := ReadWrite
} .otherwise {
// Issue a normal read to the backing memory
io.request.bits.operation := Read
io.bus.request.bits.operation := Read
}
} .otherwise {
// no request coming in so don't send a request out
io.request.valid := false.B
io.bus.request.valid := false.B
}

// Response path
when (io.response.valid) {
when (io.memwrite) {
when (io.bus.response.valid) {
when (io.pipeline.memwrite) {
// Perform writedata modification and send it down io.request.writedata.
val writedata = Wire (UInt (32.W))

// When not writing a whole word
when (io.maskmode =/= 2.U) {
when (io.pipeline.maskmode =/= 2.U) {
// Read in the existing piece of data at the address, so we "overwrite" only part of it
val offset = io.address (1, 0)
val offset = io.pipeline.address (1, 0)
val readdata = Wire (UInt (32.W))

readdata := io.response.bits.data
readdata := io.bus.response.bits.data

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) {
when (io.pipeline.maskmode === 0.U) {
data := readdata & ~(0xff.U << (offset * 8.U))
} .otherwise {
data := readdata & ~(0xffff.U << (offset * 8.U))
}
writedata := data | (io.writedata << (offset * 8.U))
writedata := data | (io.pipeline.writedata << (offset * 8.U))
} .otherwise {
// Write the entire word
writedata := io.writedata
writedata := io.pipeline.writedata
}

io.request.bits.writedata := writedata
} .elsewhen (io.memread) {
io.bus.request.bits.writedata := writedata
} .elsewhen (io.pipeline.memread) {
// Perform normal masking and sign extension on the read data
val readdata_mask = Wire(UInt(32.W))
val readdata_mask_sext = Wire(UInt(32.W))

val offset = io.address(1,0)
when (io.maskmode === 0.U) {
val offset = io.pipeline.address(1,0)
when (io.pipeline.maskmode === 0.U) {
// Byte
readdata_mask := (io.response.bits.data >> (offset * 8.U)) & 0xff.U
} .elsewhen (io.maskmode === 1.U) {
readdata_mask := (io.bus.response.bits.data >> (offset * 8.U)) & 0xff.U
} .elsewhen (io.pipeline.maskmode === 1.U) {
// Half-word
readdata_mask := (io.response.bits.data >> (offset * 8.U)) & 0xffff.U
readdata_mask := (io.bus.response.bits.data >> (offset * 8.U)) & 0xffff.U
} .otherwise {
readdata_mask := io.response.bits.data
readdata_mask := io.bus.response.bits.data
}

when (io.sext) {
when (io.maskmode === 0.U) {
when (io.pipeline.sext) {
when (io.pipeline.maskmode === 0.U) {
// Byte sign extension
readdata_mask_sext := Cat(Fill(24, readdata_mask(7)), readdata_mask(7, 0))
} .elsewhen (io.maskmode === 1.U) {
} .elsewhen (io.pipeline.maskmode === 1.U) {
// Half-word sign extension
readdata_mask_sext := Cat(Fill(16, readdata_mask(15)), readdata_mask(15, 0))
} .otherwise {
Expand All @@ -123,7 +123,7 @@ class DCombinMemPort extends BaseDMemPort {
readdata_mask_sext := readdata_mask
}

io.readdata := readdata_mask_sext
io.pipeline.readdata := readdata_mask_sext
}
}
}
43 changes: 22 additions & 21 deletions src/main/scala/components/memory/memory-noncombin-ports.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ class OutstandingReq extends Bundle {
* The I/O for this module is defined in [[IMemPortIO]].
*/
class INonCombinMemPort extends ICombinMemPort {
io.pipeline.good := io.bus.response.valid
}

/**
Expand All @@ -44,38 +45,38 @@ 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.response.valid && (outstandingReq.valid && outstandingReq.bits.operation === MemoryOperation.Read))
when (io.valid && (io.memread || io.memwrite) && ready) {
val ready = !outstandingReq.valid || (io.bus.response.valid && (outstandingReq.valid && outstandingReq.bits.operation === MemoryOperation.Read))
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.memread && io.memwrite))
assert (! (io.pipeline.memread && io.pipeline.memwrite))

// On either a read or write we must read a whole block from memory. Store the necessary
// information to redirect the memory's response back into itself through a write
// operation and get the right subset of the block on a read.
outstandingReq.bits.address := io.address
outstandingReq.bits.writedata := io.writedata
outstandingReq.bits.maskmode := io.maskmode
outstandingReq.bits.sext := io.sext
when (io.memwrite) {
outstandingReq.bits.address := io.pipeline.address
outstandingReq.bits.writedata := io.pipeline.writedata
outstandingReq.bits.maskmode := io.pipeline.maskmode
outstandingReq.bits.sext := io.pipeline.sext
when (io.pipeline.memwrite) {
outstandingReq.bits.operation := Write
} .otherwise {
outstandingReq.bits.operation := Read
}
sending := true.B

// Program memory to perform a read. Always read since we must read before write.
io.request.bits.address := io.address
io.request.bits.writedata := 0.U
io.request.bits.operation := Read
io.request.valid := true.B
io.bus.request.bits.address := io.pipeline.address
io.bus.request.bits.writedata := 0.U
io.bus.request.bits.operation := Read
io.bus.request.valid := true.B
} .otherwise {
// no request coming in so don't send a request out
io.request.valid := false.B
io.bus.request.valid := false.B
sending := false.B
}

// Response path
when (io.response.valid) {
when (io.bus.response.valid) {
assert(outstandingReq.valid)
when (outstandingReq.bits.operation === MemoryOperation.Write) {
val writedata = Wire (UInt (32.W))
Expand All @@ -85,7 +86,7 @@ class DNonCombinMemPort extends BaseDMemPort {
// Read in the existing piece of data at the address, so we "overwrite" only part of it
val offset = outstandingReq.bits.address (1, 0)
val readdata = Wire (UInt (32.W))
readdata := io.response.bits.data
readdata := io.bus.response.bits.data
val data = Wire (UInt (32.W))
// Mask the portion of the existing data so it can be or'd with the writedata
when (outstandingReq.bits.maskmode === 0.U) {
Expand All @@ -104,8 +105,8 @@ class DNonCombinMemPort extends BaseDMemPort {
request.address := outstandingReq.bits.address
request.writedata := writedata
request.operation := Write
io.request.bits := request
io.request.valid := true.B
io.bus.request.bits := request
io.bus.request.valid := true.B
} .otherwise {
// Response is valid and we don't have a stored write.
// Perform masking and sign extension on read data when memory is outputting it
Expand All @@ -115,12 +116,12 @@ class DNonCombinMemPort extends BaseDMemPort {
val offset = outstandingReq.bits.address(1,0)
when (outstandingReq.bits.maskmode === 0.U) {
// Byte
readdata_mask := (io.response.bits.data >> (offset * 8.U)) & 0xff.U
readdata_mask := (io.bus.response.bits.data >> (offset * 8.U)) & 0xff.U
} .elsewhen (outstandingReq.bits.maskmode === 1.U) {
// Half-word
readdata_mask := (io.response.bits.data >> (offset * 8.U)) & 0xffff.U
readdata_mask := (io.bus.response.bits.data >> (offset * 8.U)) & 0xffff.U
} .otherwise {
readdata_mask := io.response.bits.data
readdata_mask := io.bus.response.bits.data
}

when (outstandingReq.bits.sext) {
Expand All @@ -138,7 +139,7 @@ class DNonCombinMemPort extends BaseDMemPort {
readdata_mask_sext := readdata_mask
}

io.readdata := readdata_mask_sext
io.pipeline.readdata := readdata_mask_sext
}
// Mark the outstanding request register as being invalid, unless sending
outstandingReq.valid := sending
Expand Down
20 changes: 11 additions & 9 deletions src/main/scala/components/memory/memory-port-bus-io.scala
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,17 @@ class Response extends Bundle {
}

/**
* The generic interface for communication between the IMem/DMemPort modules and the backing memory.
*
* Input: request, the ready/valid interface for a MemPort module to issue Requests to. Memory
* will only accept a request when both request.valid (the MemPort is supplying valid data)
* and request.ready (the memory is idling for a request) are high.
*
* Output: response, the valid interface for the data outputted by memory if it was requested to read.
* the bits in response.bits should only be treated as valid data when response.valid is high.
*/
* The generic interface for communication between the IMem/DMemPort modules and the backing memory.
* This interface corresponds with the port <=> memory interface between the
* memory port and the backing memory.
*
* Input: request, the ready/valid interface for a MemPort module to issue Requests to. Memory
* will only accept a request when both request.valid (the MemPort is supplying valid data)
* and request.ready (the memory is idling for a request) are high.
*
* Output: response, the valid interface for the data outputted by memory if it was requested to read.
* the bits in response.bits should only be treated as valid data when response.valid is high.
*/
class MemPortBusIO extends Bundle {
val request = Flipped(Decoupled (new Request))
val response = Valid (new Response)
Expand Down
Loading