| @ -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] | |||||