Skip to content

Instantly share code, notes, and snippets.

@fzakaria
Created February 17, 2022 05:27
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save fzakaria/083c1b83a9387f4a5cc37ffa8c299931 to your computer and use it in GitHub Desktop.
Save fzakaria/083c1b83a9387f4a5cc37ffa8c299931 to your computer and use it in GitHub Desktop.
package hw5
import chisel3._
import chisel3.util._
case class CacheParams(capacity: Int, blockSize: Int, associativity: Int, addrLen: Int = 8, bitsPerWord: Int = 8) {
require((1 << addrLen) >= capacity)
require(capacity > blockSize)
require(isPow2(capacity) && isPow2(blockSize) && isPow2(associativity) && isPow2(bitsPerWord))
// inputs capacity & blockSize are in units of words
val numExtMemBlocks = (1 << addrLen) / blockSize
val memBlockAddrBits = log2Ceil(numExtMemBlocks)
val numSets = capacity / blockSize / associativity
val numOffsetBits = log2Ceil(blockSize)
val numIndexBits = log2Ceil(numSets)
val numTagBits = addrLen - (numOffsetBits + numIndexBits)
}
class MockDRAM(p: CacheParams) extends Module {
def CacheBlock(): Vec[UInt] = Vec(p.blockSize, UInt(p.bitsPerWord.W))
// addresses in terms of blocks
val io = IO(new Bundle {
val rAddr = Input(UInt(p.memBlockAddrBits.W))
val rEn = Input(Bool())
val rData = Output(CacheBlock())
val wAddr = Input(UInt(p.memBlockAddrBits.W))
val wEn = Input(Bool())
val wData = Input(CacheBlock())
})
// Fixed memory latency of 1 cycle
val dram = SyncReadMem(p.numExtMemBlocks, CacheBlock())
io.rData := DontCare
when (io.rEn) {
io.rData := dram(io.rAddr)
}
when (io.wEn) {
dram(io.wAddr) := io.wData
}
//printf(p"mem: ${dram(5.U)}\n")
}
object Cache {
val ready :: lookup :: fetch :: Nil = Enum(3)
}
class Cache(val p: CacheParams) extends Module {
val io = IO(new Bundle {
val in = Flipped(Decoupled(new Bundle {
val addr = UInt(p.addrLen.W)
val write = Bool()
val wData = UInt(p.bitsPerWord.W)
}))
val hit = Output(Bool()) // helpful for testing
val out = Valid(UInt(p.bitsPerWord.W)) // sets valid to true to indicate completion (even for writes)
})
// extract fields from address
val tag = io.in.bits.addr(p.addrLen - 1, p.numOffsetBits + p.numIndexBits)
val index = io.in.bits.addr(p.numOffsetBits + p.numIndexBits - 1, p.numOffsetBits)
val offset = io.in.bits.addr(p.numOffsetBits - 1, 0)
// essentially making a type alias to make it easy to declare
def CacheBlock(): Vec[UInt] = Vec(p.blockSize, UInt(p.bitsPerWord.W))
// backing memory
val extMem = Module(new MockDRAM(p))
}
class DMCache(p: CacheParams) extends Cache(p) {
require(p.associativity == 1)
// BEGIN SOLUTION
import Cache._
val state = RegInit(ready)
io.hit := false.B
io.out.bits := DontCare
io.out.valid := false.B
// start off ready
io.in.ready := true.B
class CacheSet extends Bundle {
val tag = UInt(p.numTagBits.W)
val block = CacheBlock()
}
val cache = SyncReadMem(p.numSets, new CacheSet())
val valid = RegInit(VecInit(Seq.fill(p.numSets)(false.B)))
extMem.io.wEn := false.B
extMem.io.wAddr := DontCare
extMem.io.wData := DontCare
extMem.io.rEn := false.B
extMem.io.rAddr := DontCare
val set = cache.read(index)
switch(state) {
is(ready) {
io.in.ready := true.B
io.out.valid := false.B
// input is ready and valid
when(io.in.fire) {
state := lookup
}
}
is(lookup) {
io.in.ready := false.B
io.out.valid := false.B
// if it is valid and our tag is the same it's a hit
io.hit := (valid(index) === true.B) && set.tag === tag
when(io.hit) {
io.out.valid := true.B
when(io.in.bits.write) {
// we are writing
set.block(offset) := io.in.bits.wData
}.otherwise {
// we are reading
io.out.bits := set.block(offset)
}
state := ready
}.otherwise {
// reset
extMem.io.wEn := false.B
extMem.io.rEn := false.B
// case 1: we are valid, therefore the tags did not match
when(valid(index)) {
// in this case, we should write back the cache block
extMem.io.wAddr := io.in.bits.addr / p.blockSize.U
extMem.io.wEn := true.B
extMem.io.wData := set.block
}
// If it is a miss, the cache sends off a request to the external memory for the missing block and moves to the Fetch state.
valid(index) := true.B // always set here
extMem.io.rAddr := io.in.bits.addr / p.blockSize.U
extMem.io.rEn := true.B
set.tag := tag
set.block := extMem.io.rData
state := fetch
}
}
is(fetch) {
io.in.ready := false.B
// For a write (which returns no data), the cache still uses the valid signal in out to indicate it is done.
io.out.valid := true.B
when(io.in.bits.write) {
// we are writing
set.block(offset) := io.in.bits.wData
}.otherwise {
// we are reading
io.out.bits := set.block(offset)
}
state := ready
}
}
printf(p"state: ${state} addr: ${io.in.bits.addr} hit: ${io.hit} tag: ${tag} index: ${index} offset: ${offset} read: ${cache.read(index)}\n")
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment