| @ -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 | |||
| @ -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, ... | |||
| @ -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 | |||
| @ -0,0 +1 @@ | |||
| sbt.version=1.3.13 | |||
| @ -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 | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -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 | |||
| } | |||
| } | |||
| **/ | |||
| } | |||
| @ -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 | |||
| } | |||
| @ -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("<output>") | |||
| opt[File]("init") action((f, c) => c.copy(initFile = f)) text("Initialization file for first RAM block") valueName("<bin>") | |||
| opt[Seq[Int]]("ram") action((r, c) => c.copy(ramBlocks = r.map(_ KiB))) text("SRAM Blocks in KiB") valueName("<block1>,<block2>") | |||
| } | |||
| 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) | |||
| } | |||
| } | |||
| } | |||
| } | |||
| @ -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] | |||