diff --git a/src/main/scala/vexriscv/demo/Murax.scala b/src/main/scala/vexriscv/demo/Murax.scala index ec6b4cd..95acf03 100644 --- a/src/main/scala/vexriscv/demo/Murax.scala +++ b/src/main/scala/vexriscv/demo/Murax.scala @@ -2,15 +2,15 @@ package vexriscv.demo import spinal.core._ import spinal.lib._ -import spinal.lib.bus.amba3.apb.{Apb3SlaveFactory, Apb3Decoder, Apb3Gpio, Apb3} +import spinal.lib.bus.amba3.apb._ import spinal.lib.bus.misc.SizeMapping import spinal.lib.com.jtag.Jtag import spinal.lib.com.uart._ import spinal.lib.io.TriStateArray -import spinal.lib.misc.{InterruptCtrl, Timer, Prescaler} -import spinal.lib.soc.pinsec.{PinsecTimerCtrlExternal, PinsecTimerCtrl} +import spinal.lib.misc.{InterruptCtrl, Prescaler, Timer} +import spinal.lib.soc.pinsec.{PinsecTimerCtrl, PinsecTimerCtrlExternal} import vexriscv.plugin._ -import vexriscv.{plugin, VexRiscvConfig, VexRiscv} +import vexriscv.{VexRiscv, VexRiscvConfig, plugin} import scala.collection.mutable.ArrayBuffer @@ -132,31 +132,10 @@ object MuraxConfig{ } } -case class SimpleBusCmd() extends Bundle{ - val wr = Bool - val address = UInt(32 bits) - val data = Bits(32 bit) - val mask = Bits(4 bit) -} - -case class SimpleBusRsp() extends Bundle{ - val data = Bits(32 bit) -} - - -case class SimpleBus() extends Bundle with IMasterSlave { - val cmd = Stream(SimpleBusCmd()) - val rsp = Flow(SimpleBusRsp()) - - override def asMaster(): Unit = { - master(cmd) - slave(rsp) - } -} case class Murax(config : MuraxConfig) extends Component{ import config._ - + val io = new Bundle { //Clocks / reset val asyncReset = in Bool @@ -203,14 +182,22 @@ case class Murax(config : MuraxConfig) extends Component{ reset = resetCtrl.systemReset, frequency = FixedFrequency(coreFrequency) ) - + val debugClockDomain = ClockDomain( clock = io.mainClk, reset = resetCtrl.mainClkReset, frequency = FixedFrequency(coreFrequency) ) - + val system = new ClockingArea(systemClockDomain) { + val simpleBusConfig = SimpleBusConfig( + addressWidth = 32, + dataWidth = 32 + ) + + //Arbiter of the cpu dBus/iBus to drive the mainBus + //Priority to dBus, !! cmd transactions can change on the fly !! + val mainBusArbiter = new MuraxMasterArbiter(simpleBusConfig) //Instanciate the CPU val cpu = new VexRiscv( @@ -222,18 +209,14 @@ case class Murax(config : MuraxConfig) extends Component{ //Checkout plugins used to instanciate the CPU to connect them to the SoC val timerInterrupt = False val externalInterrupt = False - var iBus : IBusSimpleBus = null - var dBus : DBusSimpleBus = null - var debugBus : DebugExtensionBus = null for(plugin <- cpu.plugins) plugin match{ - case plugin : IBusSimplePlugin => iBus = plugin.iBus + case plugin : IBusSimplePlugin => mainBusArbiter.io.iBus <> plugin.iBus case plugin : DBusSimplePlugin => { if(!pipelineDBus) - dBus = plugin.dBus + mainBusArbiter.io.dBus <> plugin.dBus else { - dBus = cloneOf(plugin.dBus) - dBus.cmd << plugin.dBus.cmd.halfPipe() - dBus.rsp <> plugin.dBus.rsp + mainBusArbiter.io.dBus.cmd << plugin.dBus.cmd.halfPipe() + mainBusArbiter.io.dBus.rsp <> plugin.dBus.rsp } } case plugin : CsrPlugin => { @@ -248,218 +231,58 @@ case class Murax(config : MuraxConfig) extends Component{ } - val mainBus = SimpleBus() - //Arbiter of the cpu dBus/iBus to drive the mainBus - //Priority to dBus, !! cmd transactions can change on the fly !! - val mainBusArbiter = new Area{ - mainBus.cmd.valid := iBus.cmd.valid || dBus.cmd.valid - mainBus.cmd.wr := dBus.cmd.valid && dBus.cmd.wr - mainBus.cmd.address := dBus.cmd.valid ? dBus.cmd.address | iBus.cmd.pc - mainBus.cmd.data := dBus.cmd.data - mainBus.cmd.mask := dBus.cmd.size.mux( - 0 -> B"0001", - 1 -> B"0011", - default -> B"1111" - ) |<< dBus.cmd.address(1 downto 0) - iBus.cmd.ready := mainBus.cmd.ready && !dBus.cmd.valid - dBus.cmd.ready := mainBus.cmd.ready - - - val rspPending = RegInit(False) clearWhen(mainBus.rsp.valid) - val rspTarget = RegInit(False) - when(mainBus.cmd.fire && !mainBus.cmd.wr){ - rspTarget := dBus.cmd.valid - rspPending := True - } - - when(rspPending && !mainBus.rsp.valid){ - iBus.cmd.ready := False - dBus.cmd.ready := False - mainBus.cmd.valid := False - } - - iBus.rsp.ready := mainBus.rsp.valid && !rspTarget - iBus.rsp.inst := mainBus.rsp.data - iBus.rsp.error := False - - dBus.rsp.ready := mainBus.rsp.valid && rspTarget - dBus.rsp.data := mainBus.rsp.data - dBus.rsp.error := False - } - - //Create an SimpleBus mapped RAM - val ram = new Area{ - val bus = SimpleBus() - val ram = Mem(Bits(32 bits), onChipRamSize / 4) - bus.rsp.valid := RegNext(bus.cmd.fire && !bus.cmd.wr) init(False) - bus.rsp.data := ram.readWriteSync( - address = (bus.cmd.address >> 2).resized, - data = bus.cmd.data, - enable = bus.cmd.valid, - write = bus.cmd.wr, - mask = bus.cmd.mask - ) - bus.cmd.ready := True - - if(onChipRamHexFile != null){ - def readHexFile(path : String, callback : (Int, Int) => Unit): Unit ={ - import scala.io.Source - def hToI(that : String, start : Int, size : Int) = Integer.parseInt(that.substring(start,start + size), 16) - - var offset = 0 - for (line <- Source.fromFile(path).getLines) { - if (line.charAt(0) == ':'){ - val byteCount = hToI(line, 1, 2) - val nextAddr = hToI(line, 3, 4) + offset - val key = hToI(line, 7, 2) - key match { - case 0 => - for(i <- 0 until byteCount){ - callback(nextAddr + i, hToI(line, 9 + i * 2, 2)) - } - case 2 => - offset = hToI(line, 9, 4) << 4 - case 4 => - offset = hToI(line, 9, 4) << 16 - case 3 => - case 1 => - } - } - } - } - - val initContent = Array.fill[BigInt](ram.wordCount)(0) - readHexFile(onChipRamHexFile,(address,data) => { - initContent(address >> 2) |= BigInt(data) << ((address & 3)*8) - }) - ram.initBigInt(initContent) - } - } - - - - //Bridge simpleBus to apb - val apbBridge = new Area{ - val simpleBus = SimpleBus() - val apb = Apb3( - addressWidth = 20, - dataWidth = 32 - ) - val simpleBusStage = SimpleBus() - simpleBusStage.cmd << (if(pipelineApbBridge) simpleBus.cmd.halfPipe() else simpleBus.cmd) - simpleBusStage.rsp >-> simpleBus.rsp - - val state = RegInit(False) - simpleBusStage.cmd.ready := False - - apb.PSEL(0) := simpleBusStage.cmd.valid - apb.PENABLE := state - apb.PWRITE := simpleBusStage.cmd.wr - apb.PADDR := simpleBusStage.cmd.address.resized - apb.PWDATA := simpleBusStage.cmd.data - - simpleBusStage.rsp.valid := False - simpleBusStage.rsp.data := apb.PRDATA - when(!state){ - state := simpleBusStage.cmd.valid - } otherwise{ - when(apb.PREADY){ - state := False - simpleBusStage.rsp.valid := !simpleBusStage.cmd.wr - simpleBusStage.cmd.ready := True - } - } - } - - //Connect the mainBus to all slaves (ram, apbBridge) - val mainBusDecoder = new Area { - val masterBus = SimpleBus() - if(!pipelineMainBus) { - masterBus.cmd << mainBus.cmd - masterBus.rsp >> mainBus.rsp - } else { - masterBus.cmd <-< mainBus.cmd - masterBus.rsp >> mainBus.rsp - } - - val specification = List[(SimpleBus,SizeMapping)]( - ram.bus -> (0x00000000l, onChipRamSize kB), - apbBridge.simpleBus -> (0xF0000000l, 1 MB) - ) - - val slaveBuses = specification.map(_._1) - val memorySpaces = specification.map(_._2) - - val hits = for((slaveBus, memorySpace) <- specification) yield { - val hit = memorySpace.hit(masterBus.cmd.address) - slaveBus.cmd.valid := masterBus.cmd.valid && hit - slaveBus.cmd.payload := masterBus.cmd.payload - hit - } - val noHit = !hits.orR - masterBus.cmd.ready := (hits,slaveBuses).zipped.map(_ && _.cmd.ready).orR || noHit - - val rspPending = RegInit(False) clearWhen(masterBus.rsp.valid) setWhen(masterBus.cmd.fire && !masterBus.cmd.wr) - val rspNoHit = RegNext(False) init(False) setWhen(noHit) - val rspSourceId = RegNextWhen(OHToUInt(hits), masterBus.cmd.fire) - masterBus.rsp.valid := slaveBuses.map(_.rsp.valid).orR || (rspPending && rspNoHit) - masterBus.rsp.payload := slaveBuses.map(_.rsp.payload).read(rspSourceId) - - when(rspPending && !masterBus.rsp.valid) { //Only one pending read request is allowed - masterBus.cmd.ready := False - slaveBuses.foreach(_.cmd.valid := False) - } - } - - val gpioACtrl = Apb3Gpio( - gpioWidth = gpioWidth + //****** MainBus slaves ******** + val ram = new MuraxSimpleBusRam( + onChipRamSize = onChipRamSize, + onChipRamHexFile = onChipRamHexFile, + simpleBusConfig = simpleBusConfig ) + + val apbBridge = new MuraxSimpleBusToApbBridge( + apb3Config = Apb3Config( + addressWidth = 20, + dataWidth = 32 + ), + pipelineBridge = pipelineApbBridge, + simpleBusConfig = simpleBusConfig + ) + + + + //******** APB peripherals ********* + val gpioACtrl = Apb3Gpio(gpioWidth = gpioWidth) io.gpioA <> gpioACtrl.io.gpio val uartCtrl = Apb3UartCtrl(uartCtrlConfig) uartCtrl.io.uart <> io.uart externalInterrupt setWhen(uartCtrl.io.interrupt) - val timer = new Area{ - val apb = Apb3( - addressWidth = 8, - dataWidth = 32 - ) - - val prescaler = Prescaler(16) - val timerA,timerB = Timer(16) - - val busCtrl = Apb3SlaveFactory(apb) - val prescalerBridge = prescaler.driveFrom(busCtrl,0x00) - - val timerABridge = timerA.driveFrom(busCtrl,0x40)( - ticks = List(True, prescaler.io.overflow), - clears = List(timerA.io.full) - ) - - val timerBBridge = timerB.driveFrom(busCtrl,0x50)( - ticks = List(True, prescaler.io.overflow), - clears = List(timerB.io.full) - ) - - val interruptCtrl = InterruptCtrl(2) - val interruptCtrlBridge = interruptCtrl.driveFrom(busCtrl,0x10) - interruptCtrl.io.inputs(0) := timerA.io.full - interruptCtrl.io.inputs(1) := timerB.io.full - timerInterrupt setWhen(interruptCtrl.io.pendings.orR) - } + val timer = new MuraxApb3Timer() + timerInterrupt setWhen(timer.io.interrupt) + //******** Memory mappings ********* val apbDecoder = Apb3Decoder( - master = apbBridge.apb, + master = apbBridge.io.apb, slaves = List( gpioACtrl.io.apb -> (0x00000, 4 kB), uartCtrl.io.apb -> (0x10000, 4 kB), - timer.apb -> (0x20000, 4 kB) + timer.io.apb -> (0x20000, 4 kB) ) ) + + val mainBusDecoder = new Area { + val logic = new MuraxSimpleBusDecoder( + master = mainBusArbiter.io.masterBus, + specification = List[(SimpleBus,SizeMapping)]( + ram.io.bus -> (0x00000000l, onChipRamSize kB), + apbBridge.io.simpleBus -> (0xF0000000l, 1 MB) + ), + pipelineMaster = pipelineMainBus + ) + } } } diff --git a/src/main/scala/vexriscv/demo/MuraxUtiles.scala b/src/main/scala/vexriscv/demo/MuraxUtiles.scala new file mode 100644 index 0000000..d3f78a4 --- /dev/null +++ b/src/main/scala/vexriscv/demo/MuraxUtiles.scala @@ -0,0 +1,228 @@ +package vexriscv.demo + +import spinal.core._ +import spinal.lib.bus.amba3.apb.{Apb3, Apb3Config, Apb3SlaveFactory} +import spinal.lib.bus.misc.SizeMapping +import spinal.lib.misc.{InterruptCtrl, Prescaler, Timer} +import spinal.lib._ +import vexriscv.plugin.{DBusSimpleBus, IBusSimpleBus} + +case class SimpleBusConfig(addressWidth : Int, dataWidth : Int) + +case class SimpleBusCmd(config : SimpleBusConfig) extends Bundle{ + val wr = Bool + val address = UInt(config.addressWidth bits) + val data = Bits(config.dataWidth bits) + val mask = Bits(4 bit) +} + +case class SimpleBusRsp(config : SimpleBusConfig) extends Bundle{ + val data = Bits(config.dataWidth bits) +} + + +case class SimpleBus(config : SimpleBusConfig) extends Bundle with IMasterSlave { + val cmd = Stream(SimpleBusCmd(config)) + val rsp = Flow(SimpleBusRsp(config)) + + override def asMaster(): Unit = { + master(cmd) + slave(rsp) + } +} + +class MuraxMasterArbiter(simpleBusConfig : SimpleBusConfig) extends Component{ + val io = new Bundle{ + val iBus = slave(IBusSimpleBus(false)) + val dBus = slave(DBusSimpleBus()) + val masterBus = master(SimpleBus(simpleBusConfig)) + } + + io.masterBus.cmd.valid := io.iBus.cmd.valid || io.dBus.cmd.valid + io.masterBus.cmd.wr := io.dBus.cmd.valid && io.dBus.cmd.wr + io.masterBus.cmd.address := io.dBus.cmd.valid ? io.dBus.cmd.address | io.iBus.cmd.pc + io.masterBus.cmd.data := io.dBus.cmd.data + io.masterBus.cmd.mask := io.dBus.cmd.size.mux( + 0 -> B"0001", + 1 -> B"0011", + default -> B"1111" + ) |<< io.dBus.cmd.address(1 downto 0) + io.iBus.cmd.ready := io.masterBus.cmd.ready && !io.dBus.cmd.valid + io.dBus.cmd.ready := io.masterBus.cmd.ready + + + val rspPending = RegInit(False) clearWhen(io.masterBus.rsp.valid) + val rspTarget = RegInit(False) + when(io.masterBus.cmd.fire && !io.masterBus.cmd.wr){ + rspTarget := io.dBus.cmd.valid + rspPending := True + } + + when(rspPending && !io.masterBus.rsp.valid){ + io.iBus.cmd.ready := False + io.dBus.cmd.ready := False + io.masterBus.cmd.valid := False + } + + io.iBus.rsp.ready := io.masterBus.rsp.valid && !rspTarget + io.iBus.rsp.inst := io.masterBus.rsp.data + io.iBus.rsp.error := False + + io.dBus.rsp.ready := io.masterBus.rsp.valid && rspTarget + io.dBus.rsp.data := io.masterBus.rsp.data + io.dBus.rsp.error := False +} + + + +class MuraxSimpleBusRam(onChipRamSize : BigInt, onChipRamHexFile : String, simpleBusConfig : SimpleBusConfig) extends Component{ + val io = new Bundle{ + val bus = slave(SimpleBus(simpleBusConfig)) + } + + val ram = Mem(Bits(32 bits), onChipRamSize / 4) + io.bus.rsp.valid := RegNext(io.bus.cmd.fire && !io.bus.cmd.wr) init(False) + io.bus.rsp.data := ram.readWriteSync( + address = (io.bus.cmd.address >> 2).resized, + data = io.bus.cmd.data, + enable = io.bus.cmd.valid, + write = io.bus.cmd.wr, + mask = io.bus.cmd.mask + ) + io.bus.cmd.ready := True + + if(onChipRamHexFile != null){ + def readHexFile(path : String, callback : (Int, Int) => Unit): Unit ={ + import scala.io.Source + def hToI(that : String, start : Int, size : Int) = Integer.parseInt(that.substring(start,start + size), 16) + + var offset = 0 + for (line <- Source.fromFile(path).getLines) { + if (line.charAt(0) == ':'){ + val byteCount = hToI(line, 1, 2) + val nextAddr = hToI(line, 3, 4) + offset + val key = hToI(line, 7, 2) + key match { + case 0 => + for(i <- 0 until byteCount){ + callback(nextAddr + i, hToI(line, 9 + i * 2, 2)) + } + case 2 => + offset = hToI(line, 9, 4) << 4 + case 4 => + offset = hToI(line, 9, 4) << 16 + case 3 => + case 1 => + } + } + } + } + + val initContent = Array.fill[BigInt](ram.wordCount)(0) + readHexFile(onChipRamHexFile,(address,data) => { + initContent(address >> 2) |= BigInt(data) << ((address & 3)*8) + }) + ram.initBigInt(initContent) + } +} + + +class MuraxSimpleBusToApbBridge(apb3Config: Apb3Config, pipelineBridge : Boolean, simpleBusConfig : SimpleBusConfig) extends Component{ + assert(apb3Config.dataWidth == simpleBusConfig.dataWidth) + + val io = new Bundle { + val simpleBus = slave(SimpleBus(simpleBusConfig)) + val apb = master(Apb3(apb3Config)) + } + + val simpleBusStage = SimpleBus(simpleBusConfig) + simpleBusStage.cmd << (if(pipelineBridge) io.simpleBus.cmd.halfPipe() else io.simpleBus.cmd) + simpleBusStage.rsp >-> io.simpleBus.rsp + + val state = RegInit(False) + simpleBusStage.cmd.ready := False + + io.apb.PSEL(0) := simpleBusStage.cmd.valid + io.apb.PENABLE := state + io.apb.PWRITE := simpleBusStage.cmd.wr + io.apb.PADDR := simpleBusStage.cmd.address.resized + io.apb.PWDATA := simpleBusStage.cmd.data + + simpleBusStage.rsp.valid := False + simpleBusStage.rsp.data := io.apb.PRDATA + when(!state) { + state := simpleBusStage.cmd.valid + } otherwise { + when(io.apb.PREADY){ + state := False + simpleBusStage.rsp.valid := !simpleBusStage.cmd.wr + simpleBusStage.cmd.ready := True + } + } +} + +class MuraxSimpleBusDecoder(master : SimpleBus, specification : List[(SimpleBus,SizeMapping)], pipelineMaster : Boolean) extends Area{ + val masterPipelined = SimpleBus(master.config) + if(!pipelineMaster) { + masterPipelined.cmd << master.cmd + masterPipelined.rsp >> master.rsp + } else { + masterPipelined.cmd <-< master.cmd + masterPipelined.rsp >> master.rsp + } + + val slaveBuses = specification.map(_._1) + val memorySpaces = specification.map(_._2) + + val hits = for((slaveBus, memorySpace) <- specification) yield { + val hit = memorySpace.hit(masterPipelined.cmd.address) + slaveBus.cmd.valid := masterPipelined.cmd.valid && hit + slaveBus.cmd.payload := masterPipelined.cmd.payload + hit + } + val noHit = !hits.orR + masterPipelined.cmd.ready := (hits,slaveBuses).zipped.map(_ && _.cmd.ready).orR || noHit + + val rspPending = RegInit(False) clearWhen(masterPipelined.rsp.valid) setWhen(masterPipelined.cmd.fire && !masterPipelined.cmd.wr) + val rspNoHit = RegNext(False) init(False) setWhen(noHit) + val rspSourceId = RegNextWhen(OHToUInt(hits), masterPipelined.cmd.fire) + masterPipelined.rsp.valid := slaveBuses.map(_.rsp.valid).orR || (rspPending && rspNoHit) + masterPipelined.rsp.payload := slaveBuses.map(_.rsp.payload).read(rspSourceId) + + when(rspPending && !masterPipelined.rsp.valid) { //Only one pending read request is allowed + masterPipelined.cmd.ready := False + slaveBuses.foreach(_.cmd.valid := False) + } +} + +class MuraxApb3Timer extends Component{ + val io = new Bundle { + val apb = slave(Apb3( + addressWidth = 8, + dataWidth = 32 + )) + val interrupt = out Bool + } + + val prescaler = Prescaler(16) + val timerA,timerB = Timer(16) + + val busCtrl = Apb3SlaveFactory(io.apb) + val prescalerBridge = prescaler.driveFrom(busCtrl,0x00) + + val timerABridge = timerA.driveFrom(busCtrl,0x40)( + ticks = List(True, prescaler.io.overflow), + clears = List(timerA.io.full) + ) + + val timerBBridge = timerB.driveFrom(busCtrl,0x50)( + ticks = List(True, prescaler.io.overflow), + clears = List(timerB.io.full) + ) + + val interruptCtrl = InterruptCtrl(2) + val interruptCtrlBridge = interruptCtrl.driveFrom(busCtrl,0x10) + interruptCtrl.io.inputs(0) := timerA.io.full + interruptCtrl.io.inputs(1) := timerB.io.full + io.interrupt := interruptCtrl.io.pendings.orR +}