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