From b9cccf90a0ea53fb7652079658172fe78ef44977 Mon Sep 17 00:00:00 2001 From: Thorsten Knoll Date: Thu, 14 Jan 2021 16:13:58 +0100 Subject: [PATCH] added MyMem template --- vexriscv/.gitignore | 35 +++ vexriscv/README.md | 201 +++++++++++++ vexriscv/build.sbt | 20 ++ vexriscv/project/build.properties | 1 + vexriscv/src/main/scala/mupq/MulPlugins.scala | 119 ++++++++ vexriscv/src/main/scala/mupq/MyMem.scala | 35 +++ vexriscv/src/main/scala/mupq/PQVexRiscv.scala | 278 ++++++++++++++++++ .../src/main/scala/mupq/PQVexRiscvSim.scala | 174 +++++++++++ vexriscv/vexriscvsim.cfg | 8 + 9 files changed, 871 insertions(+) create mode 100644 vexriscv/.gitignore create mode 100644 vexriscv/README.md create mode 100644 vexriscv/build.sbt create mode 100644 vexriscv/project/build.properties create mode 100644 vexriscv/src/main/scala/mupq/MulPlugins.scala create mode 100644 vexriscv/src/main/scala/mupq/MyMem.scala create mode 100644 vexriscv/src/main/scala/mupq/PQVexRiscv.scala create mode 100644 vexriscv/src/main/scala/mupq/PQVexRiscvSim.scala create mode 100644 vexriscv/vexriscvsim.cfg diff --git a/vexriscv/.gitignore b/vexriscv/.gitignore new file mode 100644 index 0000000..9eb978b --- /dev/null +++ b/vexriscv/.gitignore @@ -0,0 +1,35 @@ +# ---> SBT +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.history +.cache +.lib/ +.ensime +.idea + +# ---> Scala +*.class +*.log +.metals + +# ---> Spinal +simWorkspace/ +tmp/ + +# ---> Project +rtl/.Xil +rtl/*.log +rtl/*.dcp +rtl/*.time +rtl/*.json +rtl/*.bit +rtl/*.v +cpu0.yaml +*.bin diff --git a/vexriscv/README.md b/vexriscv/README.md new file mode 100644 index 0000000..4de2495 --- /dev/null +++ b/vexriscv/README.md @@ -0,0 +1,201 @@ +# About the source of this VexRiscv-Simulation +This is a stipped down version of the pqriscv resporitory: +https://github.com/mupq/pqriscv +The original README of this repository starts below and might not fit for all functionality anymore. +You might want to read the README in the root folder of this repo instead. +Modifications: Thorsten Knoll, Oct 2020 + +# PQVexRiscV +[VexRiscv](https://github.com/SpinalHDL/VexRiscv) based Target platforms +for the [pqriscv](https://github.com/mupq/pqriscv) project + +## Introduction +The goal of this project is to implement a simple test-platform for the +VexRiscv CPU, to be used as a reference platform for benchmarking and +experimenting with PQC scheme implementations. + +## Setup +You'll need the following + +* Java JDK **==** 1.8 +* [SBT](https://www.scala-sbt.org) >= 1.2.8, the Scala Build Tool +* (For iCE40 based FPGAs) [Icestorm FPGA Toolchain](http://www.clifford.at/icestorm/), including [NextPNR](https://github.com/YosysHQ/nextpnr) +* (For Xilinx based FPGAs) Vivado ~= 2018.3 (probably also works with older and newer versions) +* (For the Simulator) [Verilator](https://www.veripool.org/wiki/verilator) +* (Optionally for Debugging) [OpenOCD for VexRiscv](https://github.com/SpinalHDL/openocd_riscv) + +## Synthesis +This project (and VexRiscv) uses the SpinalHDL language, which is +essentially a DSL for hardware design on top of the Scala language. +The Scala sources will then generate equivalent Verilog (or VHDL) of +circuits described in SpinalHDL. +So to create a bitstream for a FPGA, you'll have to generate the verilog +file first and then run synthesis. + +Just run `sbt` in the project root and run any of the main classes to +generate the verilog source for one of the targets. +The `rtl` folder contains a Makefile to generate the bitstream for the +FPGAs (the Makefile will also run `sbt` to generate the Verilog). +For example: + +```sh +make -C rtl TARGETS=PQVexRiscvUP5K +``` + +## Simulation +Simulating a VexRiscv core is also possible. + +```sh +sbt "runMain mupq.PQVexRiscvSim --init initfile.bin --ram 256,128 --uart uartoutput.txt" +``` + +Adapt the options to your liking. The `--init` option points to a binary +file which is loaded into the simulated RAM as initialization. Remove +the option to leave the RAM blank, you can also load your binary via +OpenOCD+GDB. + +The `--ram` option determines the memory architecture. The +simulator will add a X KiB sized block of RAM to the memory architecture +for each integer. Default is two blocks of ram, one for meant for code, +one for data. The RAM blocks start at address `0x80000000` of the +VexRiscv core and are placed back-to-back. + +The `--uart` block may point to a file to which the simulated UART +output of the core is appended. When this option is skipped, UART is +redirected to stdout. + +## OpenOCD for VexRiscv +All boards (including the simulator) support debugging via a JTAG port. +For that you'll need a suitable debugging adapter (anything FT2232 +should do) and [OpenOCD port for VexRiscv](https://github.com/SpinalHDL/openocd_riscv). +You can use the following to compile a stripped down version of the +tool, which also adds the suffix `-vexriscv` to the program name, as +well as placing all the data into a different dir, so the installation +won't clash with any other OpenOCD version on your system. Adapt the +prefix/datarootdir to your taste. + +```sh +./bootstrap +./configure --prefix=/usr/local --program-suffix=-vexriscv \ + --datarootdir=/usr/local/share/vexriscv --enable-maintainer-mode \ + --disable-werror --enable-ft232r --enable-ftdi --enable-jtag_vpi \ + --disable-aice --disable-amtjtagaccel --disable-armjtagew \ + --disable-assert --disable-at91rm9200 --disable-bcm2835gpio \ + --disable-buspira --disable-cmsis-dap --disable-doxygen-html \ + --disable-doxygen-pdf --disable-dummy --disable-ep93xx \ + --disable-gw16012 --disable-imx_gpio --disable-jlink \ + --disable-kitprog --disable-minidriver-dummy --disable-oocd_trace \ + --disable-opendous --disable-openjtag --disable-osbdm \ + --disable-parport --disable-parport-giveio --disable-parport-ppdev \ + --disable-presto --disable-remote-bitbang --disable-rlink \ + --disable-stlink --disable-sysfsgpio --disable-ti-icdi \ + --disable-ulink --disable-usb-blaster --disable-usb-blaster-2 \ + --disable-usbprog --disable-verbose-jtag-io \ + --disable-verbose-usb-comms --disable-verbose-usb-io \ + --disable-vsllink --disable-xds110 --disable-zy1000 \ + --disable-zy1000-master +make +# sudo make install +``` + +Some templates for connecting OpenOCD and a Debug adapter to the boards +are at the ready in the project root. For example: + +```sh +openocd-vexriscv -f pqvexriscvsim.cfg +``` + +Since OpenOCD opens up a gdbserver, you can debug on your target with +GDB. For example: + +```sh +riscv64-unknown-elf-gdb -ex 'set remotetimeout 10' -ex 'target remote :3333' -ex load -ex 'break main' my_awesome_program.elf +``` + +## FPGA Platforms +This project will support multiple FPGA targets in future: + +* [iCE40 UltraPlus 5K](https://www.latticesemi.com/en/Products/FPGAandCPLD/iCE40UltraPlus) as, for example, present in the [iCE40 UltraPlus Breakout Board](https://www.latticesemi.com/en/Products/DevelopmentBoardsAndKits/iCE40UltraPlusBreakoutBoard) +* WIP: [Icoboard](http://icoboard.org/), which in turn uses the [iCE40 HX 8K](https://www.latticesemi.com/Products/FPGAandCPLD/iCE40) +* WIP: [Xilinx 7 Series](https://www.xilinx.com/products/silicon-devices/fpga.html), with an example file for the [Digilent Arty A7 board](https://store.digilentinc.com/arty-a7-artix-7-fpga-development-board-for-makers-and-hobbyists/) + +### iCE40 UltraPlus 5k +A basic design with the default VexRiscv plugins should fit on the iCE40 +UP5K. +As the UP5K features 8 DSP blocks, a non-blocking multiplier will fit +comfortably. +Furthermore, four 32kb sized SRAM blocks are present on the FPGA, which +are used to implement two separate 64kb large blocks on the memory bus. +You should link your code to the lower half (starting `0x80000000`), and +stack / data to the upper half (starting `0x80010000`). + +The `rtl` folder contains a constraint file to place the design on the +UltraPlus Breakout Board (though other boards should work fine, as long +as the use the UP5K, just adapt the PCF as necessary). +It exposes the JTAG and UART pins to IO ports located on header B as +follows: + +| Function | Pin | +|------------|---------| +| JTAG `TDO` | iob_23b | +| JTAG `TCK` | iob_25b | +| JTAG `TDI` | iob_24a | +| JTAG `TMS` | iob_29b | +| UART `TXD` | iob_8a | +| UART `RXD` | iob_9b | + +You can use any FTDI FT2232H or FT232H based debugger probe together +with `openocd-vexriscv`, just adapt the connection script +`PQVexRiscvUP5K.cfg` accordingly. +Tip: You could use the +[FT_PROG](https://www.ftdichip.com/Support/Utilities.htm#FT_PROG) to +change the serial number of a generic FT232H breakout board [(e,g, the +Adafruit FT232H Breakout board)](https://www.adafruit.com/product/2264). +Add a line `ftdi_serial "XXX"` to the connection script, and `openocd` +will automatically pick the right FTDI. +Another well known FTDI based probe is the [Olimex +ARM-USB-TINY-H](https://www.olimex.com/Products/ARM/JTAG/ARM-USB-TINY-H/) +(see the example `openocd-vexriscv` connection script for the Icoboard +`pqvexriscvicoboard.cfg`). + +### Icoboarda (WIP) +This FPGA target is still a heavy work in progress. +The FPGA itself has more LUTs than the UP5K, however, doesn't feature +any DSPs. +Thus, the design will likely use a smaller multi-cycle multiplier to +implement the multiplication instruction. +The large SRAM blocks of the UP5K are also missing, however, the +Icoboard offers a 8MBit large SRAM chip, which will be used for +code **and** data. + +### Xilinx 7 Series +The Digilent Arty A7 board uses the Xilinx Artix-7 XC7A35T (or XC7A100T +depending on the model), which will comfortably fit even larger variants +of the VexRiscv core). +The Arty board also exists in other variants using the Spartan-7 series +chip, which should also be large enough for the VexRiscv. +Similarly to the UP5K, the default design will use two separate 64kb +blocks for code and data each. + +On the Arty Boards, the UART is exposed on the shared FPGA configuration +and UART USB port of the board. +The JTAG (for the VexRiscv core, **not** the JTAG for the FPGA chip) is +exposed on the PMOD JD connector. + +| Function | Pin (PMOD JD) | +|----------|---------------| +| `TDO` | 1 | +| `TCK` | 3 | +| `TDI` | 7 | +| `TMS` | 8 | + +If you use the Olimex ARM-USB-TINY-H, you'll also need to connect the +VREF. +Note, that the pinout is exactly the same as the [SiFive Freedom E310 +Arty FPGA Dev Kit](https://github.com/sifive/freedom), except that +the `nRST`, `nTRST` remain unused. + +## Internals + +*TODO*: Write up some infos on the platforms, what features of VexRiscv +are enabled, how the memory architecture works, ... diff --git a/vexriscv/build.sbt b/vexriscv/build.sbt new file mode 100644 index 0000000..c30394f --- /dev/null +++ b/vexriscv/build.sbt @@ -0,0 +1,20 @@ +ThisBuild / organization := "mupq" + +ThisBuild / scalaVersion := "2.11.12" + +lazy val pqvexriscv = (project in file(".")) + .settings( + name := "pqvexriscv", + version := "0.1", + libraryDependencies ++= Seq( + "org.scalatest" %% "scalatest" % "3.0.5" % "test", + compilerPlugin("com.github.spinalhdl" % "spinalhdl-idsl-plugin_2.11" % "1.4.0") + ), + run / connectInput := true, + outputStrategy := Some(StdoutOutput), + ).dependsOn(vexRiscv) + +lazy val vexRiscv = RootProject(uri("git://github.com/SpinalHDL/VexRiscv#2942d0652a89646c5225bee15dd55cc3b0871766")) + +fork := true + diff --git a/vexriscv/project/build.properties b/vexriscv/project/build.properties new file mode 100644 index 0000000..0837f7a --- /dev/null +++ b/vexriscv/project/build.properties @@ -0,0 +1 @@ +sbt.version=1.3.13 diff --git a/vexriscv/src/main/scala/mupq/MulPlugins.scala b/vexriscv/src/main/scala/mupq/MulPlugins.scala new file mode 100644 index 0000000..568939d --- /dev/null +++ b/vexriscv/src/main/scala/mupq/MulPlugins.scala @@ -0,0 +1,119 @@ +package mupq + +import vexriscv._ +import vexriscv.plugin._ +import spinal.core._ + +/** + * A multiplication plugin using only 16-bit multiplications + */ +class Mul16Plugin extends Plugin[VexRiscv]{ + + object MUL_LL extends Stageable(UInt(32 bits)) + object MUL_LH extends Stageable(UInt(32 bits)) + object MUL_HL extends Stageable(UInt(32 bits)) + object MUL_HH extends Stageable(UInt(32 bits)) + + object MUL extends Stageable(Bits(64 bits)) + + object IS_MUL extends Stageable(Bool) + + override def setup(pipeline: VexRiscv): Unit = { + import Riscv._ + import pipeline.config._ + + + val actions = List[(Stageable[_ <: BaseType],Any)]( + SRC1_CTRL -> Src1CtrlEnum.RS, + SRC2_CTRL -> Src2CtrlEnum.RS, + REGFILE_WRITE_VALID -> True, + BYPASSABLE_EXECUTE_STAGE -> False, + BYPASSABLE_MEMORY_STAGE -> False, + RS1_USE -> True, + RS2_USE -> True, + IS_MUL -> True + ) + + val decoderService = pipeline.service(classOf[DecoderService]) + decoderService.addDefault(IS_MUL, False) + decoderService.add(List( + MULX -> actions + )) + + } + + override def build(pipeline: VexRiscv): Unit = { + import pipeline._ + import pipeline.config._ + + // Prepare signed inputs for the multiplier in the next stage. + // This will map them best to an FPGA DSP. + execute plug new Area { + import execute._ + val a,b = Bits(32 bit) + + a := input(SRC1) + b := input(SRC2) + + val aLow = a(15 downto 0).asUInt + val bLow = b(15 downto 0).asUInt + val aHigh = a(31 downto 16).asUInt + val bHigh = b(31 downto 16).asUInt + + insert(MUL_LL) := aLow * bLow + insert(MUL_LH) := aLow * bHigh + insert(MUL_HL) := aHigh * bLow + insert(MUL_HH) := aHigh * bHigh + } + + memory plug new Area { + import memory._ + + val ll = UInt(32 bits) + val lh = UInt(33 bits) + val hl = UInt(32 bits) + val hh = UInt(32 bits) + + ll := input(MUL_LL) + lh := input(MUL_LH).resized + hl := input(MUL_HL) + hh := input(MUL_HH) + + val hllh = lh + hl + insert(MUL) := ((hh ## ll(31 downto 16)).asUInt + hllh) ## ll(15 downto 0) + } + + writeBack plug new Area { + import writeBack._ + val aSigned,bSigned = Bool + switch(input(INSTRUCTION)(13 downto 12)) { + is(B"01") { + aSigned := True + bSigned := True + } + is(B"10") { + aSigned := True + bSigned := False + } + default { + aSigned := False + bSigned := False + } + } + + val a = (aSigned && input(SRC1).msb) ? input(SRC2).asUInt | U(0) + val b = (bSigned && input(SRC2).msb) ? input(SRC1).asUInt | U(0) + + when(arbitration.isValid && input(IS_MUL)){ + switch(input(INSTRUCTION)(13 downto 12)){ + is(B"00"){ + output(REGFILE_WRITE_DATA) := input(MUL)(31 downto 0) + } + is(B"01",B"10",B"11"){ + output(REGFILE_WRITE_DATA) := (((input(MUL)(63 downto 32)).asUInt + ~a) + (~b + 2)).asBits + } + } + } + } + } +} diff --git a/vexriscv/src/main/scala/mupq/MyMem.scala b/vexriscv/src/main/scala/mupq/MyMem.scala new file mode 100644 index 0000000..3ad7208 --- /dev/null +++ b/vexriscv/src/main/scala/mupq/MyMem.scala @@ -0,0 +1,35 @@ +package mupq + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba3.apb._ + +class MyMem() extends Component { + val io = new Bundle { + val bus = slave(Apb3(Apb3Config(addressWidth = 20, dataWidth = 32))) + val interrupt = out Bool + } + + /** + val coproc = new KeccakCoproMultimemWrapper(dataWidth, keccakN) + **/ + val busReady = Reg(Bool) init(false) + val busAccess = io.bus.PENABLE && io.bus.PSEL(0) + io.bus.PRDATA := 0 + busReady := busAccess && !busReady + io.bus.PREADY := busReady + io.interrupt := False + /** + when(busReady) { + when(io.bus.PWRITE) { + start := io.bus.PWDATA(0) // Set flags on write, will trigger the coprocessor + interruptEn := io.bus.PWDATA(2) + } otherwise { + io.bus.PRDATA(0) := busy // Assemble flags for read + io.bus.PRDATA(1) := interrupt + io.bus.PRDATA(2) := interruptEn + interrupt := False + } + } + **/ +} diff --git a/vexriscv/src/main/scala/mupq/PQVexRiscv.scala b/vexriscv/src/main/scala/mupq/PQVexRiscv.scala new file mode 100644 index 0000000..ba04488 --- /dev/null +++ b/vexriscv/src/main/scala/mupq/PQVexRiscv.scala @@ -0,0 +1,278 @@ +package mupq + +import scala.collection.mutable.ArrayBuffer + +import spinal.core._ +import spinal.lib._ +import spinal.lib.bus.amba3.apb._ +import spinal.lib.bus.misc._ +import spinal.lib.bus.simple._ +import spinal.lib.io._ +import spinal.lib.com.jtag._ +import spinal.lib.com.uart._ + +import vexriscv._ +import vexriscv.demo.MuraxApb3Timer +import vexriscv.plugin._ + +abstract class PQVexRiscv( + cpuPlugins : () => Seq[Plugin[VexRiscv]], + ibusRange : SizeMapping, + genUART : Boolean = true, + gpioWidth : Int = 0, + genTimer : Boolean = false +) extends Component { + val coreFrequency : HertzNumber + + /* Clock and resets */ + + val asyncReset: Bool = Bool + + val mainClock: Bool = Bool + + val resetCtrlClockDomain: ClockDomain = ClockDomain( + clock = mainClock, + config = ClockDomainConfig(resetKind = BOOT)) + + val resetCtrl = new ClockingArea(resetCtrlClockDomain) { + val bufferedReset = BufferCC(asyncReset) + + val mainClockReset = RegNext(bufferedReset) + val systemClockReset = RegNext(bufferedReset) + } + + val systemClockDomain: ClockDomain = ClockDomain( + clock = mainClock, + reset = resetCtrl.systemClockReset, + frequency = FixedFrequency(coreFrequency)) + + val debugClockDomain: ClockDomain = ClockDomain( + clock = mainClock, + reset = resetCtrl.mainClockReset, + frequency = FixedFrequency(coreFrequency)) + + /* Bus interconnect */ + val busConfig = PipelinedMemoryBusConfig( + addressWidth = 32, + dataWidth = 32 + ) + + val busSlaves = ArrayBuffer[(PipelinedMemoryBus, SizeMapping)]() + val busMasters = ArrayBuffer[(PipelinedMemoryBus, SizeMapping)]() + + /* VexRiscv Core */ + var jtag : Jtag = null + + val core = new ClockingArea(systemClockDomain) { + val timerInterrupt = False + val externalInterrupt = False + + val config = VexRiscvConfig( + plugins = cpuPlugins() ++ Seq(new DebugPlugin(debugClockDomain, 3))) + + val cpu = new VexRiscv(config) + /* Wire the Busses / Lines to the plugins */ + var ibus : PipelinedMemoryBus = PipelinedMemoryBus(busConfig) + var dbus : PipelinedMemoryBus = PipelinedMemoryBus(busConfig) + for (plugin <- cpu.plugins) plugin match { + case plugin: IBusSimplePlugin => + val cpuibus = plugin.iBus.toPipelinedMemoryBus() + ibus.cmd <-/< cpuibus.cmd + ibus.rsp >> cpuibus.rsp + case plugin: DBusSimplePlugin => + val cpudbus = plugin.dBus.toPipelinedMemoryBus() + dbus.cmd <-/< cpudbus.cmd + dbus.rsp >> cpudbus.rsp + plugin.dBus.rsp.error := False + case plugin: CsrPlugin => + plugin.externalInterrupt := externalInterrupt + plugin.timerInterrupt := timerInterrupt + case plugin: DebugPlugin => + plugin.debugClockDomain { + resetCtrl.systemClockReset setWhen (RegNext(plugin.io.resetOut)) + jtag = plugin.io.bus.fromJtag() + } + case _ => + } + + busMasters += dbus -> SizeMapping(0l, (1l << 32l)) + busMasters += ibus -> ibusRange + } + + /* Peripherals */ + + var gpio : TriStateArray = null + + var uart : Uart = null + + val peripherals = new ClockingArea(systemClockDomain) { + if (gpioWidth > 0) { + gpio = TriStateArray(gpioWidth bits) + } + + if (genUART) { + uart = Uart() + } + + if(genUART || gpioWidth > 0 || genTimer) { + val apbBridge = new PipelinedMemoryBusToApbBridge( + apb3Config = Apb3Config( + addressWidth = 20, + dataWidth = 32 + ), + pipelineBridge = false, + pipelinedMemoryBusConfig = busConfig + ) + + busSlaves += apbBridge.io.pipelinedMemoryBus -> SizeMapping(0xF0000000l, 1 MiB) + + val apbMapping = ArrayBuffer[(Apb3, SizeMapping)]() + + if (gpioWidth > 0) { + val gpioACtrl = Apb3Gpio(gpioWidth = gpioWidth, withReadSync = true) + gpio <> gpioACtrl.io.gpio + apbMapping += gpioACtrl.io.apb -> (0x00000, 4 KiB) + } + + if (genUART) { + val uartCtrlConfig = UartCtrlMemoryMappedConfig( + uartCtrlConfig = UartCtrlGenerics( + dataWidthMax = 8, + clockDividerWidth = 20, + preSamplingSize = 1, + samplingSize = 3, + postSamplingSize = 1 + ), + initConfig = UartCtrlInitConfig( + baudrate = 115200, + dataLength = 7, //7 => 8 bits + parity = UartParityType.NONE, + stop = UartStopType.ONE + ), + busCanWriteClockDividerConfig = false, + busCanWriteFrameConfig = false, + txFifoDepth = 16, + rxFifoDepth = 16 + ) + + val uartCtrl = Apb3UartCtrl(uartCtrlConfig) + uart <> uartCtrl.io.uart + core.externalInterrupt setWhen(uartCtrl.io.interrupt) + apbMapping += uartCtrl.io.apb -> (0x10000, 4 KiB) + } + + if (genTimer) { + val timer = new MuraxApb3Timer() + core.timerInterrupt setWhen(timer.io.interrupt) + apbMapping += timer.io.apb -> (0x20000, 4 KiB) + } + + val myMem = new MyMem() + core.timerInterrupt setWhen(myMem.io.interrupt) + apbMapping += myMem.io.bus -> (0x30000, 4 KiB) + + val apbDecoder = Apb3Decoder( + master = apbBridge.io.apb, + slaves = apbMapping + ) + } + } + + def buildInterconnect() : Unit = { + assert(!SizeMapping.verifyOverlapping(busSlaves.map(_._2))) + val crossbar = new ClockingArea(systemClockDomain) { + val interconnect = new PipelinedMemoryBusInterconnect() + interconnect.perfConfig() + /* Setup the interconnect */ + interconnect.addSlaves(busSlaves: _*) + /* Check which masters overlap with which slaves */ + def overlaps(a : SizeMapping, b : SizeMapping) : Boolean = if (a.base < b.base) a.end >= b.base else b.end >= a.base + interconnect.addMasters(busMasters.map(m => m._1 -> busSlaves.filter(s => overlaps(m._2, s._2)).map(s => s._1).toSeq): _*) + } + } + + Component.current.addPrePopTask(() => buildInterconnect()) +} + +object PQVexRiscv +{ + type PluginSeq = Seq[Plugin[VexRiscv]] + type PluginGen = () => PluginSeq + + /** Basic set of Plugins (conforms mostly to rv32i) */ + def baseConfig(base: PluginGen = () => Seq()) = () => base() ++ Seq( + new IBusSimplePlugin( + resetVector = 0x80000000l, + cmdForkOnSecondStage = true, + cmdForkPersistence = false, + prediction = NONE, + catchAccessFault = false, + compressedGen = false + ), + new DBusSimplePlugin( + catchAddressMisaligned = false, + catchAccessFault = false, + earlyInjection = false + ), + new CsrPlugin( + CsrPluginConfig.smallest(0x80000000l).copy( + mtvecAccess = CsrAccess.READ_WRITE, + mcycleAccess = CsrAccess.READ_ONLY, + minstretAccess = CsrAccess.READ_ONLY + ) + ), + new DecoderSimplePlugin( + catchIllegalInstruction = false + ), + new RegFilePlugin( + regFileReadyKind = plugin.SYNC, + zeroBoot = false + ), + new IntAluPlugin, + new SrcPlugin( + separatedAddSub = false, + executeInsertion = false + ), + new FullBarrelShifterPlugin, + new HazardSimplePlugin( + bypassExecute = true, + bypassMemory = true, + bypassWriteBack = true, + bypassWriteBackBuffer = true, + pessimisticUseSrc = false, + pessimisticWriteRegFile = false, + pessimisticAddressMatch = false + ), + new BranchPlugin( + earlyBranch = false, + catchAddressMisaligned = false + ), + new YamlPlugin("cpu0.yaml") + ) + + /** Plugins for a small multiplier */ + def smallMultiplier = Seq( + new MulDivIterativePlugin( + genMul = true, + genDiv = true, + mulUnrollFactor = 1, + divUnrollFactor = 1 + ) + ) + + /** Config with a small multiplier */ + def withSmallMultiplier(base: PluginGen = baseConfig()) = () => base() ++ smallMultiplier + + /** Plugins for a multiplier for FPGAs */ + def dspMultiplier = Seq( + new Mul16Plugin, + new MulDivIterativePlugin( + genMul = false, + genDiv = true, + divUnrollFactor = 1 + ) + ) + + /** Config with a multiplier for FPGAs */ + def withDSPMultiplier(base: PluginGen = baseConfig()) = () => base() ++ dspMultiplier +} diff --git a/vexriscv/src/main/scala/mupq/PQVexRiscvSim.scala b/vexriscv/src/main/scala/mupq/PQVexRiscvSim.scala new file mode 100644 index 0000000..9a15603 --- /dev/null +++ b/vexriscv/src/main/scala/mupq/PQVexRiscvSim.scala @@ -0,0 +1,174 @@ +package mupq + +import java.io.{File, FileInputStream, FileOutputStream, IOException, OutputStream} + +import scopt.OptionParser + +import spinal.sim._ +import spinal.core._ +import spinal.lib._ +import spinal.core.sim._ + +import spinal.lib.bus.simple._ +import spinal.lib.bus.misc.SizeMapping +import spinal.lib.io.TriStateArray +import spinal.lib.com.jtag.Jtag +import spinal.lib.com.uart.Uart +import spinal.lib.com.jtag.sim.JtagTcp + +import vexriscv.VexRiscv +import vexriscv.plugin.Plugin + +case class PipelinedMemoryBusRam(size : BigInt, initialContent : File = null) extends Component{ + require(size % 4 == 0, "Size must be multiple of 4 bytes") + require(size > 0, "Size must be greater than zero") + val busConfig = PipelinedMemoryBusConfig(log2Up(size), 32) + val io = new Bundle{ + val bus = slave(PipelinedMemoryBus(busConfig)) + } + + val ram = Mem(Bits(32 bits), size / 4) + io.bus.rsp.valid := RegNext(io.bus.cmd.fire && !io.bus.cmd.write) init(False) + io.bus.rsp.data := ram.readWriteSync( + address = io.bus.cmd.address >> 2, + data = io.bus.cmd.data, + enable = io.bus.cmd.valid, + write = io.bus.cmd.write, + mask = io.bus.cmd.mask + ) + io.bus.cmd.ready := True + + if (initialContent != null) { + val input = new FileInputStream(initialContent) + val initContent = Array.fill[BigInt](ram.wordCount)(0) + val fileContent = Array.ofDim[Byte](Seq(input.available, initContent.length * 4).min) + input.read(fileContent) + for ((byte, addr) <- fileContent.zipWithIndex) { + val l = java.lang.Byte.toUnsignedLong(byte) << ((addr & 3) * 8) + initContent(addr >> 2) |= BigInt(l) + } + ram.initBigInt(initContent) + } +} + + + + +class PQVexRiscvSim( + val ramBlockSizes : Seq[BigInt] = Seq[BigInt](256 KiB, 128 KiB), + val initialContent : File = null, + val coreFrequency : HertzNumber = 12 MHz, + cpuPlugins : () => Seq[Plugin[VexRiscv]] = PQVexRiscv.withDSPMultiplier() +) extends PQVexRiscv( + cpuPlugins = cpuPlugins, + ibusRange = SizeMapping(0x80000000l, ramBlockSizes.reduce(_ + _)) +) { + val io = new Bundle { + val asyncReset = in Bool + val mainClock = in Bool + val uart = master(Uart()) + val jtag = slave(Jtag()) + } + + asyncReset := io.asyncReset + mainClock := io.mainClock + + uart <> io.uart + jtag <> io.jtag + + val memory = new ClockingArea(systemClockDomain) { + val ramBlocks = ramBlockSizes.zipWithIndex.map(t => PipelinedMemoryBusRam(t._1, if (t._2 == 0) initialContent else null)) + var curAddr : BigInt = 0x80000000l + for (block <- ramBlocks) { + busSlaves += block.io.bus -> SizeMapping(curAddr, block.size) + curAddr += block.size + } + } +} + +object PQVexRiscvSim { + + def main(args: Array[String]) = { + case class PQVexRiscvSimConfig( + uartOutFile: OutputStream = System.out, + initFile: File = null, + ramBlocks: Seq[BigInt] = Seq(256 KiB, 128 KiB), + cpuPlugins: () => Seq[Plugin[VexRiscv]] = PQVexRiscv.withDSPMultiplier() + ) + val optParser = new OptionParser[PQVexRiscvSimConfig]("PQVexRiscvSim") { + head("PQVexRiscvSim simulator") + help("help") text("print usage text") + opt[File]("uart") action((f, c) => c.copy(uartOutFile = new FileOutputStream(f, true))) text("File for UART output (will be appended)") valueName("") + opt[File]("init") action((f, c) => c.copy(initFile = f)) text("Initialization file for first RAM block") valueName("") + opt[Seq[Int]]("ram") action((r, c) => c.copy(ramBlocks = r.map(_ KiB))) text("SRAM Blocks in KiB") valueName(",") + } + + val config = optParser.parse(args, PQVexRiscvSimConfig()) match { + case Some(config) => config + case None => ??? + } + + val compiled = SimConfig.allOptimisation.compile { + new PQVexRiscvSim(config.ramBlocks, config.initFile, cpuPlugins=config.cpuPlugins) + } + + compiled.doSim("PqVexRiscvSim", 42) { dut => + val mainClkPeriod = (1e12 / dut.coreFrequency.toDouble).toLong + val jtagClkPeriod = mainClkPeriod * 4 + val uartBaudRate = 115200 + val uartBaudPeriod = (1e12 / uartBaudRate.toDouble).toLong + + val clockDomain = ClockDomain(dut.io.mainClock, dut.io.asyncReset) + clockDomain.forkStimulus(mainClkPeriod) + + val tcpJtag = JtagTcp( + jtag = dut.io.jtag, + jtagClkPeriod = jtagClkPeriod + ) + + println(s"Simulating ${dut.getClass.getName} with JtagTcp on port 7894") + + val uartPin = dut.io.uart.txd + + val uartDecoder = fork { + sleep(1) + waitUntil(uartPin.toBoolean == true) + try { + while (true) { + waitUntil(uartPin.toBoolean == false) + sleep(uartBaudPeriod / 2) + if (uartPin.toBoolean != false) { + println("\rUART frame error (start bit)") + } else { + sleep(uartBaudPeriod) + var byte = 0 + var i = 0 + while (i < 8) { + if (uartPin.toBoolean) { + byte |= 1 << i + } + sleep(uartBaudPeriod) + i += 1 + } + if (uartPin.toBoolean) { + config.uartOutFile.write(byte) + } else { + println("\rUART frame error (stop bit)") + } + } + } + } catch { + case io: IOException => + } + println("\rUART decoder stopped") + } + + var running = true + + while (running) { + sleep(mainClkPeriod * 50000) + } + } + } + +} diff --git a/vexriscv/vexriscvsim.cfg b/vexriscv/vexriscvsim.cfg new file mode 100644 index 0000000..392b9fb --- /dev/null +++ b/vexriscv/vexriscvsim.cfg @@ -0,0 +1,8 @@ +# Adapt this to your favourite FTDI-based debugger +source [find interface/jtag_tcp.cfg] + +# The Murax target needs a YAML file, even if it is empty +set MURAX_CPU0_YAML cpu0.yaml + +# The Murax target should work for all PQVexRiscv based chips +source [find target/murax.cfg]