#60 Got the new instruction cache design passing the standard regressions
This commit is contained in:
parent
bc0af02c97
commit
fd4da77084
|
@ -21,7 +21,7 @@ trait Pipeline {
|
|||
|
||||
def service[T](clazz : Class[T]) = {
|
||||
val filtered = plugins.filter(o => clazz.isAssignableFrom(o.getClass))
|
||||
assert(filtered.length == 1)
|
||||
assert(filtered.length == 1, s"??? ${clazz.getName}")
|
||||
filtered.head.asInstanceOf[T]
|
||||
}
|
||||
|
||||
|
|
|
@ -81,7 +81,6 @@ object TestsWorkspace {
|
|||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true,
|
||||
atomicEntriesCount = 2
|
||||
),
|
||||
// memoryTranslatorPortConfig = null
|
||||
|
|
|
@ -88,8 +88,7 @@ object BrieyConfig{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
|
|
|
@ -41,8 +41,7 @@ object GenFull extends App{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
portTlbSize = 6
|
||||
|
|
|
@ -42,8 +42,7 @@ object GenFullNoMmu extends App{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
)
|
||||
),
|
||||
new StaticMemoryTranslatorPlugin(
|
||||
|
|
|
@ -43,8 +43,7 @@ object GenFullNoMmuMaxPerf extends App{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = false
|
||||
catchUnaligned = true
|
||||
)
|
||||
),
|
||||
new StaticMemoryTranslatorPlugin(
|
||||
|
|
|
@ -96,76 +96,77 @@ object LinuxGen {
|
|||
new DummyFencePlugin(), //TODO should be removed for design with caches
|
||||
|
||||
//Uncomment the whole IBusSimplePlugin and comment IBusCachedPlugin if you want uncached iBus config
|
||||
// new IBusSimplePlugin(
|
||||
// resetVector = 0x80000000l,
|
||||
// cmdForkOnSecondStage = false,
|
||||
// cmdForkPersistence = false,
|
||||
// prediction = NONE,
|
||||
// historyRamSizeLog2 = 10,
|
||||
// catchAccessFault = true,
|
||||
// compressedGen = true,
|
||||
// busLatencyMin = 1,
|
||||
// injectorStage = true,
|
||||
// memoryTranslatorPortConfig = withMmu generate MmuPortConfig(
|
||||
// portTlbSize = 4
|
||||
// )
|
||||
// ),
|
||||
|
||||
//Uncomment the whole IBusCachedPlugin and comment IBusSimplePlugin if you want cached iBus config
|
||||
new IBusCachedPlugin(
|
||||
new IBusSimplePlugin(
|
||||
resetVector = 0x80000000l,
|
||||
compressedGen = true,
|
||||
cmdForkOnSecondStage = false,
|
||||
cmdForkPersistence = false,
|
||||
prediction = NONE,
|
||||
injectorStage = true,
|
||||
config = InstructionCacheConfig(
|
||||
cacheSize = 4096,
|
||||
bytePerLine = 32,
|
||||
wayCount = 1,
|
||||
addressWidth = 32,
|
||||
cpuDataWidth = 32,
|
||||
memDataWidth = 32,
|
||||
catchIllegalAccess = true,
|
||||
catchAccessFault = true,
|
||||
asyncTagMemory = false,
|
||||
twoCycleRam = false,
|
||||
twoCycleCache = true
|
||||
),
|
||||
memoryTranslatorPortConfig = MmuPortConfig(
|
||||
portTlbSize = 4
|
||||
)
|
||||
),
|
||||
// ).newTightlyCoupledPort(TightlyCoupledPortParameter("iBusTc", a => a(30 downto 28) === 0x0 && a(5))),
|
||||
new DBusSimplePlugin(
|
||||
catchAddressMisaligned = true,
|
||||
historyRamSizeLog2 = 10,
|
||||
catchAccessFault = true,
|
||||
earlyInjection = false,
|
||||
atomicEntriesCount = 1,
|
||||
compressedGen = true,
|
||||
busLatencyMin = 1,
|
||||
injectorStage = true,
|
||||
memoryTranslatorPortConfig = withMmu generate MmuPortConfig(
|
||||
portTlbSize = 4
|
||||
)
|
||||
),
|
||||
// new DBusCachedPlugin(
|
||||
// config = new DataCacheConfig(
|
||||
// cacheSize = 4096,
|
||||
// bytePerLine = 32,
|
||||
// wayCount = 1,
|
||||
// addressWidth = 32,
|
||||
// cpuDataWidth = 32,
|
||||
// memDataWidth = 32,
|
||||
// catchAccessError = true,
|
||||
// catchIllegal = true,
|
||||
// catchUnaligned = true,
|
||||
// catchMemoryTranslationMiss = true,
|
||||
// atomicEntriesCount = 2
|
||||
// ),
|
||||
// // memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
// portTlbSize = 6
|
||||
// )
|
||||
// ),
|
||||
// new StaticMemoryTranslatorPlugin(
|
||||
// ioRange = _(31 downto 28) === 0xF
|
||||
// ),
|
||||
|
||||
//Uncomment the whole IBusCachedPlugin and comment IBusSimplePlugin if you want cached iBus config
|
||||
// new IBusCachedPlugin(
|
||||
// resetVector = 0x80000000l,
|
||||
// compressedGen = true,
|
||||
// prediction = NONE,
|
||||
// injectorStage = true,
|
||||
// config = InstructionCacheConfig(
|
||||
// cacheSize = 4096,
|
||||
// bytePerLine = 32,
|
||||
// wayCount = 1,
|
||||
// addressWidth = 32,
|
||||
// cpuDataWidth = 32,
|
||||
// memDataWidth = 32,
|
||||
// catchIllegalAccess = true,
|
||||
// catchAccessFault = true,
|
||||
// asyncTagMemory = false,
|
||||
// twoCycleRam = false,
|
||||
// twoCycleCache = true
|
||||
// )
|
||||
// ),
|
||||
// memoryTranslatorPortConfig = MmuPortConfig(
|
||||
// portTlbSize = 4
|
||||
// )
|
||||
// ),
|
||||
// ).newTightlyCoupledPort(TightlyCoupledPortParameter("iBusTc", a => a(30 downto 28) === 0x0 && a(5))),
|
||||
// new DBusSimplePlugin(
|
||||
// catchAddressMisaligned = true,
|
||||
// catchAccessFault = true,
|
||||
// earlyInjection = false,
|
||||
// atomicEntriesCount = 1,
|
||||
// memoryTranslatorPortConfig = withMmu generate MmuPortConfig(
|
||||
// portTlbSize = 4
|
||||
// )
|
||||
// ),
|
||||
new DBusCachedPlugin(
|
||||
config = new DataCacheConfig(
|
||||
cacheSize = 4096,
|
||||
bytePerLine = 32,
|
||||
wayCount = 1,
|
||||
addressWidth = 32,
|
||||
cpuDataWidth = 32,
|
||||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
atomicEntriesCount = 2
|
||||
)
|
||||
// ),
|
||||
// memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MmuPortConfig(
|
||||
// portTlbSize = 4
|
||||
// )
|
||||
),
|
||||
new StaticMemoryTranslatorPlugin(
|
||||
ioRange = _(31 downto 28) === 0xF
|
||||
),
|
||||
// new MemoryTranslatorPlugin(
|
||||
// tlbSize = 32,
|
||||
// virtualRange = _(31 downto 28) === 0xC,
|
||||
|
@ -237,12 +238,12 @@ object LinuxGen {
|
|||
new YamlPlugin("cpu0.yaml")
|
||||
)
|
||||
)
|
||||
if(withMmu) config.plugins += new MmuPlugin(
|
||||
virtualRange = a => True,
|
||||
// virtualRange = x => x(31 downto 24) =/= 0x81, //TODO It fix the DTB kernel access (workaround)
|
||||
ioRange = (x => if(litex) x(31 downto 28) === 0xB || x(31 downto 28) === 0xE || x(31 downto 28) === 0xF else x(31 downto 28) === 0xF),
|
||||
allowUserIo = true
|
||||
)
|
||||
// if(withMmu) config.plugins += new MmuPlugin(
|
||||
// virtualRange = a => True,
|
||||
// // virtualRange = x => x(31 downto 24) =/= 0x81, //TODO It fix the DTB kernel access (workaround)
|
||||
// ioRange = (x => if(litex) x(31 downto 28) === 0xB || x(31 downto 28) === 0xE || x(31 downto 28) === 0xF else x(31 downto 28) === 0xF),
|
||||
// allowUserIo = true
|
||||
// )
|
||||
config
|
||||
}
|
||||
|
||||
|
@ -265,7 +266,7 @@ object LinuxGen {
|
|||
// }
|
||||
// }
|
||||
|
||||
SpinalConfig(mergeAsyncProcess = true).generateVerilog {
|
||||
SpinalConfig(mergeAsyncProcess = true, anonymSignalPrefix = "zz").generateVerilog {
|
||||
|
||||
|
||||
val toplevel = new VexRiscv(configFull(
|
||||
|
|
|
@ -66,8 +66,7 @@ object VexRiscvAvalonForSim{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
|
|
|
@ -63,8 +63,7 @@ object VexRiscvAvalonWithIntegratedJtag{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
|
|
|
@ -64,8 +64,7 @@ object VexRiscvAxi4WithIntegratedJtag{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
|
|
|
@ -62,8 +62,7 @@ object VexRiscvCachedWishboneForSim{
|
|||
memDataWidth = 32,
|
||||
catchAccessError = true,
|
||||
catchIllegal = true,
|
||||
catchUnaligned = true,
|
||||
catchMemoryTranslationMiss = true
|
||||
catchUnaligned = true
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
// memoryTranslatorPortConfig = MemoryTranslatorPortConfig(
|
||||
|
|
|
@ -8,23 +8,24 @@ import spinal.lib.bus.avalon.{AvalonMM, AvalonMMConfig}
|
|||
import spinal.lib.bus.wishbone.{Wishbone, WishboneConfig}
|
||||
import spinal.lib.bus.simple._
|
||||
|
||||
case class DataCacheConfig( cacheSize : Int,
|
||||
bytePerLine : Int,
|
||||
wayCount : Int,
|
||||
addressWidth : Int,
|
||||
cpuDataWidth : Int,
|
||||
memDataWidth : Int,
|
||||
catchAccessError : Boolean,
|
||||
catchIllegal : Boolean,
|
||||
catchUnaligned : Boolean,
|
||||
catchMemoryTranslationMiss : Boolean,
|
||||
clearTagsAfterReset : Boolean = true,
|
||||
waysHitRetime : Boolean = true,
|
||||
tagSizeShift : Int = 0, //Used to force infering ram
|
||||
atomicEntriesCount : Int = 0){
|
||||
case class DataCacheConfig(cacheSize : Int,
|
||||
bytePerLine : Int,
|
||||
wayCount : Int,
|
||||
addressWidth : Int,
|
||||
cpuDataWidth : Int,
|
||||
memDataWidth : Int,
|
||||
catchAccessError : Boolean,
|
||||
catchIllegal : Boolean,
|
||||
catchUnaligned : Boolean,
|
||||
earlyWaysHits : Boolean = true,
|
||||
earlyDataMux : Boolean = false,
|
||||
tagSizeShift : Int = 0, //Used to force infering ram
|
||||
atomicEntriesCount : Int = 0){
|
||||
|
||||
assert(!(earlyDataMux && !earlyWaysHits))
|
||||
def burstSize = bytePerLine*8/memDataWidth
|
||||
val burstLength = bytePerLine/(memDataWidth/8)
|
||||
def catchSomething = catchUnaligned || catchMemoryTranslationMiss || catchIllegal || catchAccessError
|
||||
def catchSomething = catchUnaligned || catchIllegal || catchAccessError
|
||||
def genAtomic = atomicEntriesCount != 0
|
||||
|
||||
def getAxi4SharedConfig() = Axi4Config(
|
||||
|
@ -64,83 +65,6 @@ case class DataCacheConfig( cacheSize : Int,
|
|||
)
|
||||
}
|
||||
|
||||
|
||||
object Bypasser{
|
||||
|
||||
//shot readValid path
|
||||
def writeFirstMemWrap[T <: Data](readValid : Bool, readLastAddress : UInt, readLastData : T,writeValid : Bool, writeAddress : UInt, writeData : T) : T = {
|
||||
val writeSample = readValid || (writeValid && writeAddress === readLastAddress)
|
||||
val writeValidReg = RegNextWhen(writeValid,writeSample)
|
||||
val writeAddressReg = RegNextWhen(writeAddress,writeSample)
|
||||
val writeDataReg = RegNextWhen(writeData,writeSample)
|
||||
(writeValidReg && writeAddressReg === readLastAddress) ? writeDataReg | readLastData
|
||||
}
|
||||
|
||||
|
||||
//short readValid path
|
||||
def writeFirstMemWrap(readValid : Bool, readLastAddress : UInt, readLastData : Bits,writeValid : Bool, writeAddress : UInt, writeData : Bits,writeMask : Bits) : Bits = {
|
||||
val writeHit = writeValid && writeAddress === readLastAddress
|
||||
val writeSample = readValid || writeHit
|
||||
val writeValidReg = RegNextWhen(writeValid,writeSample)
|
||||
val writeAddressReg = RegNextWhen(writeAddress,writeSample)
|
||||
val writeDataReg = Reg(writeData)
|
||||
val writeMaskReg = Reg(Bits(widthOf(writeData)/8 bits))
|
||||
val writeDataRegBytes = writeDataReg.subdivideIn(8 bits)
|
||||
val writeDataBytes = writeData.subdivideIn(8 bits)
|
||||
val ret = cloneOf(readLastData)
|
||||
val retBytes = ret.subdivideIn(8 bits)
|
||||
val readLastDataBytes = readLastData.subdivideIn(8 bits)
|
||||
val writeRegHit = writeValidReg && writeAddressReg === readLastAddress
|
||||
for(b <- writeMask.range){
|
||||
when(writeHit && writeMask(b)){
|
||||
writeMaskReg(b) := True
|
||||
}
|
||||
when(readValid) {
|
||||
writeMaskReg(b) := writeMask(b)
|
||||
}
|
||||
when(readValid || (writeHit && writeMask(b))){
|
||||
writeDataRegBytes(b) := writeDataBytes(b)
|
||||
}
|
||||
|
||||
retBytes(b) := (writeRegHit && writeMaskReg(b)) ? writeDataRegBytes(b) | readLastDataBytes(b)
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
//Long sample path
|
||||
// def writeFirstRegWrap[T <: Data](sample : Bool, sampleAddress : UInt,lastAddress : UInt, readData : T, writeValid : Bool, writeAddress : UInt, writeData : T) : (T,T) = {
|
||||
// val hit = writeValid && (sample ? sampleAddress | lastAddress) === writeAddress
|
||||
// val bypass = hit ? writeData | readData
|
||||
// val reg = RegNextWhen(bypass,sample || hit)
|
||||
// (reg,bypass)
|
||||
// }
|
||||
|
||||
//Short sample path
|
||||
def writeFirstRegWrap[T <: Data](sample : Bool, sampleAddress : UInt,sampleLastAddress : UInt, sampleData : T, writeValid : Bool, writeAddress : UInt, writeData : T) = {
|
||||
val bypass = (!sample || (writeValid && sampleAddress === writeAddress)) ? writeData | sampleData
|
||||
val regEn = sample || (writeValid && sampleLastAddress === writeAddress)
|
||||
val reg = RegNextWhen(bypass,regEn)
|
||||
reg
|
||||
}
|
||||
|
||||
def writeFirstRegWrap(sample : Bool, sampleAddress : UInt,sampleLastAddress : UInt, sampleData : Bits, writeValid : Bool, writeAddress : UInt, writeData : Bits,writeMask : Bits) = {
|
||||
val byteCount = widthOf(writeMask)
|
||||
val sampleWriteHit = writeValid && sampleAddress === writeAddress
|
||||
val sampleLastHit = writeValid && sampleLastAddress === writeAddress
|
||||
val regBytes = Vec(Bits(8 bits),byteCount)
|
||||
for(b <- writeMask.range){
|
||||
val bypass = Mux(!sample || (sampleWriteHit && writeMask(b)), writeData(b*8, 8 bits), sampleData(b*8, 8 bits))
|
||||
val regEn = sample || (sampleLastHit && writeMask(b))
|
||||
regBytes(b) := RegNextWhen(bypass,regEn)
|
||||
}
|
||||
regBytes.asBits
|
||||
}
|
||||
}
|
||||
|
||||
object DataCacheCpuCmdKind extends SpinalEnum{
|
||||
val MEMORY,MANAGMENT = newElement()
|
||||
}
|
||||
|
||||
object DataCacheCpuExecute{
|
||||
implicit def implArgs(that : DataCacheCpuExecute) = that.args
|
||||
}
|
||||
|
@ -148,23 +72,22 @@ object DataCacheCpuExecute{
|
|||
case class DataCacheCpuExecute(p : DataCacheConfig) extends Bundle with IMasterSlave{
|
||||
val isValid = Bool
|
||||
val isStuck = Bool
|
||||
val address = UInt(p.addressWidth bit)
|
||||
// val haltIt = Bool
|
||||
val args = DataCacheCpuExecuteArgs(p)
|
||||
|
||||
override def asMaster(): Unit = {
|
||||
out(isValid, isStuck, args)
|
||||
out(isValid, isStuck, args, address)
|
||||
// in(haltIt)
|
||||
}
|
||||
}
|
||||
|
||||
case class DataCacheCpuExecuteArgs(p : DataCacheConfig) extends Bundle{
|
||||
val kind = DataCacheCpuCmdKind()
|
||||
val wr = Bool
|
||||
val address = UInt(p.addressWidth bit)
|
||||
//val address = UInt(p.addressWidth bit) Given on the side, as it's also part of the main pipeline
|
||||
val data = Bits(p.cpuDataWidth bit)
|
||||
val size = UInt(2 bits)
|
||||
val forceUncachedAccess = Bool
|
||||
val clean, invalidate, way = Bool
|
||||
val isAtomic = ifGen(p.genAtomic){Bool}
|
||||
// val all = Bool //Address should be zero when "all" is used
|
||||
}
|
||||
|
@ -173,12 +96,11 @@ case class DataCacheCpuMemory(p : DataCacheConfig) extends Bundle with IMasterSl
|
|||
val isValid = Bool
|
||||
val isStuck = Bool
|
||||
val isRemoved = Bool
|
||||
val haltIt = Bool
|
||||
val address = UInt(p.addressWidth bit)
|
||||
val mmuBus = MemoryTranslatorBus()
|
||||
|
||||
override def asMaster(): Unit = {
|
||||
out(isValid, isStuck, isRemoved)
|
||||
in(haltIt)
|
||||
out(isValid, isStuck, isRemoved, address)
|
||||
slave(mmuBus)
|
||||
}
|
||||
}
|
||||
|
@ -190,14 +112,15 @@ case class DataCacheCpuWriteBack(p : DataCacheConfig) extends Bundle with IMaste
|
|||
val isUser = Bool
|
||||
val haltIt = Bool
|
||||
val data = Bits(p.cpuDataWidth bit)
|
||||
val mmuMiss, illegalAccess, unalignedAccess , accessError = Bool
|
||||
val badAddr = UInt(32 bits)
|
||||
val address = UInt(p.addressWidth bit)
|
||||
val mmuException, unalignedAccess , accessError = Bool
|
||||
val clearAtomicEntries = ifGen(p.genAtomic) {Bool}
|
||||
|
||||
// val exceptionBus = if(p.catchSomething) Flow(ExceptionCause()) else null
|
||||
|
||||
override def asMaster(): Unit = {
|
||||
out(isValid,isStuck,isUser)
|
||||
in(haltIt, data, mmuMiss,illegalAccess , unalignedAccess, accessError, badAddr)
|
||||
out(isValid,isStuck,isUser, address)
|
||||
in(haltIt, data, mmuException, unalignedAccess, accessError)
|
||||
outWithNull(clearAtomicEntries)
|
||||
}
|
||||
}
|
||||
|
@ -207,10 +130,13 @@ case class DataCacheCpuBus(p : DataCacheConfig) extends Bundle with IMasterSlave
|
|||
val memory = DataCacheCpuMemory(p)
|
||||
val writeBack = DataCacheCpuWriteBack(p)
|
||||
|
||||
val redo = Bool()
|
||||
|
||||
override def asMaster(): Unit = {
|
||||
master(execute)
|
||||
master(memory)
|
||||
master(writeBack)
|
||||
in(redo)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -370,7 +296,6 @@ case class DataCacheMemBus(p : DataCacheConfig) extends Bundle with IMasterSlave
|
|||
|
||||
class DataCache(p : DataCacheConfig) extends Component{
|
||||
import p._
|
||||
import DataCacheCpuCmdKind._
|
||||
assert(wayCount == 1)
|
||||
assert(cpuDataWidth == memDataWidth)
|
||||
|
||||
|
@ -379,6 +304,7 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
val mem = master(DataCacheMemBus(p))
|
||||
// val flushDone = out Bool //It pulse at the same time than the manager.request.fire
|
||||
}
|
||||
|
||||
val haltCpu = False
|
||||
val lineWidth = bytePerLine*8
|
||||
val lineCount = cacheSize/bytePerLine
|
||||
|
@ -397,14 +323,13 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
|
||||
|
||||
class LineInfo() extends Bundle{
|
||||
val used = Bool
|
||||
val dirty = Bool
|
||||
val valid, error = Bool()
|
||||
val address = UInt(tagRange.length bit)
|
||||
}
|
||||
|
||||
val tagsReadCmd = Flow(UInt(log2Up(wayLineCount) bits))
|
||||
val tagsWriteCmd = Flow(new Bundle{
|
||||
// val way = UInt(log2Up(wayCount) bits)
|
||||
val way = Bits(wayCount bits)
|
||||
val address = UInt(log2Up(wayLineCount) bits)
|
||||
val data = new LineInfo()
|
||||
})
|
||||
|
@ -413,13 +338,39 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
|
||||
val dataReadCmd = Flow(UInt(log2Up(wayWordCount) bits))
|
||||
val dataWriteCmd = Flow(new Bundle{
|
||||
// val way = UInt(log2Up(wayCount) bits)
|
||||
val way = Bits(wayCount bits)
|
||||
val address = UInt(log2Up(wayWordCount) bits)
|
||||
val data = Bits(wordWidth bits)
|
||||
val mask = Bits(wordWidth/8 bits)
|
||||
})
|
||||
|
||||
|
||||
|
||||
io.mem.cmd.valid := False
|
||||
io.mem.cmd.payload.assignDontCare()
|
||||
|
||||
val ways = for(i <- 0 until wayCount) yield new Area{
|
||||
val tags = Mem(new LineInfo(), wayLineCount)
|
||||
val data = Mem(Bits(wordWidth bit), wayWordCount)
|
||||
|
||||
//Reads
|
||||
val tagsReadRsp = tags.readSync(tagsReadCmd.payload, tagsReadCmd.valid && !io.cpu.execute.isStuck)
|
||||
val dataReadRsp = data.readSync(dataReadCmd.payload, dataReadCmd.valid && !io.cpu.execute.isStuck)
|
||||
|
||||
//Writes
|
||||
when(tagsWriteCmd.valid && tagsWriteCmd.way(i)){
|
||||
tags(tagsWriteCmd.address) := tagsWriteCmd.data
|
||||
}
|
||||
when(dataWriteCmd.valid && dataWriteCmd.way(i)){
|
||||
data.write(
|
||||
address = dataWriteCmd.address,
|
||||
data = dataWriteCmd.data,
|
||||
mask = dataWriteCmd.mask
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
tagsReadCmd.valid := False
|
||||
tagsReadCmd.payload.assignDontCare()
|
||||
dataReadCmd.valid := False
|
||||
|
@ -428,219 +379,74 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
tagsWriteCmd.payload.assignDontCare()
|
||||
dataWriteCmd.valid := False
|
||||
dataWriteCmd.payload.assignDontCare()
|
||||
io.mem.cmd.valid := False
|
||||
io.mem.cmd.payload.assignDontCare()
|
||||
|
||||
|
||||
val way = new Area{
|
||||
val tags = Mem(new LineInfo(),wayLineCount)
|
||||
val data = Mem(Bits(wordWidth bit),wayWordCount)
|
||||
|
||||
when(tagsWriteCmd.valid){
|
||||
tags(tagsWriteCmd.address) := tagsWriteCmd.data
|
||||
}
|
||||
when(dataWriteCmd.valid){
|
||||
data.write(
|
||||
address = dataWriteCmd.address,
|
||||
data = dataWriteCmd.data,
|
||||
mask = dataWriteCmd.mask
|
||||
)
|
||||
}
|
||||
|
||||
val tagReadRspOneAddress = RegNextWhen(tagsReadCmd.payload, tagsReadCmd.valid)
|
||||
val tagReadRspOne = Bypasser.writeFirstMemWrap(
|
||||
readValid = tagsReadCmd.valid,
|
||||
readLastAddress = tagReadRspOneAddress,
|
||||
readLastData = tags.readSync(tagsReadCmd.payload,enable = tagsReadCmd.valid),
|
||||
writeValid = tagsWriteCmd.valid,
|
||||
writeAddress = tagsWriteCmd.address,
|
||||
writeData = tagsWriteCmd.data
|
||||
)
|
||||
|
||||
val dataReadRspOneKeepAddress = False
|
||||
val dataReadRspOneAddress = RegNextWhen(dataReadCmd.payload, dataReadCmd.valid && !dataReadRspOneKeepAddress)
|
||||
val dataReadRspOneWithoutBypass = data.readSync(dataReadCmd.payload,enable = dataReadCmd.valid)
|
||||
val dataReadRspOne = Bypasser.writeFirstMemWrap(
|
||||
readValid = dataReadCmd.valid,
|
||||
readLastAddress = dataReadRspOneAddress,
|
||||
readLastData = dataReadRspOneWithoutBypass,
|
||||
writeValid = dataWriteCmd.valid,
|
||||
writeAddress = dataWriteCmd.address,
|
||||
writeData = dataWriteCmd.data,
|
||||
writeMask = dataWriteCmd.mask
|
||||
)
|
||||
|
||||
val tagReadRspTwoEnable = !io.cpu.writeBack.isStuck
|
||||
val tagReadRspTwoRegIn = (tagsWriteCmd.valid && tagsWriteCmd.address === tagReadRspOneAddress) ? tagsWriteCmd.data | tagReadRspOne
|
||||
val tagReadRspTwo = RegNextWhen(tagReadRspTwoRegIn ,tagReadRspTwoEnable)
|
||||
|
||||
|
||||
val dataReadRspTwoEnable = !io.cpu.writeBack.isStuck
|
||||
val dataReadRspTwo = Bypasser.writeFirstRegWrap(
|
||||
sample = dataReadRspTwoEnable,
|
||||
sampleAddress = dataReadRspOneAddress,
|
||||
sampleLastAddress = RegNextWhen(dataReadRspOneAddress, dataReadRspTwoEnable),
|
||||
sampleData = dataReadRspOne,
|
||||
writeValid = dataWriteCmd.valid,
|
||||
writeAddress = dataWriteCmd.address,
|
||||
writeData = dataWriteCmd.data,
|
||||
writeMask = dataWriteCmd.mask
|
||||
)
|
||||
}
|
||||
|
||||
when(io.cpu.execute.isValid && !io.cpu.execute.isStuck){
|
||||
tagsReadCmd.valid := True
|
||||
tagsReadCmd.valid := True
|
||||
dataReadCmd.valid := True
|
||||
tagsReadCmd.payload := io.cpu.execute.address(lineRange)
|
||||
|
||||
dataReadCmd.valid := True
|
||||
dataReadCmd.payload := io.cpu.execute.address(lineRange.high downto wordRange.low) //TODO FMAX maybe critical path could be default
|
||||
dataReadCmd.payload := io.cpu.execute.address(lineRange.high downto wordRange.low)
|
||||
}
|
||||
|
||||
|
||||
val cpuMemoryStageNeedReadData = Bool()
|
||||
|
||||
val victim = new Area{
|
||||
val requestIn = Stream(cloneable(new Bundle{
|
||||
// val way = UInt(log2Up(wayCount) bits)
|
||||
val address = UInt(p.addressWidth bits)
|
||||
}))
|
||||
requestIn.valid := False
|
||||
requestIn.payload.assignDontCare()
|
||||
|
||||
val request = requestIn.halfPipe()
|
||||
request.ready := False
|
||||
|
||||
val buffer = Mem(Bits(p.memDataWidth bits),memTransactionPerLine << tagSizeShift) // WARNING << tagSizeShift could resolve cyclone II issue, //.add(new AttributeString("ramstyle","M4K"))
|
||||
|
||||
//Send line read commands to fill the buffer
|
||||
val readLineCmdCounter = Reg(UInt(log2Up(memTransactionPerLine + 1) bits)) init(0)
|
||||
val dataReadCmdOccure = False
|
||||
val dataReadRestored = RegInit(False)
|
||||
when(request.valid){
|
||||
when(!readLineCmdCounter.msb) {
|
||||
readLineCmdCounter := readLineCmdCounter + 1
|
||||
//dataReadCmd := request.address(lineRange.high downto wordRange.low) Done in the manager
|
||||
dataReadCmdOccure := True
|
||||
dataReadCmd.valid := True
|
||||
dataReadCmd.payload := request.address(lineRange) @@ readLineCmdCounter(readLineCmdCounter.high - 1 downto 0)
|
||||
way.dataReadRspOneKeepAddress := True
|
||||
} otherwise {
|
||||
when(!dataReadRestored && cpuMemoryStageNeedReadData) {
|
||||
dataReadCmd.valid := True
|
||||
dataReadCmd.payload := way.dataReadRspOneAddress //Restore stage one readed value
|
||||
}
|
||||
dataReadRestored := True
|
||||
}
|
||||
}
|
||||
|
||||
dataReadRestored clearWhen(request.ready)
|
||||
io.cpu.memory.haltIt := cpuMemoryStageNeedReadData && request.valid && !dataReadRestored
|
||||
|
||||
//Fill the buffer with line read responses
|
||||
val readLineRspCounter = Reg(UInt(log2Up(memTransactionPerLine + 1) bits)) init(0)
|
||||
when(Delay(dataReadCmdOccure,1, init=False)){
|
||||
buffer(readLineRspCounter.resized) := way.dataReadRspOneWithoutBypass
|
||||
readLineRspCounter := readLineRspCounter + 1
|
||||
}
|
||||
|
||||
//Send buffer read commands
|
||||
val bufferReadCounter = Reg(UInt(log2Up(memTransactionPerLine + 1) bits)) init(0)
|
||||
val bufferReadStream = Stream(buffer.addressType)
|
||||
bufferReadStream.valid := readLineRspCounter > bufferReadCounter
|
||||
bufferReadStream.payload := bufferReadCounter.resized
|
||||
when(bufferReadStream.fire){
|
||||
bufferReadCounter := bufferReadCounter + 1
|
||||
}
|
||||
val bufferReaded = buffer.streamReadSync(bufferReadStream).stage
|
||||
bufferReaded.ready := False
|
||||
|
||||
//Send memory writes from bufffer read responses
|
||||
val bufferReadedCounter = Reg(UInt(log2Up(memTransactionPerLine) bits)) init(0)
|
||||
val memCmdAlreadyUsed = False
|
||||
when(bufferReaded.valid) {
|
||||
io.mem.cmd.valid := True
|
||||
io.mem.cmd.wr := True
|
||||
io.mem.cmd.address := request.address(tagRange.high downto lineRange.low) @@ U(0,lineRange.low bit)
|
||||
io.mem.cmd.length := p.burstLength-1
|
||||
io.mem.cmd.data := bufferReaded.payload
|
||||
io.mem.cmd.mask := (1<<(wordWidth/8))-1
|
||||
io.mem.cmd.last := bufferReadedCounter === bufferReadedCounter.maxValue
|
||||
|
||||
when(!memCmdAlreadyUsed && io.mem.cmd.ready){
|
||||
bufferReaded.ready := True
|
||||
bufferReadedCounter := bufferReadedCounter + 1
|
||||
when(bufferReadedCounter === bufferReadedCounter.maxValue){
|
||||
request.ready := True
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
val counter = Counter(memTransactionPerLine)
|
||||
when(request.ready){
|
||||
readLineCmdCounter.msb := False
|
||||
readLineRspCounter.msb := False
|
||||
bufferReadCounter.msb := False
|
||||
def collisionProcess(readAddress : UInt, readMask : Bits): Bits ={
|
||||
val ret = Bits(wayCount bits)
|
||||
for(i <- 0 until wayCount){
|
||||
ret(i) := dataWriteCmd.valid && dataWriteCmd.way(i) && dataWriteCmd.address === readAddress && (readMask & dataWriteCmd.mask) =/= 0
|
||||
}
|
||||
ret
|
||||
}
|
||||
|
||||
|
||||
|
||||
val stageA = new Area{
|
||||
val request = RegNextWhen(io.cpu.execute.args, !io.cpu.memory.isStuck)
|
||||
io.cpu.memory.mmuBus.cmd.isValid := io.cpu.memory.isValid && request.kind === MEMORY //TODO filter request kind
|
||||
io.cpu.memory.mmuBus.cmd.virtualAddress := request.address
|
||||
io.cpu.memory.mmuBus.cmd.bypassTranslation := request.way
|
||||
io.cpu.memory.mmuBus.end := !io.cpu.memory.isStuck || io.cpu.memory.isRemoved
|
||||
cpuMemoryStageNeedReadData := io.cpu.memory.isValid && request.kind === MEMORY && !request.wr
|
||||
}
|
||||
|
||||
val stageB = new Area {
|
||||
val request = RegNextWhen(stageA.request, !io.cpu.writeBack.isStuck)
|
||||
val mmuRsp = RegNextWhen(io.cpu.memory.mmuBus.rsp, !io.cpu.writeBack.isStuck)
|
||||
val waysHit = if(waysHitRetime)
|
||||
RegNextWhen(way.tagReadRspTwoRegIn.used && io.cpu.memory.mmuBus.rsp.physicalAddress(tagRange) === way.tagReadRspTwoRegIn.address,!io.cpu.writeBack.isStuck) //Manual retiming
|
||||
else
|
||||
way.tagReadRspTwo.used && mmuRsp.physicalAddress(tagRange) === way.tagReadRspTwo.address
|
||||
|
||||
|
||||
//Loader interface
|
||||
val loaderValid = False
|
||||
val loaderReady = False
|
||||
val loadingDone = RegNext(loaderValid && loaderReady) init(False) //one cycle pulse
|
||||
|
||||
//delayedXX are used to relax logic timings in flush and evict modes
|
||||
val delayedIsStuck = RegNext(io.cpu.writeBack.isStuck)
|
||||
val delayedWaysHitValid = RegNext(waysHit)
|
||||
|
||||
val victimNotSent = RegInit(False) clearWhen(victim.requestIn.ready) setWhen(!io.cpu.memory.isStuck)
|
||||
val loadingNotDone = RegInit(False) clearWhen(loaderReady) setWhen(!io.cpu.memory.isStuck)
|
||||
|
||||
val writeMask = request.size.mux (
|
||||
val stage0 = new Area{
|
||||
val mask = io.cpu.execute.size.mux (
|
||||
U(0) -> B"0001",
|
||||
U(1) -> B"0011",
|
||||
default -> B"1111"
|
||||
) |<< mmuRsp.physicalAddress(1 downto 0)
|
||||
) |<< io.cpu.execute.address(1 downto 0)
|
||||
val colisions = collisionProcess(io.cpu.execute.address(lineRange.high downto wordRange.low), mask)
|
||||
}
|
||||
|
||||
val stageA = new Area{
|
||||
def stagePipe[T <: Data](that : T) = RegNextWhen(that, !io.cpu.memory.isStuck)
|
||||
val request = stagePipe(io.cpu.execute.args)
|
||||
val mask = stagePipe(stage0.mask)
|
||||
io.cpu.memory.mmuBus.cmd.isValid := io.cpu.memory.isValid
|
||||
io.cpu.memory.mmuBus.cmd.virtualAddress := io.cpu.memory.address
|
||||
io.cpu.memory.mmuBus.cmd.bypassTranslation := False
|
||||
io.cpu.memory.mmuBus.end := !io.cpu.memory.isStuck || io.cpu.memory.isRemoved
|
||||
|
||||
val wayHits = earlyWaysHits generate ways.map(way => (io.cpu.memory.mmuBus.rsp.physicalAddress(tagRange) === way.tagsReadRsp.address && way.tagsReadRsp.valid))
|
||||
val dataMux = earlyDataMux generate MuxOH(wayHits, ways.map(_.dataReadRsp))
|
||||
val colisions = stagePipe(stage0.colisions) | collisionProcess(io.cpu.memory.address(lineRange.high downto wordRange.low), mask) //Assume the writeback stage will never be unstall memory acces while memory stage is stalled
|
||||
}
|
||||
|
||||
val stageB = new Area {
|
||||
def stagePipe[T <: Data](that : T) = RegNextWhen(that, !io.cpu.writeBack.isStuck)
|
||||
val request = RegNextWhen(stageA.request, !io.cpu.writeBack.isStuck)
|
||||
val mmuRspFreeze = False
|
||||
val mmuRsp = RegNextWhen(io.cpu.memory.mmuBus.rsp, !io.cpu.writeBack.isStuck && !mmuRspFreeze)
|
||||
val tagsReadRsp = ways.map(w => stagePipe(w.tagsReadRsp))
|
||||
val dataReadRsp = !earlyDataMux generate ways.map(w => stagePipe(w.dataReadRsp))
|
||||
val waysHits = if(earlyWaysHits) stagePipe(B(stageA.wayHits)) else B(tagsReadRsp.map(tag => mmuRsp.physicalAddress(tagRange) === tag.address && tag.valid).asBits())
|
||||
val waysHit = waysHits.orR
|
||||
val dataMux = if(earlyDataMux) stagePipe(stageA.dataMux) else MuxOH(waysHits, dataReadRsp)
|
||||
val mask = stagePipe(stageA.mask)
|
||||
val colisions = stagePipe(stageA.colisions)
|
||||
|
||||
//Loader interface
|
||||
val loaderValid = False
|
||||
|
||||
|
||||
val hadMemRspErrorReg = RegInit(False)
|
||||
val hadMemRspError = (io.mem.rsp.valid && io.mem.rsp.error) || hadMemRspErrorReg
|
||||
hadMemRspErrorReg := hadMemRspError && io.cpu.writeBack.haltIt
|
||||
|
||||
io.cpu.writeBack.haltIt := io.cpu.writeBack.isValid
|
||||
io.cpu.writeBack.mmuMiss := False
|
||||
io.cpu.writeBack.illegalAccess := False
|
||||
io.cpu.writeBack.unalignedAccess := False
|
||||
io.cpu.writeBack.accessError := (if(catchAccessError) hadMemRspError && !io.cpu.writeBack.haltIt else False)
|
||||
io.cpu.writeBack.badAddr := request.address
|
||||
|
||||
//Evict the cache after reset logics
|
||||
val bootEvicts = if(clearTagsAfterReset) new Area {
|
||||
val flusher = new Area {
|
||||
val valid = RegInit(True)
|
||||
mmuRsp.physicalAddress init (0)
|
||||
when(valid) {
|
||||
tagsWriteCmd.valid := valid
|
||||
tagsWriteCmd.address := mmuRsp.physicalAddress(lineRange)
|
||||
tagsWriteCmd.data.used := False
|
||||
tagsWriteCmd.way.setAll()
|
||||
tagsWriteCmd.data.valid := False
|
||||
when(mmuRsp.physicalAddress(lineRange) =/= lineCount - 1) {
|
||||
mmuRsp.physicalAddress.getDrivingReg(lineRange) := mmuRsp.physicalAddress(lineRange) + 1
|
||||
io.cpu.writeBack.haltIt := True
|
||||
|
@ -651,7 +457,7 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
}
|
||||
|
||||
|
||||
val atomic = if(genAtomic) new Area{
|
||||
val atomic = genAtomic generate new Area{
|
||||
case class AtomicEntry() extends Bundle{
|
||||
val valid = Bool()
|
||||
val size = UInt(2 bits)
|
||||
|
@ -664,11 +470,11 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
}
|
||||
val entries = Vec(Reg(AtomicEntry()).init, atomicEntriesCount)
|
||||
val entriesAllocCounter = Counter(atomicEntriesCount)
|
||||
val entriesHit = entries.map(e => e.valid && e.size === request.size && e.address === request.address).orR
|
||||
val entriesHit = entries.map(e => e.valid && e.size === request.size && e.address === io.cpu.writeBack.address).orR
|
||||
when(io.cpu.writeBack.isValid && request.isAtomic && !request.wr){
|
||||
entries(entriesAllocCounter).valid := True
|
||||
entries(entriesAllocCounter).size := request.size
|
||||
entries(entriesAllocCounter).address := request.address
|
||||
entries(entriesAllocCounter).size := request.size //TODO remove size stuff
|
||||
entries(entriesAllocCounter).address := io.cpu.writeBack.address
|
||||
when(!io.cpu.writeBack.isStuck){
|
||||
entriesAllocCounter.increment()
|
||||
}
|
||||
|
@ -676,128 +482,127 @@ class DataCache(p : DataCacheConfig) extends Component{
|
|||
when(io.cpu.writeBack.clearAtomicEntries){
|
||||
entries.foreach(_.valid := False)
|
||||
}
|
||||
}
|
||||
|
||||
val memCmdSent = RegInit(False) setWhen (io.mem.cmd.ready) clearWhen (!io.cpu.writeBack.isStuck)
|
||||
|
||||
io.cpu.redo := mmuRsp.refilling
|
||||
io.cpu.writeBack.accessError := False
|
||||
io.cpu.writeBack.mmuException := io.cpu.writeBack.isValid && (if(catchIllegal) mmuRsp.exception || (!mmuRsp.allowWrite && request.wr) || (!mmuRsp.allowRead && !request.wr) || (!mmuRsp.allowUser && io.cpu.writeBack.isUser) else False)
|
||||
io.cpu.writeBack.unalignedAccess := io.cpu.writeBack.isValid && (if(catchUnaligned) ((request.size === 2 && mmuRsp.physicalAddress(1 downto 0) =/= 0) || (request.size === 1 && mmuRsp.physicalAddress(0 downto 0) =/= 0)) else False)
|
||||
|
||||
when(request.isAtomic && ! entriesHit){
|
||||
writeMask := 0
|
||||
}
|
||||
} else null
|
||||
|
||||
when(io.cpu.writeBack.isValid) {
|
||||
if (catchMemoryTranslationMiss) {
|
||||
io.cpu.writeBack.mmuMiss := ??? //TODO mmuRsp.miss
|
||||
}
|
||||
switch(request.kind) {
|
||||
is(MANAGMENT) {
|
||||
when(delayedIsStuck && ???){ //TODO!mmuRsp.miss) {
|
||||
when(delayedWaysHitValid || (request.way && way.tagReadRspTwo.used)) {
|
||||
io.cpu.writeBack.haltIt.clearWhen(!(victim.requestIn.valid && !victim.requestIn.ready))
|
||||
victim.requestIn.valid := request.clean && way.tagReadRspTwo.dirty
|
||||
tagsWriteCmd.valid := victim.requestIn.ready
|
||||
} otherwise{
|
||||
io.cpu.writeBack.haltIt := False
|
||||
}
|
||||
}
|
||||
when(request.forceUncachedAccess || mmuRsp.isIoAccess) {
|
||||
io.cpu.writeBack.haltIt.clearWhen(request.wr ? io.mem.cmd.ready | io.mem.rsp.valid)
|
||||
|
||||
victim.requestIn.address := way.tagReadRspTwo.address @@ mmuRsp.physicalAddress(lineRange) @@ U((lineRange.low - 1 downto 0) -> false)
|
||||
tagsWriteCmd.address := mmuRsp.physicalAddress(lineRange)
|
||||
tagsWriteCmd.data.used := !request.invalidate
|
||||
tagsWriteCmd.data.dirty := !request.clean
|
||||
}
|
||||
is(MEMORY) {
|
||||
val illegal = if(catchIllegal) (request.wr && !mmuRsp.allowWrite) || (!request.wr && !mmuRsp.allowRead) || (io.cpu.writeBack.isUser && !mmuRsp.allowUser) else False
|
||||
val unaligned = if(catchUnaligned) ((request.size === 2 && mmuRsp.physicalAddress(1 downto 0) =/= 0) || (request.size === 1 && mmuRsp.physicalAddress(0 downto 0) =/= 0)) else False
|
||||
io.cpu.writeBack.illegalAccess := illegal
|
||||
io.cpu.writeBack.unalignedAccess := unaligned
|
||||
when((Bool(!catchMemoryTranslationMiss) || ???) && !illegal && !unaligned) { //TODO !mmuRsp.miss
|
||||
when(request.forceUncachedAccess || mmuRsp.isIoAccess) {
|
||||
val memCmdSent = RegInit(False)
|
||||
when(!victim.request.valid) {
|
||||
//Avoid mixing memory request while victim is pending
|
||||
io.mem.cmd.wr := request.wr
|
||||
io.mem.cmd.address := mmuRsp.physicalAddress(tagRange.high downto wordRange.low) @@ U(0, wordRange.low bit)
|
||||
io.mem.cmd.mask := writeMask
|
||||
io.mem.cmd.data := request.data
|
||||
io.mem.cmd.length := 0
|
||||
io.mem.cmd.last := True
|
||||
io.mem.cmd.valid := !memCmdSent
|
||||
io.mem.cmd.wr := request.wr
|
||||
io.mem.cmd.address := mmuRsp.physicalAddress(tagRange.high downto wordRange.low) @@ U(0, wordRange.low bit)
|
||||
io.mem.cmd.mask := mask
|
||||
io.mem.cmd.data := request.data
|
||||
io.mem.cmd.length := 0
|
||||
io.mem.cmd.last := True
|
||||
} otherwise {
|
||||
when(waysHit || request.wr) { //Do not require a cache refill ?
|
||||
//Data cache update
|
||||
dataWriteCmd.valid setWhen(request.wr && waysHit)
|
||||
dataWriteCmd.address := mmuRsp.physicalAddress(lineRange.high downto wordRange.low)
|
||||
dataWriteCmd.data := request.data
|
||||
dataWriteCmd.mask := mask
|
||||
dataWriteCmd.way := waysHits
|
||||
|
||||
when(!memCmdSent) {
|
||||
io.mem.cmd.valid := True
|
||||
memCmdSent setWhen (io.mem.cmd.ready)
|
||||
}
|
||||
//Write through
|
||||
io.mem.cmd.valid setWhen(request.wr)
|
||||
io.mem.cmd.wr := True
|
||||
io.mem.cmd.address := mmuRsp.physicalAddress(tagRange.high downto wordRange.low) @@ U(0, wordRange.low bit)
|
||||
io.mem.cmd.mask := mask
|
||||
io.mem.cmd.data := request.data
|
||||
io.mem.cmd.length := 0
|
||||
io.mem.cmd.last := True
|
||||
io.cpu.writeBack.haltIt clearWhen(!request.wr || io.mem.cmd.ready)
|
||||
|
||||
io.cpu.writeBack.haltIt.clearWhen(memCmdSent && (io.mem.rsp.fire || request.wr)) //Cut mem.cmd.ready path but insert one cycle stall when write
|
||||
}
|
||||
memCmdSent clearWhen (!io.cpu.writeBack.isStuck)
|
||||
} otherwise {
|
||||
when(waysHit || !loadingNotDone) {
|
||||
io.cpu.writeBack.haltIt := False
|
||||
dataWriteCmd.valid := request.wr
|
||||
dataWriteCmd.address := mmuRsp.physicalAddress(lineRange.high downto wordRange.low)
|
||||
dataWriteCmd.data := request.data
|
||||
dataWriteCmd.mask := writeMask
|
||||
//On write to read colisions
|
||||
io.cpu.redo := !request.wr && (colisions & waysHits) =/= 0
|
||||
} otherwise { //Do refill
|
||||
|
||||
tagsWriteCmd.valid := (!loadingNotDone) || request.wr
|
||||
tagsWriteCmd.address := mmuRsp.physicalAddress(lineRange)
|
||||
tagsWriteCmd.data.used := True
|
||||
tagsWriteCmd.data.dirty := request.wr
|
||||
tagsWriteCmd.data.address := mmuRsp.physicalAddress(tagRange)
|
||||
} otherwise {
|
||||
val victimRequired = way.tagReadRspTwo.used && way.tagReadRspTwo.dirty
|
||||
loaderValid := loadingNotDone && !(victimNotSent && victim.request.isStall) //Additional condition used to be sure of that all previous victim are written into the RAM
|
||||
victim.requestIn.valid := victimRequired && victimNotSent
|
||||
victim.requestIn.address := way.tagReadRspTwo.address @@ mmuRsp.physicalAddress(lineRange) @@ U((lineRange.low - 1 downto 0) -> false)
|
||||
}
|
||||
}
|
||||
}
|
||||
//Emit cmd
|
||||
io.mem.cmd.valid setWhen(!memCmdSent)
|
||||
io.mem.cmd.wr := False
|
||||
io.mem.cmd.address := mmuRsp.physicalAddress(tagRange.high downto lineRange.low) @@ U(0,lineRange.low bit)
|
||||
io.mem.cmd.length := p.burstLength-1
|
||||
io.mem.cmd.last := True
|
||||
|
||||
loaderValid setWhen(io.mem.cmd.ready)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
when(request.forceUncachedAccess || mmuRsp.isIoAccess){
|
||||
io.cpu.writeBack.data := io.mem.rsp.data
|
||||
if(catchAccessError) io.cpu.writeBack.accessError := io.mem.rsp.valid && io.mem.rsp.error
|
||||
} otherwise {
|
||||
io.cpu.writeBack.data := dataMux
|
||||
if(catchAccessError) io.cpu.writeBack.accessError := (waysHits & B(tagsReadRsp.map(_.error))) =/= 0
|
||||
}
|
||||
|
||||
//remove side effects on exceptions
|
||||
when(mmuRsp.refilling || io.cpu.writeBack.accessError || io.cpu.writeBack.mmuException || io.cpu.writeBack.unalignedAccess){
|
||||
io.mem.cmd.valid := False
|
||||
tagsWriteCmd.valid := False
|
||||
dataWriteCmd.valid := False
|
||||
loaderValid := False
|
||||
io.cpu.writeBack.haltIt := False
|
||||
}
|
||||
|
||||
assert(!(io.cpu.writeBack.isValid && !io.cpu.writeBack.haltIt && io.cpu.writeBack.isStuck), "writeBack stuck by another plugin is not allowed")
|
||||
io.cpu.writeBack.data := (request.forceUncachedAccess || mmuRsp.isIoAccess) ? io.mem.rsp.data | way.dataReadRspTwo //not multi ways
|
||||
|
||||
if(genAtomic){
|
||||
when(request.isAtomic && request.wr){
|
||||
io.cpu.writeBack.data := (!atomic.entriesHit).asBits.resized
|
||||
}
|
||||
when(request.isAtomic && !atomic.entriesHit){
|
||||
io.mem.cmd.mask := 0
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//The whole life of a loading task, the corresponding manager request is present
|
||||
val loader = new Area{
|
||||
val valid = RegNext(stageB.loaderValid) init(False)
|
||||
val valid = RegInit(False) setWhen(stageB.loaderValid)
|
||||
val baseAddress = stageB.mmuRsp.physicalAddress
|
||||
|
||||
val memCmdSent = RegInit(False)
|
||||
when(valid && !memCmdSent) {
|
||||
io.mem.cmd.valid := True
|
||||
io.mem.cmd.wr := False
|
||||
io.mem.cmd.address := baseAddress(tagRange.high downto lineRange.low) @@ U(0,lineRange.low bit)
|
||||
io.mem.cmd.length := p.burstLength-1
|
||||
io.mem.cmd.last := True
|
||||
}
|
||||
|
||||
when(valid && io.mem.cmd.ready){
|
||||
memCmdSent := True
|
||||
}
|
||||
|
||||
when(valid && !memCmdSent) {
|
||||
victim.memCmdAlreadyUsed := True
|
||||
}
|
||||
|
||||
val counter = Counter(memTransactionPerLine)
|
||||
val waysAllocator = Reg(Bits(wayCount bits)) init(1)
|
||||
val error = RegInit(False)
|
||||
|
||||
when(valid && io.mem.rsp.valid){
|
||||
dataWriteCmd.valid := True
|
||||
dataWriteCmd.address := baseAddress(lineRange) @@ counter
|
||||
dataWriteCmd.data := io.mem.rsp.data
|
||||
dataWriteCmd.mask := (1<<(wordWidth/8))-1
|
||||
dataWriteCmd.mask.setAll()
|
||||
dataWriteCmd.way := waysAllocator
|
||||
error := error | io.mem.rsp.error
|
||||
counter.increment()
|
||||
}
|
||||
|
||||
|
||||
when(counter.willOverflow){
|
||||
memCmdSent := False
|
||||
valid := False
|
||||
stageB.loaderReady := True
|
||||
|
||||
//Update tags
|
||||
tagsWriteCmd.valid := True
|
||||
tagsWriteCmd.address := baseAddress(lineRange)
|
||||
tagsWriteCmd.data.valid := True
|
||||
tagsWriteCmd.data.address := baseAddress(tagRange)
|
||||
tagsWriteCmd.data.error := error || io.mem.rsp.error
|
||||
tagsWriteCmd.way := waysAllocator
|
||||
|
||||
waysAllocator := (waysAllocator ## waysAllocator.msb).resized
|
||||
|
||||
error := False
|
||||
}
|
||||
|
||||
io.cpu.redo setWhen(valid)
|
||||
stageB.mmuRspFreeze setWhen(stageB.loaderValid || valid)
|
||||
}
|
||||
}
|
|
@ -26,6 +26,7 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
var mmuBus : MemoryTranslatorBus = null
|
||||
var exceptionBus : Flow[ExceptionCause] = null
|
||||
var privilegeService : PrivilegeService = null
|
||||
var redoBranch : Flow[UInt] = null
|
||||
|
||||
object MEMORY_ENABLE extends Stageable(Bool)
|
||||
object MEMORY_MANAGMENT extends Stageable(Bool)
|
||||
|
@ -44,7 +45,7 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
SRC_USE_SUB_LESS -> False,
|
||||
MEMORY_ENABLE -> True,
|
||||
RS1_USE -> True
|
||||
) ++ (if (catchUnaligned) List(IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB) else Nil) //Used for access fault bad address in memory stage
|
||||
) ++ (if (catchSomething) List(IntAluPlugin.ALU_CTRL -> IntAluPlugin.AluCtrlEnum.ADD_SUB) else Nil) //Used for access fault bad address in memory stage
|
||||
|
||||
val loadActions = stdActions ++ List(
|
||||
SRC2_CTRL -> Src2CtrlEnum.IMI,
|
||||
|
@ -75,13 +76,14 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
decoderService.add(
|
||||
key = LR,
|
||||
values = loadActions.filter(_._1 != SRC2_CTRL) ++ Seq(
|
||||
SRC2_CTRL -> Src2CtrlEnum.RS,
|
||||
SRC_ADD_ZERO -> True,
|
||||
MEMORY_ATOMIC -> True
|
||||
)
|
||||
)
|
||||
decoderService.add(
|
||||
key = SC,
|
||||
values = storeActions.filter(_._1 != SRC2_CTRL) ++ Seq(
|
||||
SRC_ADD_ZERO -> True,
|
||||
REGFILE_WRITE_VALID -> True,
|
||||
BYPASSABLE_EXECUTE_STAGE -> False,
|
||||
BYPASSABLE_MEMORY_STAGE -> False,
|
||||
|
@ -98,6 +100,7 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
))
|
||||
|
||||
mmuBus = pipeline.service(classOf[MemoryTranslator]).newTranslationPort(MemoryTranslatorPort.PRIORITY_DATA ,memoryTranslatorPortConfig)
|
||||
redoBranch = pipeline.service(classOf[JumpService]).createJumpInterface(pipeline.writeBack)
|
||||
|
||||
if(catchSomething)
|
||||
exceptionBus = pipeline.service(classOf[ExceptionService]).newExceptionPort(pipeline.writeBack)
|
||||
|
@ -141,8 +144,8 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
val size = input(INSTRUCTION)(13 downto 12).asUInt
|
||||
cache.io.cpu.execute.isValid := arbitration.isValid && input(MEMORY_ENABLE)
|
||||
cache.io.cpu.execute.isStuck := arbitration.isStuck
|
||||
cache.io.cpu.execute.address := input(SRC_ADD).asUInt
|
||||
cache.io.cpu.execute.args.wr := input(MEMORY_WR)
|
||||
cache.io.cpu.execute.args.address := input(SRC_ADD).asUInt
|
||||
cache.io.cpu.execute.args.data := size.mux(
|
||||
U(0) -> input(RS2)( 7 downto 0) ## input(RS2)( 7 downto 0) ## input(RS2)(7 downto 0) ## input(RS2)(7 downto 0),
|
||||
U(1) -> input(RS2)(15 downto 0) ## input(RS2)(15 downto 0),
|
||||
|
@ -150,19 +153,18 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
)
|
||||
cache.io.cpu.execute.args.size := size
|
||||
cache.io.cpu.execute.args.forceUncachedAccess := False
|
||||
cache.io.cpu.execute.args.kind := input(MEMORY_MANAGMENT) ? DataCacheCpuCmdKind.MANAGMENT | DataCacheCpuCmdKind.MEMORY
|
||||
cache.io.cpu.execute.args.clean := input(INSTRUCTION)(28)
|
||||
cache.io.cpu.execute.args.invalidate := input(INSTRUCTION)(29)
|
||||
cache.io.cpu.execute.args.way := input(INSTRUCTION)(30)
|
||||
if(genAtomic) {
|
||||
cache.io.cpu.execute.args.isAtomic := False
|
||||
when(input(MEMORY_ATOMIC)){
|
||||
cache.io.cpu.execute.args.isAtomic := True
|
||||
cache.io.cpu.execute.args.address := input(SRC1).asUInt
|
||||
}
|
||||
}
|
||||
|
||||
insert(MEMORY_ADDRESS_LOW) := cache.io.cpu.execute.args.address(1 downto 0)
|
||||
insert(MEMORY_ADDRESS_LOW) := cache.io.cpu.execute.address(1 downto 0)
|
||||
|
||||
when(cache.io.cpu.redo && arbitration.isValid && input(MEMORY_ENABLE)){
|
||||
arbitration.haltItself := True
|
||||
}
|
||||
}
|
||||
|
||||
memory plug new Area{
|
||||
|
@ -170,10 +172,9 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
cache.io.cpu.memory.isValid := arbitration.isValid && input(MEMORY_ENABLE)
|
||||
cache.io.cpu.memory.isStuck := arbitration.isStuck
|
||||
cache.io.cpu.memory.isRemoved := arbitration.removeIt
|
||||
arbitration.haltItself setWhen(cache.io.cpu.memory.haltIt)
|
||||
cache.io.cpu.memory.address := U(input(REGFILE_WRITE_DATA))
|
||||
|
||||
cache.io.cpu.memory.mmuBus <> mmuBus
|
||||
arbitration.haltItself setWhen (mmuBus.cmd.isValid && ???) //TODO !mmuBus.rsp.hit && !mmuBus.rsp.miss
|
||||
}
|
||||
|
||||
writeBack plug new Area{
|
||||
|
@ -181,20 +182,36 @@ class DBusCachedPlugin(config : DataCacheConfig,
|
|||
cache.io.cpu.writeBack.isValid := arbitration.isValid && input(MEMORY_ENABLE)
|
||||
cache.io.cpu.writeBack.isStuck := arbitration.isStuck
|
||||
cache.io.cpu.writeBack.isUser := (if(privilegeService != null) privilegeService.isUser() else False)
|
||||
cache.io.cpu.writeBack.address := U(input(REGFILE_WRITE_DATA))
|
||||
if(genAtomic) cache.io.cpu.writeBack.clearAtomicEntries := service(classOf[IContextSwitching]).isContextSwitching
|
||||
|
||||
if(catchSomething) {
|
||||
exceptionBus.valid := cache.io.cpu.writeBack.mmuMiss || cache.io.cpu.writeBack.accessError || cache.io.cpu.writeBack.illegalAccess || cache.io.cpu.writeBack.unalignedAccess
|
||||
exceptionBus.badAddr := cache.io.cpu.writeBack.badAddr
|
||||
exceptionBus.valid := False //cache.io.cpu.writeBack.mmuMiss || cache.io.cpu.writeBack.accessError || cache.io.cpu.writeBack.illegalAccess || cache.io.cpu.writeBack.unalignedAccess
|
||||
exceptionBus.badAddr := U(input(REGFILE_WRITE_DATA))
|
||||
exceptionBus.code.assignDontCare()
|
||||
when(cache.io.cpu.writeBack.illegalAccess || cache.io.cpu.writeBack.accessError){
|
||||
exceptionBus.code := (input(MEMORY_WR) ? U(7) | U(5)).resized
|
||||
}
|
||||
when(cache.io.cpu.writeBack.unalignedAccess){
|
||||
exceptionBus.code := (input(MEMORY_WR) ? U(6) | U(4)).resized
|
||||
}
|
||||
when(cache.io.cpu.writeBack.mmuMiss){
|
||||
exceptionBus.code := 13
|
||||
|
||||
redoBranch.valid := False
|
||||
redoBranch.payload := input(PC)
|
||||
arbitration.flushAll setWhen(redoBranch.valid)
|
||||
|
||||
when(cache.io.cpu.writeBack.isValid) {
|
||||
if (catchAccessError) when(cache.io.cpu.writeBack.accessError) {
|
||||
exceptionBus.valid := True
|
||||
exceptionBus.code := (input(MEMORY_WR) ? U(7) | U(5)).resized
|
||||
}
|
||||
|
||||
if (catchUnaligned) when(cache.io.cpu.writeBack.unalignedAccess) {
|
||||
exceptionBus.valid := True
|
||||
exceptionBus.code := (input(MEMORY_WR) ? U(6) | U(4)).resized
|
||||
}
|
||||
when (cache.io.cpu.writeBack.mmuException) {
|
||||
exceptionBus.valid := True
|
||||
exceptionBus.code := (input(MEMORY_WR) ? U(15) | U(13)).resized
|
||||
}
|
||||
when(cache.io.cpu.redo) {
|
||||
redoBranch.valid := True
|
||||
exceptionBus.valid := False
|
||||
}
|
||||
}
|
||||
}
|
||||
arbitration.haltItself.setWhen(cache.io.cpu.writeBack.haltIt)
|
||||
|
|
|
@ -33,9 +33,8 @@ class StaticMemoryTranslatorPlugin(ioRange : UInt => Bool) extends Plugin[VexRis
|
|||
port.bus.rsp.allowExecute := True
|
||||
port.bus.rsp.allowUser := True
|
||||
port.bus.rsp.isIoAccess := ioRange(port.bus.rsp.physicalAddress)
|
||||
???
|
||||
// port.bus.rsp.miss := False
|
||||
// port.bus.rsp.hit := True
|
||||
port.bus.rsp.exception := False
|
||||
port.bus.rsp.refilling := False
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,19 +1,19 @@
|
|||
[*]
|
||||
[*] GTKWave Analyzer v3.3.100 (w)1999-2019 BSI
|
||||
[*] Sat Mar 30 09:33:33 2019
|
||||
[*] Mon Apr 1 21:53:07 2019
|
||||
[*]
|
||||
[dumpfile] "/home/miaou/pro/VexRiscv/src/test/cpp/regression/linux.vcd"
|
||||
[dumpfile_mtime] "Sat Mar 30 09:16:30 2019"
|
||||
[dumpfile_size] 249834424
|
||||
[dumpfile] "/home/miaou/pro/VexRiscv/src/test/cpp/regression/rv32ui-p-lw.vcd"
|
||||
[dumpfile_mtime] "Mon Apr 1 21:52:20 2019"
|
||||
[dumpfile_size] 1974526
|
||||
[savefile] "/home/miaou/pro/VexRiscv/src/test/cpp/regression/fail.gtkw"
|
||||
[timestart] 106663042
|
||||
[timestart] 348
|
||||
[size] 1920 1030
|
||||
[pos] -458 -215
|
||||
*-5.000000 106541900 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[pos] -1 -1
|
||||
*-2.000000 357 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1 -1
|
||||
[treeopen] TOP.
|
||||
[treeopen] TOP.VexRiscv.
|
||||
[sst_width] 287
|
||||
[signals_width] 465
|
||||
[signals_width] 563
|
||||
[sst_expanded] 1
|
||||
[sst_vpaned_height] 279
|
||||
@28
|
||||
|
@ -21,43 +21,37 @@ TOP.VexRiscv.writeBack_arbitration_isFiring
|
|||
@22
|
||||
TOP.VexRiscv.writeBack_PC[31:0]
|
||||
TOP.VexRiscv.writeBack_INSTRUCTION[31:0]
|
||||
TOP.VexRiscv.writeBack_RegFilePlugin_regFileWrite_payload_address[4:0]
|
||||
TOP.VexRiscv.writeBack_RegFilePlugin_regFileWrite_payload_data[31:0]
|
||||
@28
|
||||
TOP.VexRiscv.CsrPlugin_exception
|
||||
TOP.VexRiscv.CsrPlugin_privilege[1:0]
|
||||
TOP.VexRiscv.writeBack_RegFilePlugin_regFileWrite_valid
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_valid
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_ready
|
||||
@22
|
||||
TOP.VexRiscv.CsrPlugin_scause_exceptionCode[3:0]
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_address[31:0]
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_data[31:0]
|
||||
@28
|
||||
TOP.VexRiscv.CsrPlugin_scause_interrupt
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_last
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_length[2:0]
|
||||
@22
|
||||
TOP.VexRiscv.IBusSimplePlugin_jump_pcLoad_payload[31:0]
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_mask[3:0]
|
||||
@28
|
||||
TOP.VexRiscv.IBusSimplePlugin_jump_pcLoad_valid
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_cmd_payload_wr
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_rsp_valid
|
||||
@22
|
||||
TOP.VexRiscv.CsrPlugin_mepc[31:0]
|
||||
TOP.VexRiscv.CsrPlugin_sepc[31:0]
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_rsp_payload_data[31:0]
|
||||
@28
|
||||
TOP.VexRiscv.decode_IS_RVC
|
||||
@24
|
||||
TOP.VexRiscv.CsrPlugin_mcycle[63:0]
|
||||
@28
|
||||
TOP.VexRiscv.decode_IS_RVC
|
||||
TOP.VexRiscv.decode_arbitration_isValid
|
||||
@22
|
||||
TOP.VexRiscv.RegFilePlugin_regFile(10)[31:0]
|
||||
@28
|
||||
TOP.dBus_cmd_valid
|
||||
TOP.dBus_cmd_ready
|
||||
@22
|
||||
TOP.dBus_cmd_payload_address[31:0]
|
||||
@28
|
||||
TOP.dBus_cmd_payload_wr
|
||||
@29
|
||||
TOP.dBus_cmd_payload_size[1:0]
|
||||
@22
|
||||
TOP.dBus_cmd_payload_data[31:0]
|
||||
TOP.dBus_rsp_data[31:0]
|
||||
@28
|
||||
TOP.dBus_rsp_error
|
||||
TOP.dBus_rsp_ready
|
||||
[color] 1
|
||||
TOP.VexRiscv.dataCache_1_.io_mem_rsp_payload_error
|
||||
[pattern_trace] 1
|
||||
[pattern_trace] 0
|
||||
|
|
|
@ -3446,6 +3446,7 @@ int main(int argc, char **argv, char **env) {
|
|||
// redo(REDO,WorkspaceRegression("deleg").withRiscvRef()->loadHex("../raw/deleg/build/deleg.hex")->bootAt(0x80000000u)->run(50e3););
|
||||
// return 0;
|
||||
|
||||
|
||||
for(int idx = 0;idx < 1;idx++){
|
||||
|
||||
#if defined(DEBUG_PLUGIN_EXTERNAL) || defined(RUN_HEX)
|
||||
|
|
|
@ -366,7 +366,6 @@ class DBusDimension extends VexRiscvDimension("DBus") {
|
|||
catchAccessError = catchAll,
|
||||
catchIllegal = catchAll,
|
||||
catchUnaligned = catchAll,
|
||||
catchMemoryTranslationMiss = catchAll,
|
||||
atomicEntriesCount = 0
|
||||
),
|
||||
memoryTranslatorPortConfig = null
|
||||
|
|
Loading…
Reference in New Issue