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