├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE.txt ├── README.md ├── apps ├── blinky │ ├── CMakeLists.txt │ └── blinky.c └── wb_uart_led │ ├── CMakeLists.txt │ └── wb_uart_led.c ├── build.sbt ├── build.sc ├── cmake ├── confighash.cmake ├── configs │ ├── 3threads.cmake │ ├── 5threads@50MHz.cmake │ ├── 8threads.cmake │ ├── default.cmake │ ├── fpga.cmake │ └── highmem.cmake ├── configverify.cmake ├── infiles │ ├── application.conf.in │ ├── hwconfig.c.in │ ├── hwconfig.h.in │ └── hwconfig.ld.in ├── lib │ └── check.cmake └── utils │ └── bash │ ├── build_fp_emu.sh │ └── gather_fpga_resources.sh ├── doc ├── drawio │ └── flexpret-cmake-architecture.drawio ├── flexpret-cmake-architecture.drawio.png ├── flexpret-pipeline.pdf └── flexpret-pipeline.svg ├── docker └── Dockerfile ├── emulator ├── CMakeLists.txt ├── clients │ ├── CMakeLists.txt │ ├── README.md │ ├── common.c │ ├── common.h │ ├── gpio.c │ ├── interrupter.c │ ├── stimuli-mt.c │ ├── stimuli.c │ └── uart.c ├── main.cpp ├── pin_event.cpp ├── pin_event.h └── printf_fsm.c ├── env.bash ├── env.fish ├── fpga ├── .gitignore ├── CMakeLists.txt ├── README.md ├── cmake │ ├── commands.cmake │ └── infiles │ │ ├── clk_wiz_config.tcl.in │ │ ├── clock.xdc.in │ │ ├── set_program_file.tcl.in │ │ └── variables.tcl.in └── zedboard │ ├── CMakeLists.txt │ ├── README.md │ ├── docs │ └── zedboard-setup.png │ ├── fp-blinky │ ├── CMakeLists.txt │ ├── README.md │ ├── rtl │ │ └── Top.v │ └── xdc │ │ └── Top.xdc │ ├── fp-bootloader │ ├── CMakeLists.txt │ ├── README.md │ ├── rtl │ │ ├── .gitignore │ │ └── Top.v │ └── xdc │ │ └── Top.xdc │ ├── leds │ ├── README.md │ ├── rtl │ │ └── Top.v │ ├── runVivadoLeds.tcl │ └── xdc │ │ └── leds.xdc │ ├── tcl │ ├── README.md │ ├── bitstream.tcl │ ├── flash.tcl │ └── setup.tcl │ └── xdc │ ├── README.md │ └── zedboard.xdc ├── scripts ├── c │ └── parse_disasm.py ├── callgraph.py ├── fpga │ └── tofpga.sh ├── hdl │ └── simify_verilog.py ├── run_multiple_tests.sh ├── send_uart.py └── serialize_app.py ├── sdk ├── .gitignore ├── CMakeLists.txt ├── README.md ├── bootloader │ ├── .gitignore │ ├── CMakeLists.txt │ ├── README.md │ ├── configure.py │ ├── loader.c │ └── startup.c ├── cmake │ ├── configderive.cmake │ ├── configs │ │ └── default.cmake │ ├── configverify.cmake │ ├── fp-app.cmake │ ├── infiles │ │ ├── emu-app.sh.in │ │ ├── fpga-app.sh.in │ │ ├── swconfig.h.in │ │ └── swconfig.ld.in │ └── riscv-toolchain.cmake ├── external │ └── CMakeLists.txt ├── lib │ ├── CMakeLists.txt │ ├── include │ │ ├── .gitignore │ │ └── flexpret │ │ │ ├── assert.h │ │ │ ├── cond.h │ │ │ ├── csrs.h │ │ │ ├── exceptions.h │ │ │ ├── flexpret.h │ │ │ ├── io.h │ │ │ ├── lock.h │ │ │ ├── pbuf.h │ │ │ ├── swconfig.h │ │ │ ├── thread.h │ │ │ ├── time.h │ │ │ ├── types.h │ │ │ ├── uart.h │ │ │ └── wb.h │ ├── linker │ │ ├── .gitignore │ │ ├── bootloader │ │ │ ├── none │ │ │ │ └── bootloader.ld │ │ │ └── use │ │ │ │ └── .gitignore │ │ └── flexpret.ld │ └── src │ │ ├── cond.c │ │ ├── ctx_switch.S │ │ ├── exceptions.c │ │ ├── io.c │ │ ├── lock.c │ │ ├── pbuf.c │ │ ├── printf_putchar.c │ │ ├── start.S │ │ ├── startup.c │ │ ├── syscalls.c │ │ ├── thread.c │ │ ├── uart.c │ │ └── wb.c └── tests │ ├── CMakeLists.txt │ ├── README.md │ ├── c-tests │ ├── CMakeLists.txt │ ├── add │ │ ├── CMakeLists.txt │ │ └── add.c │ ├── calloc │ │ ├── CMakeLists.txt │ │ └── calloc.c │ ├── fib │ │ ├── CMakeLists.txt │ │ └── fib.c │ ├── global │ │ ├── CMakeLists.txt │ │ └── global.c │ ├── gpio │ │ ├── CMakeLists.txt │ │ └── gpio.c │ ├── hwlock │ │ ├── CMakeLists.txt │ │ └── hwlock.c │ ├── interrupt-delay │ │ ├── CMakeLists.txt │ │ └── interrupt-delay.c │ ├── interrupt │ │ ├── CMakeLists.txt │ │ ├── interrupt.c │ │ ├── interrupt.h │ │ └── main.c │ ├── lbu │ │ ├── CMakeLists.txt │ │ └── lbu.c │ ├── malloc │ │ ├── CMakeLists.txt │ │ └── malloc.c │ ├── realloc │ │ ├── CMakeLists.txt │ │ └── realloc.c │ ├── sections │ │ ├── CMakeLists.txt │ │ └── sections.c │ ├── syscall │ │ ├── CMakeLists.txt │ │ └── syscall.c │ ├── time │ │ ├── CMakeLists.txt │ │ └── time.c │ └── wb_uart │ │ ├── .gitignore │ │ ├── CMakeLists.txt │ │ ├── Makefile │ │ ├── testfile.txt │ │ └── wb_uart.c │ └── mt-c-tests │ ├── CMakeLists.txt │ ├── add │ ├── CMakeLists.txt │ └── add.c │ ├── cond │ ├── CMakeLists.txt │ └── cond.c │ ├── heap │ ├── CMakeLists.txt │ └── heap.c │ ├── interrupt │ ├── CMakeLists.txt │ └── main.c │ ├── lockowner │ ├── CMakeLists.txt │ └── lockowner.c │ ├── printf │ ├── CMakeLists.txt │ └── printf.c │ ├── swlock │ ├── CMakeLists.txt │ └── swlock.c │ ├── syscall │ ├── CMakeLists.txt │ └── syscall.c │ ├── threadcancel │ ├── CMakeLists.txt │ └── threadcancel.c │ ├── threadprint │ ├── CMakeLists.txt │ └── threadprint.c │ └── uart │ ├── CMakeLists.txt │ ├── Makefile │ ├── client │ └── stimuli.py │ ├── data.txt │ └── uart.c └── src ├── main ├── resources │ ├── .gitignore │ ├── DualPortBramEmulator.v │ └── DualPortBramFPGA.v └── scala │ ├── Core │ ├── ALU.scala │ ├── CSR.scala │ ├── Datapath.scala │ ├── MMIO.scala │ ├── Multiplier.scala │ ├── RegisterFile.scala │ ├── Top.scala │ ├── config.scala │ ├── constants.scala │ ├── control.scala │ ├── core.scala │ ├── decode.scala │ ├── dspm.scala │ ├── instructions.scala │ ├── ispm.scala │ ├── loadstore.scala │ ├── lock.scala │ ├── main.scala │ ├── scheduler.scala │ └── util.scala │ ├── Wishbone │ ├── WishboneBus.scala │ ├── WishboneMaster.scala │ └── WishboneUart.scala │ └── uart │ ├── Const.v │ ├── Counter.v │ ├── Gateway.v │ ├── HardRegister.v │ ├── IORegister.v │ ├── ParityGen.v │ ├── Register.v │ ├── Reverse.v │ ├── ShiftRegister.v │ ├── UART.v │ ├── UAReceiver.v │ └── UATransmitter.v └── test └── scala ├── core ├── ALUTest.scala ├── BasicJumpsTest.scala ├── BasicMemoryTest.scala ├── CSRTest.scala ├── DatapathTest.scala ├── ImemSimulator.scala ├── LoadStoreTest.scala ├── LockTest.scala ├── MMIOTest.scala ├── RegisterFileTest.scala ├── SimpleCoreTest.scala └── StoreMaskTest.scala └── wb ├── WishboneDeviceUtils.scala ├── WishboneMasterTest.scala └── WishboneUartTest.scala /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | ci: 7 | name: ci 8 | runs-on: ubuntu-latest 9 | steps: 10 | - name: Checkout 11 | uses: actions/checkout@v3 12 | with: 13 | submodules: recursive 14 | - name: Setup Scala 15 | uses: olafurpg/setup-scala@v10 16 | with: 17 | java-version: adopt@1.8 18 | - name: Cache Scala 19 | uses: coursier/cache-action@v5 20 | - name: Install dependencies 21 | run: | 22 | sudo apt install verilator cmake -y 23 | wget -q --show-progress https://github.com/xpack-dev-tools/riscv-none-elf-gcc-xpack/releases/download/v14.2.0-2/xpack-riscv-none-elf-gcc-14.2.0-2-linux-x64.tar.gz -O gcc.tar.gz 24 | tar xvf gcc.tar.gz --directory=/opt 25 | echo "RISCV_TOOL_PATH_PREFIX=/opt/xpack-riscv-none-elf-gcc-14.2.0-2" >> $GITHUB_ENV 26 | - name: Run Chisel unit tests 27 | run: sbt 'test' 28 | - name: Build emulator and SDK 29 | run: source env.bash && cmake -Bbuild && cd build && make all install 30 | - name: Run C tests 31 | run: | 32 | source env.bash 33 | cd sdk && cmake -B build && cd build && make && ctest 34 | - name: Run multithreaded C tests 35 | run: | 36 | source env.bash 37 | # Run multiple tests with script 38 | ./scripts/run_multiple_tests.sh -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bloop/ 2 | .metals/ 3 | .vscode/ 4 | .idea 5 | .bsp 6 | project/ 7 | target/ 8 | build/ 9 | bin/ 10 | out/ 11 | test_run_dir/ 12 | 13 | mill 14 | firrtl.jar 15 | .DS_Store 16 | 17 | # Generated files 18 | **/*.mem 19 | **/*.mem.serialized 20 | **/*.out 21 | **/*.dump 22 | **/*.vcd 23 | **/*.riscv 24 | **/*.map 25 | **/*.orig 26 | **/*.dump 27 | **/*.a 28 | **/*.elf 29 | **/callgraph 30 | 31 | **/fp-emu 32 | 33 | # Misc 34 | .vscode/ 35 | project/ 36 | target/ 37 | .bloop/ 38 | emulator/obj_dir/ 39 | emulator/*.v 40 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "programs/lib/printf"] 2 | path = sdk/lib/printf 3 | url = https://github.com/pretis/printf.git 4 | [submodule "sdk/external/printf"] 5 | path = sdk/external/printf 6 | url = https://github.com/pretis/printf.git 7 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013, The Regents of the University of California. 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | * Redistributions of source code must retain the above copyright 7 | notice, this list of conditions and the following disclaimer. 8 | * Redistributions in binary form must reproduce the above copyright 9 | notice, this list of conditions and the following disclaimer in the 10 | documentation and/or other materials provided with the distribution. 11 | * Neither the name of the nor the 12 | names of its contributors may be used to endorse or promote products 13 | derived from this software without specific prior written permission. 14 | 15 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND 16 | ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 17 | WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 18 | DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY 19 | DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 20 | (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; 21 | LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND 22 | ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 23 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS 24 | SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 25 | -------------------------------------------------------------------------------- /apps/blinky/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Set toolchain to RISC-V; must be done before call to project() 4 | include($ENV{FP_SDK_PATH}/cmake/riscv-toolchain.cmake) 5 | 6 | project(blinky 7 | LANGUAGES C 8 | DESCRIPTION "An application fit for FPGA which blinks LEDs" 9 | VERSION 1.0.0 10 | ) 11 | 12 | set(DEBUG true) 13 | set(TARGET "fpga") 14 | 15 | add_executable(blinky blinky.c) 16 | 17 | include($ENV{FP_SDK_PATH}/cmake/fp-app.cmake) 18 | 19 | # Add sdk as subdirectory out-of-tree and link it 20 | add_subdirectory($ENV{FP_SDK_PATH} BINARY_DIR) 21 | target_link_libraries(blinky fp-sdk) 22 | 23 | fp_add_outputs(blinky) 24 | -------------------------------------------------------------------------------- /apps/blinky/blinky.c: -------------------------------------------------------------------------------- 1 | /** 2 | Example program where core0 blinks the LED which should be connected to port0. 3 | */ 4 | #include 5 | #include 6 | 7 | void set_ledmask(const uint8_t byte) 8 | { 9 | gpo_write_0((byte >> 0) & 0b11); 10 | gpo_write_1((byte >> 2) & 0b11); 11 | gpo_write_2((byte >> 4) & 0b11); 12 | gpo_write_3((byte >> 6) & 0b11); 13 | } 14 | 15 | int main() { 16 | uint8_t count = 0; 17 | while(1) { 18 | set_ledmask(count++); 19 | for (int i = 0; i < 100000; i++); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /apps/wb_uart_led/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 3.22) 2 | 3 | # Set toolchain to RISC-V; must be done before call to project() 4 | include($ENV{FP_SDK_PATH}/cmake/riscv-toolchain.cmake) 5 | 6 | project(wb_uart_led 7 | LANGUAGES C 8 | DESCRIPTION "An application fit for FPGA which sets LEDs and prints UART input back to user" 9 | VERSION 1.0.0 10 | ) 11 | 12 | add_executable(wb_uart_led wb_uart_led.c) 13 | 14 | # Specific to this application 15 | set(DEBUG False) 16 | set(TARGET "fpga") 17 | 18 | include($ENV{FP_SDK_PATH}/cmake/fp-app.cmake) 19 | 20 | # Add sdk as subdirectory out-of-tree and link it 21 | add_subdirectory($ENV{FP_SDK_PATH} BINARY_DIR) 22 | target_link_libraries(wb_uart_led fp-sdk) 23 | 24 | fp_add_outputs(wb_uart_led) 25 | -------------------------------------------------------------------------------- /apps/wb_uart_led/wb_uart_led.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | 6 | 7 | int main(void) { 8 | gpo_set_ledmask(0x55); 9 | while (1) { 10 | uint8_t byte = uart_receive(); 11 | gpo_set_ledmask(byte); 12 | uart_send(byte); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /build.sbt: -------------------------------------------------------------------------------- 1 | 2 | ThisBuild / scalaVersion := "2.12.13" 3 | ThisBuild / version := "0.1.0" 4 | ThisBuild / organization := "PRETIS" 5 | 6 | val chiselVersion = "3.5.6" 7 | 8 | lazy val flexpret = (project in file(".")) 9 | .settings( 10 | name := "flexpret", 11 | libraryDependencies ++= Seq( 12 | "edu.berkeley.cs" %% "chisel3" % chiselVersion, 13 | "edu.berkeley.cs" %% "chiseltest" % "0.5.6" % "test", 14 | "io.github.t-crest" % "soc-comm" % "0.1.5", 15 | "com.github.pureconfig" %% "pureconfig" % "0.17.6" 16 | ), 17 | scalacOptions ++= Seq( 18 | "-language:reflectiveCalls", 19 | "-deprecation", 20 | "-feature", 21 | "-Xcheckinit", 22 | "-P:chiselplugin:genBundleElements", 23 | ), 24 | addCompilerPlugin("edu.berkeley.cs" % "chisel3-plugin" % chiselVersion cross CrossVersion.full), 25 | ) -------------------------------------------------------------------------------- /build.sc: -------------------------------------------------------------------------------- 1 | import mill._, scalalib._ 2 | import coursier.maven.MavenRepository 3 | 4 | /** 5 | * Scala 2.12 module that is source-compatible with 2.11. 6 | * This is due to Chisel's use of structural types. See 7 | * https://github.com/freechipsproject/chisel3/issues/606 8 | */ 9 | trait HasXsource211 extends ScalaModule { 10 | override def scalacOptions = T { 11 | super.scalacOptions() ++ Seq( 12 | "-deprecation", 13 | "-unchecked", 14 | "-Xsource:2.11" 15 | ) 16 | } 17 | } 18 | 19 | trait HasChisel3 extends ScalaModule { 20 | override def ivyDeps = Agg( 21 | ivy"edu.berkeley.cs::chisel3:3.5.5" 22 | ) 23 | } 24 | 25 | trait HasChiselTests extends CrossSbtModule { 26 | def repositories() = super.repositories ++ Seq( 27 | MavenRepository("https://oss.sonatype.org/content/repositories/snapshots"), 28 | MavenRepository("https://oss.sonatype.org/content/repositories/releases") 29 | ) 30 | object test extends Tests { 31 | override def ivyDeps = Agg( 32 | // ivy"org.scalatest::scalatest:3.0.4", 33 | ivy"edu.berkeley.cs::chiseltest:0.5.+" 34 | ) 35 | def testFrameworks = Seq("org.scalatest.tools.Framework") 36 | 37 | // sbt-like testOnly command 38 | def testOnly(args: String*) = T.command { 39 | super.runMain("org.scalatest.run", args: _*) 40 | } 41 | } 42 | } 43 | 44 | trait HasMacroParadise extends ScalaModule { 45 | // Enable macro paradise for @chiselName et al 46 | val macroPlugins = Agg(ivy"org.scalamacros:::paradise:2.1.0") 47 | def scalacPluginIvyDeps = macroPlugins 48 | def compileIvyDeps = macroPlugins 49 | } 50 | 51 | trait HasCompilerPLugin extends ScalaModule { 52 | // Enable macro paradise for @chiselName et al 53 | val compilerPlugins = Agg(ivy"edu.berkeley.cs::chisel3-pluginorg:3.5.+") 54 | def scalacPluginIvyDeps = compilerPlugins 55 | def compileIvyDeps = compilerPlugins 56 | } 57 | 58 | object flexpret extends CrossSbtModule with HasCompilerPLugin with HasChisel3 with HasChiselTests with HasXsource211 with HasMacroParadise { 59 | def crossScalaVersion = "2.12.10" 60 | def mainClass = Some("Core.CoreMain") 61 | } 62 | -------------------------------------------------------------------------------- /cmake/confighash.cmake: -------------------------------------------------------------------------------- 1 | # Common functionality for constructing/reconstrucing configurations hashes. 2 | # Used both by FlexPRET and sdk target. 3 | # Assume the variables used here are already set, e.g., by ./configs/default.cmake 4 | 5 | set(config_params 6 | THREADS 7 | FLEX 8 | ISPM_KBYTES 9 | DSPM_KBYTES 10 | MULTIPLIER 11 | SUFFIX 12 | CLK_FREQ_MHZ 13 | UART_BAUDRATE 14 | ) 15 | 16 | set(UNIQUE_CONFIG_STRING "") 17 | foreach(e ${config_params}) 18 | string(APPEND UNIQUE_CONFIG_STRING "${e} = ${${e}}\n") 19 | endforeach() 20 | 21 | set(UNIQUE_CONFIG_FILE 22 | "${CMAKE_CURRENT_BINARY_DIR}/config_readable.txt" 23 | ) 24 | 25 | function(calculate_crc32) 26 | file(WRITE ${UNIQUE_CONFIG_FILE} ${UNIQUE_CONFIG_STRING}) 27 | 28 | execute_process( 29 | COMMAND "python3" "-c" "import zlib; f = open(\"${UNIQUE_CONFIG_FILE}\", 'rb'); s = f.read(); print(hex(zlib.crc32(s)).replace('0x', ''))" 30 | OUTPUT_VARIABLE hashvar 31 | OUTPUT_STRIP_TRAILING_WHITESPACE 32 | ) 33 | set(CRC32_HASH "${hashvar}" PARENT_SCOPE) 34 | endfunction() 35 | -------------------------------------------------------------------------------- /cmake/configs/3threads.cmake: -------------------------------------------------------------------------------- 1 | include("$ENV{FP_PATH}/cmake/configs/default.cmake") 2 | 3 | set(THREADS 3) 4 | -------------------------------------------------------------------------------- /cmake/configs/5threads@50MHz.cmake: -------------------------------------------------------------------------------- 1 | include("$ENV{FP_PATH}/cmake/configs/default.cmake") 2 | 3 | set(THREADS 5) 4 | set(CLK_FREQ_MHZ 50) 5 | -------------------------------------------------------------------------------- /cmake/configs/8threads.cmake: -------------------------------------------------------------------------------- 1 | include("$ENV{FP_PATH}/cmake/configs/default.cmake") 2 | 3 | set(THREADS 8) 4 | set(ISPM_KBYTES 128) 5 | set(DSPM_KBYTES 128) 6 | -------------------------------------------------------------------------------- /cmake/configs/default.cmake: -------------------------------------------------------------------------------- 1 | # This is the default configuration for FlexPRET 2 | # Feel free to copy this file and create your own 3 | # You can override which configuration file to use either in the top-level 4 | # CMakeLists.txt or from the command-line when running cmake, like so: 5 | # Like so: cmake -DCMAKE_CONFIG=my_config --build . 6 | 7 | # See `../configverify.cmake` for valid options for each configuration parameter 8 | 9 | # Note: Using CACHE with FORCE everywhere does not really make sense, but it is 10 | # advantageous to connect a description to the variable instead of a comment 11 | 12 | # Valid: [1-8] 13 | set(THREADS 4 CACHE STRING "How many hardware threads to build FlexPRET with" FORCE) 14 | 15 | # Valid: 16 | set(FLEX true CACHE BOOL "Whether to compile FlexPRET with flexible hardware sheduler" FORCE) 17 | 18 | # Valid: Any power of 2, but should fit in FPGA 19 | set(ISPM_KBYTES 64 CACHE STRING "Size of the intruction scratchpad memmory (kB)" FORCE) 20 | 21 | # Valid: Any power of 2, but should fit in FPGA 22 | set(DSPM_KBYTES 64 CACHE STRING "Size of the data scratchpad memmory (kB)" FORCE) 23 | 24 | # Valid: 25 | set(MULTIPLIER false CACHE BOOL "Whether to use a multiplier in FlexPRET (currently not used)" FORCE) 26 | 27 | # Valid: : 28 | # min: base RV32I 29 | # ex: min+exceptions (necessary) 30 | # ti: ex+timing instructions 31 | # all: ti+ all exception causes and stats 32 | set(SUFFIX all CACHE STRING "Features enabled. min -> base RV32I, ex -> min + exceptions (necessary), ti -> ex + timing instructions, all -> ti + all exception causes and status" FORCE) 33 | 34 | # Valid: <50/100> (and probably more, but these have been tested) 35 | set(CLK_FREQ_MHZ 100 CACHE STRING "Requested clock frequency in MHz" FORCE) 36 | 37 | # Valid: <9600, 19200,38400, 57600, 115200> (potentially more) 38 | set(UART_BAUDRATE 115200 CACHE STRING "Requested UART baudrate in Hz" FORCE) 39 | -------------------------------------------------------------------------------- /cmake/configs/fpga.cmake: -------------------------------------------------------------------------------- 1 | include("$ENV{FP_PATH}/cmake/configs/default.cmake") 2 | 3 | # Try to nudge up ISPM size as much as possible 4 | set(ISPM_KBYTES 96) 5 | -------------------------------------------------------------------------------- /cmake/configs/highmem.cmake: -------------------------------------------------------------------------------- 1 | include("$ENV{FP_PATH}/cmake/configs/default.cmake") 2 | 3 | set(ISPM_KBYTES 256) 4 | set(DSPM_KBYTES 256) 5 | -------------------------------------------------------------------------------- /cmake/configverify.cmake: -------------------------------------------------------------------------------- 1 | # The purpose of this file is to check that parameters are within certain bounds 2 | # and let the user know if they have chosen an invalid configuration 3 | 4 | # Contains check_parameter function 5 | include(${CMAKE_CURRENT_LIST_DIR}/lib/check.cmake) 6 | 7 | # For each parameter set in the configuration, we set a list of possible options 8 | set(THREADS_OPTIONS 1 2 3 4 5 6 7 8) 9 | set(FLEX_OPTIONS true false) 10 | set(ISPM_KBYTES_OPTIONS 16 32 64 96 128 256 512) 11 | set(DSPM_KBYTES_OPTIONS 16 32 64 96 128 256 512) 12 | set(MULTIPLIER_OPTIONS true false) 13 | set(SUFFIX_OPTIONS min ex ti all) 14 | set(CLK_FREQ_MHZ_OPTIONS 50 100) 15 | set(UART_BAUDRATE_OPTIONS 9600 19200 38400 57600 115200) # Potentially more work 16 | 17 | # Then we check whether the parameter is any of the possible options 18 | check_parameter(THREADS THREADS_OPTIONS FATAL_ERROR) 19 | check_parameter(FLEX FLEX_OPTIONS FATAL_ERROR) 20 | check_parameter(ISPM_KBYTES ISPM_KBYTES_OPTIONS WARNING) 21 | check_parameter(DSPM_KBYTES DSPM_KBYTES_OPTIONS WARNING) 22 | check_parameter(MULTIPLIER MULTIPLIER_OPTIONS FATAL_ERROR) 23 | check_parameter(SUFFIX SUFFIX_OPTIONS FATAL_ERROR) 24 | check_parameter(CLK_FREQ_MHZ CLK_FREQ_MHZ_OPTIONS WARNING) 25 | check_parameter(UART_BAUDRATE UART_BAUDRATE_OPTIONS WARNING) 26 | -------------------------------------------------------------------------------- /cmake/infiles/application.conf.in: -------------------------------------------------------------------------------- 1 | threads = @THREADS@ 2 | flex = @FLEX@ 3 | clk-freq-m-hz = @CLK_FREQ_MHZ@ 4 | imem-config = { 5 | bypass = false, size-kb = @ISPM_KBYTES@ 6 | }, 7 | d-mem-kb = @DSPM_KBYTES@ 8 | mul = @MULTIPLIER@ 9 | priv = false 10 | features = "@SUFFIX@" 11 | uart-baudrate = @UART_BAUDRATE@ 12 | -------------------------------------------------------------------------------- /cmake/infiles/hwconfig.c.in: -------------------------------------------------------------------------------- 1 | /** 2 | * This FlexPRET configuration file is auto-generated, and based on the 3 | * configuration used during build. 4 | * 5 | * The file can be compiled into a build, which makes the variable 6 | * available to the program. 7 | * 8 | * Do not edit. 9 | * 10 | */ 11 | 12 | const char *hwconfig_string = "\ 13 | FP_THREADS = @THREADS@\n\ 14 | FP_FLEX = @FLEX@\n\ 15 | FP_ISPM_KBYTES = @ISPM_KBYTES@\n\ 16 | FP_DSPM_KBYTES = @DSPM_KBYTES@\n\ 17 | FP_MULTIPLIER = @MULTIPLIER@\n\ 18 | FP_SUFFIX = @SUFFIX@\n\ 19 | FP_CLK_FREQ_MHZ = @CLK_FREQ_MHZ@\n\ 20 | FP_UART_BAUDRATE = @UART_BAUDRATE@\n\ 21 | "; 22 | -------------------------------------------------------------------------------- /cmake/infiles/hwconfig.h.in: -------------------------------------------------------------------------------- 1 | /** 2 | * This FlexPRET configuration file is auto-generated, and based on the 3 | * configuration used during build. 4 | * 5 | * Do not edit. 6 | * 7 | */ 8 | 9 | #ifndef FP_AUTOGEN_CONFIG_H 10 | #define FP_AUTOGEN_CONFIG_H 11 | 12 | #define FP_THREADS @THREADS@ 13 | #define FP_FLEX @FLEX@ 14 | #define FP_ISPM_KBYTES @ISPM_KBYTES@ 15 | #define FP_DSPM_KBYTES @DSPM_KBYTES@ 16 | #define FP_MULTIPLIER @MULTIPLIER@ 17 | #define FP_SUFFIX @SUFFIX@ 18 | #define FP_CLK_FREQ_MHZ @CLK_FREQ_MHZ@ 19 | #define FP_UART_BAUDRATE @UART_BAUDRATE@ 20 | 21 | #endif // FP_AUTOGEN_CONFIG_H 22 | -------------------------------------------------------------------------------- /cmake/infiles/hwconfig.ld.in: -------------------------------------------------------------------------------- 1 | /** 2 | * This FlexPRET configuration file is auto-generated, and based on the 3 | * configuration used during build. 4 | * 5 | * Do not edit. 6 | * 7 | */ 8 | 9 | ISPM_START = 0x00000000 ; 10 | DSPM_START = 0x20000000 ; 11 | BUS_START = 0x40000000 ; 12 | ISPM_END = ISPM_START + (@ISPM_KBYTES@ << 10) ; /* 2^10 = kB */ 13 | DSPM_END = DSPM_START + (@DSPM_KBYTES@ << 10) ; /* 2^10 = kB */ 14 | BUS_END = BUS_START + 0x400 ; 15 | 16 | NUM_THREADS = @THREADS@ ; 17 | -------------------------------------------------------------------------------- /cmake/lib/check.cmake: -------------------------------------------------------------------------------- 1 | # Standardized function to check a parameter 2 | function(check_parameter parameter options severity) 3 | # set_property will only affect cmake GUI users, but we do it for the sake 4 | # of completeness 5 | set_property(CACHE ${parameter} PROPERTY STRINGS ${options}) 6 | 7 | # Check if parameter is in the recommened/valid list 8 | list(FIND ${options} ${${parameter}} index) 9 | 10 | # If not found, print out warning/error and recommened/valid options 11 | if(index EQUAL -1) 12 | set(msg_part "${parameter} should be one of:\n") 13 | 14 | foreach(option ${${options}}) 15 | string(APPEND msg_part "* ${option}\n") 16 | endforeach() 17 | 18 | message(${severity} ${msg_part}) 19 | endif() 20 | endfunction() 21 | -------------------------------------------------------------------------------- /cmake/utils/bash/build_fp_emu.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Build FlexPRET emulator 3 | # 4 | # Arguments: 5 | # $1: CRC32 hash 6 | # Rest: Emulator sources 7 | 8 | set -e 9 | 10 | # Generates VerilatorTop.v 11 | sbt 'run verilator h'"$1"' --no-dedup --target-dir build/emulator' 12 | 13 | # Fetch dual port BRAM from resources 14 | cp src/main/resources/DualPortBramEmulator.v build/emulator/DualPortBram.v 15 | 16 | 17 | # Run Verilator command 18 | cd build/emulator 19 | verilator --cc VerilatorTop.v --exe --trace --trace-structs --trace-underscore --build "${@:2}" 20 | 21 | # Copy Verilator generated binary 22 | cp obj_dir/VVerilatorTop fp-emu 23 | -------------------------------------------------------------------------------- /cmake/utils/bash/gather_fpga_resources.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Gather the resources needed to build FPGA with Vivado 3 | # 4 | # Arguments: 5 | # $1: CRC32 hash 6 | # $2: Current build folder 7 | # $3: Current source folder 8 | # $4: ISPM file, i.e., the program file 9 | 10 | set -e 11 | 12 | # Generate FpgaTop.v 13 | sbt 'run fpga h'"$1"' --no-dedup --target-dir '"$2" 14 | 15 | prjfolder=$(pwd) 16 | 17 | # Rename FpgaTop.v to flexpret.v 18 | cd $2 19 | mv FpgaTop.v rtl/flexpret.v 20 | 21 | # https://stackoverflow.com/questions/5947742/how-to-change-the-output-color-of-echo-in-linux 22 | RED='\033[0;31m' 23 | NC='\033[0m' 24 | 25 | if ! [ -f $4 ]; then 26 | printf "${RED}Could not find $4 needed to build FPGA.${NC}\n" 27 | exit 1 28 | fi 29 | 30 | # Copy program into build 31 | cp $4 rtl/ispm.mem 32 | 33 | # Copy dual port BRAM 34 | cp $prjfolder/src/main/resources/DualPortBramFPGA.v rtl/DualPortBram.v 35 | 36 | # Copy project's Top.v into build 37 | cp $3/rtl/Top.v rtl 38 | 39 | # Create top-level constraints.xdc file 40 | cat xdc/clock.xdc $3/xdc/Top.xdc > xdc/constraints.xdc 41 | 42 | # Create bitstream_runnable.tcl 43 | cat tcl/variables.tcl $3/../tcl/setup.tcl $3/../tcl/bitstream.tcl > tcl/bitstream_runnable.tcl 44 | 45 | # Create flash_runnable.tcl 46 | cat tcl/variables.tcl $3/../tcl/flash.tcl > tcl/flash_runnable.tcl 47 | -------------------------------------------------------------------------------- /doc/flexpret-cmake-architecture.drawio.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pretis/flexpret/53af96f5beb22c1bb6a77f455b6ec73066885a12/doc/flexpret-cmake-architecture.drawio.png -------------------------------------------------------------------------------- /doc/flexpret-pipeline.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pretis/flexpret/53af96f5beb22c1bb6a77f455b6ec73066885a12/doc/flexpret-pipeline.pdf -------------------------------------------------------------------------------- /docker/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM phusion/baseimage:0.10.0 2 | CMD ["/sbin/my_init"] 3 | 4 | # RISC-V 5 | # 9a8a0aa9 march 8 2015 6 | RUN apt-get update \ 7 | && apt-get install -y \ 8 | autoconf \ 9 | automake \ 10 | autotools-dev \ 11 | curl \ 12 | libmpc-dev \ 13 | libmpfr-dev \ 14 | libgmp-dev \ 15 | libusb-1.0-0-dev \ 16 | gawk \ 17 | build-essential \ 18 | bison \ 19 | flex \ 20 | texinfo \ 21 | gperf \ 22 | libtool \ 23 | patchutils \ 24 | bc \ 25 | zlib1g-dev \ 26 | device-tree-compiler \ 27 | pkg-config \ 28 | unzip \ 29 | && rm -rf /var/lib/apt/lists/* 30 | 31 | ARG RISCV_HASH=9a8a0aa98571c97291702e2e283fc1056f3ce2e2 32 | WORKDIR /tmp 33 | RUN curl -LO https://github.com/riscv/riscv-gnu-toolchain/archive/$RISCV_HASH.zip \ 34 | && unzip $RISCV_HASH.zip \ 35 | && cd riscv-gnu-toolchain-$RISCV_HASH \ 36 | && ./configure --prefix=/opt/riscv-$RISCV_HASH \ 37 | && make \ 38 | && cd .. \ 39 | && rm -rf riscv-gnu-toolchain-$RISCV_HASH $RISCV_HASH.zip 40 | 41 | RUN apt-get update \ 42 | && apt-get install -y \ 43 | cmake \ 44 | cmake-doc \ 45 | openjdk-8-jre \ 46 | openjdk-8-jdk \ 47 | vim \ 48 | python \ 49 | python3 \ 50 | bsdmainutils \ 51 | && rm -rf /var/lib/apt/lists/* 52 | 53 | ENV PATH="${PATH}:/opt/riscv-$RISCV_HASH/bin" 54 | 55 | WORKDIR /opt/ 56 | RUN curl -LO https://github.com/pretis/flexpret/archive/master.zip \ 57 | && unzip master.zip \ 58 | && rm master.zip \ 59 | && cd flexpret-master \ 60 | && echo $PATH \ 61 | && make run 62 | 63 | RUN apt-get clean && rm -rf /var/lib/apt/lists/* /tmp/* /var/tmp/* 64 | -------------------------------------------------------------------------------- /emulator/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_custom_target(emulator ALL 2 | DEPENDS ${CMAKE_CURRENT_BINARY_DIR}/fp-emu 3 | ) 4 | 5 | set(EMULATOR_SOURCES 6 | "${CMAKE_CURRENT_SOURCE_DIR}/main.cpp" 7 | "${CMAKE_CURRENT_SOURCE_DIR}/printf_fsm.c" 8 | "${CMAKE_CURRENT_SOURCE_DIR}/pin_event.cpp" 9 | "${CMAKE_BINARY_DIR}/hwconfig.c" 10 | ) 11 | 12 | add_custom_command( 13 | OUTPUT "${CMAKE_CURRENT_BINARY_DIR}/fp-emu" 14 | COMMAND bash 15 | "$ENV{FP_PATH}/cmake/utils/bash/build_fp_emu.sh" 16 | "${CRC32_HASH}" 17 | ${EMULATOR_SOURCES} 18 | WORKING_DIRECTORY 19 | ${PROJECT_SOURCE_DIR} 20 | BYPRODUCTS 21 | "${CMAKE_CURRENT_BINARY_DIR}/VerilatorTop.v" 22 | "${CMAKE_CURRENT_BINARY_DIR}/DualPortBram.v" 23 | "${CMAKE_CURRENT_BINARY_DIR}/obj_dir" 24 | DEPENDS 25 | ${SCALA_SOURCES} 26 | VERBATIM 27 | ) 28 | 29 | add_subdirectory(clients) 30 | -------------------------------------------------------------------------------- /emulator/clients/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # Very specific to current setup, if we expand on this later this will 2 | # need some refactoring. But no need to add complexity until then 3 | 4 | add_library(clients-common STATIC common.c) 5 | target_include_directories(clients-common PUBLIC ".") 6 | 7 | function(clients_add_executable target) 8 | add_executable(${target} ${target}.c) 9 | target_link_libraries(${target} PRIVATE clients-common) 10 | endfunction() 11 | 12 | clients_add_executable(gpio gpio.c) 13 | clients_add_executable(interrupter interrupter.c) 14 | clients_add_executable(uart uart.c) 15 | clients_add_executable(stimuli stimuli.c) 16 | clients_add_executable(stimuli-mt stimuli-mt.c) 17 | -------------------------------------------------------------------------------- /emulator/clients/README.md: -------------------------------------------------------------------------------- 1 | # Emulator clients 2 | 3 | The emulator has the `--client` command line option, which will open a incoming socket where a client can connect and send it events to be applied to the pins of the emulated FlexPRET. When the option is selected, the emulator hangs until a client has connected. Without the option selected, the emulator will run as normal. 4 | 5 | ## How it works 6 | 7 | The client transmits one or several `pin_event_t` which contains information about when an event shall occur on a pin and whether it should be high or low. See the `common.h` header file for more detailed description of each field. 8 | 9 | ```C 10 | typedef struct { 11 | uint32_t pin; 12 | uint32_t in_n_cycles; 13 | bool high_low; 14 | } pin_event_t; 15 | ``` 16 | 17 | E.g., setting a pin high and immediately low again can be done by sending two `pin_event_t` which sets that pin high and low again immediately after. In code, this would be the same as transmitting the pair: 18 | 19 | ```C 20 | static pin_event_t interrupt[2] = { 21 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = HIGH }, 22 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = LOW }, 23 | }; 24 | ``` 25 | 26 | ## Use cases 27 | 28 | Below follows some example use cases, highlighting why this server/client architecture is useful. 29 | * Generating interrupts whenever user hits `enter`. This is as simple as transmitting two `pin_event_t` which sets a pin high and then low. 30 | * Transmitting UART communication based on stdin from user. This includes transmitting 10 `pin_event_t`; 1 start bit, 8 data bits, 1 stop bit. 31 | * Simulating an Inertial Measurement Unit (IMU) which transmits accelerometer readings over an UART interface. 32 | 33 | Note that the server can have several clients connect, making it possible to run both these use cases on different (or same) pins at the same time. This means one could run a test which both transmits UART communication and triggers an interrupt at the same time. 34 | 35 | ## Adding more clients 36 | 37 | Feel free to develop more clients for specific use cases. In that case, create a new file in this folder and make sure to add it to the makefile for compilation. More complex clients might require an improvement of the current build system. 38 | 39 | ## Compilation 40 | 41 | Either compile the clients directly using the Makefile or let them all be compiled together with the emulator. 42 | -------------------------------------------------------------------------------- /emulator/clients/common.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | #include "common.h" 6 | 7 | int setup_socket(void) { 8 | int status, valread, client_fd; 9 | struct sockaddr_in serv_addr; 10 | if ((client_fd = socket(AF_INET, SOCK_STREAM, 0)) < 0) { 11 | printf("\nSocket creation error \n"); 12 | return -1; 13 | } 14 | 15 | serv_addr.sin_family = AF_INET; 16 | serv_addr.sin_port = htons(CLIENT_PORT); 17 | 18 | // Convert IPv4 and IPv6 addresses from text to binary form 19 | if (inet_pton(AF_INET, "127.0.0.1", &serv_addr.sin_addr) <= 0) { 20 | printf("\nInvalid address/ Address not supported \n"); 21 | return -1; 22 | } 23 | 24 | if ((status = connect(client_fd, (struct sockaddr*)&serv_addr, 25 | sizeof(serv_addr))) < 0) { 26 | printf("Connection failed \n"); 27 | return -1; 28 | } 29 | 30 | return client_fd; 31 | } -------------------------------------------------------------------------------- /emulator/clients/common.h: -------------------------------------------------------------------------------- 1 | #ifndef EMULATOR_CLIENT_COMMON_H 2 | #define EMULATOR_CLIENT_COMMON_H 3 | 4 | #include 5 | #include 6 | 7 | #define CLIENT_PORT (8080) 8 | #define HIGH (1) 9 | #define LOW (0) 10 | 11 | typedef struct { 12 | /** 13 | * Which pin the event occurs on; this number is determined by the macros 14 | * found below. 15 | * 16 | */ 17 | uint32_t pin; 18 | 19 | /** 20 | * The number of cycles until the event should occur relative to the time 21 | * of the last PinEvent in the list of pending events. 22 | * 23 | * E.g., if the list already contains three events like this: 24 | * 25 | * { 26 | * { .pin = MY_PIN, .in_n_cycles = 300, .high_low = HIGH }, 27 | * { .pin = MY_PIN, .in_n_cycles = 500, .high_low = LOW }, 28 | * { .pin = MY_PIN, .in_n_cycles = 250, .high_low = HIGH }, 29 | * }; 30 | * 31 | * when the emulator receives the list it will start counting. When it hits 32 | * 300 it will trigger the first event and restart the counter. When it hits 33 | * 500 it will trigger the second event and restart the counter. And so on. 34 | * 35 | */ 36 | uint32_t in_n_cycles; 37 | 38 | /** 39 | * Whether to set the pin HIGH (1) or LOW (0). 40 | * 41 | */ 42 | bool high_low; 43 | } pin_event_t; 44 | 45 | #define PIN_IO_INT_EXTS_0 0 46 | #define PIN_IO_INT_EXTS_1 1 47 | #define PIN_IO_INT_EXTS_2 2 48 | #define PIN_IO_INT_EXTS_3 3 49 | #define PIN_IO_INT_EXTS_4 4 50 | #define PIN_IO_INT_EXTS_5 5 51 | #define PIN_IO_INT_EXTS_6 6 52 | #define PIN_IO_INT_EXTS_7 7 53 | 54 | // Each GPI_x has 1 pin (but can and should be expanded in the future) 55 | #define GPI_PORT_SIZE (1) 56 | #define PIN_IO_GPI_0(pin) (pin + 7 + (1 * GPI_PORT_SIZE)) 57 | #define PIN_IO_GPI_1(pin) (pin + 7 + (2 * GPI_PORT_SIZE)) 58 | #define PIN_IO_GPI_2(pin) (pin + 7 + (3 * GPI_PORT_SIZE)) 59 | #define PIN_IO_GPI_3(pin) (pin + 7 + (4 * GPI_PORT_SIZE)) 60 | 61 | #define PIN_UART_START (PIN_IO_GPI_3(GPI_PORT_SIZE)) 62 | #define PIN_IO_UART_RX (PIN_UART_START + 1) 63 | #define PIN_IO_UART_TX (PIN_UART_START + 2) 64 | 65 | 66 | // Add more here... 67 | 68 | int setup_socket(void); 69 | 70 | #endif // EMULATOR_CLIENT_COMMON_H 71 | -------------------------------------------------------------------------------- /emulator/clients/gpio.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 3 | * @brief A simple client that can be used with the emulator when the --client 4 | * option is given to the emulator. It toggles a few GPIO pins for testing purposes, 5 | * and should toggle pins in accordance with the 6 | * ../../programs/tests/c-tests/gpio test. 7 | * 8 | * The GPIO pins are set when the user presses . 9 | * 10 | */ 11 | 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include "common.h" 20 | 21 | #define EVENT_INITIALIZER_SET_PIN(p) (pin_event_t) \ 22 | { .pin = p, .in_n_cycles = 10000, .high_low = HIGH } 23 | 24 | #define EVENT_INITIALIZER_CLR_PIN(p) (pin_event_t) \ 25 | { .pin = p, .in_n_cycles = 10000, .high_low = LOW } 26 | 27 | static pin_event_t test_vector_port0[] = { 28 | // Set and clear pins in some random-ish fashion 29 | EVENT_INITIALIZER_SET_PIN(PIN_IO_GPI_0(0)), 30 | EVENT_INITIALIZER_SET_PIN(PIN_IO_GPI_0(2)), 31 | EVENT_INITIALIZER_SET_PIN(PIN_IO_GPI_0(7)), 32 | EVENT_INITIALIZER_SET_PIN(PIN_IO_GPI_0(3)), 33 | EVENT_INITIALIZER_SET_PIN(PIN_IO_GPI_0(5)), 34 | 35 | EVENT_INITIALIZER_CLR_PIN(PIN_IO_GPI_0(0)), 36 | EVENT_INITIALIZER_CLR_PIN(PIN_IO_GPI_0(2)), 37 | EVENT_INITIALIZER_CLR_PIN(PIN_IO_GPI_0(7)), 38 | EVENT_INITIALIZER_CLR_PIN(PIN_IO_GPI_0(3)), 39 | EVENT_INITIALIZER_CLR_PIN(PIN_IO_GPI_0(5)), 40 | }; 41 | 42 | static void make_global_queue(pin_event_t *queue, const int queue_length) 43 | { 44 | assert(queue); 45 | for (int i = 1; i < queue_length; i++) { 46 | queue[i].in_n_cycles += queue[i-1].in_n_cycles; 47 | } 48 | } 49 | 50 | int main(int argc, char const* argv[]) 51 | { 52 | int client_fd = setup_socket(); 53 | if (client_fd < 0) { 54 | exit(1); 55 | } 56 | 57 | make_global_queue(test_vector_port0, sizeof(test_vector_port0) / sizeof(test_vector_port0[0])); 58 | 59 | // Wait for user to press 60 | getchar(); 61 | send(client_fd, test_vector_port0, sizeof(test_vector_port0), 0); 62 | 63 | // Wait for another to exit 64 | getchar(); 65 | 66 | close(client_fd); 67 | return 0; 68 | } 69 | -------------------------------------------------------------------------------- /emulator/clients/stimuli-mt.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 3 | * @brief A simple client that is coupled with the interrupt.c test. It provides 4 | * 20 interrupts with some delay, which the test needs. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | #include "../../build/hwconfig.h" 17 | 18 | static pin_event_t long_interrupt[] = { 19 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = HIGH }, 20 | 21 | // Wait a long time before setting low again to test that we don't end in 22 | // an infinite interrupt cycle 23 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 1000 * FP_THREADS, .high_low = LOW }, 24 | }; 25 | 26 | static pin_event_t interrupt[] = { 27 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = HIGH }, 28 | 29 | // Wait FP_THREADS cycles before setting low again so the HW thread gets 30 | // enough time to react 31 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = FP_THREADS, .high_low = LOW }, 32 | }; 33 | 34 | int main(int argc, char *const* argv) 35 | { 36 | // Allow emulator to initialize before we connect 37 | sleep(1); 38 | int client_fd = setup_socket(); 39 | 40 | sleep(1); 41 | for (int i = 1; i < FP_THREADS; i++) { 42 | pin_event_t long_interrupt_tid[2]; 43 | memcpy(long_interrupt_tid, long_interrupt, sizeof(long_interrupt)); 44 | long_interrupt_tid[0].pin += i; 45 | long_interrupt_tid[1].pin += i; 46 | 47 | if (send(client_fd, long_interrupt_tid, sizeof(long_interrupt), 0) < 0) { 48 | printf("Could not send interrupt tid\n"); 49 | } 50 | usleep((int) (5e4 * FP_THREADS)); 51 | } 52 | 53 | sleep(1); 54 | 55 | for (int i = 1; i < FP_THREADS; i++) { 56 | pin_event_t interrupt_tid[2]; 57 | memcpy(interrupt_tid, interrupt, sizeof(interrupt)); 58 | interrupt_tid[0].pin += i; 59 | interrupt_tid[1].pin += i; 60 | 61 | if (send(client_fd, interrupt_tid, sizeof(interrupt), 0) < 0) { 62 | printf("Could not send interrupt tid\n"); 63 | } 64 | usleep((int) (5e4 * FP_THREADS)); 65 | } 66 | 67 | usleep((int) (10e4 * FP_THREADS)); 68 | 69 | for (int i = 0; i < 50; i++) { 70 | if (send(client_fd, interrupt, sizeof(interrupt), 0) < 0) { 71 | break; 72 | } 73 | usleep((int) (5e4 * FP_THREADS)); 74 | } 75 | 76 | close(client_fd); 77 | return 0; 78 | } 79 | -------------------------------------------------------------------------------- /emulator/clients/stimuli.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 3 | * @brief A simple client that is coupled with the interrupt.c test. It provides 4 | * 20 interrupts with some delay, which the test needs. 5 | * 6 | */ 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include "common.h" 16 | #include "../../build/hwconfig.h" 17 | 18 | static pin_event_t long_interrupt[] = { 19 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = HIGH }, 20 | 21 | // Wait a long time before setting low again to test that we don't end in 22 | // an infinite interrupt cycle 23 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 1000 * FP_THREADS, .high_low = LOW }, 24 | }; 25 | 26 | static pin_event_t interrupt[] = { 27 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = 0, .high_low = HIGH }, 28 | 29 | // Wait FP_THREADS cycles before setting low again so the HW thread gets 30 | // enough time to react 31 | { .pin = PIN_IO_INT_EXTS_0, .in_n_cycles = FP_THREADS, .high_low = LOW }, 32 | }; 33 | 34 | int main(int argc, char *const* argv) 35 | { 36 | // Allow emulator to initialize before we connect 37 | sleep(1); 38 | int client_fd = setup_socket(); 39 | 40 | sleep(3); 41 | if (send(client_fd, long_interrupt, sizeof(long_interrupt), 0) < 0) { 42 | printf("Could not send long interrupt\n"); 43 | } 44 | 45 | usleep((int) (10e4 * FP_THREADS)); 46 | 47 | for (int i = 0; i < 50; i++) { 48 | if (send(client_fd, interrupt, sizeof(interrupt), 0) < 0) { 49 | break; 50 | } 51 | usleep((int) (5e4 * FP_THREADS)); 52 | } 53 | 54 | close(client_fd); 55 | return 0; 56 | } 57 | -------------------------------------------------------------------------------- /emulator/pin_event.h: -------------------------------------------------------------------------------- 1 | #ifndef PIN_EVENT_H 2 | #define PIN_EVENT_H 3 | 4 | #include 5 | #include "clients/common.h" 6 | 7 | void eventlist_accept_clients(void); 8 | void eventlist_listen(void); 9 | void eventlist_set_pin(VVerilatorTop *top); 10 | 11 | #endif // PIN_EVENT_H 12 | -------------------------------------------------------------------------------- /env.bash: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | # From https://stackoverflow.com/questions/59895/how-do-i-get-the-directory-where-a-bash-script-is-located-from-within-the-script 4 | # The top solution does not work on macOS. 5 | # The solution below comes from this answer: https://stackoverflow.com/a/179231 6 | pushd . > '/dev/null'; 7 | SCRIPT_PATH="${BASH_SOURCE[0]:-$0}"; 8 | 9 | while [ -h "$SCRIPT_PATH" ]; 10 | do 11 | cd "$( dirname -- "$SCRIPT_PATH"; )"; 12 | SCRIPT_PATH="$( readlink -f -- "$SCRIPT_PATH"; )"; 13 | done 14 | 15 | cd "$( dirname -- "$SCRIPT_PATH"; )" > '/dev/null'; 16 | SCRIPT_PATH="$( pwd; )"; 17 | popd > '/dev/null'; 18 | 19 | FLEXPRET_ROOT=$SCRIPT_PATH 20 | export PATH="$FLEXPRET_ROOT/build/emulator:$PATH" 21 | export FP_PATH="$FLEXPRET_ROOT" 22 | export FP_SDK_PATH="$FLEXPRET_ROOT/sdk" 23 | -------------------------------------------------------------------------------- /env.fish: -------------------------------------------------------------------------------- 1 | # From: https://stackoverflow.com/questions/19672673/can-a-fish-script-tell-what-directory-its-stored-in 2 | set -x FLEXPRET_ROOT (cd (dirname (status -f)); and pwd) 3 | set -x PATH $PATH:$FLEXPRET_ROOT/build/emulator 4 | set -x FP_PATH $FLEXPRET_ROOT 5 | set -x FP_SDK_PATH $FLEXPRET_ROOT/sdk 6 | -------------------------------------------------------------------------------- /fpga/.gitignore: -------------------------------------------------------------------------------- 1 | **/.Xil 2 | **/vivado 3 | **/vivado* 4 | **/*.bit -------------------------------------------------------------------------------- /fpga/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(INFILE_LOCATION ${CMAKE_CURRENT_SOURCE_DIR}/cmake/infiles) 2 | 3 | # Needed by generated Vivado .tcl files, we use it to set the number of cores 4 | # Vivado should be using 5 | include(ProcessorCount) 6 | ProcessorCount(NCORES) 7 | 8 | 9 | add_subdirectory(zedboard) 10 | -------------------------------------------------------------------------------- /fpga/README.md: -------------------------------------------------------------------------------- 1 | # FPGA 2 | 3 | FlexPRET may be run on an FPGA. Each supported FPGA board has its own folder. Currently, the only supported board is the *Zedboard*. Refer to the `README.md` there for getting started. 4 | 5 | To create an FPGA bitstream, first run `make fpga` in the top-level directory. This will generate an `FpgaTop.v` file based on the Chisel code. The makefile system here will pick up that file. 6 | -------------------------------------------------------------------------------- /fpga/cmake/infiles/clk_wiz_config.tcl.in: -------------------------------------------------------------------------------- 1 | set_property -dict [list CONFIG.CLKOUT1_REQUESTED_OUT_FREQ {@CLK_FREQ_MHZ@} CONFIG.MMCM_CLKOUT0_DIVIDE_F {@CLK_PERIOD_NS@.000} CONFIG.CLKOUT1_JITTER {151.636}] [get_ips clk_wiz_0] 2 | -------------------------------------------------------------------------------- /fpga/cmake/infiles/clock.xdc.in: -------------------------------------------------------------------------------- 1 | ## @CLK_FREQ_MHZ@ MHz clock 2 | set_property -dict {PACKAGE_PIN Y9 IOSTANDARD LVCMOS33} [get_ports {INPUT_CLOCK}]; # "GCLK" 3 | create_clock -period @CLK_PERIOD_NS@.000 -name sys_clk_pin -waveform {0.000 @CLK_HALF_PERIOD_NS@.000} -add [get_ports INPUT_CLOCK] 4 | -------------------------------------------------------------------------------- /fpga/cmake/infiles/set_program_file.tcl.in: -------------------------------------------------------------------------------- 1 | set_property PROGRAM.FILE {${CMAKE_CURRENT_BINARY_DIR}/bitstream.bit} [get_hw_devices @PART_SHORT@_1] 2 | -------------------------------------------------------------------------------- /fpga/cmake/infiles/variables.tcl.in: -------------------------------------------------------------------------------- 1 | set outputDir ./vivado 2 | set projectName @TARGET_NAME@ 3 | set boardName @BOARD_NAME@ 4 | set partShort @PART_SHORT@ 5 | set part @PART@ 6 | set nCores @NCORES@ 7 | -------------------------------------------------------------------------------- /fpga/zedboard/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # These variables are needed by Vivado .tcl files 2 | set(BOARD_NAME zedboard) 3 | set(PART_SHORT xc7z020) 4 | set(PART xc7z020clg484-1) 5 | 6 | set(FP_FPGA_BOARD_PATH ${CMAKE_CURRENT_LIST_DIR}) 7 | 8 | add_subdirectory(fp-bootloader) 9 | add_subdirectory(fp-blinky) 10 | -------------------------------------------------------------------------------- /fpga/zedboard/docs/zedboard-setup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pretis/flexpret/53af96f5beb22c1bb6a77f455b6ec73066885a12/fpga/zedboard/docs/zedboard-setup.png -------------------------------------------------------------------------------- /fpga/zedboard/fp-blinky/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET_NAME fp-fpga-blinky) 2 | 3 | add_custom_target(${TARGET_NAME} DEPENDS 4 | COMMAND "vivado" 5 | "-mode" "batch" 6 | "-journal" "${CMAKE_CURRENT_BINARY_DIR}/vivado/vivado.jou" 7 | "-log" "${CMAKE_CURRENT_BINARY_DIR}/vivado/vivado.log" 8 | "-source" "${CMAKE_CURRENT_BINARY_DIR}/tcl/flash_runnable.tcl" 9 | DEPENDS 10 | "${CMAKE_CURRENT_BINARY_DIR}/tcl/flash_runnable.tcl" 11 | "${CMAKE_CURRENT_BINARY_DIR}/bitstream.bit" 12 | ) 13 | 14 | set(ISPM_FILE $ENV{FP_PATH}/apps/blinky/build/blinky.mem) 15 | 16 | # We want to include these commands because we do not want to type them out 17 | # for each FPGA project 18 | include(${PROJECT_SOURCE_DIR}/fpga/cmake/commands.cmake) 19 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-blinky/README.md: -------------------------------------------------------------------------------- 1 | # FlexPRET blinky 2 | 3 | This sample project runs the software application `blinky` on FlexPRET. This does not utilize a bootloader. To build the project, the `blinky` app needs to be compiled. 4 | 5 | ## Building 6 | 7 | Assuming the FlexPRET build already has been installed to the SDK (see [top-level README.md](../../../README.md) for more information), `blinky` can be compiled with 8 | 9 | ``` 10 | # Step into apps folder 11 | cd $FP_PATH/apps 12 | 13 | # Run CMake and compile blinky 14 | cmake -B build && cd build && make blinky 15 | ``` 16 | 17 | With `blinky` built, this project can be built with 18 | 19 | ``` 20 | cd $FP_PATH/build && make fp-fpga-blinky 21 | ``` 22 | 23 | This will take some time. After it has been built, it will automatically attempt to upload the `bitstream.bit` file to the FPGA. To do this step, your computer must be connected to the FPGA. 24 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-blinky/rtl/Top.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ps 2 | 3 | module Top( 4 | input INPUT_CLOCK, 5 | output [7:0] LEDS, 6 | output UART_TX, 7 | input UART_RX 8 | ); 9 | 10 | // Outputs 11 | wire sysclk; 12 | 13 | clk_wiz_0 inst( 14 | // Clock out ports 15 | .clk_out1(sysclk), 16 | // Status and control signals 17 | .reset(reset), 18 | .locked(locked), 19 | // Clock in ports 20 | .clk_in1(INPUT_CLOCK) 21 | ); 22 | 23 | FpgaTop flexpret( 24 | .clock(sysclk), 25 | .io_gpio_out_0(LEDS[1:0]), 26 | .io_gpio_out_1(LEDS[3:2]), 27 | .io_gpio_out_2(LEDS[5:4]), 28 | .io_gpio_out_3(LEDS[7:6]), 29 | .io_uart_rx(UART_RX), 30 | .io_uart_tx(UART_TX) 31 | ); 32 | 33 | endmodule 34 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-blinky/xdc/Top.xdc: -------------------------------------------------------------------------------- 1 | ## This file is a general .xdc for the zedboard 2 | ## To use it in a project: 3 | ## - uncomment the lines corresponding to used pins 4 | ## - rename the used ports (in each line, after get_ports) according to the top level signal names in the project 5 | 6 | ## Clock signal 100 MHz 7 | set_property -dict {PACKAGE_PIN Y9 IOSTANDARD LVCMOS33} [get_ports {INPUT_CLOCK}]; # "GCLK" 8 | create_clock -period 10.000 -name sys_clk_pin -waveform {0.000 5.000} -add [get_ports INPUT_CLOCK] 9 | 10 | ## LEDs 11 | set_property -dict {PACKAGE_PIN T22 IOSTANDARD LVCMOS33} [get_ports {LEDS[0]}]; # "LD0" 12 | set_property -dict {PACKAGE_PIN T21 IOSTANDARD LVCMOS33} [get_ports {LEDS[1]}]; # "LD1" 13 | set_property -dict {PACKAGE_PIN U22 IOSTANDARD LVCMOS33} [get_ports {LEDS[2]}]; # "LD2" 14 | set_property -dict {PACKAGE_PIN U21 IOSTANDARD LVCMOS33} [get_ports {LEDS[3]}]; # "LD3" 15 | set_property -dict {PACKAGE_PIN V22 IOSTANDARD LVCMOS33} [get_ports {LEDS[4]}]; # "LD4" 16 | set_property -dict {PACKAGE_PIN W22 IOSTANDARD LVCMOS33} [get_ports {LEDS[5]}]; # "LD5" 17 | set_property -dict {PACKAGE_PIN U19 IOSTANDARD LVCMOS33} [get_ports {LEDS[6]}]; # "LD6" 18 | set_property -dict {PACKAGE_PIN U14 IOSTANDARD LVCMOS33} [get_ports {LEDS[7]}]; # "LD7" 19 | 20 | ## UART pins 21 | set_property -dict { PACKAGE_PIN U17 IOSTANDARD LVCMOS33 } [get_ports { UART_TX }]; # UART_TX is connected to IO7 22 | set_property -dict { PACKAGE_PIN V15 IOSTANDARD LVCMOS33 } [get_ports { UART_RX }]; # UART RX is connected to IO4 23 | 24 | 25 | # ---------------------------------------------------------------------------- 26 | # IOSTANDARD Constraints 27 | # 28 | # Note that these IOSTANDARD constraints are applied to all IOs currently 29 | # assigned within an I/O bank. If these IOSTANDARD constraints are 30 | # evaluated prior to other PACKAGE_PIN constraints being applied, then 31 | # the IOSTANDARD specified will likely not be applied properly to those 32 | # pins. Therefore, bank wide IOSTANDARD constraints should be placed 33 | # within the XDC file in a location that is evaluated AFTER all 34 | # PACKAGE_PIN constraints within the target bank have been evaluated. 35 | # 36 | # Un-comment one or more of the following IOSTANDARD constraints according to 37 | # the bank pin assignments that are required within a design. 38 | # ---------------------------------------------------------------------------- 39 | 40 | # Note that the bank voltage for IO Bank 33 is fixed to 3.3V on ZedBoard. 41 | set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 33]]; 42 | 43 | # Set the bank voltage for IO Bank 34 to 1.8V by default. 44 | # set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 34]]; 45 | # set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 34]]; 46 | set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 34]]; 47 | 48 | # Set the bank voltage for IO Bank 35 to 1.8V by default. 49 | # set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 35]]; 50 | # set_property IOSTANDARD LVCMOS25 [get_ports -of_objects [get_iobanks 35]]; 51 | set_property IOSTANDARD LVCMOS18 [get_ports -of_objects [get_iobanks 35]]; 52 | 53 | # Note that the bank voltage for IO Bank 13 is fixed to 3.3V on ZedBoard. 54 | set_property IOSTANDARD LVCMOS33 [get_ports -of_objects [get_iobanks 13]]; 55 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-bootloader/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TARGET_NAME fp-fpga-bootloader) 2 | 3 | set(ISPM_FILE $ENV{FP_SDK_PATH}/build/bootloader/bootloader.mem) 4 | 5 | add_custom_target(${TARGET_NAME} DEPENDS 6 | COMMAND "vivado" 7 | "-mode" "batch" 8 | "-journal" "${CMAKE_CURRENT_BINARY_DIR}/vivado/vivado.jou" 9 | "-log" "${CMAKE_CURRENT_BINARY_DIR}/vivado/vivado.log" 10 | "-source" "${CMAKE_CURRENT_BINARY_DIR}/tcl/flash_runnable.tcl" 11 | DEPENDS 12 | ${ISPM_FILE} 13 | "${CMAKE_CURRENT_BINARY_DIR}/tcl/flash_runnable.tcl" 14 | "${CMAKE_CURRENT_BINARY_DIR}/bitstream.bit" 15 | ) 16 | 17 | # We want to include these commands because we do not want to type them out 18 | # for each FPGA project 19 | include(${PROJECT_SOURCE_DIR}/fpga/cmake/commands.cmake) 20 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-bootloader/README.md: -------------------------------------------------------------------------------- 1 | # FlexPRET bootloader 2 | 3 | This sample project runs the software application `bootloader` on FlexPRET. This will likely be the most common use-case for running software on your FPGA, because it significantly reduces the time taken to upload new software to the FPGA. Not using the bootloader would require the entire `bitstream.bit` FPGA image to be rebuilt every time new software is uplaoded. 4 | 5 | ## Building 6 | 7 | Assuming the FlexPRET build already has been installed to the SDK (see [top-level README.md](../../../README.md) for more information), `bootloader` can be compiled with 8 | 9 | ``` 10 | # Step into SDK folder 11 | cd $FP_SDK_PATH 12 | 13 | # Run CMake and compile bootloader 14 | cmake -B build && cd build && make bootloader 15 | ``` 16 | 17 | With the `bootloader` built, this project can be built with 18 | 19 | ``` 20 | cd $FP_PATH/build && make fp-fpga-bootloader 21 | ``` 22 | 23 | This will take some time. After it has been built, it will automatically attempt to upload the `bitstream.bit` file to the FPGA. To do this step, your computer must be connected to the FPGA. 24 | 25 | ## Uploading software with bootloader 26 | 27 | When compiling a software application that uses the bootloader, the CMake variable `TARGET` must be set to `fpga`. When this is set, the build system will produce a bash script that uploads software using the bootloader. Under the hood, this script uses `serialize_app.py` and `send_uart.py`. See the generated script for more details. 28 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-bootloader/rtl/.gitignore: -------------------------------------------------------------------------------- 1 | DualPortBramFPGA.v 2 | flexpret.v 3 | ispm.mem 4 | -------------------------------------------------------------------------------- /fpga/zedboard/fp-bootloader/rtl/Top.v: -------------------------------------------------------------------------------- 1 | `timescale 1ns / 1ps 2 | 3 | module Top( 4 | input INPUT_CLOCK, 5 | input RESET, 6 | output [6:0] LEDS, 7 | input [1:0] SWS, 8 | output UART_TX, 9 | input UART_RX, 10 | input JA3, 11 | input JA4, 12 | output JA7 13 | ); 14 | 15 | // Outputs 16 | wire sysclk; 17 | 18 | clk_wiz_0 inst( 19 | // Clock out ports 20 | .clk_out1(sysclk), 21 | // Status and control signals 22 | .reset(RESET), 23 | .locked(locked), 24 | // Clock in ports 25 | .clk_in1(INPUT_CLOCK) 26 | ); 27 | 28 | FpgaTop flexpret( 29 | .clock(sysclk), 30 | .reset(RESET), 31 | .io_gpio_out_0(LEDS[1:0]), 32 | .io_gpio_out_1(LEDS[3:2]), 33 | .io_gpio_out_2(LEDS[5:4]), 34 | .io_gpio_out_3({JA7, LEDS[6]}), 35 | .io_uart_rx(UART_RX), 36 | .io_uart_tx(UART_TX), 37 | .io_gpio_in_0(SWS[0]), 38 | .io_gpio_in_1(SWS[1]), 39 | .io_int_exts_1(JA3), 40 | .io_int_exts_2(JA4) 41 | ); 42 | 43 | endmodule 44 | -------------------------------------------------------------------------------- /fpga/zedboard/leds/README.md: -------------------------------------------------------------------------------- 1 | # LEDS 2 | 3 | This simple example just sets four LEDs and leave four of them off. The example is meant to verify that the computer <-> FPGA setup is correct. FlexPRET is not involved in this project. 4 | 5 | ## Usage 6 | 7 | Due to the simplicity of the project, it is not part of the CMake build system. To build the project, run the command: 8 | 9 | `vivado -mode batch -source runVivadoLeds.tcl` 10 | 11 | If it works, you should see every other LED light up. 12 | -------------------------------------------------------------------------------- /fpga/zedboard/leds/rtl/Top.v: -------------------------------------------------------------------------------- 1 | module Top( 2 | output [7:0] LEDS 3 | ); 4 | 5 | assign LEDS = 8'b01010101; 6 | 7 | endmodule 8 | -------------------------------------------------------------------------------- /fpga/zedboard/leds/runVivadoLeds.tcl: -------------------------------------------------------------------------------- 1 | # TCL script automating synthesis, implementation and programming of a Zedboard 2 | puts "Running synthesis, implementation and programming script for Zedboard" 3 | set outputDir ./vivado 4 | set projectName zedboard_leds 5 | file mkdir $outputDir/$projectName 6 | create_project -force -part xc7z020clg484-1 $outputDir/$projectName 7 | 8 | puts "Adding design sources" 9 | # Add default ISPM contents with bootloader 10 | #add_files -norecurse ispm.mem 11 | 12 | # Add verilog sources 13 | add_files -norecurse rtl/Top.v 14 | 15 | # Add constraints file 16 | add_files -fileset constrs_1 -norecurse xdc/leds.xdc 17 | update_compile_order -fileset sources_1 18 | 19 | puts "Running synthesis" 20 | 21 | # Synthesis 22 | reset_run synth_1 23 | launch_runs synth_1 -jobs 15 24 | wait_on_runs synth_1 25 | 26 | # Implementation and bitstream generation 27 | puts "Running implementation" 28 | launch_runs impl_1 -to_step write_bitstream -jobs 15 29 | wait_on_runs impl_1 30 | 31 | 32 | # Export Bitstream 33 | puts "Exporting bitstream" 34 | file copy -force $outputDir/$projectName.runs/impl_1/Top.bit zedboard.bit 35 | 36 | puts "Programming Zedboard" 37 | # Programming attached Zedboard 38 | open_hw_manager 39 | connect_hw_server -allow_non_jtag 40 | open_hw_target 41 | 42 | # TODO: Fix hard code path 43 | set thisFile [ dict get [ info frame 0 ] file ] 44 | set thisFolder [ file dirname thisFile ] 45 | set_property PROGRAM.FILE { zedboard.bit } [get_hw_devices xc7z020_1] 46 | current_hw_device [get_hw_devices xc7z020_1] 47 | refresh_hw_device -update_hw_probes false [lindex [get_hw_devices xc7z020_1] 0] 48 | 49 | # Program Zedboard 50 | program_hw_devices [get_hw_devices xc7z020_1] 51 | 52 | # close project 53 | puts "Finished" 54 | close_project -------------------------------------------------------------------------------- /fpga/zedboard/tcl/README.md: -------------------------------------------------------------------------------- 1 | # TCL 2 | 3 | These `.tcl` scripts are not runnable alone; they must be combined together to run. This is done automatically by the CMake build system. 4 | -------------------------------------------------------------------------------- /fpga/zedboard/tcl/bitstream.tcl: -------------------------------------------------------------------------------- 1 | puts "Running synthesis" 2 | 3 | # Synthesis 4 | reset_run synth_1 5 | launch_runs synth_1 -jobs $nCores 6 | wait_on_runs synth_1 7 | 8 | # Implementation and bitstream generation 9 | puts "Running implementation" 10 | launch_runs impl_1 -to_step write_bitstream -jobs $nCores 11 | wait_on_runs impl_1 12 | 13 | # Export Bitstream 14 | puts "Exporting bitstream" 15 | file copy -force $outputDir/$projectName.runs/impl_1/Top.bit bitstream.bit 16 | -------------------------------------------------------------------------------- /fpga/zedboard/tcl/flash.tcl: -------------------------------------------------------------------------------- 1 | puts "Programming Zedboard" 2 | 3 | # Programming attached Zedboard 4 | open_hw_manager 5 | connect_hw_server -allow_non_jtag 6 | open_hw_target 7 | source tcl/set_program_file.tcl 8 | current_hw_device [get_hw_devices ${partShort}_1] 9 | refresh_hw_device -update_hw_probes false [lindex [get_hw_devices ${partShort}_1] 0] 10 | 11 | # Program Zedboard 12 | program_hw_devices [get_hw_devices ${partShort}_1] 13 | -------------------------------------------------------------------------------- /fpga/zedboard/tcl/setup.tcl: -------------------------------------------------------------------------------- 1 | file mkdir $outputDir/$projectName 2 | create_project -force -part $part $outputDir/$projectName 3 | 4 | puts "Adding design sources" 5 | 6 | # Add recursively and assume all rtl is located here 7 | add_files rtl 8 | 9 | # Add recursively and assume all xdc is located here 10 | add_files -fileset constrs_1 -norecurse { xdc/constraints.xdc } 11 | update_compile_order -fileset sources_1 12 | 13 | puts "Creating clocking wizard IP" 14 | # Create clocking wizard IP to control the frequency of the CPU 15 | create_ip -name clk_wiz -vendor xilinx.com -library ip -version 6.0 -module_name clk_wiz_0 16 | 17 | # This is automatically generated based on configuration 18 | source tcl/clk_wiz_config.tcl 19 | update_compile_order -fileset sources_1 20 | -------------------------------------------------------------------------------- /fpga/zedboard/xdc/README.md: -------------------------------------------------------------------------------- 1 | # zedboard.xdc 2 | 3 | This file is kept here for reference. If you need to update an `.xdc` file, refer to this one, because it contains all default constraints. 4 | -------------------------------------------------------------------------------- /scripts/c/parse_disasm.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # parse_disasm.py 5 | # Parse the output of riscv32-unknown-elf-objdump and put it into a Scala 6 | # array constant, or readmemh .mem file. 7 | # 8 | # Copyright 2021 Edward Wang 9 | 10 | from dataclasses import dataclass 11 | import re 12 | import sys 13 | from typing import List 14 | 15 | @dataclass(frozen=True) 16 | class Instruction: 17 | offset: str # e.g. c 18 | instr: str # e.g. 00008067 19 | comment: str # e.g. jr ra 20 | 21 | def parse_disasm(contents: str) -> List[Instruction]: 22 | """ 23 | Parse the given objdump output. 24 | """ 25 | output: List[Instruction] = [] 26 | 27 | # Strip tabs 28 | contents = contents.replace("\t", " ") 29 | 30 | for m in re.findall(r'([a-f\d]+):\s+?([a-f\d]+)\s+(.+?)$', contents, re.MULTILINE): 31 | output.append(Instruction(offset=m[0], instr=m[1], comment=m[2])) 32 | 33 | return output 34 | 35 | def to_scala(prog: List[Instruction]) -> List[str]: 36 | """ 37 | Format the given program as a Scala array constant line-by-line. 38 | """ 39 | output: List[str] = [] 40 | 41 | output.append("(") 42 | 43 | for instr in prog: 44 | output.append(" " + f"/* {instr.offset} */ \"h{instr.instr}\", // {instr.comment}") 45 | 46 | output.append(")") 47 | 48 | return output 49 | 50 | def to_readmemh(prog: List[Instruction]) -> List[str]: 51 | """ 52 | Format the given program as a readmemh file. 53 | """ 54 | output: List[str] = [] 55 | for instr in prog: 56 | output.append(instr.instr) 57 | return output 58 | 59 | def main(args: List[str]) -> int: 60 | if len(args) < 3: 61 | print(f"Usage: {args[0]} ", file=sys.stderr) 62 | return 1 63 | 64 | objdump_out: str = str(args[1]) 65 | type_output: str = str(args[2]) 66 | if type_output not in ('scala', 'readmemh'): 67 | print(f"Invalid output type {type_output}", file=sys.stderr) 68 | return 1 69 | 70 | with open(objdump_out, 'r') as f: 71 | contents = str(f.read()) 72 | 73 | prog = parse_disasm(contents) 74 | 75 | output: List[str] = [] 76 | if type_output == 'scala': 77 | output = to_scala(prog) 78 | else: 79 | output = to_readmemh(prog) 80 | 81 | for line in output: 82 | print(line) 83 | 84 | return 0 85 | 86 | if __name__ == '__main__': 87 | sys.exit(main(sys.argv)) 88 | -------------------------------------------------------------------------------- /scripts/callgraph.py: -------------------------------------------------------------------------------- 1 | import re 2 | import argparse 3 | 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument('top', help='Top level function to highlight from') 6 | 7 | args = parser.parse_args() 8 | 9 | LIGHT_BLUE = "blue" 10 | ALL_CALLED = list() 11 | TOP_FUNCTION = args.top 12 | 13 | with open('callgraph/expands.txt', 'r') as f: 14 | callgraph = f.readlines() 15 | 16 | def from_line_to_function_name(line): 17 | return re.findall('"([^"]*)"', line)[0] 18 | 19 | def find_all_called_by_fuction(raw_callgraph, function): 20 | for line in raw_callgraph: 21 | if not '->' in line: 22 | continue 23 | lhs, rhs = line.split('->') 24 | lhs_function = from_line_to_function_name(lhs) 25 | rhs_function = from_line_to_function_name(rhs) 26 | if function == lhs_function and rhs_function not in ALL_CALLED: 27 | ALL_CALLED.append(rhs_function) 28 | find_all_called_by_fuction(raw_callgraph, rhs_function) 29 | 30 | find_all_called_by_fuction(callgraph, TOP_FUNCTION) 31 | 32 | callgraph.remove('}\n') 33 | callgraph.append("\"" + TOP_FUNCTION + "\" [color=" + LIGHT_BLUE + ", style=filled];\n") 34 | for call in ALL_CALLED: 35 | callgraph.append("\"" + call + "\" [color=" + LIGHT_BLUE + "];\n") 36 | callgraph.append('}\n') 37 | 38 | with open('callgraph/expands.txt.modified', 'w') as f: 39 | f.writelines(callgraph) 40 | -------------------------------------------------------------------------------- /scripts/fpga/tofpga.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # Create memory initialization files for readmemh() 3 | # tofpga.sh [path to program] [path to fpga project] 4 | # Assumes 16kb memory size 5 | 6 | function fill { 7 | x=`wc -l < $1` 8 | while [ $x -lt 4096 ] 9 | do 10 | echo "00000000" >> $1 11 | x=$(($x+1)) 12 | done 13 | } 14 | ispm=$1.inst.mem 15 | dspm=$1.data.mem 16 | 17 | fill $ispm 18 | fill $dspm 19 | cp $ispm $2/ispm 20 | cp $dspm $2/dspm 21 | 22 | -------------------------------------------------------------------------------- /scripts/hdl/simify_verilog.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python3 2 | # -*- coding: utf-8 -*- 3 | # 4 | # simify_verilog.py 5 | # Parse the given Verilog file to make it suitable for simulation. 6 | # 1) It inserts the readmemh call into the ispm to pre-load it. 7 | # 2) It starts VCD dumping in the main module. 8 | # 9 | # Copyright 2021 Edward Wang 10 | 11 | import re 12 | import sys 13 | from typing import List 14 | 15 | def add_ispm(contents: str) -> str: 16 | """ 17 | Add the given .mem filename as a loadmemh to the ispm. 18 | initial $readmemh("instr_mem.mem", ispm); 19 | """ 20 | m = re.search(r'reg \[\d+:0\] ispm \[0:\d+\];', contents, re.MULTILINE) 21 | assert m is not None 22 | 23 | orig_str = m.group() 24 | new_str = f""" 25 | initial begin 26 | $value$plusargs("ispm=%s", mem_file_name); 27 | $readmemh(mem_file_name, ispm); 28 | end 29 | {orig_str} 30 | """ 31 | 32 | return contents.replace(orig_str, new_str) 33 | 34 | def add_vcd(contents: str) -> str: 35 | """ 36 | Add the given filename as a VCD target dump. 37 | Also adds some magic values to: 38 | - finish the simulation 39 | - print the next value 40 | """ 41 | m = re.search(r'module Top\([\s\S]+?\);', contents, re.MULTILINE) 42 | assert m is not None 43 | 44 | orig_str = m.group() 45 | vcd_blob = """ 46 | initial begin 47 | $dumpfile("trace.vcd"); 48 | $dumpvars; 49 | end 50 | """ 51 | 52 | return contents.replace(orig_str, f"{orig_str} {vcd_blob}") 53 | 54 | def main(args: List[str]) -> int: 55 | if len(args) < 2: 56 | print(f"Usage: {args[0]} Top.v", file=sys.stderr) 57 | return 1 58 | 59 | with open(args[1], 'r') as f: 60 | contents = str(f.read()) 61 | 62 | contents = add_vcd(contents) 63 | 64 | print(contents) 65 | 66 | return 0 67 | 68 | if __name__ == '__main__': 69 | sys.exit(main(sys.argv)) 70 | -------------------------------------------------------------------------------- /scripts/run_multiple_tests.sh: -------------------------------------------------------------------------------- 1 | set -e 2 | 3 | # $1: Which config to run 4 | run_test() { 5 | # Get a clean state 6 | rm -rf $FP_PATH/build && rm -rf $FP_SDK_PATH/build && rm -rf $FP_SDK_PATH/flexpret 7 | 8 | # Build FlexPRET 9 | cd $FP_PATH && cmake -DFP_CONFIG=$1 -B build && cmake --build build && cmake --install build 10 | 11 | # Build SDK and run tests 12 | cd $FP_SDK_PATH && cmake -B build && cmake --build build && ctest --test-dir build --output-on-failure 13 | } 14 | 15 | # These configurations correspond with the files inside `./flexpret/cmake/configs` 16 | configs=( 17 | highmem 8threads 3threads 5threads@50MHz 18 | ) 19 | 20 | # Run test on each config 21 | for cfg in "${configs[@]}" 22 | do 23 | run_test $cfg 24 | done 25 | -------------------------------------------------------------------------------- /scripts/send_uart.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.8 2 | import serial 3 | import os 4 | import sys 5 | import time 6 | 7 | usb_path = sys.argv[1] 8 | baudrate = int(sys.argv[2]) 9 | file = sys.argv[3] 10 | data = None 11 | 12 | if sys.platform.startswith('linux') and not os.path.exists(usb_path): 13 | exit(f"Could not find USB->UART bridge {usb_path}") 14 | 15 | with open(file, "rb") as f: 16 | data = f.read() 17 | 18 | try: 19 | com = serial.Serial(usb_path, baudrate) 20 | except Exception as e: 21 | print(f"Couldnt open USB-UART bridge ({usb_path}); is it connected?" + str(e)) 22 | exit(1) 23 | 24 | time.sleep(0.1) 25 | 26 | try: 27 | com.write(data) 28 | except Exception as e: 29 | print("Couldnt write app over uart e:" + str(e)) 30 | pass 31 | -------------------------------------------------------------------------------- /scripts/serialize_app.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import argparse 4 | parser = argparse.ArgumentParser() 5 | parser.add_argument("input", help="Input memory file") 6 | parser.add_argument("output", help="Output serialized app") 7 | 8 | args = parser.parse_args() 9 | 10 | DBG=False 11 | 12 | 13 | def DBG_PRINT(str): 14 | if DBG: 15 | print(str) 16 | 17 | SYNC_ID = 0xC0DE 18 | 19 | with open(args.input, "r") as fr: 20 | lines = fr.readlines() 21 | DBG_PRINT(f"Got {len(lines)} lines") 22 | length = len(lines) * 4 23 | lengthField = length.to_bytes(4, "little") 24 | DBG_PRINT(f"lengthField={lengthField}") 25 | 26 | with open(args.output, "wb") as fw: 27 | syncField = SYNC_ID.to_bytes(2, "little") 28 | DBG_PRINT(f"SyncField={syncField}") 29 | fw.write(syncField) 30 | fw.write(lengthField) 31 | 32 | for l in lines: 33 | DBG_PRINT(f"Line={l} hex_string={bytearray.fromhex(l)}") 34 | bs = bytes.fromhex(l) 35 | if len(bs) < 4: 36 | while len(bs) < 4: 37 | bs = bytes.fromhex("00") + bs 38 | 39 | assert(len(bs) == 4) 40 | fw.write(bs) 41 | fw.write(syncField) 42 | -------------------------------------------------------------------------------- /sdk/.gitignore: -------------------------------------------------------------------------------- 1 | # This is the hardware configuration installed from FlexPRET build; do not track it 2 | flexpret 3 | -------------------------------------------------------------------------------- /sdk/README.md: -------------------------------------------------------------------------------- 1 | # FlexPRET SDK 2 | 3 | This directory contains the necessary tools to develop software for FlexPRET. Note that using this SDK requires a FlexPRET hardware installation. Getting one is achieved by building FlexPRET in the top-level directory and installing it here with `make install`. 4 | 5 | There are a number of components to this SDK. The bulk of source code is found in the `./lib` folder, which provides linker scripts and a library for interfacing with FlexPRET. External third-party libraries are found in `./external`. Unit tests are available in `./tests`. Finally, a bootloader can be found in `./bootloader`. 6 | 7 | Refer to the apps folder in the top-level directory (`../apps`) for examples on how to build an application using the SDK. Refer to the unit tests in the `./tests` folder and the apps for examples on how to use the SDK. 8 | 9 | ## Bootloader 10 | 11 | The bootloader is required to quickly transfer new software to FlexPRET when running on an FPGA. Refer to [its docs](./bootloader/README.md) for additional information. 12 | 13 | Note that applications that target the FPGA cannot be compiled until the bootloader first has been built. This is because they need to know the size of the bootloader to know how large their offset should be in the compiled executable. 14 | 15 | Upon successful build of the bootloader, it will create the file `./lib/linker/bootloader/use/bootloader.ld`, which contains the size of the bootloader. 16 | -------------------------------------------------------------------------------- /sdk/bootloader/.gitignore: -------------------------------------------------------------------------------- 1 | final.mem 2 | app -------------------------------------------------------------------------------- /sdk/bootloader/README.md: -------------------------------------------------------------------------------- 1 | # Bootloader 2 | 3 | There are a few things to note about the bootloader. 4 | 1. It is compiled to be as little as possible. This means functions like `printf` are not available when bootloading. 5 | 2. When the bootloader intializes, you can choose between loading an already integrated application ('static' loading) or loading an application in run-time ('dynamic' loading). 6 | 3. The use case of having a bootloader is to be able to easily swap out software running on an FPGA. The idea is that the user can transmit new programs over UART. Without the bootloader, new programs would have to be integrated into an FPGA image, which is very time-consuming. 7 | 8 | ## Static loading 9 | 10 | The bootloader will be compiled with an application placed directly after the bootloader. This is mostly for testing/debugging purposes - if the application is, e.g., `blinky`, it will be easy to confirm that the bootloader and FPGA is behaving well. 11 | 12 | ## Dynamic loading 13 | 14 | The bootloader can dynamically load an application from UART. This will overwrite the currently loaded application. Note that this application MUST be compiled with an offset in its start address; this is done automatically when the `TARGET` cmake variable is set to `fpga`. To verify correct bahavior, you may inspect the generated `.dump` file and check that the `_start` function has a non-zero address. 15 | 16 | ## Load configuration 17 | 18 | Dynamic loading is selected if `(gpi_read_0() & 0b1) == 0b1` evaluates to true - otherwise static loading is selected. The general purpose input pin should be connected to a switch on an FPGA board, so the user can easily configure whether to use static or dynamic loading. 19 | 20 | Furthermore, a system-wide reset should be connected to a button. The system-wide reset should reset the entire CPU so it starts at program counter zero and restarts the bootloader. 21 | 22 | ## Once built 23 | 24 | Once the bootloader is built, the build system will automatically install a `bootloader.ld` file to the `./lib/linker/bootloader/use` directory. Applications that target FlexPRET on FPGA refuse to build unless this file exists. The reason for this is that the linker script needs to know the application's offset in the instruction memory before it can be linked. 25 | 26 | Applications that do not use the bootloader will include `./lib/linker/bootloader/none/bootloder.ld` in its linker script. When using the bootloader the `./lib/linker/bootloader/use/bootloder.ld` file is included instead. (Note the difference in `use` vs. `none` in the paths.) 27 | -------------------------------------------------------------------------------- /sdk/bootloader/configure.py: -------------------------------------------------------------------------------- 1 | import os 2 | import sys 3 | from pathlib import Path 4 | 5 | # Check that environment is set up correctly 6 | if os.environ.get('FP_PATH') == None: 7 | print('Environment not set up, missing FP_PATH') 8 | exit(1) 9 | 10 | if os.environ.get('FP_SDK_PATH') == None: 11 | print('Environment not set up, missing FP_SDK_PATH') 12 | exit(1) 13 | 14 | if len(sys.argv) != 2: 15 | print(f'Usage: {sys.argv[0]} ') 16 | exit(1) 17 | 18 | # There are two files of importance: bootloader-first.mem and 19 | # bootloader.mem 20 | # We expect this script to be run three times: 21 | # 1. When none of these files exist; in this case we generate an 22 | # empty header file 23 | # 2. When only bootloader-first.mem exists; in this case we generate 24 | # a header file with the size of the bootloader-first.mem 25 | # 3. When both bootloader-first.mem and bootloader.mem exist. In this 26 | # case we verify that their sizes are equal 27 | 28 | sdk_folder = Path(os.environ.get('FP_SDK_PATH')) 29 | bootloader_build = sdk_folder / 'build' / 'bootloader' 30 | 31 | 32 | if (bootloader_build / 'bootloader-first.mem').is_file(): 33 | bootloader_first_exist = True 34 | else: 35 | bootloader_first_exist = False 36 | 37 | if (bootloader_build / 'bootloader-second.mem').is_file(): 38 | bootloader_exist = True 39 | else: 40 | bootloader_exist = False 41 | 42 | if sys.argv[1] == 'first' and not bootloader_first_exist: 43 | # First run: Make empty header 44 | (bootloader_build / 'empty').mkdir(exist_ok=True) 45 | with open(bootloader_build / 'empty' / 'location.h', 'w') as f: 46 | f.write('') 47 | 48 | elif sys.argv[1] == 'second' and bootloader_first_exist and not bootloader_exist: 49 | # Second run: Make header with size of first 50 | path = bootloader_build / 'bootloader-first.mem' 51 | lines = sum(1 for _ in open(path)) 52 | 53 | (bootloader_build / 'actual').mkdir(exist_ok=True) 54 | with open(bootloader_build / 'actual' / 'location.h', 'w') as f: 55 | f.write(f'#define APP_LOCATION {4*lines}') 56 | 57 | elif sys.argv[1] == 'third' and bootloader_first_exist and bootloader_exist: 58 | # Third run: Verify 59 | path_bl_first = bootloader_build / 'bootloader-first.mem' 60 | path_bl_final = bootloader_build / 'bootloader-second.mem' 61 | 62 | lines_bl_first = sum(1 for _ in open(path_bl_first)) 63 | lines_bl_final = sum(1 for _ in open(path_bl_final)) 64 | if lines_bl_first != lines_bl_final: 65 | print('Verification failed. Removing bad artifacts.') 66 | path_bl_first.unlink() 67 | path_bl_final.unlink() 68 | exit(1) 69 | else: 70 | btl_path = sdk_folder / 'lib' / 'linker' / 'bootloader' / 'use' / 'bootloader.ld' 71 | file = f""" 72 | /** 73 | * This flexpret core config file is auto-generated, and based on the 74 | * configuration of the compiled bootloader. 75 | * 76 | * Do not edit. 77 | * 78 | */ 79 | BOOTLOADER_SIZE = {4*lines_bl_final}; 80 | """ 81 | with open(btl_path, 'w') as f: 82 | f.write(file) 83 | else: 84 | print('Invalid run of script') 85 | exit(1) 86 | 87 | -------------------------------------------------------------------------------- /sdk/bootloader/startup.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /* Linker */ 4 | extern uint32_t __etext; 5 | extern uint32_t __sdata; 6 | extern uint32_t __edata; 7 | extern uint32_t __sbss; 8 | extern uint32_t __ebss; 9 | extern uint32_t end; 10 | 11 | //prototype of main 12 | int main(void); 13 | 14 | extern void (*application)(void); 15 | extern volatile bool boot_done; 16 | 17 | void Reset_Handler() { 18 | // Get hartid 19 | uint32_t hartid = read_hartid(); 20 | // Only thread 0 performs the setup, 21 | // the other threads busy wait until ready. 22 | if (hartid == 0) { 23 | // Copy .data section into the RAM 24 | uint32_t size = &__edata - &__sdata; 25 | uint32_t *pDst = (uint32_t*)&__sdata; // RAM 26 | uint32_t *pSrc = (uint32_t*)&__etext; // ROM 27 | 28 | for (uint32_t i = 0; i < size; i++) { 29 | *pDst++ = *pSrc++; 30 | } 31 | 32 | // Init. the .bss section to zero in RAM 33 | size = (uint32_t)&__ebss - (uint32_t)&__sbss; 34 | pDst = (uint32_t*)&__sbss; 35 | for(uint32_t i = 0; i < size; i++) { 36 | *pDst++ = 0; 37 | } 38 | 39 | // Jump to main (which should be the bootloader) 40 | main(); 41 | } else { 42 | while (!boot_done); 43 | 44 | /** 45 | * In the case where there is no bootloader, all threads directly 46 | * boot into the application. That should be the case here too. 47 | * This really only works because the application has its own 48 | * Reset_Handler function (see startup.c in lib) which deals with these 49 | * threads. 50 | * 51 | */ 52 | application(); 53 | } 54 | 55 | // Infinite loop 56 | while (1); 57 | } -------------------------------------------------------------------------------- /sdk/cmake/configderive.cmake: -------------------------------------------------------------------------------- 1 | # Derive other variables from the pure configuration 2 | 3 | # Calculate log2(STACKSIZE) 4 | execute_process( 5 | COMMAND "python3" "-c" "import math; print(int(math.log2(${STACKSIZE})))" 6 | OUTPUT_VARIABLE STACKSIZE_BITS 7 | ) 8 | 9 | # For configuration of newlib's reentracy feature; see comments in 10 | # https://github.com/eblot/newlib/blob/master/newlib/libc/include/reent.h 11 | if (${THREADS} GREATER 1) 12 | set(NEWLIB_REENTRANCY_METHOD __DYNAMIC_REENT__) 13 | else() 14 | set(NEWLIB_REENTRANCY_METHOD __SINGLE_THREADED__) 15 | endif() 16 | -------------------------------------------------------------------------------- /sdk/cmake/configs/default.cmake: -------------------------------------------------------------------------------- 1 | # Analagous to `../cmake/configs`, this configuration file sets software 2 | # configuration (as opposed to hardware configuration). The structure is 3 | # similar to that of the hardware configurations. 4 | 5 | # Valid: Any power of 2 6 | set(STACKSIZE 2048 CACHE STRING "The number of bytes allocated to each thread's stack" FORCE) 7 | 8 | -------------------------------------------------------------------------------- /sdk/cmake/configverify.cmake: -------------------------------------------------------------------------------- 1 | # Import function common to checking hardware and software config 2 | include("$ENV{FP_PATH}/cmake/lib/check.cmake") 3 | 4 | set(STACKSIZE_OPTIONS 512 1024 2048 4096) 5 | 6 | check_parameter(STACKSIZE STACKSIZE_OPTIONS FATAL_ERROR) 7 | -------------------------------------------------------------------------------- /sdk/cmake/infiles/emu-app.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is auto generated by CMake and should not be modified. 4 | # 5 | # This script will attempt to run your compiled software on the FlexPRET 6 | # emulator. 7 | # 8 | # You got this script because the CMake variable `TARGET` was set to 9 | # `emulator` or not set at all. To get a script that attempts to uploade 10 | # your program to an FPGA with a bootloader instead, set the `TARGET` 11 | # variable to `fpga` in your application's `CMakeLists.txt`. 12 | 13 | set -e 14 | 15 | if [[ -z "${FP_PATH}" ]]; then 16 | echo "Environment variable FP_PATH not set!" 17 | exit 1 18 | fi 19 | 20 | if [[ -z "${FP_SDK_PATH}" ]]; then 21 | echo "Environment variable FP_SDK_PATH not set!" 22 | exit 1 23 | fi 24 | 25 | # Launch installed FlexPRET emulator with instruction memory file 26 | # @$ adds additional aguments from command line, .e.g, --trace 27 | ${FP_SDK_PATH}/flexpret/fp-emu +ispm=@IMEM_LOCATION@/@IMEM_NAME@ $@ 28 | -------------------------------------------------------------------------------- /sdk/cmake/infiles/fpga-app.sh.in: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | # 3 | # This file is auto generated by CMake and should not be modified. 4 | # 5 | # This script will attempt to upload your compiled software to an FPGA, 6 | # assuming correct hardware setup and that it runs the bootloader found in 7 | # `$FP_SDK_PATH/bootloader`. 8 | # 9 | # You got this script because the CMake variable `TARGET` was set to 10 | # `fpga`. Either specify `emulator` or nothing to get a script that runs 11 | # the emulator instead. 12 | 13 | set -e 14 | 15 | if [[ -z "${FP_PATH}" ]]; then 16 | echo "Environment variable FP_PATH not set!" 17 | exit 1 18 | fi 19 | 20 | if [[ -z "${FP_SDK_PATH}" ]]; then 21 | echo "Environment variable FP_SDK_PATH not set!" 22 | exit 1 23 | fi 24 | 25 | flashdev=@FP_FLASH_DEVICE@ 26 | baudrate=@UART_BAUDRATE@ 27 | imem_path=@IMEM_LOCATION@/@IMEM_NAME@ 28 | imem_serialized_path=/tmp/@IMEM_NAME@.serialized 29 | 30 | echo "Serializing app and placing its result in /tmp" 31 | python3 ${FP_PATH}/scripts/serialize_app.py ${imem_path} ${imem_serialized_path} 32 | 33 | echo "Flashing serialized app on device ${flashdev} with baudrate ${baudrate}" 34 | 35 | # Script takes usb_path, baudrate, and file 36 | python3 ${FP_PATH}/scripts/send_uart.py ${flashdev} ${baudrate} ${imem_serialized_path} 37 | 38 | # Run picocom to capture stdout 39 | picocom -b $baudrate $flashdev --imap lfcrlf 40 | -------------------------------------------------------------------------------- /sdk/cmake/infiles/swconfig.h.in: -------------------------------------------------------------------------------- 1 | /** 2 | * This FlexPRET configuration file is auto-generated, and based on the 3 | * configuration used during build. 4 | * 5 | * Do not edit. 6 | * 7 | */ 8 | 9 | #ifndef FP_INTERNAL_SWCONFIG_H 10 | #define FP_INTERNAL_SWCONFIG_H 11 | 12 | #define FP_STACKSIZE_BITS @STACKSIZE_BITS@ 13 | 14 | #endif // FP_INTERNAL_SWCONFIG_H 15 | -------------------------------------------------------------------------------- /sdk/cmake/infiles/swconfig.ld.in: -------------------------------------------------------------------------------- 1 | /** 2 | * This FlexPRET configuration file is auto-generated, and based on the 3 | * configuration used during build. 4 | * 5 | * Do not edit. 6 | * 7 | */ 8 | 9 | STACKSIZE_BITS = @STACKSIZE_BITS@ ; 10 | STACKSIZE = (1 << STACKSIZE_BITS) ; 11 | -------------------------------------------------------------------------------- /sdk/cmake/riscv-toolchain.cmake: -------------------------------------------------------------------------------- 1 | # Copyright 2020 The IREE Authors 2 | # 3 | # Licensed under the Apache License v2.0 with LLVM Exceptions. 4 | # See https://llvm.org/LICENSE.txt for license information. 5 | # SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception 6 | 7 | # Based on https://github.com/openxla/iree/blob/main/build_tools/cmake/riscv.toolchain.cmake 8 | 9 | cmake_minimum_required(VERSION 3.13) 10 | 11 | # Ensure the file does not get included twice 12 | # If it does get included multiple times, flags will appear multiple times too 13 | # because they are appended to already existing flags 14 | include_guard(GLOBAL) 15 | 16 | set(CMAKE_SYSTEM_NAME Generic) # Explicitly set for cross-compilation 17 | set(CMAKE_SYSTEM_PROCESSOR riscv64) 18 | set(RISCV_HOST_TAG linux) 19 | 20 | set(RISCV_TOOL_PATH $ENV{RISCV_TOOL_PATH_PREFIX} CACHE PATH "RISC-V tool path" FORCE) 21 | 22 | set(RISCV_TOOLCHAIN_ROOT "${RISCV_TOOL_PATH}/bin" CACHE PATH "RISC-V compiler path") 23 | set(RISCV_TOOLCHAIN_PREFIX "riscv-none-elf-" CACHE STRING "RISC-V toolchain prefix") 24 | set(CMAKE_FIND_ROOT_PATH ${RISCV_TOOLCHAIN_ROOT}) 25 | list(APPEND CMAKE_PREFIX_PATH "${RISCV_TOOLCHAIN_ROOT}") 26 | 27 | set(CMAKE_C_COMPILER "${RISCV_TOOLCHAIN_ROOT}/${RISCV_TOOLCHAIN_PREFIX}gcc") 28 | set(CMAKE_CXX_COMPILER "${RISCV_TOOLCHAIN_ROOT}/${RISCV_TOOLCHAIN_PREFIX}g++") 29 | set(CMAKE_STRIP "${RISCV_TOOLCHAIN_ROOT}/${RISCV_TOOLCHAIN_PREFIX}strip") 30 | set(CMAKE_OBJDUMP "${RISCV_TOOLCHAIN_ROOT}/${RISCV_TOOLCHAIN_PREFIX}objdump") 31 | set(CMAKE_OBJCOPY "${RISCV_TOOLCHAIN_ROOT}/${RISCV_TOOLCHAIN_PREFIX}objcopy") 32 | 33 | set(RISCV_COMPILER_FLAGS "-g -Os -static -march=rv32i_zicsr -mabi=ilp32 -nostartfiles --specs=nosys.specs -ffunction-sections -fdata-sections -Wl,--gc-sections") 34 | set(RISCV_COMPILER_FLAGS_CXX) 35 | set(RISCV_COMPILER_FLAGS_DEBUG) 36 | set(RISCV_COMPILER_FLAGS_RELEASE) 37 | set(RISCV_LINKER_FLAGS) 38 | set(RISCV_LINKER_FLAGS_EXE "" CACHE STRING "Linker flags for RISCV executables") 39 | 40 | # Check if the current OS is macOS 41 | if(${CMAKE_HOST_SYSTEM_NAME} STREQUAL "Darwin") 42 | # Override macOS-specific settings 43 | set(CMAKE_OSX_ARCHITECTURES "") 44 | set(CMAKE_OSX_SYSROOT "") 45 | endif() 46 | 47 | set(CMAKE_C_FLAGS "${RISCV_COMPILER_FLAGS} ${CMAKE_C_FLAGS}") 48 | set(CMAKE_CXX_FLAGS "${RISCV_COMPILER_FLAGS} ${RISCV_COMPILER_FLAGS_CXX} ${CMAKE_CXX_FLAGS}") 49 | set(CMAKE_ASM_FLAGS "${RISCV_COMPILER_FLAGS} ${CMAKE_ASM_FLAGS}") 50 | set(CMAKE_C_FLAGS_DEBUG "${RISCV_COMPILER_FLAGS_DEBUG} ${CMAKE_C_FLAGS_DEBUG}") 51 | set(CMAKE_CXX_FLAGS_DEBUG "${RISCV_COMPILER_FLAGS_DEBUG} ${CMAKE_CXX_FLAGS_DEBUG}") 52 | set(CMAKE_ASM_FLAGS_DEBUG "${RISCV_COMPILER_FLAGS_DEBUG} ${CMAKE_ASM_FLAGS_DEBUG}") 53 | set(CMAKE_C_FLAGS_RELEASE "${RISCV_COMPILER_FLAGS_RELEASE} -DNDEBUG") 54 | set(CMAKE_CXX_FLAGS_RELEASE "${RISCV_COMPILER_FLAGS_RELEASE} -DNDEBUG") 55 | set(CMAKE_ASM_FLAGS_RELEASE "${RISCV_COMPILER_FLAGS_RELEASE} -DNDEBUG") 56 | set(CMAKE_SHARED_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${CMAKE_SHARED_LINKER_FLAGS}") 57 | set(CMAKE_MODULE_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${CMAKE_MODULE_LINKER_FLAGS}") 58 | set(CMAKE_EXE_LINKER_FLAGS "${RISCV_LINKER_FLAGS} ${RISCV_LINKER_FLAGS_EXE} ${CMAKE_EXE_LINKER_FLAGS}") 59 | -------------------------------------------------------------------------------- /sdk/external/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # We do not install printf stuff anyway, so this just removes a warning 2 | if (NOT DEFINED CMAKE_INSTALL_LIBDIR) 3 | set(CMAKE_INSTALL_LIBDIR "") 4 | endif() 5 | 6 | set(BUILD_STATIC_LIBRARY True) 7 | 8 | # Configure library to be as small as possible 9 | set(SUPPORT_DECIMAL_SPECIFIERS false) 10 | set(SUPPORT_EXPONENTIAL_SPECIFIERS false) 11 | set(SUPPORT_MSVC_STYLE_INTEGER_SPECIFIERS false) 12 | set(SUPPORT_WRITEBACK_SPECIFIER false) 13 | set(SUPPORT_LONG_LONG false) 14 | set(CHECK_FOR_NUL_IN_FORMAT_SPECIFIER false) 15 | 16 | #[[ 17 | We specify `EXCLUDE_FROM_ALL` because `printf` is its own standalone project. 18 | Also, if a top-level CMakeLists.txt adds our SDK, it would add this one too. 19 | Then running `make install` will also run `printf`'s install commands, which 20 | we do not want. 21 | ]] 22 | add_subdirectory(printf EXCLUDE_FROM_ALL) 23 | 24 | #[[ 25 | The `printf` submodule implements the "printf familiy" suffized with an underscore, 26 | like so: `printf_`, `sprintf_`. (See bottom of `./printf/src/printf/printf.c`) 27 | Then it does `#define printf_ printf`, which is referred to as "aliasing" of 28 | function names in the documentation. 29 | 30 | This is done in the header file using `#define`. However, that requires the header 31 | file to always be included in source code that uses `printf`. It is better to 32 | define these macros on the command line so they are always set. 33 | ]] 34 | target_compile_definitions(printf PUBLIC 35 | printf_=printf 36 | sprintf_=sprintf 37 | vsprintf_=vsprintf 38 | snprintf_=snprintf 39 | vsnprintf_=vsnprintf 40 | vprintf_=vprintf 41 | ) 42 | 43 | target_compile_definitions(fp-sdk PUBLIC HAVE_PRINTF) 44 | target_include_directories(fp-sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/printf/src) 45 | target_link_libraries(fp-sdk PUBLIC printf) 46 | -------------------------------------------------------------------------------- /sdk/lib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(SOURCES 2 | start.S 3 | ctx_switch.S 4 | cond.c 5 | exceptions.c 6 | io.c 7 | lock.c 8 | pbuf.c 9 | startup.c 10 | syscalls.c 11 | thread.c 12 | uart.c 13 | wb.c 14 | ) 15 | 16 | list(TRANSFORM SOURCES PREPEND "${CMAKE_CURRENT_SOURCE_DIR}/src/") 17 | 18 | target_sources(fp-sdk PRIVATE ${SOURCES}) 19 | 20 | #[[ 21 | This is necessary because on the order of which libraries appear on the command 22 | line matters: https://stackoverflow.com/questions/45135/why-does-the-order-in-which-libraries-are-linked-sometimes-cause-errors-in-gcc 23 | Since the printf library depends on printf_putchar and fp-sdk library depends on 24 | the printf library, the simplest solution is to just add printf_putchar.c 25 | to the public interface and recompile it for each appplication. 26 | ]] 27 | target_sources(fp-sdk PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/src/printf_putchar.c) 28 | 29 | if (${TARGET} STREQUAL "fpga") 30 | target_compile_definitions(fp-sdk PUBLIC __FPGA__) 31 | else() 32 | target_compile_definitions(fp-sdk PUBLIC __EMULATOR__) 33 | endif() 34 | 35 | target_compile_definitions(fp-sdk PUBLIC 36 | _REENT_SMALL 37 | ${NEWLIB_REENTRANCY_METHOD} # Set in configderive.cmake 38 | ) 39 | 40 | target_include_directories(fp-sdk PUBLIC 41 | "$" 42 | "$" 43 | "$" 44 | ) 45 | 46 | target_link_options(fp-sdk PUBLIC 47 | "-L" "${CMAKE_CURRENT_SOURCE_DIR}/linker" "-T" "flexpret.ld" 48 | ) 49 | -------------------------------------------------------------------------------- /sdk/lib/include/.gitignore: -------------------------------------------------------------------------------- 1 | internal 2 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/assert.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_ASSERT_H 2 | #define FLEXPRET_ASSERT_H 3 | 4 | #include 5 | #include 6 | 7 | // NDEBUG is used by the standard library to filter out asserts, so it's a good 8 | // idea to use the same variable 9 | #ifdef NDEBUG 10 | // Assert calls shall never have side effects that are necessary for program 11 | // execution: https://barrgroup.com/blog/how-and-when-use-cs-assert-macro 12 | #define fp_assert(cond, fmt, ...) ((void)0) 13 | #else 14 | #define fp_assert(cond, fmt, ...) do { \ 15 | if(((cond) == false)) { \ 16 | _fp_abort(fmt, ## __VA_ARGS__); \ 17 | } \ 18 | } while(0) 19 | #endif // NDEBUG 20 | 21 | #endif 22 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/cond.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_COND_H 2 | #define FLEXPRET_COND_H 3 | 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | typedef struct { 10 | volatile bool waiting[FP_THREADS]; 11 | fp_lock_t *lock; 12 | } fp_cond_t; 13 | #define FP_COND_INITIALIZER(lock_ptr) { .waiting = THREAD_ARRAY_INITIALIZER(false), .lock = lock_ptr} 14 | 15 | /** 16 | * @brief Wait on the condition variable. The lock associated with `cond` must be held 17 | * 18 | * @param cond 19 | * @return fp_ret_t 20 | */ 21 | fp_ret_t fp_cond_wait(fp_cond_t * cond); 22 | 23 | /** 24 | * @brief Wait on the condition variable with a timeout. Timeout is a 64 bit absolute timepoint given in nanosconds 25 | * 26 | * @param cond 27 | * @return fp_ret_t FP_SUCCESS or FP_TIMEOUT 28 | */ 29 | fp_ret_t fp_cond_timed_wait(fp_cond_t * cond, uint64_t timeout); 30 | 31 | /** 32 | * @brief Signal a condition variable and wake up a waiting thread. 33 | * FIXME: There is no fairness implemented. The threads with lower hartid are prioritized 34 | * 35 | * @param cond 36 | * @return fp_ret_t 37 | */ 38 | fp_ret_t fp_cond_signal(fp_cond_t * cond); 39 | 40 | /** 41 | * @brief Signal all waiting threads 42 | * 43 | * @param cond 44 | * @return fp_ret_t 45 | */ 46 | fp_ret_t fp_cond_broadcast(fp_cond_t * cond); 47 | 48 | #endif -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/csrs.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_CSRS_H 2 | #define FLEXPRET_CSRS_H 3 | 4 | #define CSR_EPC 0x502 5 | #define CSR_SLOTS 0x503 6 | #define CSR_IMEM_PROT 0x505 7 | #define CSR_EVEC 0x508 8 | #define CSR_CAUSE 0x509 9 | #define CSR_STATUS 0x50a 10 | #define CSR_COREID 0x510 11 | #define CSR_TMODES 0x504 12 | 13 | #define CSR_HARTID 0x50b 14 | 15 | #define CSR_FROMHOST 0x51f 16 | #define CSR_TOHOST_BASE 0x530 17 | #define CSR_TOHOST(tid) (CSR_TOHOST_BASE + tid) 18 | 19 | #define CSR_HWLOCK 0x520 20 | #define CSR_COMPARE_DU_WU 0x521 21 | #define CSR_COMPARE_IE_EE 0x522 22 | 23 | #define CSR_CYCLE 0xc00 24 | #define CSR_TIME 0xc01 25 | #define CSR_INSTRET 0xc02 26 | 27 | #define CSR_UARCH0 0xcc0 28 | #define CSR_UARCH1 0xcc1 29 | #define CSR_UARCH2 0xcc2 30 | #define CSR_UARCH3 0xcc3 31 | #define CSR_UARCH4 0xcc4 32 | #define CSR_UARCH5 0xcc5 33 | #define CSR_UARCH6 0xcc6 34 | #define CSR_UARCH7 0xcc7 35 | #define CSR_UARCH8 0xcc8 36 | #define CSR_UARCH9 0xcc9 37 | #define CSR_UARCH10 0xcca 38 | #define CSR_UARCH11 0xccb 39 | #define CSR_UARCH12 0xccc 40 | #define CSR_UARCH13 0xccd 41 | #define CSR_UARCH14 0xcce 42 | #define CSR_UARCH15 0xccf 43 | #define CSR_CONFIGHASH 0xcd0 44 | 45 | #define CSR_COUNTH 0x586 46 | #define CSR_CYCLEH 0xc80 47 | #define CSR_TIMEH 0xc81 48 | #define CSR_INSTRETH 0xc82 49 | 50 | #ifdef __ASSEMBLY__ 51 | #define __ASM_STR(x) x 52 | #else 53 | #define __ASM_STR(x) #x 54 | #endif 55 | 56 | #ifndef __ASSEMBLER__ 57 | 58 | #define read_csr(reg) ({ long __tmp; \ 59 | asm volatile ("csrr %0, " __ASM_STR(reg) : "=r"(__tmp)); \ 60 | __tmp; }) 61 | 62 | #define write_csr(reg, val) \ 63 | asm volatile ("csrw " __ASM_STR(reg) ", %0" :: "r"(val)) 64 | 65 | #define swap_csr(reg, val) ({ long __tmp; \ 66 | asm volatile ("csrrw %0, " __ASM_STR(reg) ", %1" : "=r"(__tmp) : "r"(val)); \ 67 | __tmp; }) 68 | 69 | #define set_csr(reg, bit) ({ long __tmp; \ 70 | if (__builtin_constant_p(bit) && (bit) < 32) \ 71 | asm volatile ("csrrs %0, " __ASM_STR(reg) ", %1" : "=r"(__tmp) : "i"(bit)); \ 72 | else \ 73 | asm volatile ("csrrs %0, " __ASM_STR(reg) ", %1" : "=r"(__tmp) : "r"(bit)); \ 74 | __tmp; }) 75 | 76 | #define clear_csr(reg, bit) ({ long __tmp; \ 77 | if (__builtin_constant_p(bit) && (bit) < 32) \ 78 | asm volatile ("csrrc %0, " __ASM_STR(reg) ", %1" : "=r"(__tmp) : "i"(bit)); \ 79 | else \ 80 | asm volatile ("csrrc %0, " __ASM_STR(reg) ", %1" : "=r"(__tmp) : "r"(bit)); \ 81 | __tmp; }) 82 | 83 | #define rdtime() ({ unsigned long __tmp; \ 84 | asm volatile ("rdtime %0" : "=r"(__tmp)); \ 85 | __tmp; }) 86 | 87 | #define rdcycle() ({ unsigned long __tmp; \ 88 | asm volatile ("rdcycle %0" : "=r"(__tmp)); \ 89 | __tmp; }) 90 | 91 | #define rdinstret() ({ unsigned long __tmp; \ 92 | asm volatile ("rdinstret %0" : "=r"(__tmp)); \ 93 | __tmp; }) 94 | 95 | #define rdlinkreg() ({ unsigned long *__tmp; \ 96 | asm volatile ("addi %0, ra, 0" : "=r"(__tmp)); \ 97 | __tmp; }) 98 | 99 | #define rdstackptr() ({ unsigned long *__tmp; \ 100 | asm volatile ("addi %0, sp, 0" : "=r"(__tmp)); \ 101 | __tmp; }) 102 | 103 | #endif 104 | 105 | #define read_coreid() read_csr(CSR_COREID) 106 | 107 | #define read_hartid() (uint32_t) read_csr(CSR_HARTID) 108 | 109 | #endif // FLEXPRET_CSRS_H 110 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/flexpret.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_H 2 | #define FLEXPRET_H 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | 18 | #ifndef NDEBUG 19 | #include 20 | #endif // NDEBUG 21 | 22 | #include 23 | #include 24 | 25 | #endif 26 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/lock.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_LOCK_H 2 | #define FLEXPRET_LOCK_H 3 | 4 | #include 5 | #include 6 | 7 | #define FP_LOCK_INITIALIZER { .locked = false, .owner = UINT32_MAX, .count = 0 } 8 | typedef struct { 9 | bool locked; 10 | uint32_t owner; 11 | uint32_t count; 12 | } fp_lock_t; 13 | 14 | /** 15 | * Acquire a hardware lock. 16 | */ 17 | void fp_hwlock_acquire(void); 18 | 19 | /** 20 | * Release a hardware lock. 21 | */ 22 | void fp_hwlock_release(void); 23 | 24 | /** 25 | * Software lock function declarations 26 | * 27 | * @param lock the software lock instance to acquire/release 28 | */ 29 | void fp_lock_acquire(fp_lock_t* lock); 30 | void fp_lock_release(fp_lock_t* lock); 31 | 32 | #endif // FLEXPRET_LOCK_H 33 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/pbuf.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @file flexpret_pbuf.h 3 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 4 | * @brief A printbuffer API so any thread can write to the Wishbone UART. 5 | * The UART is only accessible by tid = 0, so the printbuffer 6 | * basically implements a method to transmit strings from one 7 | * tid to another. 8 | * 9 | * Use pump to transmit strings and drain to receive the strings. 10 | * 11 | * @copyright Copyright (c) 2024 12 | * 13 | */ 14 | 15 | #ifndef FLEXPRET_PBUF_H 16 | #define FLEXPRET_PBUF_H 17 | 18 | #include 19 | #include 20 | 21 | #define PRINT_BUFFER_SIZE (128) 22 | #define PRINTBUFFER_INITIALIZER \ 23 | (struct PrintBuffer) { \ 24 | .rdpos = 0, \ 25 | .wrpos = 0, \ 26 | .lock = FP_LOCK_INITIALIZER, \ 27 | } 28 | 29 | /** 30 | * A buffer to transfer strings from one hardware thread to another. 31 | * The lock is used to synchronize the reader and writer threads. 32 | * 33 | */ 34 | struct PrintBuffer { 35 | char buffer[PRINT_BUFFER_SIZE]; 36 | volatile uint32_t rdpos; 37 | volatile uint32_t wrpos; 38 | fp_lock_t lock; 39 | }; 40 | 41 | /** 42 | * @brief Write strings from the current thread into the buffer. Typically, threads 43 | * not connected to its own UART device should be doing this. 44 | * 45 | * @param pbuf The print buffer to write to 46 | * @param pump The string to write 47 | * @param pump_len The length of the string to write (suggest using `strlen`) 48 | */ 49 | void printbuffer_pump(struct PrintBuffer *pbuf, char *pump, const uint32_t pump_len); 50 | 51 | /** 52 | * @brief Read strings from the current thread. Typically, threads connected to 53 | * its own UART device should be doing this, and write the string to 54 | * the UART device after. 55 | * 56 | * @param pbuf The print buffer to read from 57 | * @param drain The string will be read into this buffer. The user should allocate 58 | * a buffer large enough to store the expected string. If unsure, 59 | * use `PRINT_BUFFER_SIZE`. 60 | * @return The number of bytes drained. Can be used e.g., to write all the 61 | * bytes read to the UART device. 62 | */ 63 | uint32_t printbuffer_drain(struct PrintBuffer *pbuf, char *drain); 64 | 65 | #endif // FLEXPRET_PBUF_H 66 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/swconfig.h: -------------------------------------------------------------------------------- 1 | #ifndef FP_SWCONFIG_H 2 | #define FP_SWCONFIG_H 3 | 4 | #include 5 | #define FP_STACKSIZE (1 << FP_STACKSIZE_BITS) 6 | 7 | #endif // FP_SWCONFIG_H 8 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/thread.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_THREAD_H 2 | #define FLEXPRET_THREAD_H 3 | 4 | #include 5 | #include 6 | 7 | #ifndef FP_THREADS 8 | #define FP_THREADS 1 9 | #endif 10 | 11 | #ifndef THREAD_STACKSIZE 12 | #define THREAD_STACKSIZE_BITS 11 13 | #define THREAD_STACKSIZE (1 << THREAD_STACKSIZE_BITS) 14 | #endif 15 | 16 | /** 17 | * Constants for FlexPRET scheduling (i.e. slots and tmodes) 18 | * These values must match those in constants.scala. 19 | */ 20 | 21 | // Maximum slots 22 | #define SLOTS_SIZE 8 23 | 24 | // Helper macros for making 25 | // fp_thread_create() and fp_thread_map() 26 | // more readable. 27 | #define HRTT true 28 | #define SRTT false 29 | 30 | // Possible values for a slot 31 | typedef enum slot_t { 32 | SLOT_T0=0, 33 | SLOT_T1, SLOT_T2, SLOT_T3, SLOT_T4, 34 | SLOT_T5, SLOT_T6, SLOT_T7, 35 | SLOT_S=14, 36 | SLOT_D=15 37 | } slot_t; 38 | 39 | // Possible values for a thread mode 40 | typedef enum tmode_t { 41 | TMODE_HA=0, // HRTT Active 42 | TMODE_HZ, // HRTT Sleeping 43 | TMODE_SA, // SRTT Active 44 | TMODE_SZ // SRTT Sleeping 45 | } tmode_t; 46 | 47 | /* FlexPRET's hardware thread scheduling APIs */ 48 | 49 | int slot_set(slot_t slots[], uint32_t length); 50 | int slot_set_hrtt(uint32_t slot, uint32_t hartid); 51 | int slot_set_srtt(uint32_t slot); 52 | int slot_disable(uint32_t slot); 53 | tmode_t tmode_get(uint32_t hartid); 54 | int tmode_set(uint32_t hartid, tmode_t val); 55 | int tmode_active(uint32_t hartid); 56 | int tmode_sleep(uint32_t hartid); 57 | 58 | 59 | /* Pthreads-like threading library APIs */ 60 | 61 | /** 62 | * This struct contains a context; i.e., the values of the registers before a 63 | * context switch occurred. A context switch typically occurs due to an interrupt. 64 | * 65 | * The struct is not in direct use anywhere, but is kept for reference. An 66 | * implementation of a context switch can be found in ../ctx_switch.S 67 | * 68 | */ 69 | typedef struct thread_ctx_t { 70 | uint32_t regs[32]; 71 | } thread_ctx_t; 72 | 73 | typedef uint32_t fp_thread_t; 74 | 75 | int fp_thread_create( 76 | bool is_hrtt, // HRTT = true, SRTT = false 77 | fp_thread_t *restrict hartid, 78 | void *(*start_routine)(void *), 79 | void *restrict arg 80 | ); 81 | int fp_thread_map( 82 | bool is_hrtt, // HRTT = true, SRTT = false 83 | fp_thread_t *restrict hartid, 84 | void *(*start_routine)(void *), 85 | void *restrict arg 86 | ); 87 | int fp_thread_join(fp_thread_t thread, void **retval); 88 | void fp_thread_exit(void *retval); 89 | int fp_thread_cancel(fp_thread_t thread); 90 | void fp_thread_testcancel(); 91 | void worker_main(); 92 | 93 | #endif -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/time.h: -------------------------------------------------------------------------------- 1 | #ifndef FLEXPRET_TIME_H 2 | #define FLEXPRET_TIME_H 3 | 4 | #include 5 | #include 6 | 7 | /** 8 | * @brief Delay execution until an absolute time. Loads the timeout 9 | * into the compare register of the thread. Then execute the 10 | * Delay Until (DU) instruction which is encoded as 0x700B. 11 | * 12 | * The program counter (PC) is not updated until the DU instuction is finished, 13 | * meaning the program will go back to sleep after an interrupt has occured 14 | * (given that the timer has not expired). 15 | * 16 | */ 17 | #define fp_delay_until(ns) do { \ 18 | write_csr(CSR_COMPARE_DU_WU, (ns)); \ 19 | __asm__ volatile(".word 0x700B;"); \ 20 | } while(0) 21 | 22 | /** 23 | * @brief Delay execution for a time duration. First read the current time 24 | * Then do a regular `fp_delay_until` 25 | * 26 | */ 27 | #define fp_delay_for(ns) do { \ 28 | uint32_t now_ns = rdtime(); \ 29 | fp_delay_until(now_ns + (ns)); \ 30 | } while(0) 31 | 32 | /** 33 | * @brief Does the same as the @p fp_delay_until pseudo-instruction, but in this case 34 | * the program counter (PC) is incremented before the instruction completes. 35 | * This means that after an interrupt has completed, the program will continue. 36 | * 37 | * The use case of this pseudo-instruction is waiting for an interrupt to occur 38 | * with a given timeout. 39 | * 40 | */ 41 | #define fp_wait_until(ns) do { \ 42 | write_csr(CSR_COMPARE_DU_WU, (ns)); \ 43 | __asm__ volatile(".word 0x702B;"); \ 44 | } while(0) 45 | 46 | /** 47 | * @brief Same as fp_delay_for, just doing wait instead of delay. 48 | * 49 | */ 50 | #define fp_wait_for(ns) do { \ 51 | uint32_t now_ns = rdtime(); \ 52 | fp_wait_until(now_ns + (ns)); \ 53 | } while(0) 54 | 55 | #define fp_nop do { \ 56 | __asm__ volatile(".word 0x00000013;"); \ 57 | } while(0) 58 | 59 | /** 60 | * @brief Read out a 64 bit timestamp. Since it is done with two read operations 61 | * we must handle a potential wrapping event where we read the lower bits BEFORE the wrap 62 | * and the higher bits AFTER the wrap. 63 | * 64 | * @return uint64_t 65 | */ 66 | static inline uint64_t rdtime64() 67 | { 68 | // Read out lower and higher 32 bits of time 69 | uint32_t hi_pre = read_csr(CSR_TIMEH); 70 | uint32_t lo = rdtime(); 71 | uint32_t hi_post = read_csr(CSR_TIMEH); 72 | 73 | uint32_t diff = hi_post - hi_pre; 74 | 75 | if(diff == 0) { 76 | return (uint64_t) ((uint64_t) hi_pre << 32) | ((uint64_t )lo); 77 | } else { 78 | // Either lo was read before wrap, or after wrap. Simple solution: Read again 79 | // FIXME: Proper fix to this problem is in HW. Provide atomic reading of the TIMEH and TIMEL registers 80 | return rdtime64(); 81 | } 82 | } 83 | 84 | #endif 85 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/types.h: -------------------------------------------------------------------------------- 1 | #ifndef LF_TYPES_H 2 | #define LF_TYPES_H 3 | 4 | #include 5 | 6 | typedef uint32_t timeout_t; 7 | 8 | // Timeout definitions 9 | #define TIMEOUT_FOREVER UINT32_MAX 10 | #define TIMEOUT_NEVER 0 11 | #define NON_BLOCKING 0 12 | 13 | #define UNUSED(x) (void) (x) 14 | 15 | #ifdef __TEST__ 16 | /** 17 | * Weak attribute allows other files to override the implementation, which is 18 | * very useful for testing purposes. This macro can be used to easily mark/unmark 19 | * functions with the attribute. 20 | * 21 | */ 22 | #define FP_TEST_OVERRIDE __attribute__((weak)) 23 | #else 24 | #define FP_TEST_OVERRIDE 25 | #endif // __TEST__ 26 | 27 | // Return types 28 | typedef enum { 29 | FP_SUCCESS = 0, 30 | FP_FAILURE = 1, 31 | FP_TIMEOUT = 2, 32 | FP_OUT_OF_MEMORY = 3 33 | } fp_ret_t; 34 | 35 | /** 36 | * Use this for initializing arrays with varying size depending on the FP_THREADS 37 | * macro. Example use case: 38 | * 39 | * static bool need_init[FP_THREADS] = THREAD_ARRAY_INITIALIZER(true); 40 | * 41 | * static uint8_t nbytes_left[FP_THREADS] = THREAD_ARRAY_INITIALIZER(5); 42 | * 43 | */ 44 | #if FP_THREADS == 1 45 | #define THREAD_ARRAY_INITIALIZER(val) { val } 46 | #elif FP_THREADS == 2 47 | #define THREAD_ARRAY_INITIALIZER(val) { val, val } 48 | #elif FP_THREADS == 3 49 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val } 50 | #elif FP_THREADS == 4 51 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val, val } 52 | #elif FP_THREADS == 5 53 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val, val, val } 54 | #elif FP_THREADS == 6 55 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val, val, val, val } 56 | #elif FP_THREADS == 7 57 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val, val, val, val, val } 58 | #elif FP_THREADS == 8 59 | #define THREAD_ARRAY_INITIALIZER(val) { val, val, val, val, val, val, val, val } 60 | #endif 61 | 62 | #endif -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/uart.h: -------------------------------------------------------------------------------- 1 | 2 | #ifndef FLEXPRET_UART_H 3 | #define FLEXPRET_UART_H 4 | 5 | #include 6 | #include 7 | #include 8 | 9 | 10 | // On the Wishbone bus, the UART has address 0 11 | #define UART_TXD 0x0UL 12 | #define UART_RXD 0x4UL 13 | #define UART_CSR 0x8UL 14 | #define UART_CONST_ADDR 0xCUL 15 | 16 | #define UART_CONST_VALUE (0x55) 17 | 18 | // Macros for parsing the CSR register value 19 | #define UART_DATA_READY(val) (val & 0x01) 20 | #define UART_TX_FULL(val) (val & 0x02) 21 | #define UART_FAULT_BAD_ADDR(val) (val & 0x04) 22 | 23 | /** 24 | * @brief Write data over UART using with the wishbone interface 25 | * 26 | * @param data The data to write 27 | */ 28 | void uart_send(uint8_t data); 29 | 30 | /** 31 | * @brief Check that there is a connection to the wishbone UART interface 32 | * by reading a magic number from it. 33 | * 34 | * @return True if available, false otherwise. 35 | */ 36 | bool uart_available(void); 37 | 38 | /** 39 | * @brief Receive a byte over UART using the wishbone interface 40 | * 41 | * @return The byte read 42 | */ 43 | uint8_t uart_receive(void); 44 | 45 | #endif // FLEXPRET_UART_H 46 | -------------------------------------------------------------------------------- /sdk/lib/include/flexpret/wb.h: -------------------------------------------------------------------------------- 1 | #ifndef WISHBONE_H 2 | #define WISHBONE_H 3 | 4 | #include 5 | 6 | /** 7 | * @brief Write data to a device on the wishbone interface 8 | * 9 | * @param addr The address of the device 10 | * @param data Data to the device 11 | */ 12 | void wb_write(uint32_t addr, uint32_t data); 13 | 14 | /** 15 | * @brief Read data from a device on the wishbone interface 16 | * 17 | * @param addr The address of the device 18 | * @return uint32_t 19 | */ 20 | uint32_t wb_read(uint32_t addr); 21 | 22 | #endif -------------------------------------------------------------------------------- /sdk/lib/linker/.gitignore: -------------------------------------------------------------------------------- 1 | internal 2 | -------------------------------------------------------------------------------- /sdk/lib/linker/bootloader/none/bootloader.ld: -------------------------------------------------------------------------------- 1 | /* 2 | * This file is included when no bootloader is used. 3 | * 4 | */ 5 | 6 | BOOTLOADER_SIZE = 0; 7 | -------------------------------------------------------------------------------- /sdk/lib/linker/bootloader/use/.gitignore: -------------------------------------------------------------------------------- 1 | bootloader.ld 2 | -------------------------------------------------------------------------------- /sdk/lib/src/cond.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | fp_ret_t fp_cond_wait(fp_cond_t * cond) { 5 | int hartid = read_hartid(); 6 | cond->waiting[hartid] = true; 7 | fp_lock_release(cond->lock); 8 | while(cond->waiting[hartid]) {} 9 | fp_lock_acquire(cond->lock); 10 | return FP_SUCCESS; 11 | } 12 | 13 | fp_ret_t fp_cond_timed_wait(fp_cond_t * cond, uint64_t timeout) { 14 | bool has_timed_out = false; 15 | int hartid = read_hartid(); 16 | cond->waiting[hartid] = true; 17 | fp_lock_release(cond->lock); 18 | while(cond->waiting[hartid] && !has_timed_out) { 19 | has_timed_out = (rdtime64() >= timeout); 20 | } 21 | fp_lock_acquire(cond->lock); 22 | 23 | if (has_timed_out) { 24 | return FP_TIMEOUT; 25 | } else { 26 | return FP_SUCCESS; 27 | } 28 | } 29 | 30 | fp_ret_t fp_cond_signal(fp_cond_t * cond) { 31 | fp_lock_acquire(cond->lock); 32 | for (int i = 0; i < FP_THREADS; i++) { 33 | if (cond->waiting[i]) { 34 | cond->waiting[i] = false; 35 | break; 36 | } 37 | } 38 | fp_lock_release(cond->lock); 39 | return FP_SUCCESS; 40 | } 41 | 42 | fp_ret_t fp_cond_broadcast(fp_cond_t * cond) { 43 | fp_lock_acquire(cond->lock); 44 | for (int i = 0; i < FP_THREADS; i++) { 45 | cond->waiting[i] = false; 46 | } 47 | fp_lock_release(cond->lock); 48 | return FP_SUCCESS; 49 | } 50 | -------------------------------------------------------------------------------- /sdk/lib/src/lock.c: -------------------------------------------------------------------------------- 1 | #include 2 | 3 | /** 4 | * Acquire a hardware lock. 5 | */ 6 | void fp_hwlock_acquire(void) { 7 | while(swap_csr(CSR_HWLOCK, 1) == 0); 8 | } 9 | 10 | /** 11 | * Release a hardware lock. 12 | */ 13 | void fp_hwlock_release(void) { 14 | if (swap_csr(CSR_HWLOCK, 0) != 1) { 15 | fp_assert(false, "Attempt to unlock hwlock but it was not locked"); 16 | } 17 | } 18 | 19 | static int do_acquire(fp_lock_t* lock) { 20 | // TODO: Poll on lock->locked instead of hwlock 21 | fp_hwlock_acquire(); 22 | if (lock->owner == read_hartid()) { 23 | lock->count++; 24 | fp_hwlock_release(); 25 | return 0; 26 | } else if (lock->locked) { 27 | //printf("warn: attempt acquire locked lock\n"); 28 | fp_hwlock_release(); 29 | return 1; 30 | } else { 31 | lock->locked = true; 32 | lock->owner = read_hartid(); 33 | lock->count = 1; 34 | fp_hwlock_release(); 35 | return 0; 36 | } 37 | } 38 | 39 | void fp_lock_acquire(fp_lock_t* lock) { 40 | // Spin lock 41 | while(do_acquire(lock)); 42 | } 43 | 44 | void fp_lock_release(fp_lock_t* lock) { 45 | fp_hwlock_acquire(); 46 | fp_assert(read_hartid() == lock->owner, 47 | "Attempt to release lock not owned by thread. thread id: %i, lock->owner: %i\n", 48 | (int) read_hartid(), (int) lock->owner); 49 | fp_assert(lock->count > 0, 50 | "Attempt to relase lock with count <= 0: count: %i\n", (int) lock->count); 51 | if (--lock->count == 0) { 52 | lock->locked = false; 53 | lock->owner = UINT32_MAX; 54 | } 55 | fp_hwlock_release(); 56 | } 57 | -------------------------------------------------------------------------------- /sdk/lib/src/pbuf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | static inline uint32_t printbuffer_get_wrsize(struct PrintBuffer *pbuf) { 5 | return pbuf->wrpos >= pbuf->rdpos ? (PRINT_BUFFER_SIZE - pbuf->wrpos + pbuf->rdpos) 6 | : (pbuf->rdpos - pbuf->wrpos); 7 | } 8 | 9 | static inline uint32_t printbuffer_get_rdsize(struct PrintBuffer *pbuf) { 10 | return PRINT_BUFFER_SIZE - printbuffer_get_wrsize(pbuf); 11 | } 12 | 13 | void printbuffer_pump(struct PrintBuffer *pbuf, char *pump, const uint32_t pump_len) { 14 | fp_lock_acquire(&pbuf->lock); 15 | 16 | const uint32_t writable = printbuffer_get_wrsize(pbuf); 17 | if (writable >= pump_len) { 18 | 19 | const uint32_t len_until_wrap = PRINT_BUFFER_SIZE - pbuf->wrpos; 20 | if (pump_len > len_until_wrap) { 21 | // Wrap around 22 | memcpy(&pbuf->buffer[pbuf->wrpos], pump, len_until_wrap); 23 | pbuf->wrpos = 0; 24 | memcpy(&pbuf->buffer[pbuf->wrpos], &pump[len_until_wrap], pump_len - len_until_wrap); 25 | pbuf->wrpos = pump_len - len_until_wrap; 26 | } else { 27 | memcpy(&pbuf->buffer[pbuf->wrpos], pump, pump_len); 28 | pbuf->wrpos += pump_len; 29 | } 30 | } 31 | 32 | fp_lock_release(&pbuf->lock); 33 | } 34 | 35 | uint32_t printbuffer_drain(struct PrintBuffer *pbuf, char *drain) { 36 | fp_lock_acquire(&pbuf->lock); 37 | 38 | uint32_t rdsize = printbuffer_get_rdsize(pbuf); 39 | if (rdsize > 0) { 40 | const uint32_t len_until_wrap = PRINT_BUFFER_SIZE - pbuf->rdpos; 41 | 42 | // Find the first string, but search at max until the edge of the buffer 43 | // (+ 1 for '\0') 44 | const uint32_t slen = strnlen(&pbuf->buffer[pbuf->rdpos], len_until_wrap) + 1; 45 | rdsize = slen < rdsize ? slen : rdsize; 46 | 47 | // Check whether we need to wrap around the buffer 48 | if (rdsize > len_until_wrap) { 49 | // Wrap around 50 | // 1. Copy end of buffer 51 | memcpy(drain, &pbuf->buffer[pbuf->rdpos], len_until_wrap); 52 | pbuf->rdpos = 0; 53 | 54 | // 2. Now begin copying from start of buffer 55 | const uint32_t left_to_read = strlen(&pbuf->buffer[pbuf->rdpos]) + 1; 56 | memcpy(&drain[len_until_wrap], &pbuf->buffer[pbuf->rdpos], left_to_read); 57 | pbuf->rdpos = left_to_read; 58 | rdsize = len_until_wrap + left_to_read; 59 | } else { 60 | memcpy(drain, &pbuf->buffer[pbuf->rdpos], rdsize); 61 | pbuf->rdpos += rdsize; 62 | } 63 | } 64 | 65 | fp_lock_release(&pbuf->lock); 66 | return rdsize; 67 | } 68 | -------------------------------------------------------------------------------- /sdk/lib/src/printf_putchar.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file printf_putchar.c 3 | * @author Magnus Mæhlum (magnusmaehlum@outlook.com) 4 | * 5 | * The ../printf submodule implements printf(), but the function calls putchar_() 6 | * which needs to be defined by the user - as per the submodule's documentation. 7 | * 8 | * This code implements the putchar_() function which either calls 9 | * _write_emulation() or _write_fpga() based on the __EMULATOR__ and 10 | * __FPGA__ defines. 11 | * 12 | */ 13 | 14 | #include 15 | #include 16 | #include 17 | #include 18 | 19 | #include 20 | #include 21 | 22 | void _write_emulation(int fd, char character) { 23 | static bool first_character[FP_THREADS] = THREAD_ARRAY_INITIALIZER(true); 24 | 25 | // Use these variables to buffer up four characters at a time and send them 26 | // together. Keep one for each thread to make it thread-safe. 27 | static uint32_t word[FP_THREADS] = THREAD_ARRAY_INITIALIZER(0); 28 | static int word_idx[FP_THREADS] = THREAD_ARRAY_INITIALIZER(0); 29 | 30 | int tid = read_hartid(); 31 | 32 | if (first_character[tid]) { 33 | // Write the additional information first, which is part of the defined 34 | // protocol between the CPU and emulator 35 | write_tohost_tid(tid, CSR_TOHOST_PRINTF); 36 | write_tohost_tid(tid, fd); 37 | first_character[tid] = false; 38 | } 39 | 40 | // Buffer up the character in the word 41 | word[tid] |= (character << (8 * word_idx[tid])); 42 | 43 | if (word_idx[tid] == 3) { 44 | // Write the word 45 | write_tohost_tid(tid, CSR_TOHOST_PRINTF); 46 | write_tohost_tid(tid, word[tid]); 47 | word_idx[tid] = 0; 48 | word[tid] = 0; 49 | } else { 50 | word_idx[tid]++; 51 | } 52 | 53 | // Last character 54 | if (character == '\0') { 55 | // If the word index is zero, then the word has already been written 56 | // and we do not need to do anything 57 | if (word_idx[tid] != 0) { 58 | // Otherwise we need to send the word 59 | write_tohost_tid(tid, CSR_TOHOST_PRINTF); 60 | write_tohost_tid(tid, word[tid]); 61 | } 62 | 63 | // Reset for next printf 64 | first_character[tid] = true; 65 | word_idx[tid] = 0; 66 | word[tid] = 0; 67 | } 68 | } 69 | 70 | void _write_fpga(int fd, char character) { 71 | UNUSED(fd); 72 | uart_send(character); 73 | } 74 | 75 | void putchar_(char character) { 76 | #ifdef __EMULATOR__ 77 | _write_emulation(1, character); // TODO: Use real file descriptors? 78 | #endif // __EMULATOR__ 79 | #ifdef __FPGA__ 80 | _write_fpga(1, character); 81 | #endif // __FPGA__ 82 | } 83 | -------------------------------------------------------------------------------- /sdk/lib/src/start.S: -------------------------------------------------------------------------------- 1 | /** 2 | * FlexPRET's startup code in RISC-V assembly 3 | * Authors: 4 | * - Edward Wang 5 | * - Shaokai Lin 6 | * - Samuel Berkun 7 | */ 8 | 9 | 10 | #include 11 | #include 12 | 13 | .section .zero_addr_function 14 | .globl _start 15 | .type _start,@function 16 | 17 | .macro setHartid 18 | /* Store thread id at t0 */ 19 | csrr t0, 0x50b // 0x50b = hartid CSR 20 | .endm 21 | 22 | .macro setStackPointer 23 | la sp, __stack_start$ 24 | 25 | // Calculate how much to subtract 26 | // from the max address based on 27 | // thread id and FP_STACKSIZE_BITS from flexpret_swconfig.h. 28 | sll t1, t0, FP_STACKSIZE_BITS // (thread_id) * 2^FP_STACKSIZE_BITS 29 | 30 | // Set the stack pointer. 31 | sub sp, sp, t1 32 | .endm 33 | 34 | .macro setGlobalPointer 35 | /** 36 | * The option norelax ensures that the assembler does not optimize the la 37 | * pseudo instruction from using an aboslute address into using an address 38 | * relative to the global pointer (gp). 39 | * 40 | * This is important because gp is not yet set. 41 | */ 42 | .option push 43 | .option norelax 44 | la gp, __global_pointer$ 45 | .option pop 46 | .endm 47 | 48 | .macro setDMemProtection 49 | // set all 8 memory regions to shared 50 | // (rather than exclusive to thread 0) 51 | li t1, 0x88888888 52 | csrw 0x50c, t1 // 0x50c = dMemProtection CSR 53 | .endm 54 | 55 | .macro setGpoProtection 56 | li t1, 0x88888888 57 | csrw 0x50d, t1 // 0x50c = GpoProtection CSR 58 | .endm 59 | 60 | _start: 61 | setHartid 62 | setStackPointer 63 | setGlobalPointer 64 | setDMemProtection 65 | setGpoProtection 66 | call Reset_Handler 67 | -------------------------------------------------------------------------------- /sdk/lib/src/uart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | void uart_send(uint8_t data) { 6 | while (UART_TX_FULL(wb_read(UART_CSR))); 7 | wb_write(UART_TXD, data); 8 | } 9 | 10 | bool uart_available(void) { 11 | /** 12 | * The UART device has a register that contains a magic number 13 | * `UART_CONST_VALUE`. The sole purpose of this is to check whether 14 | * we can use the UART device. 15 | * 16 | */ 17 | return wb_read(UART_CONST_ADDR) == UART_CONST_VALUE; 18 | } 19 | 20 | uint8_t uart_receive(void) { 21 | while(!UART_DATA_READY(wb_read(UART_CSR))); 22 | return wb_read(UART_RXD); 23 | } 24 | -------------------------------------------------------------------------------- /sdk/lib/src/wb.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define WB_BASE 0x40000000UL 5 | 6 | struct WishboneBus { 7 | volatile uint32_t read_addr; 8 | volatile uint32_t write_addr; 9 | volatile uint32_t write_data; 10 | volatile uint32_t read_data; 11 | volatile uint32_t status; 12 | }; 13 | 14 | #define WISHBONE_BUS ((struct WishboneBus *) (WB_BASE)) 15 | 16 | /** 17 | * About the NOP instructions: 18 | * 19 | * The Wishbone bus needs two clock cycles between subsequenct read/write operations. 20 | * When the program is optimized with -Os, it will not adhere to this constraint. 21 | * Therefore we need to insert NOP instructions. 22 | * 23 | * Note that the NOP instructions are not necessary in many cases. E.g., if 24 | * the -O0 flag is passed, or if multiple hardware threads are running. Here we 25 | * assume the worst case to ensure it always works. 26 | * 27 | * The while loops do not need any NOP instructions, because they compile to 28 | * a load followed by a branch instruction. Loads always an extra cycle. 29 | * 30 | */ 31 | 32 | // Write and block until it was successful 33 | // FIXME: Instead we could block until ready, then write? 34 | void wb_write(uint32_t addr, uint32_t data) { 35 | WISHBONE_BUS->write_data = data; 36 | fp_nop; 37 | fp_nop; 38 | WISHBONE_BUS->write_addr = addr; 39 | fp_nop; 40 | fp_nop; 41 | while(!WISHBONE_BUS->status); // TODO: Move while up and remove ! 42 | } 43 | 44 | uint32_t wb_read(uint32_t addr) { 45 | WISHBONE_BUS->read_addr = addr; 46 | fp_nop; 47 | fp_nop; 48 | while(!WISHBONE_BUS->status); 49 | return WISHBONE_BUS->read_data; 50 | } 51 | -------------------------------------------------------------------------------- /sdk/tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | if (NOT ${DEBUG}) 2 | message(FATAL_ERROR 3 | "Cannot run tests when printf is disabled because it is used to determine\ 4 | whether the test passes or fails." 5 | ) 6 | endif() 7 | 8 | # Printed when application finishes with exit code 0 9 | set(REGEX_PASS_DEFAULT "Finish") 10 | 11 | # Printed when application finishes with non-zero exit code 12 | set(REGEX_FAIL_DEFAULT "Abort") 13 | 14 | # Run the program and search for "Finish" 15 | function(fp_add_test_runs_to_completion name) 16 | add_test( 17 | NAME "${name}_runs_to_completion" 18 | COMMAND 19 | "${CMAKE_SOURCE_DIR}/bin/${name}" 20 | ) 21 | set_property( 22 | TEST "${name}_runs_to_completion" 23 | PROPERTY PASS_REGULAR_EXPRESSION ${REGEX_PASS_DEFAULT} 24 | ) 25 | set_property( 26 | TEST "${name}_runs_to_completion" 27 | PROPERTY FAIL_REGULAR_EXPRESSION ${REGEX_FAIL_DEFAULT} 28 | ) 29 | set_property( 30 | TEST "${name}_runs_to_completion" 31 | PROPERTY TIMEOUT 60 32 | ) 33 | endfunction() 34 | 35 | # Run the program in the background (&) with an arbitrary command, which typically 36 | # is a client that connects to the emulator and sets FlexPRET's external pins 37 | # (e.g., to generate interrupts or UART signals) 38 | function(fp_add_test_runs_to_completion_client name client_cmd) 39 | add_test( 40 | NAME "${name}_runs_to_completion_client" 41 | COMMAND "bash" "-c" 42 | "$ENV{FP_SDK_PATH}/bin/${name} --client & sleep 1 && ${client_cmd}" 43 | ) 44 | set_property( 45 | TEST "${name}_runs_to_completion_client" 46 | PROPERTY PASS_REGULAR_EXPRESSION ${REGEX_PASS_DEFAULT} 47 | ) 48 | set_property( 49 | TEST "${name}_runs_to_completion_client" 50 | PROPERTY FAIL_REGULAR_EXPRESSION ${REGEX_FAIL_DEFAULT} 51 | ) 52 | set_property( 53 | TEST "${name}_runs_to_completion_client" 54 | PROPERTY TIMEOUT 60 55 | ) 56 | endfunction() 57 | 58 | set(FP_EMULATOR_CLIENTS_GENERIC $ENV{FP_PATH}/build/emulator/clients) 59 | 60 | add_subdirectory(c-tests) 61 | 62 | # Only add multithreaded tests if the number of hardware threads is greater than 1 63 | if (${THREADS} GREATER 1) 64 | add_subdirectory(mt-c-tests) 65 | endif() 66 | -------------------------------------------------------------------------------- /sdk/tests/README.md: -------------------------------------------------------------------------------- 1 | # Tests 2 | 3 | Tests generally target specific features we need to test, such as system calls or heap allocation. Single-threaded tests are in `c-tests`, multi-threaded tests are in `mt-c-tests`. 4 | 5 | When new tests are developed, make sure to add them to the list of tests in the `CMakeLists.txt`, and call the function `fp_add_test_runs_to_completion` or `fp_add_test_runs_to_completion_client` to register it with CTest. 6 | 7 | ## Adding stimuli to pins 8 | 9 | The emulator lets you add stimuli to pins (e.g., an interrupt, UART communication) by connecting clients. See [emulator client README.md](../../emulator/clients/README.md) for more information on this feature. This is done by running the `fp-emu` with the `--client` option and running the chosen client afterwards. 10 | 11 | General purpose clients are available in the `/emulator/clients` folder, but a test might need stimuli that is very specific to that test only. 12 | 13 | When a client needs to be part of a test, use the `fp_add_test_runs_to_completion_client` and specify the command to run as the second argument to this function. See e.g., [CMakeLists.txt for interrupt.c](./c-tests/interrupt/CMakeLists.txt) or [CMakeLists.txt for interrupt-delay.c](./c-tests/interrupt-delay/CMakeLists.txt) for examples. 14 | 15 | You might need to develop your own client for your specific needs. 16 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(add) 2 | add_subdirectory(calloc) 3 | add_subdirectory(fib) 4 | add_subdirectory(global) 5 | add_subdirectory(gpio) 6 | add_subdirectory(hwlock) 7 | add_subdirectory(interrupt) 8 | add_subdirectory(interrupt-delay) 9 | add_subdirectory(lbu) 10 | add_subdirectory(malloc) 11 | add_subdirectory(realloc) 12 | add_subdirectory(sections) 13 | add_subdirectory(syscall) 14 | add_subdirectory(time) 15 | add_subdirectory(wb_uart) 16 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/add/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME add) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include($ENV{FP_SDK_PATH}/cmake/fp-app.cmake) 5 | target_link_libraries(${TESTNAME} fp-sdk) 6 | 7 | fp_add_outputs(${TESTNAME}) 8 | fp_add_test_runs_to_completion(${TESTNAME}) 9 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/add/add.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int add(int a, int b) { 5 | return a + b; 6 | } 7 | 8 | int main() { 9 | 10 | int x = 1; 11 | printf("x is %i\n", x); 12 | int y = 2; 13 | printf("y is %i\n", y); 14 | 15 | int z = add(x, y); 16 | printf("z is %i\n", z); 17 | fp_assert(z == 3, "1 + 2 =/= 3"); 18 | return 0; 19 | } 20 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/calloc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME calloc) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/calloc/calloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | 5 | int main() { 6 | // Allocate an array 7 | uint32_t length = 10; 8 | uint32_t *arr = calloc(length, sizeof(uint32_t)); 9 | 10 | fp_assert(arr, "Array allocation unsucessful"); 11 | 12 | // Check that array was initialized to zero 13 | for (uint32_t i = 0; i < length; i++) { 14 | fp_assert(arr[i] == 0, "Array not initialized to zeros"); 15 | } 16 | 17 | for (uint32_t i = 0; i < length; i++) { 18 | arr[i] = i; 19 | } 20 | 21 | for (uint32_t i = 0; i < length; i++) { 22 | fp_assert(arr[i] == i, "Array element not set"); 23 | printf("arr[%i] = %i\n", (int) i, (int) arr[i]); 24 | } 25 | 26 | // Free the memory. 27 | free(arr); 28 | 29 | return 0; 30 | } 31 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/fib/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME fib) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/fib/fib.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | // A humble Fibonacci function. 5 | uint32_t fib(uint32_t n) { 6 | if (n == 0) return 0; 7 | n--; 8 | uint32_t a = 0; 9 | uint32_t b = 1; 10 | while(n > 0) { 11 | uint32_t new_b = a + b; 12 | a = b; 13 | b = new_b; 14 | n--; 15 | } 16 | return b; 17 | } 18 | 19 | int main() { 20 | uint32_t x = fib(16); 21 | printf("fib(16) is %i\n", (int) x); 22 | 23 | // Correct value 24 | fp_assert(x == 987, "Incorrect value for fib(16)\n"); 25 | 26 | x = fib(20); 27 | printf("fib(20) is %i\n", (int) x); 28 | 29 | // Correct value 30 | fp_assert(x == 6765, "Incorrect value for fib(20)\n"); 31 | 32 | return 0; 33 | } 34 | 35 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/global/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME global) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/global/global.c: -------------------------------------------------------------------------------- 1 | // This test checks if global variables are properly initialized. 2 | #include 3 | #include 4 | 5 | int x = 1; 6 | int y = 2; 7 | 8 | int main() { 9 | printf("global variable x is %i\n", x); 10 | printf("global variable y is %i\n", y); 11 | 12 | fp_assert(x == 1, "x not properly set"); 13 | fp_assert(y == 2, "y not properly set"); 14 | 15 | return 0; 16 | } 17 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/gpio/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME gpio) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/hwlock/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME hwlock) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/hwlock/hwlock.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | 6 | // Print the hardware thread id. 7 | printf("HW thread id: %i\n", (int) read_hartid()); 8 | 9 | // Acquire the lock. 10 | fp_hwlock_acquire(); 11 | 12 | // Release the lock. 13 | fp_hwlock_release(); 14 | 15 | printf("HW lock sucessfully acquired and released\n"); 16 | return 0; 17 | } 18 | 19 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/interrupt-delay/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME interrupt-delay) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | include($ENV{FP_SDK_PATH}/flexpret/hwconfig.cmake) 6 | 7 | # Configuration points 8 | set(DELAY_BASE 250) 9 | set(NINTERRUPTS 5) 10 | 11 | math(EXPR DELAY "${DELAY_BASE} * ${THREADS}") 12 | 13 | target_link_libraries(${TESTNAME} fp-sdk) 14 | target_compile_definitions(${TESTNAME} PRIVATE NINTERRUPTS=${NINTERRUPTS}) 15 | 16 | fp_add_outputs(${TESTNAME} fp-sdk) 17 | 18 | # The test sends NINTERRUPTS interrupts to the program with a certain delay 19 | fp_add_test_runs_to_completion_client( 20 | ${TESTNAME} 21 | "${FP_EMULATOR_CLIENTS_GENERIC}/interrupter -a -n ${NINTERRUPTS} -d ${DELAY}" 22 | ) 23 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/interrupt-delay/interrupt-delay.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file interrupt-delay.c 3 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 4 | * @brief This test case can be used to benchmark the delay for interrupt 5 | * handling. Interrupt can be emulated using the interrupter client 6 | * in the emulator folder. Run this program with the --client argument 7 | * to connect the client. 8 | * 9 | * A successful run should give an output looking something like this: 10 | * 11 | * Pin client enabled 12 | * [0]: Iteration 0: Interrupt: 7823960, {0}: 7828800, 13 | * [0]: Iteration 1: Interrupt: 8479360, {0}: 8484160, 14 | * [0]: Iteration 2: Interrupt: 9145200, {0}: 9150080, 15 | * [0]: Iteration 3: Interrupt: 9812640, {0}: 9817440, 16 | * [0]: Iteration 4: Interrupt: 10475400, {0}: 10480240, 17 | * [0]: Iteration 5: Interrupt: 11141320, {0}: 11146160, 18 | * [0]: Iteration 6: Interrupt: 11806160, {0}: 11810880, 19 | * [0]: Iteration 7: Interrupt: 12465320, {0}: 12470080, 20 | * [0]: Iteration 8: Interrupt: 13129000, {0}: 13133840, 21 | * [0]: Iteration 9: Interrupt: 13795640, {0}: 13800480, 22 | * [0]: ../../../..//programs/lib/syscalls/syscalls.c: 49: Finish 23 | * 24 | * 25 | */ 26 | 27 | #include 28 | #include 29 | #include 30 | 31 | #include 32 | 33 | // Defined in makefile 34 | #ifndef NINTERRUPTS 35 | #define NINTERRUPTS (10) 36 | #endif // ifndef NINTERRUPTS 37 | 38 | #define TIMESTAMP_SIZE (2 * NINTERRUPTS) 39 | 40 | 41 | static uint64_t *timestamps; 42 | static volatile bool got_int = false; 43 | 44 | void isr_timestamp(void) 45 | { 46 | static int ntimes = 0; 47 | timestamps[2 * ntimes++ + 0] = rdtime64(); 48 | got_int = true; 49 | } 50 | 51 | int main(void) 52 | { 53 | int ntimes = 0; 54 | register_isr(EXC_CAUSE_EXTERNAL_INT, isr_timestamp); 55 | 56 | // Using malloc because it can hold bigger arrays for when the number of 57 | // interrupts becomes very large 58 | timestamps = malloc(TIMESTAMP_SIZE * sizeof(uint64_t)); 59 | fp_interrupt_enable(); 60 | 61 | while (ntimes < NINTERRUPTS) { 62 | if (got_int) { 63 | timestamps[2 * ntimes++ + 1] = rdtime64(); 64 | got_int = false; 65 | } 66 | } 67 | 68 | fp_interrupt_disable(); 69 | 70 | for (int i = 0; i < NINTERRUPTS; i++) { 71 | printf("Iteration %i: Interrupt: %lli, {0}: %lli,\n", 72 | i, timestamps[2 * i + 0], timestamps[2 * i + 1]); 73 | } 74 | free(timestamps); 75 | } 76 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/interrupt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME interrupt) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c main.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion_client( 10 | ${TESTNAME} 11 | ${FP_EMULATOR_CLIENTS_GENERIC}/stimuli 12 | ) 13 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/interrupt/interrupt.h: -------------------------------------------------------------------------------- 1 | /** 2 | * @brief These functions will be used by the multi-threaded version of the 3 | * interrupt tests as well. 4 | * 5 | */ 6 | 7 | void reset_flags(void); 8 | void *test_long_interrupt(void *args); 9 | void *test_two_interrupts(void *args); 10 | void *test_disabled_interrupts(void *args); 11 | void *test_low_timeout(void *args); 12 | void *test_interrupt_expire_with_expire(void *args); 13 | void *test_exception_expire_with_expire(void *args); 14 | void *test_fp_delay_until(void *args); 15 | void *test_fp_wait_until(void *args); 16 | void *test_external_interrupt(void *args); 17 | void *test_external_interrupt_disabled(void *args); 18 | void *test_du_not_stopped_by_int(void *args); 19 | void *test_wu_stopped_by_int(void *args); 20 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/interrupt/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include "interrupt.h" 3 | 4 | #define do_run(test) do { \ 5 | test(NULL); \ 6 | printf(#test " ran sucessfully\n"); \ 7 | reset_flags(); \ 8 | } while(0) 9 | 10 | int main(void) { 11 | do_run(test_long_interrupt); 12 | do_run(test_two_interrupts); 13 | do_run(test_two_interrupts); 14 | do_run(test_disabled_interrupts); 15 | do_run(test_low_timeout); 16 | do_run(test_fp_delay_until); 17 | do_run(test_fp_wait_until); 18 | do_run(test_external_interrupt); 19 | do_run(test_external_interrupt_disabled); 20 | 21 | if (!test_du_not_stopped_by_int(NULL)) { 22 | printf("delay for not stopped early (as expected)\n"); 23 | } 24 | 25 | reset_flags(); 26 | 27 | if (!test_wu_stopped_by_int(NULL)) { 28 | printf("wait for stopped early (as expected)\n"); 29 | } 30 | 31 | reset_flags(); 32 | 33 | return 0; 34 | } 35 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/lbu/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME lbu) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/lbu/lbu.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #define X_INIT 0x10E0 5 | #define Y_INIT 2 6 | 7 | int main() { 8 | 9 | int x = X_INIT; // x = 4320 10 | int y = Y_INIT; 11 | printf("x is %i\n", x); 12 | printf("y is %i\n", y); 13 | 14 | // Check if basic inline assembly works. 15 | asm volatile ( 16 | "add %0, %0, %1" 17 | : "+r"(y) 18 | : "r"(x) 19 | ); 20 | 21 | printf("Ran inline assembly that adds places x + y in y\n"); 22 | printf("y is %i\n", y); 23 | fp_assert(y == (X_INIT + Y_INIT), "Inline assembly add got incorrect value"); 24 | 25 | void *p = (void*)0x20004000; 26 | 27 | // Store word 28 | int z; 29 | asm volatile ( 30 | "sw %1, 0(%2)\n\t" 31 | "lw %0, 0(%2)" 32 | : "=r"(z) 33 | : "r"(x), "r"(p) 34 | ); 35 | 36 | printf("Ran inline assembly that stores x and loads it into z\n"); 37 | printf("z is %i\n", z); 38 | fp_assert(z == x, "Inline assembly store and load got incorrect value"); 39 | 40 | // Check lbu implementation. 41 | asm volatile ( 42 | "check_lbu: \n\t" 43 | "lbu %0, 0(%1)" 44 | : "=r"(z) 45 | : "r"(p) 46 | ); 47 | 48 | printf("Ran inline assembly that loads the first byte of x into z\n"); 49 | printf("x = 0x%x\n", x); 50 | printf("z = 0x%x\n", z); 51 | 52 | fp_assert(z == (X_INIT & 0xFF), "Inline assembly load one byte got incorrect value"); 53 | 54 | return 0; 55 | } 56 | 57 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/malloc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME malloc) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/malloc/malloc.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | #include 8 | 9 | #define A_INIT 100 10 | #define B_INIT 200 11 | #define C_INIT 300 12 | 13 | #define NELEMENTS_ARRAY (10) 14 | 15 | extern char __sheap; 16 | extern char __eheap; 17 | 18 | int main() { 19 | 20 | uint32_t* a = malloc(sizeof(uint32_t)); 21 | uint32_t* b = malloc(sizeof(uint32_t)); 22 | uint32_t* c = malloc(sizeof(uint32_t)); 23 | uint32_t* d = malloc(sizeof(uint32_t)); 24 | 25 | fp_assert(a && b && c && d, "Variable malloc failed"); 26 | 27 | *a = A_INIT; 28 | *b = B_INIT; 29 | *c = C_INIT; 30 | *d = *a + *b + *c; 31 | 32 | fp_assert(*a == A_INIT, "Incorrect value for a"); 33 | fp_assert(*b == B_INIT, "Incorrect value for b"); 34 | fp_assert(*c == C_INIT, "Incorrect value for c"); 35 | fp_assert(*d == (A_INIT + B_INIT + C_INIT), "Incorrect value for d"); 36 | 37 | printf("a has address %p with value %i\n", a, (int) *a); 38 | printf("b has address %p with value %i\n", b, (int) *b); 39 | printf("c has address %p with value %i\n", c, (int) *c); 40 | printf("d has address %p with value %i\n", d, (int) *d); 41 | 42 | free(a); 43 | free(b); 44 | free(c); 45 | free(d); 46 | 47 | uint32_t *array = malloc(sizeof(uint32_t) * NELEMENTS_ARRAY); 48 | fp_assert(array, "Could not allocate %i bytes", sizeof(uint32_t) * NELEMENTS_ARRAY); 49 | free(array); 50 | 51 | // signal uses internal malloc functionality; check that it does not mess 52 | // up anything 53 | signal(SIGINT, exit); 54 | uint32_t *new = malloc(sizeof(uint32_t) * NELEMENTS_ARRAY); 55 | fp_assert(new, "Could not allocate %i bytes", sizeof(uint32_t) * NELEMENTS_ARRAY); 56 | free(new); 57 | 58 | // Check that an extremely large malloc does not work 59 | char *massive = malloc(0x30000000); 60 | fp_assert(massive == NULL, "Massive malloc worked\n"); 61 | fp_assert(errno == ENOMEM, "Errno not as expected, was: %s\n", strerror(errno)); 62 | 63 | printf("Could not allocate massive array: %s (as expected)\n", strerror(errno)); 64 | 65 | /** 66 | * Malloc the entire heap and check that it is no longer possible to malloc 67 | * anything. This is the last test, since we have no way of getting the heap 68 | * back :) 69 | * 70 | * To malloc the entire heap, it needs to be done in diminishing blocks, since 71 | * the heap is likely fragmented from earlier use. It will not work to just 72 | * malloc the entire heap in one go - that will leave some fragments. 73 | * 74 | */ 75 | uint32_t block_size = 0x10000; 76 | while (block_size > 1) { 77 | if (malloc(block_size) == NULL) { 78 | block_size /= 2; 79 | } 80 | } 81 | 82 | fp_assert(malloc(1) == NULL, "Could malloc when not expected\n"); 83 | fp_assert(errno == ENOMEM, "Errno not set as expected\n"); 84 | 85 | printf("Sucessfully malloc'ed entire heap\n"); 86 | 87 | return 0; 88 | } 89 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/realloc/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME realloc) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/realloc/realloc.c: -------------------------------------------------------------------------------- 1 | // Example from https://www.geeksforgeeks.org/g-fact-66/ 2 | #include 3 | #include 4 | #include 5 | #include 6 | 7 | int main() { 8 | int *ptr = (int *)malloc(sizeof(int)*2); 9 | int i; 10 | int *ptr_new; 11 | 12 | *ptr = 1; 13 | *(ptr + 1) = 2; 14 | 15 | ptr_new = (int *)realloc(ptr, sizeof(int)*3); 16 | *(ptr_new + 2) = 3; 17 | for(i = 0; i < 3; i++) { 18 | printf("realloced[%i] is %i\n", i, *(ptr_new + i)); 19 | fp_assert(*(ptr_new + i) == (i + 1), "Incorrect value"); 20 | } 21 | 22 | return 0; 23 | } 24 | 25 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/sections/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME sections) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/syscall/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME syscall) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/syscall/syscall.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file syscall.c 3 | * @author Magnus Mæhlum (magnusmaehlum@outlook.com) 4 | * 5 | * This test checks both that the system calls in ../../lib/syscalls/syscalls.c 6 | * work as expected. It also checks that the errno variable works as expected. 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | int main() { 18 | int ret; 19 | 20 | ret = close(28); 21 | fp_assert(ret == -1, "Return value not as expected"); 22 | fp_assert(errno == ENOSYS, "Errno not as expected"); 23 | 24 | ret = getpid(); 25 | fp_assert(ret == -1, "Return value not as expected"); 26 | fp_assert(errno == ENOSYS, "Errno not as expected"); 27 | 28 | struct timeval tv; 29 | ret = gettimeofday(&tv, NULL); 30 | fp_assert(ret == 0, "Return value not as expected"); 31 | fp_assert(errno == 0, "Errno not as expected"); 32 | 33 | printf("tv.tv_sec is %lli\n", tv.tv_sec); 34 | printf("tv.tv_usec is %li\n", tv.tv_usec); 35 | 36 | exit(0); 37 | 38 | // Should never reach this 39 | fp_assert(0, "Unreachable code reached"); 40 | } 41 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/time/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME time) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/time/time.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | int main() { 5 | uint32_t t1 = rdtime(); 6 | printf("First rdtime() call: %i\n", (int) t1); 7 | 8 | uint32_t t2 = rdtime(); 9 | printf("Second rdtime() call: %i\n", (int) t2); 10 | 11 | fp_assert(t1 < t2, "Second call to rdtime() had smaller time value"); 12 | 13 | uint64_t t3 = rdtime64(); 14 | printf("First rdtime64() call:\n\t[63-32]: %i\n\t[31- 0]: %i\n", (int) t3, (int) (t3 >> 32)); 15 | 16 | uint64_t t4 = rdtime64(); 17 | printf("Second rdtime64() call:\n\t[63-32]: %i\n\t[31- 0]: %i\n", (int) t4, (int) (t4 >> 32)); 18 | 19 | fp_assert(t3 < t4, "Second call to rdtime64() had smaller value"); 20 | } 21 | 22 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/wb_uart/.gitignore: -------------------------------------------------------------------------------- 1 | file.txt* -------------------------------------------------------------------------------- /sdk/tests/c-tests/wb_uart/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME wb_uart) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | target_compile_definitions(${TESTNAME} PRIVATE NINTERRUPTS=${NINTERRUPTS}) 8 | 9 | # Change this if you want to try a different file 10 | set(TESTFILE "${CMAKE_CURRENT_SOURCE_DIR}/testfile.txt") 11 | 12 | # Generate a header file that contains the initial file in a byte array using 13 | # `xxd` 14 | execute_process( 15 | COMMAND "xxd" "-i" 16 | INPUT_FILE "${TESTFILE}" 17 | OUTPUT_FILE "${CMAKE_CURRENT_SOURCE_DIR}/file.txt.h" 18 | OUTPUT_STRIP_TRAILING_WHITESPACE 19 | ) 20 | 21 | fp_add_outputs(${TESTNAME} fp-sdk) 22 | 23 | # This client command will transfer the file over UART 24 | # The test then compares the received data with the generated header file from 25 | # earlier 26 | fp_add_test_runs_to_completion_client( 27 | ${TESTNAME} 28 | "${FP_EMULATOR_CLIENTS_GENERIC}/uart --file ${TESTFILE}" 29 | ) 30 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/wb_uart/Makefile: -------------------------------------------------------------------------------- 1 | FLEXPRET_ROOT_DIR = ../../../../ 2 | NAME = wb_uart 3 | APP_SOURCES = *.c 4 | 5 | TESTFILE := testfile.txt 6 | 7 | CLIENT := $(FLEXPRET_ROOT_DIR)/emulator/clients/build/uart.elf 8 | CLIENT_CMD := ./$(CLIENT) --file $(TESTFILE) 9 | 10 | $(shell cp $(TESTFILE) file.txt && xxd -i file.txt > file.txt.h) 11 | 12 | include $(FLEXPRET_ROOT_DIR)/Makefrag 13 | include $(FLEXPRET_ROOT_DIR)/emulator/clients/clients.mk 14 | 15 | 16 | -------------------------------------------------------------------------------- /sdk/tests/c-tests/wb_uart/testfile.txt: -------------------------------------------------------------------------------- 1 | Hello world!! 2 | 3 | Goodbye world :) -------------------------------------------------------------------------------- /sdk/tests/c-tests/wb_uart/wb_uart.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | uint8_t file_txt[] = { 5 | #include "file.txt.h" 6 | }; 7 | uint32_t file_txt_len = sizeof(file_txt); 8 | 9 | int main(void) { 10 | fp_assert(uart_available(), "Uart is not available when expected to be\n"); 11 | 12 | char *file = malloc(file_txt_len); 13 | if (!file) { 14 | printf("File too big to malloc; cannot print it after test\n"); 15 | } 16 | for (uint32_t i = 0; i < file_txt_len; i++) { 17 | uint8_t byte = uart_receive(); 18 | fp_assert(file_txt[i] == byte, "Bytes not as expected\n"); 19 | if (file) file[i] = byte; 20 | } 21 | 22 | fp_assert(uart_available(), "Uart is not available when expected to be\n"); 23 | 24 | uint32_t j = 0; 25 | do { 26 | printf("File[%li]:\n%s\n", j, &file[512*j]); 27 | } while (j++ < (file_txt_len / 512)); 28 | 29 | fp_assert(uart_available(), "Uart is not available when expected to be\n"); 30 | 31 | for (uint32_t i = 0; i < file_txt_len; i++) { 32 | uart_send(file_txt[i]); 33 | } 34 | 35 | return 0; 36 | } 37 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | add_subdirectory(add) 2 | add_subdirectory(cond) 3 | add_subdirectory(heap) 4 | add_subdirectory(interrupt) 5 | add_subdirectory(lockowner) 6 | add_subdirectory(printf) 7 | add_subdirectory(swlock) 8 | add_subdirectory(syscall) 9 | 10 | # FIXME: Magnus: For some reason, this test hangs in CI when using 8 hw threads, 11 | # but works perfectly fine on my local computer with that same configuration. 12 | # Commenting it out so CI will pass. 13 | #add_subdirectory(threadcancel) 14 | 15 | add_subdirectory(threadprint) 16 | 17 | # FIXME: The UART test is not yet functional 18 | # The reason being that it requires accurate timing which the client 19 | # cannot provide 20 | # Should implement another "client" which is tightly coupled with the emulator 21 | #add_subdirectory(uart) 22 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/add/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME add-mt) 2 | 3 | add_executable(${TESTNAME} add.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/add/add.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | 8 | void* t1_do_work(void* num) { 9 | uint32_t* _num = (uint32_t*) num; 10 | fp_hwlock_acquire(); 11 | *_num += 1; 12 | fp_hwlock_release(); 13 | return NULL; 14 | } 15 | 16 | void* t2_do_work(void* num) { 17 | uint32_t* _num = (uint32_t*) num; 18 | fp_hwlock_acquire(); 19 | *_num += 2; 20 | fp_hwlock_release(); 21 | return NULL; 22 | } 23 | 24 | int main() { 25 | 26 | uint32_t* num = malloc(sizeof(uint32_t)); 27 | *num = 0; 28 | printf("num initialized to: %i\n", (int) *num); 29 | 30 | fp_thread_t tid[2]; 31 | int err = fp_thread_create(HRTT, &tid[0], t1_do_work, num); 32 | fp_assert(err == 0, "Could not create thread 0: %s\n", strerror(errno)); 33 | err = fp_thread_create(HRTT, &tid[1], t2_do_work, num); 34 | fp_assert(err == 0, "Could not create thread 1: %s\n", strerror(errno)); 35 | 36 | void * exit_code_t1; 37 | void * exit_code_t2; 38 | fp_thread_join(tid[0], &exit_code_t1); 39 | fp_thread_join(tid[1], &exit_code_t2); 40 | 41 | printf("num finished as: %i\n", (int) *num); 42 | 43 | return 0; 44 | } 45 | 46 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/cond/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME cond) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/cond/cond.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | 6 | fp_lock_t lock = FP_LOCK_INITIALIZER; 7 | fp_cond_t cond = FP_COND_INITIALIZER(&lock); 8 | int count = 0; 9 | 10 | void* t1() { 11 | fp_lock_acquire(&lock); 12 | fp_cond_wait(&cond); 13 | count++; 14 | fp_lock_release(&lock); 15 | return NULL; 16 | } 17 | 18 | void* t2() { 19 | fp_lock_acquire(&lock); 20 | fp_cond_wait(&cond); 21 | count++; 22 | fp_lock_release(&lock); 23 | return NULL; 24 | } 25 | 26 | int test_signal() { 27 | count=0; 28 | fp_thread_t tid[2]; 29 | int errno = fp_thread_create(HRTT, &tid[0], t1, NULL); 30 | fp_assert(errno == 0, "Could not create thread"); 31 | errno = fp_thread_create(HRTT, &tid[1], t2, NULL); 32 | fp_assert(errno == 0, "Could not create thread"); 33 | 34 | fp_delay_for(100000); 35 | printf("count is %i\n", count); 36 | fp_assert(count == 0, "Incorrect value for count"); 37 | fp_cond_signal(&cond); 38 | 39 | fp_delay_for(100000); 40 | printf("count is %i\n", count); 41 | fp_assert(count == 1, "Incorrect value for count"); 42 | fp_cond_signal(&cond); 43 | 44 | fp_delay_for(100000); 45 | printf("count is %i\n", count); 46 | fp_assert(count == 2, "Incorrect value for count"); 47 | 48 | 49 | void * exit_code_t1; 50 | void * exit_code_t2; 51 | fp_thread_join(tid[0], &exit_code_t1); 52 | fp_thread_join(tid[1], &exit_code_t2); 53 | return 0; 54 | } 55 | 56 | int test_broadcast() { 57 | count=0; 58 | fp_thread_t tid[2]; 59 | int errno = fp_thread_create(HRTT, &tid[0], t1, NULL); 60 | fp_assert(errno == 0, "Could not create thread"); 61 | errno = fp_thread_create(HRTT, &tid[1], t2, NULL); 62 | fp_assert(errno == 0, "Could not create thread"); 63 | fp_delay_for(100000); 64 | printf("count is %i\n", count); 65 | fp_assert(count == 0, "Incorrect value for count"); 66 | fp_cond_broadcast(&cond); 67 | fp_delay_for(100000); 68 | printf("count is %i\n", count); 69 | fp_assert(count == 2, "Incorrect value for count"); 70 | 71 | void * exit_code_t1; 72 | void * exit_code_t2; 73 | fp_thread_join(tid[0], &exit_code_t1); 74 | fp_thread_join(tid[1], &exit_code_t2); 75 | return 0; 76 | } 77 | 78 | void test_timed_wait() { 79 | 80 | fp_lock_acquire(&lock); 81 | uint64_t t1 = rdtime64(); 82 | printf("t1 is %lli\n", t1); 83 | uint64_t wakeup = t1 + 100000; 84 | printf("wakeup is %lli\n", wakeup); 85 | 86 | fp_cond_timed_wait(&cond, wakeup); 87 | uint64_t t2 = rdtime64(); 88 | printf("t2 is %lli\n", t2); 89 | 90 | fp_assert(t2 > wakeup, "rdtime64() got value less than waketime"); 91 | fp_lock_release(&lock); 92 | } 93 | 94 | int main() { 95 | 96 | test_signal(); 97 | test_broadcast(); 98 | test_timed_wait(); 99 | } 100 | 101 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/heap/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME heap) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/heap/heap.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file heap.c 3 | * @author Magnus Mæhlum (magnmaeh@stud.ntnu.no) 4 | * @brief Try to malloc from several threads at the same time to check that it 5 | * is thread-safe. 6 | * 7 | */ 8 | 9 | #include 10 | #include 11 | 12 | #include 13 | #include 14 | 15 | /** 16 | * These functions are found in ../../lib/syscalls/syscalls.c. 17 | * 18 | * If they are overwritten with nothing, the test will crash. Otherwise, if the 19 | * implementation in syscalls.c is used, the test will succeed. 20 | * 21 | * Try to comment it in and see for yourself :) 22 | */ 23 | #if 0 24 | void __malloc_lock(struct _reent *r) {} 25 | void __malloc_unlock(struct _reent *r) {} 26 | #endif 27 | 28 | void *task_heap_user(void *arg) { 29 | uint32_t hartid = read_hartid(); 30 | int iterations = (int) arg; 31 | for (int i = 0; i < iterations; i++) { 32 | uint32_t *data = malloc(sizeof(uint32_t)); 33 | fp_assert(data, "Could not malloc\n"); 34 | *data = 42 * hartid; 35 | fp_assert(*data == (42 * hartid), "Data was changed by another thread\n"); 36 | free(data); 37 | } 38 | return NULL; 39 | } 40 | 41 | int main() { 42 | fp_thread_t tid[FP_THREADS-1]; 43 | for (int i = 0; i < FP_THREADS-1; i++) { 44 | fp_assert(fp_thread_create(HRTT, &tid[i], task_heap_user, (void *)100) == 0, 45 | "Could not create thread"); 46 | } 47 | 48 | void *exit_codes[FP_THREADS-1]; 49 | for (int i = 0; i < FP_THREADS-1; i++) { 50 | fp_thread_join(tid[i], &exit_codes[i]); 51 | fp_assert(exit_codes[i] == 0, "Thread's exit code was non-zero"); 52 | } 53 | 54 | printf("Multiple threads successfully allocated memory on the heap at the same time without corruption\n"); 55 | 56 | return 0; 57 | } 58 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/interrupt/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME interrupt-mt) 2 | 3 | add_executable(${TESTNAME} ../../c-tests/interrupt/interrupt.c main.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion_client( 10 | ${TESTNAME} 11 | ${FP_EMULATOR_CLIENTS_GENERIC}/stimuli-mt 12 | ) 13 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/interrupt/main.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | #include "../../c-tests/interrupt/interrupt.h" 4 | 5 | void run_simultaneous_tests(const int n, void *(*test)(void *), void **args) { 6 | fp_thread_t *threads = malloc(n * sizeof(fp_thread_t)); 7 | fp_assert(threads, "Could not malloc threads\n"); 8 | for (int i = 0; i < n; i++) { 9 | fp_assert( 10 | fp_thread_create(HRTT, &threads[i], test, args[i]) == 0, 11 | "Could not create thread %i\n", i+1 12 | ); 13 | } 14 | 15 | for (int i = 0; i < n; i++) { 16 | fp_thread_join(threads[i], NULL); 17 | } 18 | 19 | free(threads); 20 | } 21 | 22 | void *trigger_du_same_time(void *args) { 23 | /** 24 | * Check that we can trigger two or more delay until instructions at the exact 25 | * same time without causing any bugs. This requires that they both use an 26 | * absolute time, which is passed through the argument. 27 | * 28 | */ 29 | uint32_t trigger_time = *((uint32_t*) (args)); 30 | 31 | fp_delay_until(trigger_time); 32 | 33 | volatile uint32_t now = rdtime(); 34 | fp_assert(trigger_time < now, 35 | "delay until triggered at same time instant as another thread failed\n"); 36 | return NULL; 37 | } 38 | 39 | void *trigger_wu_same_time(void *args) { 40 | /** 41 | * Check that we can trigger two or more delay until instructions at the exact 42 | * same time without causing any bugs. This requires that they both use an 43 | * absolute time, which is passed through the argument. 44 | * 45 | */ 46 | uint32_t trigger_time = *((uint32_t*) (args)); 47 | 48 | fp_wait_until(trigger_time); 49 | 50 | volatile uint32_t now = rdtime(); 51 | fp_assert(trigger_time < now, 52 | "delay until triggered at same time instant as another thread failed\n"); 53 | return NULL; 54 | } 55 | 56 | #define do_run(nthreads, test) do { \ 57 | run_simultaneous_tests(nthreads, test, NULL); \ 58 | printf(#test " passed for %i hw threads running simultaneously\n", nthreads); \ 59 | reset_flags(); \ 60 | } while (0) 61 | 62 | int main(void) { 63 | int nthreads = FP_THREADS-1; 64 | 65 | volatile uint32_t now = rdtime(); 66 | 67 | // Set up absolute time for when the delay until instructions shall trigger 68 | // The trigger must be big enough that a single thread does not have time 69 | // to execute it before another thread is allocated the next. Otherwise 70 | // the same thread will simply run all triggers sequentually. 71 | uint32_t trigger = now + 1000000; 72 | uint32_t* triggers[FP_THREADS] = THREAD_ARRAY_INITIALIZER(&trigger); 73 | run_simultaneous_tests(nthreads, trigger_du_same_time, (void **) triggers); 74 | 75 | printf("trigger_du_same_time passed\n"); 76 | 77 | // Set up the trigger variable again; it's pointer is read in the function 78 | // so we don't need to do much else 79 | now = rdtime(); 80 | trigger = now + 10000; 81 | run_simultaneous_tests(nthreads, trigger_wu_same_time, (void **) triggers); 82 | 83 | printf("trigger_wu_same_time passed\n"); 84 | 85 | do_run(nthreads, test_long_interrupt); 86 | do_run(nthreads, test_external_interrupt); 87 | do_run(nthreads, test_external_interrupt_disabled); 88 | do_run(nthreads, test_two_interrupts); 89 | do_run(nthreads, test_two_interrupts); 90 | do_run(nthreads, test_disabled_interrupts); 91 | do_run(nthreads, test_low_timeout); 92 | do_run(nthreads, test_fp_delay_until); 93 | do_run(nthreads, test_fp_wait_until); 94 | } 95 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/lockowner/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME lockowner) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/lockowner/lockowner.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | fp_lock_t lock = FP_LOCK_INITIALIZER; 9 | 10 | void* t1_do_work() { 11 | fp_lock_acquire(&lock); 12 | return NULL; 13 | } 14 | 15 | void* t2_do_work() { 16 | fp_lock_release(&lock); 17 | return NULL; 18 | } 19 | 20 | int main() { 21 | 22 | // Intentionally map the do_work functions to 23 | // two different threads. 24 | fp_thread_t tid[2] = {1, 2}; 25 | 26 | // Map t1_do_work to thread 1 specifically by 27 | // calling fp_thread_map() instead of fp_thread_create(). 28 | // 29 | // If we use fp_thread_create(), t2_do_work will be mapped 30 | // to thread 1 again after it returns from 31 | // t1_do_work and the exception does not get triggered. 32 | int errno = fp_thread_map(HRTT, &tid[0], t1_do_work, NULL); 33 | fp_assert(errno == 0, "Could not create thread\n"); 34 | void * exit_code_t1; 35 | fp_thread_join(tid[0], &exit_code_t1); 36 | 37 | // Map t2_do_work to thread 2 specifically. 38 | // Expect an exception raised by thread 2. 39 | errno = fp_thread_map(HRTT, &tid[0], t2_do_work, NULL); 40 | fp_assert(errno == 0, "Could not create thread\n"); 41 | 42 | void * exit_code_t2; 43 | fp_thread_join(tid[1], &exit_code_t2); 44 | 45 | printf("Test sucess\n"); 46 | 47 | return 0; 48 | } 49 | 50 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/printf/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME printf-mt) 2 | 3 | add_executable(${TESTNAME} printf.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/printf/printf.c: -------------------------------------------------------------------------------- 1 | #include 2 | #include 3 | 4 | #include 5 | 6 | // Using floats increases the final .mem by approximately 30 kB 7 | #define HAVE_FLOATS (PRINTF_SUPPORT_DECIMAL_SPECIFIERS == 1) 8 | 9 | int i = 22; 10 | const char *some_string = "This is some string!"; 11 | 12 | #if HAVE_FLOATS 13 | float f = 8.928; 14 | #endif 15 | 16 | void *printer(void *args) { 17 | UNUSED(args); 18 | 19 | #if HAVE_FLOATS 20 | printf("The variable f is: %f\n", f); 21 | #endif 22 | 23 | printf("The variable i is: %i\n", i); 24 | printf("Some string is: %s\n", some_string); 25 | return NULL; 26 | } 27 | 28 | int main() { 29 | printf("Hello world %i\n", i); 30 | 31 | fp_thread_t tid[FP_THREADS-1]; 32 | for (int i = 0; i < FP_THREADS-1; i++) { 33 | fp_assert(fp_thread_create(HRTT, &tid[i], printer, NULL) == 0, "Could not create thread"); 34 | } 35 | 36 | void *exit_codes[FP_THREADS-1]; 37 | for (int i = 0; i < FP_THREADS-1; i++) { 38 | fp_thread_join(tid[i], &exit_codes[i]); 39 | fp_assert(exit_codes[i] == 0, "Thread's exit code was non-zero"); 40 | } 41 | 42 | printf("Bye to all threads\n"); 43 | return 0; 44 | } 45 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/swlock/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME swlock) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/swlock/swlock.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | fp_lock_t lock = FP_LOCK_INITIALIZER; 9 | 10 | void* t1_do_work(void* num) { 11 | uint32_t* _num = (uint32_t*) num; 12 | fp_lock_acquire(&lock); 13 | *_num += 1; 14 | printf("num is %i\n", (int) *_num); 15 | fp_lock_release(&lock); 16 | return NULL; 17 | } 18 | 19 | void* t2_do_work(void* num) { 20 | uint32_t* _num = (uint32_t*) num; 21 | fp_lock_acquire(&lock); 22 | *_num += 2; 23 | printf("num is %i\n", (int) *_num); 24 | fp_lock_release(&lock); 25 | return NULL; 26 | } 27 | 28 | int main() { 29 | uint32_t* num = malloc(sizeof(uint32_t)); 30 | *num = 0; 31 | printf("num is %i\n", (int) *num); 32 | 33 | fp_thread_t tid[2]; 34 | int errno = fp_thread_create(HRTT, &tid[0], t1_do_work, num); 35 | fp_assert(errno == 0, "Could not create thread\n"); 36 | errno = fp_thread_create(HRTT, &tid[1], t2_do_work, num); 37 | fp_assert(errno == 0, "Could not create thread\n"); 38 | 39 | void * exit_code_t1; 40 | void * exit_code_t2; 41 | fp_thread_join(tid[0], &exit_code_t1); 42 | fp_thread_join(tid[1], &exit_code_t2); 43 | 44 | printf("num is %i\n", (int) *num); 45 | 46 | return 0; 47 | } 48 | 49 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/syscall/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME syscall-mt) 2 | 3 | add_executable(${TESTNAME} syscall.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/syscall/syscall.c: -------------------------------------------------------------------------------- 1 | /** 2 | * @file syscall.c 3 | * @author Magnus Mæhlum (magnusmaehlum@outlook.com) 4 | * 5 | * This is the threaded version of ../../c-tests/syscall/syscall.c 6 | * Notably, it checks that errno is thread-safe. 7 | * 8 | */ 9 | 10 | #include 11 | #include 12 | 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | 20 | void* t1_gettimeofday(void* arg) { 21 | int iterations = (*(int *) arg); 22 | struct timeval tv; 23 | for (int i = 0; i < iterations; i++) { 24 | gettimeofday(&tv, NULL); 25 | fp_assert(errno == 0, "Errno not as expected: %s\n", strerror(errno)); 26 | } 27 | return NULL; 28 | } 29 | 30 | void* t2_close(void* arg) { 31 | int iterations = (*(int *) arg); 32 | for (int i = 0; i < iterations; i++) { 33 | close(22); 34 | fp_assert(errno == ENOSYS, "Errno not as expected\n"); 35 | } 36 | return NULL; 37 | } 38 | 39 | int main() { 40 | int iterations = 100; 41 | fp_thread_t tid[2]; 42 | int ok = fp_thread_create(HRTT, &tid[0], t1_gettimeofday, &iterations); 43 | fp_assert(ok == 0, "Could not create thread\n"); 44 | ok = fp_thread_create(HRTT, &tid[1], t2_close, &iterations); 45 | fp_assert(ok == 0, "Could not create thread\n"); 46 | 47 | void * exit_code_t1; 48 | void * exit_code_t2; 49 | fp_thread_join(tid[0], &exit_code_t1); 50 | fp_thread_join(tid[1], &exit_code_t2); 51 | 52 | // Try to create a thread which does not make sense and expect error code 53 | fp_thread_t invalid_tid = 99; 54 | ok = fp_thread_map(HRTT, &invalid_tid, t2_close, NULL); 55 | fp_assert(ok == 1 && errno == EINVAL, "Error codes not as expected\n"); 56 | 57 | // Try to create a thread with an id already in use and expect error code 58 | fp_thread_t tid_in_use; 59 | fp_assert(fp_thread_create(HRTT, &tid_in_use, t2_close, &iterations) == 0, "Could not create thread\n"); 60 | ok = fp_thread_map(HRTT, &tid_in_use, t2_close, NULL); 61 | fp_assert(ok == 1 && errno == EBUSY, "Error codes not as expected\n"); 62 | 63 | printf("Test success\n"); 64 | 65 | return 0; 66 | } 67 | 68 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/threadcancel/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME threadcancel) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/threadcancel/threadcancel.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | fp_lock_t lock = FP_LOCK_INITIALIZER; 9 | bool ready = false; 10 | 11 | // Intentionally map the do_work functions to 12 | // two different threads. 13 | fp_thread_t tid[2] = {1, 2}; 14 | 15 | // t1 to be canceled by t2 16 | void* t1_do_work() { 17 | fp_lock_acquire(&lock); 18 | ready = true; 19 | fp_lock_release(&lock); 20 | while(1) { 21 | fp_thread_testcancel(); 22 | } 23 | printf("Got to non reachable code\n"); // Not reachable. 24 | fp_assert(0, "Unreachable code reached"); 25 | } 26 | 27 | // t2 to cancel t1 28 | void* t2_do_work() { 29 | while(!ready); 30 | fp_thread_cancel(tid[0]); 31 | printf("Sucessfully cancelled thread %i\n", (int) tid[0]); 32 | return NULL; 33 | } 34 | 35 | int main() { 36 | 37 | int errno = fp_thread_map(HRTT, &tid[0], t1_do_work, NULL); 38 | fp_assert(errno == 0, "Could not create thread"); 39 | errno = fp_thread_map(HRTT, &tid[1], t2_do_work, NULL); 40 | fp_assert(errno == 0, "Could not create thread"); 41 | 42 | void * exit_code_t1; 43 | void * exit_code_t2; 44 | fp_thread_join(tid[0], &exit_code_t1); 45 | fp_thread_join(tid[1], &exit_code_t2); 46 | 47 | printf("Test sucess\n"); 48 | 49 | return 0; 50 | } 51 | 52 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/threadprint/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME threadprint) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME}) 9 | fp_add_test_runs_to_completion(${TESTNAME}) 10 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/threadprint/threadprint.c: -------------------------------------------------------------------------------- 1 | /* A threaded version of add.c */ 2 | #include 3 | #include 4 | #include 5 | 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | 13 | void* pumper(void* arg) { 14 | struct PrintBuffer *printbuf = arg; 15 | 16 | char writebuf[128]; 17 | int len = snprintf(writebuf, sizeof(writebuf), 18 | "Hello world from tid: %i\n", (int) read_hartid()); 19 | 20 | printbuffer_pump(printbuf, writebuf, len); 21 | return NULL; 22 | } 23 | 24 | int main(void) { 25 | printf("Hello world from tid: %i\n", (int) read_hartid()); 26 | 27 | // One more than strictly necessary 28 | struct PrintBuffer bufs[FP_THREADS] = THREAD_ARRAY_INITIALIZER( 29 | PRINTBUFFER_INITIALIZER 30 | ); 31 | 32 | int ret = 0; 33 | fp_thread_t tid[FP_THREADS-1]; 34 | for (int i = 0; i < FP_THREADS-1; i++) { 35 | ret = fp_thread_create(HRTT, &tid[i], pumper, &bufs[i]); 36 | fp_assert(ret == 0, "Could not create thread"); 37 | } 38 | 39 | for (int i = 0; i < FP_THREADS-1; i++) { 40 | fp_thread_join(tid[i], NULL); 41 | } 42 | 43 | char printable[128]; 44 | for (int i = 0; i < FP_THREADS-1; i++) { 45 | uint32_t nbytes = 0; 46 | while ((nbytes = printbuffer_drain(&bufs[i], printable)) > 0) { 47 | for (uint32_t j = 0; j < nbytes; j++) { 48 | uart_send(printable[j]); 49 | } 50 | } 51 | } 52 | 53 | return 0; 54 | } 55 | 56 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/uart/CMakeLists.txt: -------------------------------------------------------------------------------- 1 | set(TESTNAME uart) 2 | 3 | add_executable(${TESTNAME} ${TESTNAME}.c) 4 | include(${CMAKE_SOURCE_DIR}/cmake/fp-app.cmake) 5 | 6 | target_link_libraries(${TESTNAME} fp-sdk) 7 | 8 | fp_add_outputs(${TESTNAME} fp-sdk) 9 | fp_add_test_runs_to_completion_client( 10 | ${TESTNAME} 11 | "python3 ${CMAKE_CURRENT_SOURCE_DIR}/client/stimuli.py" 12 | ) 13 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/uart/Makefile: -------------------------------------------------------------------------------- 1 | FLEXPRET_ROOT_DIR = ../../../../ 2 | NAME = uart 3 | APP_SOURCES = *.c 4 | 5 | CLIENT_CMD = python3 client/stimuli.py 6 | 7 | include $(FLEXPRET_ROOT_DIR)/Makefrag 8 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/uart/client/stimuli.py: -------------------------------------------------------------------------------- 1 | import socket as s 2 | import struct 3 | import time 4 | 5 | CLOCKFREQ = int(50e6) 6 | UART_BAUDRATE = 9600 7 | CLKS_PER_BAUD = int(CLOCKFREQ / UART_BAUDRATE) 8 | 9 | def make_uart_float(pin, value): 10 | bytevals = list(struct.pack('!f', value)) 11 | 12 | return make_uart_byte(pin, bytevals[0]) \ 13 | + make_uart_byte(pin, bytevals[1]) \ 14 | + make_uart_byte(pin, bytevals[2]) \ 15 | + make_uart_byte(pin, bytevals[3]) 16 | 17 | def make_uart_byte(pin, byte): 18 | def set_pin(highlow): 19 | return make_pinevent(pin, CLKS_PER_BAUD, highlow) 20 | 21 | # Start bit 22 | byte_signals = make_pinevent(pin, 2 * CLKS_PER_BAUD, 0) 23 | 24 | # Data bits 25 | for i in range(8): 26 | byte_signals += set_pin(byte & (1 << i)) 27 | 28 | # Stop bit 29 | byte_signals += set_pin(1) 30 | return byte_signals 31 | 32 | def make_pinevent(pin, ncycles, highlow): 33 | return struct.pack('III', pin, ncycles, highlow) 34 | 35 | def connect_to_emulator(): 36 | client = s.socket(s.AF_INET, s.SOCK_STREAM) 37 | client.connect(('localhost', 8080)) 38 | 39 | return client 40 | 41 | def transmit(pin, data): 42 | for d in data: 43 | client.send(make_uart_float(pin, d)) 44 | 45 | 46 | client = connect_to_emulator() 47 | 48 | # Wait for emulator to initilize 49 | time.sleep(2) 50 | 51 | UART_PIN_0 = 8 52 | UART_PIN_1 = 9 53 | 54 | # Set pins high, which indicates the UART line is idle 55 | client.send(make_pinevent(UART_PIN_0, 0, 1)) 56 | client.send(make_pinevent(UART_PIN_1, 0, 1)) 57 | 58 | # Will read the data in the file into an array named 'data' 59 | with open('data.txt', 'r') as f: 60 | code = 'data = [' + f.read() + ']' 61 | exec(code) 62 | 63 | time.sleep(1) 64 | transmit(UART_PIN_0, data) 65 | #time.sleep(12) 66 | #transmit(UART_PIN_1, data) 67 | #time.sleep(12) 68 | #transmit(UART_PIN_0, data) 69 | -------------------------------------------------------------------------------- /sdk/tests/mt-c-tests/uart/data.txt: -------------------------------------------------------------------------------- 1 | 33.020, 2 | 2132.23, 3 | -123, 4 | -86545, 5 | 0.0012, 6 | 66.231, 7 | -------------------------------------------------------------------------------- /src/main/resources/.gitignore: -------------------------------------------------------------------------------- 1 | application.conf 2 | -------------------------------------------------------------------------------- /src/main/resources/DualPortBramEmulator.v: -------------------------------------------------------------------------------- 1 | // Copied from Yaman Umuroglu's `fpga-tidbits` https://github.com/maltanar/fpga-tidbits 2 | // ------------------------------------------------------------------------------------- 3 | 4 | // the dual-port BRAM Verilog below is adapted from Dan Strother's example: 5 | // http://danstrother.com/2010/09/11/inferring-rams-in-fpgas/ 6 | 7 | module DualPortBram #( 8 | parameter DATA = 72, 9 | parameter ADDR = 10 10 | ) ( 11 | input wire clk, 12 | 13 | // Port A 14 | input wire a_wr, 15 | input wire [ADDR-1:0] a_addr, 16 | input wire [DATA-1:0] a_din, 17 | output reg [DATA-1:0] a_dout, 18 | 19 | // Port B 20 | input wire b_wr, 21 | input wire [ADDR-1:0] b_addr, 22 | input wire [DATA-1:0] b_din, 23 | output reg [DATA-1:0] b_dout 24 | ); 25 | 26 | // Shared memory 27 | reg [DATA-1:0] mem [(2**ADDR)-1:0]; 28 | 29 | 30 | // Port A 31 | always @(posedge clk) begin 32 | a_dout <= mem[a_addr]; 33 | if(a_wr) begin 34 | a_dout <= a_din; 35 | mem[a_addr] <= a_din; 36 | end 37 | end 38 | 39 | // Port B 40 | always @(posedge clk) begin 41 | b_dout <= mem[b_addr]; 42 | if(b_wr) begin 43 | b_dout <= b_din; 44 | mem[b_addr] <= b_din; 45 | end 46 | end 47 | 48 | // Load its content from the file which is passed from the CLI 49 | initial begin 50 | string mem_file_name; 51 | $value$plusargs("ispm=%s", mem_file_name); 52 | $readmemh(mem_file_name, mem); 53 | end 54 | 55 | endmodule 56 | -------------------------------------------------------------------------------- /src/main/resources/DualPortBramFPGA.v: -------------------------------------------------------------------------------- 1 | // Copied from Yaman Umuroglu's `fpga-tidbits` https://github.com/maltanar/fpga-tidbits 2 | // ------------------------------------------------------------------------------------- 3 | 4 | // the dual-port BRAM Verilog below is adapted from Dan Strother's example: 5 | // http://danstrother.com/2010/09/11/inferring-rams-in-fpgas/ 6 | 7 | module DualPortBram #( 8 | parameter DATA = 72, 9 | parameter ADDR = 10 10 | ) ( 11 | input wire clk, 12 | 13 | // Port A 14 | input wire a_wr, 15 | input wire [ADDR-1:0] a_addr, 16 | input wire [DATA-1:0] a_din, 17 | output reg [DATA-1:0] a_dout, 18 | 19 | // Port B 20 | input wire b_wr, 21 | input wire [ADDR-1:0] b_addr, 22 | input wire [DATA-1:0] b_din, 23 | output reg [DATA-1:0] b_dout 24 | ); 25 | 26 | // Shared memory 27 | reg [DATA-1:0] mem [(2**ADDR)-1:0]; 28 | 29 | 30 | // Port A 31 | always @(posedge clk) begin 32 | a_dout <= mem[a_addr]; 33 | if(a_wr) begin 34 | a_dout <= a_din; 35 | mem[a_addr] <= a_din; 36 | end 37 | end 38 | 39 | // Port B 40 | always @(posedge clk) begin 41 | b_dout <= mem[b_addr]; 42 | if(b_wr) begin 43 | b_dout <= b_din; 44 | mem[b_addr] <= b_din; 45 | end 46 | end 47 | 48 | // Load its content from the file `ispm.mem` 49 | initial begin 50 | $readmemh("ispm.mem", mem); 51 | end 52 | 53 | endmodule 54 | -------------------------------------------------------------------------------- /src/main/scala/Core/ALU.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: ALU.scala 3 | Description: ALU 4 | Author: Michael Zimmer (mzimmer@eecs.berkeley.edu) 5 | Author: Edward Wang (edwardw@eecs.berkeley.edu) 6 | Contributors: 7 | License: See LICENSE.txt 8 | ******************************************************************************/ 9 | package flexpret.core 10 | 11 | import chisel3._ 12 | import chisel3.util._ 13 | 14 | /** 15 | * ALU operation types. 16 | */ 17 | object ALUTypes extends ChiselEnum { 18 | val Add = Value 19 | val Sub = Value 20 | val ShiftLeft = Value 21 | val ShiftRightArithmetic = Value 22 | val ShiftRightLogical = Value 23 | val And = Value 24 | val Or = Value 25 | val Xor = Value 26 | val LessThanSigned = Value 27 | val LessThanUnsigned = Value 28 | 29 | val Unknown = BitPat("b" + "?" * ALUTypes.getWidth) 30 | 31 | // Syntactic sugar 32 | def unapply(x: UInt): Option[ALUTypes.Type] = Some(ALUTypes(x)) 33 | } 34 | 35 | object ExampleEnum extends ChiselEnum { 36 | val Foo, Bar = Value 37 | } 38 | 39 | /** 40 | * Helpful BitPat aliases for ALU types. For use in a BitPat-based decoder. 41 | */ 42 | object ALUBitPats { 43 | // Work around Chisel3 #1871 44 | def toBitPat(aluLit: ALUTypes.Type) = BitPat(aluLit.litValue.U(ALUTypes.getWidth.W)) 45 | 46 | val ALU_X = ALUTypes.Unknown 47 | val ALU_ADD = toBitPat(ALUTypes.Add) 48 | val ALU_SUB = toBitPat(ALUTypes.Sub) 49 | val ALU_SL = toBitPat(ALUTypes.ShiftLeft) 50 | val ALU_SRA = toBitPat(ALUTypes.ShiftRightArithmetic) 51 | val ALU_SRL = toBitPat(ALUTypes.ShiftRightLogical) 52 | val ALU_AND = toBitPat(ALUTypes.And) 53 | val ALU_OR = toBitPat(ALUTypes.Or) 54 | val ALU_XOR = toBitPat(ALUTypes.Xor) 55 | val ALU_LTS = toBitPat(ALUTypes.LessThanSigned) 56 | val ALU_LTU = toBitPat(ALUTypes.LessThanUnsigned) 57 | } 58 | 59 | class ALU extends Module { 60 | val io = IO(new Bundle { 61 | val op1 = Input(UInt(32.W)) 62 | val op2 = Input(UInt(32.W)) 63 | val shift = Input(UInt(5.W)) // used only for io.shifting operations. SRLI requires 5 bits. 64 | val func = Input(ALUTypes()) 65 | val result = Output(UInt(32.W)) 66 | }) 67 | 68 | val dontCare = Wire(UInt(32.W)) 69 | dontCare := DontCare 70 | 71 | import ALUTypes._ 72 | io.result := MuxLookup(io.func.asUInt, dontCare, Seq[(UInt, UInt)]( 73 | Add.asUInt -> (io.op1 + io.op2), 74 | Sub.asUInt -> (io.op1 - io.op2), 75 | ShiftLeft.asUInt -> (io.op1 << io.shift)(31, 0), 76 | ShiftRightArithmetic.asUInt -> (io.op1.asSInt >> io.shift).asUInt, 77 | ShiftRightLogical.asUInt -> (io.op1 >> io.shift), 78 | And.asUInt -> (io.op1 & io.op2), 79 | Or.asUInt -> (io.op1 | io.op2), 80 | Xor.asUInt -> (io.op1 ^ io.op2), 81 | LessThanSigned.asUInt -> (io.op1.asSInt < io.op2.asSInt).asUInt, 82 | LessThanUnsigned.asUInt -> (io.op1 < io.op2), 83 | 84 | )) 85 | } 86 | 87 | -------------------------------------------------------------------------------- /src/main/scala/Core/Multiplier.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: Multiplier.scala: 3 | Description: Multi-stage multiplier. 4 | Author: Michael Zimmer (mzimmer@eecs.berkeley.edu) 5 | Author: Edward Wang (edwardw@eecs.berkeley.edu) 6 | Contributors: 7 | License: See LICENSE.txt 8 | ******************************************************************************/ 9 | package flexpret.core 10 | 11 | import chisel3._ 12 | import chisel3.util._ 13 | 14 | import Core.FlexpretConstants._ 15 | 16 | class Multiplier extends Module { 17 | val io = IO(new Bundle { 18 | val op1 = Input(UInt(32.W)) 19 | val op2 = Input(UInt(32.W)) 20 | val func = Input(UInt(4.W)) 21 | val result = Output(UInt(32.W)) 22 | }) 23 | 24 | val op1 = Mux(io.func === MUL_HU, Cat(0.U(1.W), io.op1).asSInt, Cat(io.op1(31), io.op1).asSInt) 25 | val op2 = Mux(io.func === MUL_HSU || io.func === MUL_HU, Cat(0.U(1.W), io.op2).asSInt, Cat(io.op2(31), io.op2).asSInt) 26 | val mul_result = op1 * op2 27 | val result = Mux(io.func === MUL_L, mul_result(31, 0), mul_result(63, 32)) 28 | 29 | // 2 cycle 30 | io.result := RegNext(result) 31 | } 32 | 33 | -------------------------------------------------------------------------------- /src/main/scala/Core/RegisterFile.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: RegisterFile.scala 3 | Description: Register file for all threads. 4 | Author: Michael Zimmer 5 | Contributors: Edward Wang 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package flexpret.core 9 | 10 | import chisel3._ 11 | import chisel3.util.{Cat, log2Ceil, MuxLookup, MuxCase} 12 | import Core.FlexpretConstants._ 13 | 14 | class RegisterFileReadIO(val threadBits: Int) extends Bundle { 15 | val thread = Input(UInt(threadBits.W)) 16 | val addr = Input(UInt(REG_ADDR_BITS.W)) 17 | val data = Output(UInt(32.W)) 18 | } 19 | 20 | class RegisterFileWriteIO(val threadBits: Int) extends Bundle { 21 | val thread = Input(UInt(threadBits.W)) 22 | val addr = Input(UInt(REG_ADDR_BITS.W)) 23 | val data = Input(UInt(32.W)) 24 | val enable = Input(Bool()) 25 | } 26 | 27 | object RegisterFile { 28 | def apply(readPorts: Int = 2, writePorts: Int = 1)(implicit conf: FlexpretConfiguration): RegisterFile = new RegisterFile(conf.threads, readPorts=readPorts, writePorts=writePorts) 29 | } 30 | 31 | class RegisterFile(val threads: Int, val readPorts: Int = 2, val writePorts: Int = 1) extends Module { 32 | /* Number of bits. */ 33 | val threadBits = log2Ceil(threads) 34 | /* Depth of the register file. */ 35 | val regDepth = 32 * threads 36 | 37 | val io = IO(new Bundle { 38 | val read = Vec(readPorts, new RegisterFileReadIO(threadBits)) 39 | val write = Vec(writePorts, new RegisterFileWriteIO(threadBits)) 40 | }) 41 | 42 | private def regfileAddress(thread: UInt, addr: UInt): UInt = { 43 | Mux(thread <= (threads - 1).U, Cat(thread, addr), 0.U) 44 | } 45 | val writeIndexes = io.write.map { port => regfileAddress(addr=port.addr, thread=port.thread) } 46 | 47 | // 1-cycle latency read and write 48 | // Note: default read-under-write behaviour is undefined! 49 | // Also define reading invalid threads to return DontCare. 50 | val regfile = SyncReadMem(regDepth, UInt(32.W)) 51 | 52 | // Read ports 53 | io.read.foreach { readPort => 54 | // Value read from regfile 55 | val readIndex = regfileAddress(addr=readPort.addr, thread=readPort.thread) 56 | 57 | val regfileRead = Mux(RegNext(readPort.thread) <= (threads - 1).U, 58 | regfile(readIndex), 59 | 0.U 60 | ) 61 | 62 | // Account for read-under-write. 63 | // If there was read-under-write, use the write port's value. 64 | // We also need to register the data and addresses since we are returning 65 | // last cycle's requests. 66 | val readUnderWrites = (writeIndexes zip io.write).map { case (writeIndex, writePort) => 67 | (RegNext(writeIndex) -> 68 | MuxCase(RegNext(writePort.data), Array( 69 | RegNext(writePort.addr === 0.U) -> 0.U, 70 | RegNext(!writePort.enable) -> regfileRead 71 | ))) 72 | } 73 | 74 | val readIndexReg = RegNext(readIndex) 75 | readPort.data := MuxLookup(readIndexReg, regfileRead, readUnderWrites) 76 | 77 | } 78 | 79 | // Write ports 80 | (io.write zip writeIndexes).foreach { case (port, writeIndex) => 81 | when(port.enable && port.addr =/= 0.U && port.thread <= (threads - 1).U) { 82 | regfile(writeIndex) := port.data 83 | } 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/main/scala/Core/Top.scala: -------------------------------------------------------------------------------- 1 | package flexpret.core 2 | import chisel3._ 3 | import chisel3.util.MixedVec 4 | import chisel3.util.experimental.loadMemoryFromFileInline // Load the contents of ISPM from file 5 | import flexpret.{WishboneBus, WishboneMaster, WishboneUart} 6 | 7 | abstract class AbstractTop(cfg: FlexpretConfiguration, cfgHash: UInt) extends Module { 8 | val core = Module(new Core(cfg, cfgHash)) 9 | 10 | val wbMaster = Module(new WishboneMaster(cfg.busAddrBits)(cfg)) 11 | val wbUart = Module(new WishboneUart()(cfg)) 12 | val wbBus = Module(new WishboneBus(cfg.busAddrBits, Seq(4))) 13 | 14 | // Connect WB bus to FlexPRET bus 15 | wbMaster.busIO <> core.io.bus 16 | 17 | // Connect WB bus to WB master 18 | wbBus.io.wbMaster <> wbMaster.wbIO 19 | 20 | // Connect WB bus to WB UART 21 | wbBus.io.wbDevices(0) <> wbUart.io.port 22 | } 23 | 24 | class VerilatorTopIO(cfg: FlexpretConfiguration) extends Bundle { 25 | val gpio = new GPIO()(cfg) 26 | val uart = new Bundle { 27 | val rx = Input(Bool()) 28 | val tx = Output(Bool()) 29 | } 30 | val to_host = Output(Vec(cfg.threads, UInt(32.W))) 31 | val int_exts = Input(Vec(cfg.threads, Bool())) 32 | val imem_store = Output(Bool()) 33 | } 34 | 35 | class VerilatorTop(cfg: FlexpretConfiguration, cfgHash: UInt) extends AbstractTop(cfg, cfgHash) { 36 | val io = IO(new VerilatorTopIO(cfg)) 37 | val regPrintNext = RegInit(VecInit(Seq.fill(cfg.threads) {false.B} )) 38 | 39 | io.gpio <> core.io.gpio 40 | 41 | // Connect rx tx signals 42 | io.uart.tx := wbUart.ioUart.tx 43 | wbUart.ioUart.rx := io.uart.rx 44 | 45 | core.io.int_exts <> io.int_exts 46 | 47 | // Drive bus input to 0 48 | core.io.dmem.driveDefaultsFlipped() 49 | core.io.imem_bus.driveDefaultsFlipped() 50 | 51 | // Catch termination from core 52 | for (tid <- 0 until cfg.threads) { 53 | io.to_host(tid) := core.io.host.to_host(tid) 54 | } 55 | 56 | io.imem_store := core.io.imem_store 57 | } 58 | 59 | class FpgaTopIO(cfg: FlexpretConfiguration) extends Bundle { 60 | val gpio = new GPIO()(cfg) 61 | val uart = new Bundle { 62 | val rx = Input(Bool()) 63 | val tx = Output(Bool()) 64 | } 65 | val int_exts = Input(Vec(cfg.threads, Bool())) 66 | } 67 | 68 | class FpgaTop(cfg: FlexpretConfiguration, cfgHash: UInt) extends AbstractTop(cfg, cfgHash) { 69 | val io = IO(new FpgaTopIO(cfg)) 70 | 71 | io.gpio <> core.io.gpio 72 | core.io.int_exts <> io.int_exts 73 | 74 | // Connect rx tx signals 75 | io.uart.tx := wbUart.ioUart.tx 76 | wbUart.ioUart.rx := io.uart.rx 77 | 78 | // Drive bus input to 0 79 | core.io.dmem.driveDefaultsFlipped() 80 | core.io.imem_bus.driveDefaultsFlipped() 81 | } 82 | -------------------------------------------------------------------------------- /src/main/scala/Core/dspm.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: dspm.scala 3 | Description: Data scratchpad memory 4 | Author: Edward Wang (edwardw@eecs.berkeley.edu) 5 | Contributors: 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package flexpret.core 9 | 10 | import chisel3._ 11 | 12 | class DataMemCoreIO(implicit conf: FlexpretConfiguration) extends Bundle { 13 | // read/write port 14 | val addr = Input(UInt((conf.dMemAddrBits-2).W)) // assume word aligned 15 | val enable = Input(Bool()) 16 | val data_out = Output(UInt(32.W)) 17 | val byte_write = Input(Vec(4, Bool())) 18 | val data_in = Input(UInt(32.W)) 19 | } 20 | 21 | class DSpm(implicit conf: FlexpretConfiguration) extends Module { 22 | val io = IO(new Bundle { 23 | val core = new DataMemCoreIO() 24 | val bus = new DataMemBusIO() 25 | }) 26 | 27 | def split(in: UInt): Vec[UInt] = { 28 | VecInit( 29 | in(7, 0), 30 | in(15, 8), 31 | in(23, 16), 32 | in(31, 24) 33 | ) 34 | } 35 | 36 | // memory for data SPM 37 | // Sequential-read, sequential-write 38 | val dspm = SyncReadMem(conf.dMemDepth, Vec(4, UInt(8.W))) 39 | 40 | // read/write port for core 41 | val corePort = dspm.read(io.core.addr, io.core.enable) 42 | io.core.data_out := corePort.asUInt 43 | when (io.core.enable) { 44 | dspm.write(io.core.addr, split(io.core.data_in), io.core.byte_write) 45 | } 46 | 47 | if(conf.dMemBusRW) { 48 | // read/write port for bus 49 | val busPort = dspm.read(io.bus.addr, io.bus.enable) 50 | io.bus.data_out := busPort.asUInt 51 | when (io.bus.enable) { 52 | dspm.write(io.bus.addr, split(io.bus.data_in), io.bus.byte_write) 53 | } 54 | } else { 55 | io.bus.data_out := 0.U 56 | } 57 | } 58 | -------------------------------------------------------------------------------- /src/main/scala/Core/ispm.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: ispm.scala 3 | Description: Instruction scratchpad memory 4 | Author: Edward Wang (edwardw@eecs.berkeley.edu) 5 | Contributors: 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package flexpret.core 9 | 10 | import chisel3._ 11 | import chisel3.util._ 12 | import _root_.dataclass.data 13 | 14 | class InstMemCoreIO(implicit conf: FlexpretConfiguration) extends Bundle { 15 | val r = new Bundle { 16 | // read port 17 | val addr = Input(UInt(conf.iMemAddrBits.W)) 18 | val enable = Input(Bool()) 19 | val data_out = Output(UInt(32.W)) 20 | } 21 | val rw = new Bundle { 22 | // read/write port 23 | val addr = Input(UInt(conf.iMemAddrBits.W)) 24 | val enable = Input(Bool()) 25 | val data_out = Output(UInt(32.W)) 26 | val write = Input(Bool()) 27 | val data_in = Input(UInt(32.W)) 28 | } 29 | } 30 | 31 | class DualPortBram(addrBits: Int, dataBits: Int) 32 | extends BlackBox(Map("DATA" -> dataBits, "ADDR" -> addrBits)) { 33 | val io = IO(new Bundle { 34 | val clk = Input(Clock()) 35 | val a_wr = Input(Bool()) 36 | val a_addr = Input(UInt(addrBits.W)) 37 | val a_din = Input(UInt(dataBits.W)) 38 | val a_dout = Output(UInt(dataBits.W)) 39 | 40 | val b_wr = Input(Bool()) 41 | val b_addr = Input(UInt(addrBits.W)) 42 | val b_din = Input(UInt(dataBits.W)) 43 | val b_dout = Output(UInt(dataBits.W)) 44 | }) 45 | } 46 | 47 | /** 48 | * Instruction scratchpad memory is a 1r1rw memory. The instruction fetch stage 49 | * has a read port, the EXE (?) also has a r/w port for stores and loads into 50 | * the IMEM. 51 | * 52 | * @param conf 53 | */ 54 | class ISpm(implicit conf: FlexpretConfiguration) extends Module { 55 | val io = IO(new Bundle { 56 | val core = new InstMemCoreIO() 57 | val bus = new InstMemBusIO() 58 | }) 59 | 60 | // NOTE: this might be dubious. Need to double-check this later. 61 | io.bus.ready := DontCare 62 | 63 | // memory for instruction SPM 64 | // sync read, sync write 65 | val ispm = Module(new DualPortBram(conf.iMemAddrBits, 32)) 66 | ispm.io.clk := clock 67 | 68 | // read port 69 | ispm.io.a_addr := io.core.r.addr 70 | ispm.io.a_wr := false.B 71 | ispm.io.a_din := 0.U 72 | io.core.r.data_out := ispm.io.a_dout 73 | 74 | // Second read/write port 75 | if (conf.iMemCoreRW || conf.iMemBusRW) { 76 | // read/write port 77 | val addr = WireDefault(0.U(32.W)) 78 | val writeData = WireDefault(0.U(32.W)) 79 | val write = WireDefault(false.B) 80 | ispm.io.b_addr := addr 81 | ispm.io.b_wr := write 82 | ispm.io.b_din := writeData 83 | val readData = ispm.io.b_dout 84 | 85 | if (conf.iMemBusRW) { 86 | io.bus.data_out := readData 87 | io.bus.ready := true.B 88 | when(io.bus.enable) { 89 | addr := io.bus.addr 90 | when(io.bus.write) { 91 | write := true.B 92 | writeData := io.bus.data_in 93 | } 94 | } 95 | } else { 96 | io.bus.data_out := DontCare 97 | } 98 | // Core has priority over bus 99 | if (conf.iMemCoreRW) { 100 | io.core.rw.data_out := readData 101 | when(io.core.rw.enable) { 102 | addr := io.core.rw.addr 103 | if(conf.iMemBusRW) io.bus.ready := false.B 104 | when(io.core.rw.write) { 105 | write := true.B 106 | writeData := io.core.rw.data_in 107 | } 108 | } 109 | } 110 | } else { 111 | io.bus.ready := false.B 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/main/scala/Core/lock.scala: -------------------------------------------------------------------------------- 1 | package flexpret.core 2 | import chisel3._ 3 | import chisel3.util._ 4 | 5 | // A simple implementation of a single lock. No fairness guarantee 6 | // Meant to be used as a spinlock. If a lock acquisition fails, 7 | // the thread will have to retry until it gets it. 8 | 9 | class LockIO(implicit val conf: FlexpretConfiguration) extends Bundle { 10 | val valid = Input(Bool()) 11 | val tid = Input(UInt(conf.threadBits.W)) 12 | val acquire = Input(Bool()) 13 | val grant = Output(Bool()) 14 | 15 | def driveDefaultsFlipped() = { 16 | valid := false.B 17 | tid := 0.U 18 | acquire := false.B 19 | } 20 | 21 | def driveDefaults() = { 22 | grant := false.B 23 | } 24 | } 25 | 26 | class Lock(implicit val conf: FlexpretConfiguration) extends Module { 27 | val io = IO(new LockIO()) 28 | io.driveDefaults() 29 | 30 | // The lock 31 | val regLocked = RegInit(false.B) 32 | val regOwner = RegInit(0.U(conf.threadBits.W)) 33 | 34 | // Handle transactions 35 | when(io.valid) { 36 | when(io.acquire) { 37 | // Thread tries to lock 38 | when(!regLocked) { 39 | regLocked := true.B 40 | regOwner := io.tid 41 | io.grant := true.B 42 | }.otherwise { 43 | io.grant := false.B 44 | } 45 | }.otherwise { 46 | assert(regLocked, cf"thread ${io.tid} tried to release unlocked lock") 47 | when (io.tid === regOwner) { 48 | regLocked := false.B 49 | regOwner := 0.U 50 | io.grant := true.B 51 | }.otherwise { 52 | io.grant := false.B 53 | assert(false.B, cf"thread-${io.tid} tried to release locked owned by thread-${regOwner}") 54 | } 55 | } 56 | 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /src/main/scala/Core/main.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: main.scala 3 | Description: Main function to elaborate the FlexPRET processor. 4 | Author: Michael Zimmer (mzimmer@eecs.berkeley.edu) 5 | Contributors: Edward Wang (edwardw@eecs.berkeley.edu) 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package Core 9 | 10 | import chisel3._ 11 | 12 | // Remove this eventually... 13 | import flexpret.core.{VerilatorTop, FpgaTop} 14 | import flexpret.core.FlexpretConfiguration 15 | 16 | object CoreMain { 17 | 18 | def verilatorMain(args: Array[String]) : Unit = { 19 | val configHash = args(0).asUInt(32.W) 20 | val chiselArgs = args.slice(1, args.length) 21 | val coreConfig = FlexpretConfiguration.fromFile() 22 | 23 | // Pass configuration to FlexPRET processor. 24 | (new chisel3.stage.ChiselStage).emitVerilog(new VerilatorTop(coreConfig, configHash), chiselArgs) 25 | } 26 | 27 | def fpgaMain(args: Array[String]) : Unit = { 28 | val configHash = args(0).asUInt 29 | val chiselArgs = args.slice(1, args.length) 30 | val coreConfig = FlexpretConfiguration.fromFile() 31 | 32 | // Pass configuration to FlexPRET processor. 33 | (new chisel3.stage.ChiselStage).emitVerilog(new FpgaTop(coreConfig, configHash), chiselArgs) 34 | } 35 | 36 | def main(args: Array[String]): Unit = { 37 | if (args.length < 2) { 38 | sys.error(s"Usage sbt run [target] [configHash]") 39 | } 40 | val target = args(0) 41 | if (target == "verilator") { 42 | verilatorMain(args.slice(1, args.length)) 43 | } else if (target == "fpga"){ 44 | fpgaMain(args.slice(1, args.length)) 45 | } else { 46 | sys.error(s"Unrecognized target $target. Currently only supports `verilator` and `fpga`") 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /src/main/scala/Core/util.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | File: util.scala 3 | Description: Various utilities and helper functions 4 | Author: Edward Wang (edwardw@eecs.berkeley.edu) 5 | Contributors: 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package flexpret.util 9 | 10 | import chisel3._ 11 | import chisel3.util.BitPat 12 | import scala.language.implicitConversions 13 | 14 | object uintToBitPatObject { 15 | implicit def uintToBitPat(x: UInt): BitPat = BitPat(x) 16 | } 17 | -------------------------------------------------------------------------------- /src/main/scala/Wishbone/WishboneBus.scala: -------------------------------------------------------------------------------- 1 | package flexpret 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | import wishbone.WishboneIO 7 | 8 | // Wishbone bus inspired by Martonis wbPlumbing repo: https://github.com/Martoni/WbPlumbing/blob/master/src/main/scala/wbplumbing/wbplumbing.scala 9 | 10 | class WishboneBusIO( 11 | val masterWidth: Int, 12 | val deviceWidths: Seq[Int] 13 | ) extends Bundle { 14 | val wbMaster = Flipped(new WishboneIO(masterWidth)) 15 | val wbDevices = MixedVec(deviceWidths.map{i => new WishboneIO(i)}) 16 | 17 | // Master address must be big enough 18 | require(masterWidth > deviceWidths.sum) 19 | // ALl slaves of same width 20 | for (w <- deviceWidths) require(w == deviceWidths.head) 21 | } 22 | 23 | class WishboneBus( 24 | val masterWidth: Int, 25 | val deviceWidths: Seq[Int] 26 | ) extends Module { 27 | val io = IO(new WishboneBusIO(masterWidth, deviceWidths)) 28 | io.wbMaster.setDefaultsFlipped() 29 | io.wbDevices.map(_.setDefaults()) 30 | 31 | var devAddr = Seq(0) 32 | for ((dev,i) <- io.wbDevices.zipWithIndex) { 33 | // Calculate address range of this device 34 | devAddr = devAddr ++ Seq(devAddr.last + (1 << deviceWidths(i))) 35 | dev.addr := io.wbMaster.addr 36 | dev.wrData := io.wbMaster.wrData 37 | 38 | // Only connect wires when the device is being addressed 39 | when(io.wbMaster.cyc 40 | && io.wbMaster.stb 41 | && io.wbMaster.addr >= devAddr(devAddr.length-2).U 42 | && io.wbMaster.addr < devAddr.last.U 43 | ) { 44 | dev.we := io.wbMaster.we 45 | dev.stb := io.wbMaster.stb 46 | dev.cyc := io.wbMaster.cyc 47 | io.wbMaster.rdData := dev.rdData 48 | io.wbMaster.ack := dev.ack 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /src/main/scala/Wishbone/WishboneMaster.scala: -------------------------------------------------------------------------------- 1 | package flexpret 2 | import chisel3._ 3 | import chisel3.util._ 4 | import Core.FlexpretConstants._ 5 | import flexpret.core.{BusIO, FlexpretConfiguration} 6 | 7 | import wishbone.{WishboneIO} 8 | 9 | class WishboneMaster(addrBits: Int)(implicit conf: FlexpretConfiguration) extends Module { 10 | val wbIO = IO(new WishboneIO(addrBits)) 11 | val busIO = IO(new BusIO()) 12 | 13 | wbIO.setDefaults() 14 | busIO.driveDefaults() // FIXME: Change to setDefaults 15 | 16 | // Registers with 1CC access latency from FlexPret core 17 | val regAddr = RegInit(0.U(conf.busAddrBits.W)) 18 | val regWriteData = RegInit(0.U(32.W)) 19 | val regReadData = RegInit(0.U(32.W)) 20 | val regStatus = RegInit(false.B) 21 | 22 | // Use a single register to delay read-out for a single cycle 23 | val regBusRead = RegInit(0.U(32.W)) 24 | busIO.data_out := regBusRead 25 | 26 | 27 | // Simple, un-optimized implementation of the wishbone protocol 28 | val sIdle :: sDoWrite :: sDoRead :: Nil = Enum(3) 29 | val regState = RegInit(sIdle) 30 | 31 | val wDoRead = WireDefault(false.B) 32 | val wDoWrite = WireDefault(false.B) 33 | assert(!(wDoRead && wDoWrite), "Both read and write at the same time") 34 | assert(!(busIO.enable && regState =/= sIdle), "Recevied bus request while busy") 35 | 36 | switch(regState) { 37 | // Idle state. Waiting for request from FlexPret Core 38 | // Decouples the FlexPret load/store instructions from 39 | // accessing the WB bus 40 | is (sIdle) { 41 | // Handle read/write transactions from core 42 | when(busIO.enable) { 43 | val addr = busIO.addr 44 | when(busIO.write) { 45 | // Handle writes 46 | when(addr === MMIO_READ_ADDR) { 47 | regAddr := busIO.data_in 48 | // Writing to the READ_ADDR will trigger a read on the WB bus 49 | wDoRead := true.B 50 | }.elsewhen(addr === MMIO_WRITE_ADDR) { 51 | regAddr := busIO.data_in 52 | // Writing to the WRITE_ADDR will trigger a write on the WB bus 53 | // the WRITE_DATA register must be written before the write to the addr 54 | wDoWrite := true.B 55 | }.elsewhen(addr === MMIO_WRITE_DATA) { 56 | regWriteData := busIO.data_in 57 | }.otherwise { 58 | assert(false.B, "Tried to write to invalid address %d on wishbone bus master",addr) 59 | } 60 | }.otherwise { 61 | // Handle reads 62 | when(addr === MMIO_READ_DATA) { 63 | regBusRead := regReadData 64 | }.elsewhen(addr === MMIO_STATUS) { 65 | regBusRead := regStatus 66 | regStatus := false.B 67 | }.otherwise { 68 | assert(false.B, "Tried to read from invalid address %d on wishbone bus master", addr) 69 | } 70 | } 71 | } 72 | when (wDoRead) { 73 | regState := sDoRead 74 | }.elsewhen(wDoWrite) { 75 | regState := sDoWrite 76 | } 77 | // assert(!wbIO.ack, "WBm in idle mode and recived ACK") 78 | } 79 | 80 | // Perform read operation. Drive read signals until we get an ack 81 | is (sDoRead) { 82 | wbIO.driveReadReq(regAddr) 83 | regStatus := false.B 84 | when(wbIO.ack) { 85 | regReadData := wbIO.rdData 86 | regStatus := true.B 87 | regState := sIdle 88 | } 89 | } 90 | 91 | // Perform write operation. Drive write signals until we get an ack 92 | is (sDoWrite) { 93 | wbIO.driveWriteReq(regAddr, regWriteData) 94 | regStatus := false.B 95 | when(wbIO.ack) { 96 | regStatus := true.B 97 | regState := sIdle 98 | } 99 | } 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /src/main/scala/Wishbone/WishboneUart.scala: -------------------------------------------------------------------------------- 1 | package flexpret 2 | 3 | import chisel3._ 4 | import chisel3.util._ 5 | 6 | import chisel.lib.uart.{Rx, Tx} 7 | import chisel.lib.fifo 8 | import wishbone.WishboneDevice 9 | import flexpret.core.FlexpretConfiguration 10 | 11 | object Constants { 12 | val TX_ADDR=0 13 | val RX_ADDR=4 14 | val CSR_ADDR=8 15 | val CONST_ADDR=12 16 | 17 | val DATA_RDY_BIT=0 18 | val TX_FIFO_FULL_BIT=1 19 | val FAULT_BAD_ADDR_BIT=2 20 | 21 | val CONST_VALUE=0x55 22 | } 23 | import Constants._ 24 | 25 | class WishboneUartIO extends Bundle { 26 | val rx = Input(Bool()) 27 | val tx = Output(Bool()) 28 | } 29 | 30 | class WishboneUart(implicit cfg: FlexpretConfiguration) extends WishboneDevice(4) { 31 | val ioUart = IO(new WishboneUartIO()) 32 | 33 | val rx = Module(new Rx(cfg.clkFreqHz, cfg.uartBaudrate)).io 34 | val tx = Module(new Tx(cfg.clkFreqHz, cfg.uartBaudrate)).io 35 | 36 | ioUart.tx := tx.txd 37 | rx.rxd := ioUart.rx 38 | 39 | 40 | // FIXME: Use Martins FIFO 41 | // MS: Queue is fine as well 42 | val rxFifo = Module(new Queue(UInt(8.W), 8)).io 43 | val txFifo = Module(new Queue(UInt(8.W), 8)).io 44 | rx.channel <> rxFifo.enq 45 | tx.channel <> txFifo.deq 46 | txFifo.enq.valid :=false.B 47 | txFifo.enq.bits := 0.U 48 | rxFifo.deq.ready := false.B 49 | 50 | val fault_bad_addr = RegInit(false.B) 51 | 52 | // Status register 1. DataRdy, 2. TxFifoFull 3. Bad address 53 | val regCSR = RegInit(0.U(3.W)) 54 | val wCSR = WireInit(VecInit(Seq.fill(3)(false.B))) 55 | wCSR(DATA_RDY_BIT) := rxFifo.deq.valid 56 | wCSR(TX_FIFO_FULL_BIT) := !txFifo.enq.ready 57 | wCSR(FAULT_BAD_ADDR_BIT) := fault_bad_addr 58 | regCSR := wCSR.asUInt 59 | 60 | // Read register 61 | val regReadData = RegInit(0.U(8.W)) 62 | 63 | // Wire it up 64 | val port = io.port 65 | port.setDefaultsFlipped() 66 | port.rdData := regReadData 67 | 68 | val sIdle :: sRead :: sWrite :: Nil = Enum(3) 69 | val regState = RegInit(sIdle) 70 | val start = port.cyc & port.stb 71 | 72 | switch (regState) { 73 | is(sIdle) { 74 | when(start) { 75 | when(port.we) { 76 | when(port.addr === TX_ADDR.U) { 77 | txFifo.enq.valid := true.B 78 | txFifo.enq.bits := port.wrData 79 | assert(txFifo.enq.fire) 80 | }.otherwise { 81 | fault_bad_addr := true.B 82 | } 83 | regState := sWrite 84 | }.otherwise { 85 | when(port.addr === RX_ADDR.U) { 86 | regReadData := rxFifo.deq.bits 87 | rxFifo.deq.ready := true.B 88 | assert(rxFifo.deq.fire) 89 | }.elsewhen(port.addr === CSR_ADDR.U) { 90 | regReadData := regCSR 91 | fault_bad_addr := false.B 92 | }.elsewhen(port.addr === CONST_ADDR.U) { 93 | regReadData := CONST_VALUE.U 94 | }.otherwise { 95 | fault_bad_addr := true.B 96 | } 97 | regState := sRead 98 | } 99 | } 100 | } 101 | 102 | is(sWrite) { 103 | port.ack := true.B 104 | regState := sIdle 105 | } 106 | 107 | is(sRead) { 108 | port.ack := true.B 109 | regState := sIdle 110 | regReadData := 0.U 111 | } 112 | } 113 | } 114 | -------------------------------------------------------------------------------- /src/test/scala/core/ALUTest.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Description: ALU tester. 3 | Author: Edward Wang 4 | Shaokai Lin 5 | Contributors: 6 | License: See LICENSE.txt 7 | ******************************************************************************/ 8 | package flexpret.core.test 9 | 10 | import chisel3._ 11 | import chiseltest._ 12 | import org.scalatest.flatspec.AnyFlatSpec 13 | import org.scalatest.matchers.should.Matchers 14 | 15 | import flexpret.core.ALU 16 | import flexpret.core.ALUTypes 17 | 18 | class ALUTest extends AnyFlatSpec with ChiselScalatestTester { 19 | behavior of "ALU" 20 | 21 | def alu = new ALU() 22 | 23 | it should "shift correctly when the shift amount uses 5 bits" in { 24 | test(alu).withAnnotations(Seq(treadle.WriteVcdAnnotation)) { c => 25 | c.io.op1.poke("h80000000".U(32.W)) 26 | c.io.shift.poke("b11111".U(5.W)) 27 | c.io.func.poke(ALUTypes.ShiftRightLogical) 28 | c.clock.step() 29 | c.io.result.expect("h00000001".U(32.W)) 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /src/test/scala/core/ImemSimulator.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Description: Test for a simple core. 3 | Author: Edward Wang 4 | Contributors: 5 | License: See LICENSE.txt 6 | ******************************************************************************/ 7 | package flexpret.core.test 8 | 9 | import org.scalatest._ 10 | 11 | import chisel3._ 12 | import org.scalatest.flatspec.AnyFlatSpec 13 | import org.scalatest.matchers.should.Matchers 14 | 15 | import chiseltest._ 16 | 17 | import Core.FlexpretConstants._ 18 | 19 | import flexpret.core._ 20 | 21 | /** 22 | * Simulate an instruction memory module on a InstMemCoreIO. 23 | * @param prog Program memory as Chisel hex strings. Example: 24 | * val prog: Seq[String] = scala.collection.immutable.Vector( 25 | * "h00100313", // li t1,1 26 | "hcc431073", // csrw 0xcc4,t1 27 | "h00000013", // nop 28 | "hcc401073", // csrw 0xcc4,zero 29 | "h00000013", // nop 30 | "hfedff06f", // j 0 31 | ) 32 | * @param defaultInstr Default instruction if out of bounds (as a Chisel hex str). 33 | * @param startAddr imem entry to start instructions at (default 0). 34 | * NOTE: this is not a byte address! e.g. 1 would correspond 35 | * to instruction at PC+4. 36 | */ 37 | class ImemSimulator( 38 | val prog: Seq[String], 39 | val defaultInstr: String, 40 | val clk: Clock, val memIO: InstMemCoreIO, 41 | val startAddr: Int = 0) { 42 | require(startAddr >= 0, "Start must be positive") 43 | 44 | def sim(cycles: Int): Unit = { 45 | var i = 0 46 | while (i < cycles) { i += 1; 47 | val addr = memIO.r.addr.peek().litValue.toInt 48 | val enabled = memIO.r.enable.peek().litValue 49 | clk.step() 50 | 51 | val imemData = prog.lift.apply(addr - startAddr).getOrElse(defaultInstr) 52 | memIO.r.data_out.poke(imemData.U) 53 | } 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /src/test/scala/core/LoadStoreTest.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Description: LoadStore tester. 3 | Author: Edward Wang 4 | Contributors: 5 | License: See LICENSE.txt 6 | ******************************************************************************/ 7 | package flexpret.core.test 8 | 9 | import chisel3._ 10 | 11 | import chiseltest._ 12 | import org.scalatest.flatspec.AnyFlatSpec 13 | import org.scalatest.matchers.should.Matchers 14 | 15 | import Core.FlexpretConstants._ 16 | import Core.LoadStore 17 | 18 | import flexpret.core.FlexpretConfiguration 19 | import flexpret.core.InstMemConfiguration 20 | 21 | class LoadStoreTest extends AnyFlatSpec with ChiselScalatestTester { 22 | behavior of "LoadStore" 23 | 24 | val threads = 1 25 | val conf = FlexpretConfiguration(threads=threads, flex=false, clkFreqMHz=100, 26 | InstMemConfiguration(bypass=false, sizeKB=512), 27 | dMemKB=512, mul=false, priv=false, features="all") 28 | def loadStore = new LoadStore()(conf=conf) 29 | 30 | it should "not crash with an invalid request if not enabled" in { 31 | test(loadStore).withAnnotations(Seq(treadle.WriteVcdAnnotation)) { c => 32 | timescope { 33 | // No load or store operation with (invalid) SH lingering 34 | c.io.load.poke(false.B) 35 | c.io.store.poke(false.B) 36 | c.io.mem_type.poke(MEM_SH) 37 | c.io.addr.poke(1.U) // intentionally misaligned 38 | c.clock.step() 39 | } 40 | } 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/test/scala/core/LockTest.scala: -------------------------------------------------------------------------------- 1 | package flexpret.core.test 2 | 3 | import org.scalatest._ 4 | 5 | import chisel3._ 6 | 7 | import chiseltest._ 8 | import org.scalatest.flatspec.AnyFlatSpec 9 | import org.scalatest.matchers.should.Matchers 10 | 11 | import Core.FlexpretConstants._ 12 | import flexpret.core.Lock 13 | import flexpret.core.FlexpretConfiguration 14 | import flexpret.core.InstMemConfiguration 15 | 16 | 17 | class LockTest extends FlatSpec with ChiselScalatestTester { 18 | behavior of "Lock" 19 | 20 | val threads = 2 21 | val conf = FlexpretConfiguration(threads=threads, flex=false, clkFreqMHz=100, 22 | InstMemConfiguration(bypass=false, sizeKB=512), 23 | dMemKB=512, mul=false, priv=false, features="all") 24 | 25 | // Acquire lock and expect grant immediatly 26 | def acquire(c: Lock, tid: Int) = { 27 | timescope { 28 | c.io.valid.poke(true.B) 29 | c.io.acquire.poke(true.B) 30 | c.io.tid.poke(tid.U) 31 | c.io.grant.expect(true.B) 32 | c.clock.step() 33 | } 34 | } 35 | 36 | // Acquire lock and expect grant immediatly 37 | def notAcuire(c: Lock, tid: Int) = { 38 | timescope { 39 | c.io.valid.poke(true.B) 40 | c.io.acquire.poke(true.B) 41 | c.io.tid.poke(tid.U) 42 | c.io.grant.expect(false.B) 43 | c.clock.step() 44 | } 45 | } 46 | 47 | def release(c: Lock, tid: Int) = { 48 | timescope { 49 | c.io.valid.poke(true.B) 50 | c.io.acquire.poke(false.B) 51 | c.io.tid.poke(tid.U) 52 | c.io.grant.expect(true.B) 53 | c.clock.step() 54 | } 55 | } 56 | 57 | 58 | it should "initialize" in { 59 | test(new Lock()(conf)) { c => 60 | c.io.grant.expect(false.B) 61 | } 62 | } 63 | it should "do simpple lock/unlock" in { 64 | test(new Lock()(conf)) { c => 65 | acquire(c,0) 66 | release(c,0) 67 | } 68 | } 69 | it should "do mutual exclusion" in { 70 | test(new Lock()(conf)) { c => 71 | acquire(c,0) 72 | 73 | for (i <- 0 until 10) { 74 | notAcuire(c,1) 75 | } 76 | release(c,0) 77 | acquire(c,1) 78 | release(c,1) 79 | } 80 | } 81 | // it should "Assert when releasing unlocked" in { 82 | // test(new Lock()(conf)) { c => 83 | // release(c,1) 84 | // } 85 | // } 86 | // it should "Assert when releasing other threads lock" in { 87 | // test(new Lock()(conf)) { c => 88 | // acquire(c,0) 89 | // release(c,1) 90 | // } 91 | // } 92 | 93 | 94 | } 95 | -------------------------------------------------------------------------------- /src/test/scala/core/StoreMaskTest.scala: -------------------------------------------------------------------------------- 1 | /****************************************************************************** 2 | Description: Store mask unit test. 3 | Author: Edward Wang 4 | Contributors: 5 | License: See LICENSE.txt 6 | ******************************************************************************/ 7 | package flexpret.core.test 8 | 9 | import chisel3._ 10 | import org.scalatest.flatspec.AnyFlatSpec 11 | 12 | import chiseltest._ 13 | import org.scalatest.flatspec.AnyFlatSpec 14 | import org.scalatest.matchers.should.Matchers 15 | 16 | import Core.StoreMask 17 | import Core.FlexpretConstants._ 18 | 19 | class StoreMaskTest extends AnyFlatSpec with ChiselScalatestTester { 20 | behavior of "StoreMask" 21 | 22 | /* Simple module for testing purposes */ 23 | class StoreMaskModule extends Module { 24 | val address = IO(Input(UInt(4.W))) 25 | val memType = IO(Input(UInt(4.W))) 26 | val mask = IO(Output(UInt(4.W))) 27 | mask := StoreMask(address, memType, enable=true.B) 28 | } 29 | 30 | it should "store bytes correctly" in { 31 | test(new StoreMaskModule) { c => 32 | c.memType.poke(MEM_SB) 33 | c.address.poke(4.U) 34 | c.mask.expect("b0001".U) 35 | c.address.poke(5.U) 36 | c.mask.expect("b0010".U) 37 | c.address.poke(6.U) 38 | c.mask.expect("b0100".U) 39 | c.address.poke(7.U) 40 | c.mask.expect("b1000".U) 41 | } 42 | } 43 | 44 | it should "store half-words correctly" in { 45 | test(new StoreMaskModule) { c => 46 | c.memType.poke(MEM_SH) 47 | c.address.poke(4.U) 48 | c.mask.expect("b0011".U) 49 | c.address.poke(6.U) 50 | c.mask.expect("b1100".U) 51 | c.address.poke(8.U) 52 | c.mask.expect("b0011".U) 53 | c.address.poke(10.U) 54 | c.mask.expect("b1100".U) 55 | } 56 | } 57 | 58 | it should "store words correctly" in { 59 | test(new StoreMaskModule) { c => 60 | c.memType.poke(MEM_SW) 61 | c.address.poke(0.U) 62 | c.mask.expect("b1111".U) 63 | c.address.poke(4.U) 64 | c.mask.expect("b1111".U) 65 | c.address.poke(12.U) 66 | c.mask.expect("b1111".U) 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/test/scala/wb/WishboneDeviceUtils.scala: -------------------------------------------------------------------------------- 1 | package flexpret.Wishbone 2 | 3 | import chisel3._ 4 | import chiseltest._ 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | import wishbone._ 7 | 8 | object WishboneDeviceUtils { 9 | def wbWrite(c: WishboneDevice, addr: Int, data: Int) = { 10 | timescope { 11 | c.io.port.we.poke(true.B) 12 | c.io.port.addr.poke(addr.U) 13 | c.io.port.wrData.poke(data.U) 14 | c.io.port.cyc.poke(true.B) 15 | c.io.port.stb.poke(true.B) 16 | c.io.port.sel.poke(15.U) 17 | c.clock.step(1) 18 | c.io.port.ack.expect(true.B) 19 | } 20 | } 21 | 22 | def wbExpectRead(c: WishboneDevice, addr: Int, eData: Int) = { 23 | timescope { 24 | c.io.port.we.poke(false.B) 25 | c.io.port.addr.poke(addr.U) 26 | c.io.port.cyc.poke(true.B) 27 | c.io.port.stb.poke(true.B) 28 | c.io.port.sel.poke(15.U) 29 | c.clock.step(1) 30 | c.io.port.ack.expect(true.B) 31 | c.io.port.rdData.expect(eData.U) 32 | 33 | } 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/test/scala/wb/WishboneUartTest.scala: -------------------------------------------------------------------------------- 1 | package interpret 2 | 3 | import chisel3._ 4 | import chiseltest._ 5 | import org.scalatest.flatspec.AnyFlatSpec 6 | 7 | import flexpret.core.{FlexpretConfiguration, InstMemConfiguration} 8 | import flexpret.Wishbone.WishboneDeviceUtils._ 9 | import flexpret.{WishboneUart, Constants} 10 | 11 | class WishboneUartTest extends AnyFlatSpec with ChiselScalatestTester { 12 | 13 | behavior of "WishboneUart" 14 | 15 | val READ_ADDR = 0 16 | val WRITE_ADDR = 4 17 | val WRITE_DATA = 8 18 | val READ_DATA = 12 19 | val STATUS = 16 20 | 21 | val cfg = FlexpretConfiguration(threads = 1, flex = false, clkFreqMHz=100, 22 | InstMemConfiguration(bypass = true, sizeKB = 4), 23 | dMemKB = 256, mul = false, priv = false, features = "all" 24 | ) 25 | 26 | def wb = new WishboneUart()(cfg) 27 | 28 | it should "initialize" in { 29 | test(wb) { c => 30 | c.io.port.rdData.expect(false.B) 31 | c.ioUart.tx.expect(true.B) 32 | } 33 | } 34 | 35 | it should "receive write" in { 36 | test(wb) { c => 37 | wbWrite(c, Constants.TX_ADDR, 8) 38 | 39 | c.clock.step(1) 40 | 41 | // Check for errors 42 | wbExpectRead(c, Constants.CSR_ADDR, 0) 43 | } 44 | } 45 | 46 | it should "do a read" in { 47 | test(wb) { c => 48 | // Read the magic register 49 | wbExpectRead(c, Constants.CONST_ADDR, Constants.CONST_VALUE) 50 | 51 | c.clock.step(1) 52 | 53 | // Check for errors in CSR 54 | wbExpectRead(c, Constants.CSR_ADDR, 0) 55 | } 56 | } 57 | 58 | it should "write a bad address, get an error and clear it" in { 59 | test(wb) { c => 60 | // Should initially be zero 61 | wbExpectRead(c, Constants.CSR_ADDR, 0) 62 | c.clock.step(1) 63 | 64 | // Write to a bad address 65 | wbWrite(c, 2, 0) 66 | c.clock.step(1) 67 | 68 | // Expect bad addr bit set 69 | wbExpectRead(c, Constants.CSR_ADDR, (1 << Constants.FAULT_BAD_ADDR_BIT)) 70 | c.clock.step(1) 71 | 72 | // Bad addr bit is automatically cleared on reading the CSR, which we just did 73 | wbExpectRead(c, Constants.CSR_ADDR, 0) 74 | } 75 | } 76 | } 77 | --------------------------------------------------------------------------------