Skip to content

Commit

Permalink
Litex SoC now supports RVLS / dual-sim, allowing to use it as a self …
Browse files Browse the repository at this point in the history
…testing testbench
  • Loading branch information
Dolu1990 committed Jan 30, 2025
1 parent c28daed commit b05e762
Show file tree
Hide file tree
Showing 3 changed files with 174 additions and 33 deletions.
176 changes: 145 additions & 31 deletions src/main/scala/vexiiriscv/soc/litex/Soc.scala
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
package vexiiriscv.soc.litex

import rvls.spinal.{FileBackend, RvlsBackend}
import spinal.core.fiber.{Fiber, hardFork}
import spinal.lib._
import spinal.core._
import spinal.core.blackboxByteEnables.generateUnblackboxableError
import spinal.core.internals.{MemTopology, PhaseContext, PhaseNetlist}
import spinal.core.sim.SimDataPimper
import spinal.core.sim.{SimDataPimper, killRandom}
import spinal.lib.bus.amba4.axi.sim.{Axi4ReadOnlyMonitor, Axi4ReadOnlySlaveAgent, Axi4WriteOnlyMonitor, Axi4WriteOnlySlaveAgent}
import spinal.lib.bus.amba4.axi.{Axi4, Axi4Config, Axi4SpecRenamer, Axi4ToTilelinkFiber}
import spinal.lib.bus.amba4.axilite.AxiLite4SpecRenamer
Expand All @@ -24,6 +25,7 @@ import spinal.lib.graphic.YcbcrConfig
import spinal.lib.graphic.vga.{TilelinkVgaCtrlFiber, TilelinkVgaCtrlSpec, Vga, VgaRgbToYcbcr, VgaYcbcrPix2}
import spinal.lib.misc.{Elf, PathTracer, TilelinkClintFiber}
import spinal.lib.misc.plic.TilelinkPlicFiber
import spinal.lib.misc.test.DualSimTracer
import spinal.lib.sim.SparseMemory
import spinal.lib.{AnalysisUtils, Delay, Flow, ResetCtrlFiber, StreamPipe, master, memPimped, slave, traversableOncePimped}
import spinal.lib.system.tag.{MemoryConnection, MemoryEndpoint, MemoryEndpointTag, MemoryTransferTag, MemoryTransfers, PMA, VirtualEndpoint}
Expand All @@ -34,9 +36,12 @@ import vexiiriscv.execute.lsu.LsuL1Plugin
import vexiiriscv.fetch.{Fetch, FetchL1Plugin, FetchPipelinePlugin, PcPlugin}
import vexiiriscv.misc.{PrivilegedPlugin, TrapPlugin}
import vexiiriscv.prediction.GSharePlugin
import vexiiriscv.riscv.Riscv
import vexiiriscv.schedule.DispatchPlugin
import vexiiriscv.soc.TilelinkVexiiRiscvFiber
import vexiiriscv.test.WhiteboxerPlugin
import vexiiriscv.soc.micro.MicroSocSim.{elfFile, traceKonata, withRvlsCheck}
import vexiiriscv.test.{VexiiRiscvProbe, WhiteboxerPlugin}
import vexiiriscv.tester.{FsmHal, FsmHalGen, FsmOption, FsmTask}

import java.awt.{Dimension, Graphics}
import java.awt.image.BufferedImage
Expand Down Expand Up @@ -472,7 +477,6 @@ object SocGen extends App{
case _ =>
}
}

})
// spinalConfig.addTransformationPhase(new EnforceSyncRamPhase)

