Created
February 17, 2022 05:27
-
-
Save fzakaria/083c1b83a9387f4a5cc37ffa8c299931 to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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