Expand Down Expand Up @@ -589,26 +593,99 @@ object SocSim extends App{
val socConfig = new SocConfig()
import socConfig._

vexiiParam.privParam.withRdTime = true
val elfs = ArrayBuffer[File]();
val bins = ArrayBuffer[(Long, File)]()
var bootstrapOpensbi = Option.empty[(Long, Long)]
val fsmTasksGen = mutable.Queue[() => FsmTask]() // Allow to script stimulus from the command line (putc / getc)
var withRvlsCheck = true
var traceWave = false
var traceKonata = false
var traceRvlsLog = false
var traceSpikeLog = false
var dualSim = false
var seed = 32

assert(new scopt.OptionParser[Unit]("VexiiRiscv") {
help("help").text("prints this usage text")
opt[String]("load-elf") unbounded() action { (v, c) => elfs += new File(v) }
opt[Seq[String]]("load-bin") unbounded() action { (v, c) => bins += java.lang.Long.parseLong(v(0).replace("0x", ""), 16) -> new File(v(1)) }
opt[Seq[String]]("opensbi-bootstrap") unbounded() action { (v, c) => bootstrapOpensbi = Some(java.lang.Long.parseLong(v(0).replace("0x", ""), 16) -> java.lang.Long.parseLong(v(1).replace("0x", ""), 16)) }
opt[Unit]("dual-sim") action { (v, c) => dualSim = true }
opt[Unit]("check-rvls") action { (v, c) => withRvlsCheck = true}
opt[Unit]("trace-konata") action { (v, c) => traceKonata = true }
opt[Unit]("trace-spike") action { (v, c) => traceSpikeLog = true }
opt[Unit]("trace-rvls") action { (v, c) => traceRvlsLog = true }
opt[Unit]("trace-wave") action { (v, c) => traceWave = true }
socConfig.addOptions(this)
FsmOption(this, fsmTasksGen)
}.parse(args, Unit).nonEmpty)

vexiiParam.lsuL1Coherency = vexiiParam.lsuL1Coherency || cpuCount > 1 || withDma
vexiiParam.privParam.withRdTime = false // To keep in sync with RVLS

val spinalConfig = SpinalConfig()
spinalConfig.addTransformationPhase(new MultiPortWritesSymplifier)

import spinal.core.sim._
SimConfig.withConfig(spinalConfig).withFstWave.compile(new Soc(socConfig)).doSimUntilVoid(seed=32){dut =>
disableSimWave()

val compiled = SimConfig.allOptimisation.withConfig(spinalConfig).withFstWave.compile(new Soc(socConfig))
dualSim match {
case true => DualSimTracer.withCb(compiled, window = 5000000 * 10000l, seed=seed)(test)
case false => compiled.doSimUntilVoid(seed=seed) { dut => disableSimWave(); test(dut, f => f) }
}

def test(dut : Soc, onTrace : (=> Unit) => Unit = cb => {}) : Unit = {
killRandom()
dut.litexCd.withSyncReset().forkStimulus(10000)

if(withJtagTap) spinal.lib.com.jtag.sim.JtagRemote(dut.debug.tap.jtag, 10000*4)
// Will load a little bootloader which will initialise a0 a1 a2 to and then jump to opensbi
val bootstrapBytes = bootstrapOpensbi.map { case (dts, opensbi) => Riscv.bootToOpensbi(dts, opensbi) }

// Create the VexiiRiscv probe's backends
val rvls = new RvlsBackend(new File(currentTestPath)).spinalSimFlusher(hzToLong(1000 Hz))
val tracerFile = traceRvlsLog.option(new FileBackend(new File(currentTestPath(), "tracer.log")))
val traceBackends = tracerFile ++ List(rvls)

// Provide the memory preinitialization traces
for(backend <- traceBackends) {
bootstrapBytes.foreach(array => backend.loadBytes(vexiiParam.resetVector.toLong, array))
for (elf <- elfs) backend.loadElf(0, elf)
for ((at, bin) <- bins) backend.loadBin(at, bin)
}

val konata = traceKonata.option(
new vexiiriscv.test.konata.Backend(new File(currentTestPath, "konata.log")).spinalSimFlusher(hzToLong(1000 Hz))
)

// Will inspect the VexiiRiscv cores behaviour
val probes = for((vexii, hartId) <- dut.system.vexiis.zipWithIndex) yield new VexiiRiscvProbe(
cpu = vexii.logic.core,
kb = konata
){
for(backend <- traceBackends) add(backend)
autoRegions()
for(backend <- traceBackends) backend.setPc(hartId, vexiiParam.resetVector)
trace = false
livenessThreshold = 21000l // Because reset controllers hold the system quite a bit
}

if(tracerFile.nonEmpty) probes.foreach(p => p.backends.remove(p.backends.indexOf(tracerFile.get)))
onTrace {
println("TRACE ENABLED")
if (traceWave) enableSimWave()
if (withRvlsCheck && traceSpikeLog) rvls.debug()
if (traceKonata) probes.foreach(_.trace = true)

tracerFile.foreach{f =>
f.spinalSimFlusher(10 * 10000)
f.spinalSimTime(10*10000)
probes.foreach(_.addHead(f)) //addHead to be sure we log things before rvls backends
}
}

val fsmHal = new FsmHalGen(fsmTasksGen)

if(withJtagTap && !dualSim) spinal.lib.com.jtag.sim.JtagRemote(dut.debug.tap.jtag, 10000*4)
if(socConfig.withCpuCd) dut.cpuClk.forkStimulus(5000)
for(video <- dut.system.video){
video.cd.forkStimulus(20000)
Expand All @@ -629,29 +706,65 @@ object SocSim extends App{
dut.cpuCd.waitRisingEdge(2)
dut.debugReset #= false
}
if(socConfig.withDma){
dut.system.dma.bus.ar.valid #= false
dut.system.dma.bus.aw.valid #= false
dut.system.dma.bus.w.valid #= false
dut.system.dma.bus.r.ready #= false
dut.system.dma.bus.b.ready #= false
}

sleep(1)

// Implements a very minimal model of the Litex peripherals, enough to get linux to run (serial port without interrupts)
val onPbus = new Area {
val axi = dut.system.patcher.pBus
val UART_REG = 0xF0001000l
val UART_REG_RXTX = UART_REG + 0*4
val UART_REG_TXFULL = UART_REG + 1*4
val UART_REG_RXEMPTY = UART_REG + 2*4
val UART_REG_EV_STATUS = UART_REG + 3*4
val UART_REG_EV_PENDING = UART_REG + 4*4
val UART_REG_EV_ENABLE = UART_REG + 5*4
new AxiLite4ReadOnlySlaveAgent(axi.ar, axi.r, dut.litexCd){

override def doRead(addr: BigInt) = {
super.doRead(addr)
axi.r.payload.data.randomize()
axi.r.payload.data #= (addr.toLong match {
case x if x < 0x40 => 0
case UART_REG_TXFULL => 0
case UART_REG_RXTX => {
if(fsmHal.putcQueue.nonEmpty) fsmHal.putcQueue.dequeue().toInt & 0xFF
else if(System.in.available() != 0) System.in.read().toInt & 0xFF
else ???
}
case UART_REG_RXEMPTY => (fsmHal.putcQueue.isEmpty || System.in.available() == 0).toInt
})
}
}
val woa = new AxiLite4WriteOnlySlaveAgent(axi.aw, axi.w, axi.b, dut.litexCd) {
override def onWrite(addr: BigInt, data: BigInt, strb: BigInt) = {
super.onWrite(addr, data, strb)
if(addr == 0xf0001000l){
print(data.toChar)
addr.toLong match {
case UART_REG_RXTX => print(data.toChar); if (fsmHal.tasks.nonEmpty) fsmHal.tasks.head.getc(fsmHal, data.toChar)
case UART_REG_EV_PENDING =>
case UART_REG_EV_ENABLE =>
}
}
}
}

val onAxi = new Area{
val ddrMemory = SparseMemory()
val ddrMemory = SparseMemory(seed = 0) //seed 0 to match RVLS behavior
bootstrapBytes.foreach( array => ddrMemory.write(vexiiParam.resetVector.toLong, array))

for (file <- elfs) {
val elf = new Elf(file, socConfig.vexiiParam.xlen)
elf.load(ddrMemory, 0)
}
for ((offset, file) <- bins) {
ddrMemory.loadBin(offset, file)
}
val axi = dut.system.patcher.mBus
val woa = new Axi4WriteOnlySlaveAgent(axi.aw, axi.w, axi.b, dut.cpuCd) {
awDriver.factor = 0.9f
Expand All @@ -668,34 +781,35 @@ object SocSim extends App{
val roa = new Axi4ReadOnlySlaveAgent(axi.ar, axi.r, dut.cpuCd, withReadInterleaveInBurst = false) {
arDriver.factor = 0.8f
rDriver.transactionDelay = () => simRandom.nextInt(3)
baseLatency = 60 * 1000
baseLatency = 70 * 1000

override def readByte(address: BigInt): Byte = {
bytesAccess += 1
ddrMemory.read(address.toLong)
}
}
}
for(y <- 0 until 600; x <- 0 until 800){
val color = (x & 0xFF) + ((y & 0xFF) << 8)// + (((x+y) & 0xFF) << 16)
onAxi.ddrMemory.write(0x40c00000 + x * 4 + y * 4 * 800, color)
// val color = (x & 0x1F)+((y & 0x3F) << 5)
// onAxi.ddrMemory.write(0x40c00000 + x*2+y*2*640, color + (color << 16))
}

var cnt = 0
def setPix(value : Int) = {
onAxi.ddrMemory.write(0x40c00000 + cnt, value)
onAxi.ddrMemory.write(0x40c00000 + cnt + 4, value)
cnt += 8
}
setPix(0x00000000)
setPix(0x000000ff)
setPix(0x0000ff00)
setPix(0x00ff0000)
setPix(0x00ffffff)
onAxi.ddrMemory.write(0x40c00000 + cnt, 0x000000FF); cnt += 4
onAxi.ddrMemory.write(0x40c00000 + cnt, 0x00800080); cnt += 4
// for(y <- 0 until 600; x <- 0 until 800){
// val color = (x & 0xFF) + ((y & 0xFF) << 8)// + (((x+y) & 0xFF) << 16)
// onAxi.ddrMemory.write(0x40c00000 + x * 4 + y * 4 * 800, color)
//// val color = (x & 0x1F)+((y & 0x3F) << 5)
//// onAxi.ddrMemory.write(0x40c00000 + x*2+y*2*640, color + (color << 16))
// }
//
// var cnt = 0
// def setPix(value : Int) = {
// onAxi.ddrMemory.write(0x40c00000 + cnt, value)
// onAxi.ddrMemory.write(0x40c00000 + cnt + 4, value)
// cnt += 8
// }
// setPix(0x00000000)
// setPix(0x000000ff)
// setPix(0x0000ff00)
// setPix(0x00ff0000)
// setPix(0x00ffffff)
// onAxi.ddrMemory.write(0x40c00000 + cnt, 0x000000FF); cnt += 4
// onAxi.ddrMemory.write(0x40c00000 + cnt, 0x00800080); cnt += 4
}
}

Expand Down
10 changes: 9 additions & 1 deletion src/main/scala/vexiiriscv/test/VexiiRiscvProbe.scala
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ class VexiiRiscvProbe(cpu : VexiiRiscv, kb : Option[konata.Backend], var withRvl
var enabled = true
var trace = true
var checkLiveness = true
var livenessThreshold = 16000l
var backends = ArrayBuffer[TraceBackend]()
val commitsCallbacks = ArrayBuffer[(Int, Long) => Unit]()
val autoStoreBroadcast = cpu.host.get[LsuCachelessPlugin].nonEmpty
Expand Down Expand Up @@ -72,6 +73,13 @@ class VexiiRiscvProbe(cpu : VexiiRiscv, kb : Option[konata.Backend], var withRvl
this
}

def addHead(tracer: TraceBackend): this.type = {
backends.insert(0, tracer)
harts.foreach(_.add(tracer))
proxies.interrupts.sync()
this
}

// Figure out the PMA (Physical Memory Attributes) from the plugins themself and notify the backends.
def autoRegions(): Unit = {
cpu.host.services.foreach {
Expand Down Expand Up @@ -565,7 +573,7 @@ class VexiiRiscvProbe(cpu : VexiiRiscv, kb : Option[konata.Backend], var withRvl
if(((wfi >> localHartId) & 1) != 0){
hart.lastCommitAt = cycle
}
if (checkLiveness && hart.lastCommitAt + 16000l < cycle) {
if (checkLiveness && hart.lastCommitAt + livenessThreshold < cycle) {
val status = if (hart.microOpAllocPtr != hart.microOpRetirePtr) f"waiting on uop 0x${hart.microOpRetirePtr}%X" else f"last uop id 0x${hart.lastUopId}%X"
simFailure(f"Vexii hasn't commited anything for too long, $status")
}
Expand Down
21 changes: 20 additions & 1 deletion src/main/scala/vexiiriscv/tester/TestFsm.scala
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package vexiiriscv.tester

import spinal.core.sim.{delayed, simSuccess}
import vexiiriscv.soc.litex.SocSim.fsmTasksGen

import scala.collection.mutable

Expand All @@ -17,7 +18,7 @@ import scala.collection.mutable
* --fsm-putc "cat /proc/cpuinfo"
* --fsm-putc-lr
* --fsm-getc #
* --fsm-success
* --fsm-success
*/
object FsmOption{
def apply(parser: scopt.OptionParser[Unit], fsmTasksGen : mutable.Queue[() => FsmTask]): Unit = {
Expand All @@ -29,6 +30,24 @@ object FsmOption{
}
}

/**
* Provide a default implementation to be used in testbenches
* Tasks are builded from a queue of landa function (To allow dual sim)
*
* The testbench need to interface with putcQueue and tasks.head.getc
*/
class FsmHalGen(fsmTasksGen : mutable.Queue[() => FsmTask]) extends FsmHal{
val tasks = mutable.Queue[FsmTask]()
for(gen <- fsmTasksGen) tasks += gen()
val putcQueue = mutable.Queue[Byte]()
override def next(): Unit = {
if (tasks.nonEmpty) tasks.dequeue()
if (tasks.nonEmpty) tasks.head.start(this)
}
override def putc(value: String): Unit = putcQueue ++= value.map(_.toByte)
if (tasks.nonEmpty) tasks.head.start(this)
}

trait FsmHal{
def putc(value : String) : Unit
def next() : Unit
Expand Down

0 comments on commit b05e762

Please sign in to comment.