From 932413bb3dc28b13dfde01c572e3b4918f44e508 Mon Sep 17 00:00:00 2001 From: peteraa Date: Fri, 7 Jun 2019 17:43:33 +0200 Subject: [PATCH] Nuke --- .gitignore | 355 +++++++++++ README.md | 31 + TODO.org | 49 ++ build.sbt | 71 +++ deliver.sh | 25 + exercise.org | 120 ++++ instructions.org | 449 ++++++++++++++ project/Dependencies.scala | 30 + project/build.properties | 1 + sbt.sh | 578 ++++++++++++++++++ src/main/scala/CPU.scala | 57 ++ src/main/scala/Const.scala | 60 ++ src/main/scala/DMem.scala | 54 ++ src/main/scala/Decoder.scala | 82 +++ src/main/scala/ID.scala | 49 ++ src/main/scala/IF.scala | 59 ++ src/main/scala/IMem.scala | 52 ++ src/main/scala/MEM.scala | 41 ++ src/main/scala/Registers.scala | 81 +++ src/main/scala/SetupSignals.scala | 51 ++ src/main/scala/Tile.scala | 75 +++ src/main/scala/ToplevelSignals.scala | 105 ++++ .../resources/tests/basic/arithmetic/arith.s | 52 ++ src/test/resources/tests/basic/forward1.s | 205 +++++++ src/test/resources/tests/basic/forward2.s | 205 +++++++ .../resources/tests/basic/immediate/addi.s | 41 ++ .../tests/basic/immediate/arithImm.s | 37 ++ .../tests/basic/immediate/arithmetic.s | 0 src/test/resources/tests/basic/load.s | 35 ++ src/test/resources/tests/basic/load2.s | 20 + .../resources/tests/programs/BTreeManyO3.s | 180 ++++++ src/test/resources/tests/programs/BTreeO3.s | 142 +++++ src/test/resources/tests/programs/add.s | 14 + src/test/resources/tests/programs/memoFib.s | 118 ++++ src/test/resources/tests/programs/naiveFib.s | 49 ++ .../resources/tests/programs/palindrome.s | 112 ++++ .../resources/tests/programs/palindromeO3.s | 120 ++++ .../tests/programs/searchRegularO0.s | 187 ++++++ .../resources/tests/programs/source/memoFib.c | 38 ++ .../tests/programs/source/naiveFib.c | 20 + .../tests/programs/source/palindrome.c | 28 + .../resources/tests/programs/source/search.c | 50 ++ .../tests/programs/source/searchMany.c | 64 ++ .../resources/tests/programs/source/square.c | 35 ++ src/test/resources/tests/programs/square.s | 80 +++ src/test/scala/Manifest.scala | 78 +++ src/test/scala/RISCV/DataTypes.scala | 357 +++++++++++ src/test/scala/RISCV/LogParser.scala | 184 ++++++ src/test/scala/RISCV/Ops.scala | 124 ++++ src/test/scala/RISCV/Parser.scala | 357 +++++++++++ src/test/scala/RISCV/VM.scala | 171 ++++++ src/test/scala/RISCV/assembler.scala | 269 ++++++++ src/test/scala/RISCV/deleteMe.scala | 178 ++++++ src/test/scala/RISCV/deleteMe2.scala | 164 +++++ src/test/scala/RISCV/printUtils.scala | 352 +++++++++++ src/test/scala/RISCV/regNames.scala | 114 ++++ src/test/scala/RISCV/testRunner.scala | 103 ++++ src/test/scala/TestUtils.scala | 166 +++++ src/test/scala/chiselTestRunner.scala | 188 ++++++ src/test/scala/fileUtils.scala | 94 +++ src/test/scala/testConf.scala | 43 ++ 61 files changed, 7249 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 TODO.org create mode 100644 build.sbt create mode 100755 deliver.sh create mode 100644 exercise.org create mode 100644 instructions.org create mode 100644 project/Dependencies.scala create mode 100644 project/build.properties create mode 100755 sbt.sh create mode 100644 src/main/scala/CPU.scala create mode 100644 src/main/scala/Const.scala create mode 100644 src/main/scala/DMem.scala create mode 100644 src/main/scala/Decoder.scala create mode 100644 src/main/scala/ID.scala create mode 100644 src/main/scala/IF.scala create mode 100644 src/main/scala/IMem.scala create mode 100644 src/main/scala/MEM.scala create mode 100644 src/main/scala/Registers.scala create mode 100644 src/main/scala/SetupSignals.scala create mode 100644 src/main/scala/Tile.scala create mode 100644 src/main/scala/ToplevelSignals.scala create mode 100644 src/test/resources/tests/basic/arithmetic/arith.s create mode 100644 src/test/resources/tests/basic/forward1.s create mode 100644 src/test/resources/tests/basic/forward2.s create mode 100644 src/test/resources/tests/basic/immediate/addi.s create mode 100644 src/test/resources/tests/basic/immediate/arithImm.s create mode 100644 src/test/resources/tests/basic/immediate/arithmetic.s create mode 100644 src/test/resources/tests/basic/load.s create mode 100644 src/test/resources/tests/basic/load2.s create mode 100644 src/test/resources/tests/programs/BTreeManyO3.s create mode 100644 src/test/resources/tests/programs/BTreeO3.s create mode 100644 src/test/resources/tests/programs/add.s create mode 100644 src/test/resources/tests/programs/memoFib.s create mode 100644 src/test/resources/tests/programs/naiveFib.s create mode 100644 src/test/resources/tests/programs/palindrome.s create mode 100644 src/test/resources/tests/programs/palindromeO3.s create mode 100644 src/test/resources/tests/programs/searchRegularO0.s create mode 100644 src/test/resources/tests/programs/source/memoFib.c create mode 100644 src/test/resources/tests/programs/source/naiveFib.c create mode 100644 src/test/resources/tests/programs/source/palindrome.c create mode 100644 src/test/resources/tests/programs/source/search.c create mode 100644 src/test/resources/tests/programs/source/searchMany.c create mode 100644 src/test/resources/tests/programs/source/square.c create mode 100644 src/test/resources/tests/programs/square.s create mode 100644 src/test/scala/Manifest.scala create mode 100644 src/test/scala/RISCV/DataTypes.scala create mode 100644 src/test/scala/RISCV/LogParser.scala create mode 100644 src/test/scala/RISCV/Ops.scala create mode 100644 src/test/scala/RISCV/Parser.scala create mode 100644 src/test/scala/RISCV/VM.scala create mode 100644 src/test/scala/RISCV/assembler.scala create mode 100644 src/test/scala/RISCV/deleteMe.scala create mode 100644 src/test/scala/RISCV/deleteMe2.scala create mode 100644 src/test/scala/RISCV/printUtils.scala create mode 100644 src/test/scala/RISCV/regNames.scala create mode 100644 src/test/scala/RISCV/testRunner.scala create mode 100644 src/test/scala/TestUtils.scala create mode 100644 src/test/scala/chiselTestRunner.scala create mode 100644 src/test/scala/fileUtils.scala create mode 100644 src/test/scala/testConf.scala diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..98947e0 --- /dev/null +++ b/.gitignore @@ -0,0 +1,355 @@ +### Project Specific stuff +test_run_dir/* +### XilinxISE template +# intermediate build files +*.bgn +*.bit +*.bld +*.cmd_log +*.drc +*.ll +*.lso +*.msd +*.msk +*.ncd +*.ngc +*.ngd +*.ngr +*.pad +*.par +*.pcf +*.prj +*.ptwx +*.rbb +*.rbd +*.stx +*.syr +*.twr +*.twx +*.unroutes +*.ut +*.xpi +*.xst +*_bitgen.xwbt +*_envsettings.html +*_map.map +*_map.mrp +*_map.ngm +*_map.xrpt +*_ngdbuild.xrpt +*_pad.csv +*_pad.txt +*_par.xrpt +*_summary.html +*_summary.xml +*_usage.xml +*_xst.xrpt + +# project-wide generated files +*.gise +par_usage_statistics.html +usage_statistics_webtalk.html +webtalk.log +webtalk_pn.xml + +# generated folders +iseconfig/ +xlnx_auto_0_xdb/ +xst/ +_ngo/ +_xmsgs/ +### Eclipse template +*.pydevproject +.metadata +.gradle +bin/ +tmp/ +*.tmp +*.bak +*.swp +*~.nib +local.properties +.settings/ +.loadpath + +# Eclipse Core +.project + +# External tool builders +.externalToolBuilders/ + +# Locally stored "Eclipse launch configurations" +*.launch + +# CDT-specific +.cproject + +# JDT-specific (Eclipse Java Development Tools) +.classpath + +# Java annotation processor (APT) +.factorypath + +# PDT-specific +.buildpath + +# sbteclipse plugin +.target + +# TeXlipse plugin +.texlipse +### C template +# Object files +*.o +*.ko +*.obj +*.elf + +# Precompiled Headers +*.gch +*.pch + +# Libraries +*.lib +*.a +*.la +*.lo + +# Shared objects (inc. Windows DLLs) +*.dll +*.so +*.so.* +*.dylib + +# Executables +*.exe +*.out +*.app +*.i*86 +*.x86_64 +*.hex + +# Debug files +*.dSYM/ +### SBT template +# Simple Build Tool +# http://www.scala-sbt.org/release/docs/Getting-Started/Directories.html#configuring-version-control + +target/ +lib_managed/ +src_managed/ +project/boot/ +.history +.cache +### Emacs template +# -*- mode: gitignore; -*- +*~ +\#*\# +/.emacs.desktop +/.emacs.desktop.lock +*.elc +auto-save-list +tramp +.\#* + +# Org-mode +.org-id-locations +*_archive + +# flymake-mode +*_flymake.* + +# eshell files +/eshell/history +/eshell/lastdir + +# elpa packages +/elpa/ + +# reftex files +*.rel + +# AUCTeX auto folder +/auto/ + +# cask packages +.cask/ +### Vim template +[._]*.s[a-w][a-z] +[._]s[a-w][a-z] +*.un~ +Session.vim +.netrwhist +*~ +### JetBrains template +# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio + +*.iml + +## Directory-based project format: +.idea/ +# if you remove the above rule, at least ignore the following: + +# User-specific stuff: +# .idea/workspace.xml +# .idea/tasks.xml +# .idea/dictionaries + +# Sensitive or high-churn files: +# .idea/dataSources.ids +# .idea/dataSources.xml +# .idea/sqlDataSources.xml +# .idea/dynamic.xml +# .idea/uiDesigner.xml + +# Gradle: +# .idea/gradle.xml +# .idea/libraries + +# Mongo Explorer plugin: +# .idea/mongoSettings.xml + +## File-based project format: +*.ipr +*.iws + +## Plugin-specific files: + +# IntelliJ +/out/ + +# mpeltonen/sbt-idea plugin +.idea_modules/ + +# JIRA plugin +atlassian-ide-plugin.xml + +# Crashlytics plugin (for Android Studio and IntelliJ) +com_crashlytics_export_strings.xml +crashlytics.properties +crashlytics-build.properties +### C++ template +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app +### OSX template +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk +### Xcode template +# Xcode +# +# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore + +## Build generated +build/ +DerivedData + +## Various settings +*.pbxuser +!default.pbxuser +*.mode1v3 +!default.mode1v3 +*.mode2v3 +!default.mode2v3 +*.perspectivev3 +!default.perspectivev3 +xcuserdata + +## Other +*.xccheckout +*.moved-aside +*.xcuserstate +### Scala template +*.class +*.log + +# sbt specific +.cache +.history +.lib/ +dist/* +target/ +lib_managed/ +src_managed/ +project/boot/ +project/plugins/project/ +.ensime +.ensime_cache/ + +# Scala-IDE specific +.scala_dependencies +.worksheet +### Java template +*.class + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.ear + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +*.fir +*.json +*.xml + +# ENSIME, metals and friends +.ensime* +.metals* +.bloop* +.projectile +target/ +scratchpad.scala +log/ +index.html diff --git a/README.md b/README.md new file mode 100644 index 0000000..ce91986 --- /dev/null +++ b/README.md @@ -0,0 +1,31 @@ +This is the coursework for the graded part of the TDT4255 course at NTNU. + +Since it is the authors opinion that most tools out there are vastly underdesigned, this project comes +with a lot of added homegrown utilities, including a RISC-V parser, assembler and interpreter. + +When you test a design with a given program, that program is first parsed, then run in a software interpreter +to get correct output, then assembled into a binary. +This binary will then be loaded to your synthesized design (your processor) by the test harness provided in +the skeleton code, along with any initial state. + +Your processor will run the supplied binary, and the changes to state (memory and registers) will be recorded +and compared with the interpreter log. + +If it matches, your processor works, if not, you get an execution trace, hopefully showing what went wrong and +where. + +To get started, read the exercise.org file, it goes over the first pieces of the puzzle. + + +If you want to learn chisel on your own and use this project please send me some feedback on what you liked, +disliked and what could have been improved :) +If you end up using it for a course you're teaching I would be thrilled too. +In this case, you can spend the time you're saving by sending a pull requests with some improvements! + +Pull requests are more than welcome! + +Nice to have list: +* More sophisticated test feedback. A detailed error report on why the processor design failed. +* Scaffolding to run synthesized designs. Preferrably targeting the PYNQ platform. +* A fix for whatever problems *you* run into when using this project. +* Either a battery of tests to find corner cases stress testing forwarders, hazard detectors etc, or even better, the tools to generate code with hazards automatically. diff --git a/TODO.org b/TODO.org new file mode 100644 index 0000000..6a7f120 --- /dev/null +++ b/TODO.org @@ -0,0 +1,49 @@ +* Tasks +** DONE File IO and test +** DONE Stop exploding the heap with logs :DDD +** DONE Fix DONE instruction for VM termination +*** DONE Add setting instructions +** DONE Add assembler +** DONE Chisel tester +** DONE Add LF +** DONE Redo colors in fansi. ANSI fucks up string formatting +** DONE Columnize log events +** DONE Chisel test log evaluator +** DONE Create giftWrapper script +** DONE Better sourceinfo stuff + Good enough + +** DONE Test options +*** DONE How much NOP pad? +*** DONE Verbosity? +*** DONE Which tests? +** DONE ish Step counter, pretty print VM log, including final memory state +** TODO More programs +*** DONE Real programs +*** TODO Basic programs + Needs more +** DONE Merge in LF changes + +** TODO Breakpoints +*** TODO VM breakpoints +**** TODO Record breakpoints in chisel tester +*** TODO Chisel breakpoints +**** TODO Freeze processor to record state +**** TODO Record breakpoints in chisel tester +*** TODO Draw breakpoints in the printer +** TODO Calculate steps needed +** TODO Unmangle derailed traces + With incorrect designs the trace printer ends up printing a lot of diveregent + unsychnronizable blocks +** DONE Fix DONE instruction +*** DONE Parse error +*** DONE Use DONE address +** DONE Hazard generator + good enough + +* Maybe +** DONE Move instruction recording to IMEM rather than IF? + Only care about what IF gets, won't have to deal with whatever logic is in IF. +** DONE Figure out why loading instructions backwards made shit werk + Not as funny as you'd think. The issue was overwriting the last written instruction with 0 + diff --git a/build.sbt b/build.sbt new file mode 100644 index 0000000..2247b5d --- /dev/null +++ b/build.sbt @@ -0,0 +1,71 @@ +def scalacOptionsVersion(scalaVersion: String): Seq[String] = { + Seq() ++ { + // If we're building with Scala > 2.11, enable the compile option + // switch to support our anonymous Bundle definitions: + // https://github.com/scala/bug/issues/10047 + CrossVersion.partialVersion(scalaVersion) match { + case Some((2, scalaMajor: Long)) if scalaMajor < 12 => Seq() + case _ => Seq("-Xsource:2.11") + } + } +} + +def javacOptionsVersion(scalaVersion: String): Seq[String] = { + Seq() ++ { + // Scala 2.12 requires Java 8. We continue to generate + // Java 7 compatible code for Scala 2.11 + // for compatibility with old clients. + CrossVersion.partialVersion(scalaVersion) match { + case Some((2, scalaMajor: Long)) if scalaMajor < 12 => + Seq("-source", "1.7", "-target", "1.7") + case _ => + Seq("-source", "1.8", "-target", "1.8") + } + } +} + +name := "FiveStage" + +version := "2.0.0" + +scalaVersion := "2.12.8" + +crossScalaVersions := Seq("2.11.12", "2.12.4") + +resolvers ++= Seq( + Resolver.sonatypeRepo("snapshots"), + Resolver.sonatypeRepo("releases") +) + +// Provide a managed dependency on X if -DXVersion="" is supplied on the command line. +val defaultVersions = Map( + "chisel3" -> "3.1.+", + "chisel-iotesters" -> "1.2.+" + ) + +libraryDependencies ++= (Seq("chisel3","chisel-iotesters").map { + dep: String => "edu.berkeley.cs" %% dep % sys.props.getOrElse(dep + "Version", defaultVersions(dep)) }) + +val versionOfScala = "2.12.4" + +val fs2Version = "0.10.3" +val catsVersion = "1.1.0" +val catsEffectVersion = "0.10" +libraryDependencies ++= Dependencies.backendDeps.value +scalacOptions ++= scalacOptionsVersion(scalaVersion.value) +scalacOptions ++= Seq("-language:reflectiveCalls") +scalacOptions ++= Seq("-Ypartial-unification") + +javacOptions ++= javacOptionsVersion(scalaVersion.value) + +// testOptions in Test += Tests.Argument("-oF") + +resolvers += Resolver.sonatypeRepo("releases") +addCompilerPlugin("org.spire-math" %% "kind-projector" % "0.9.7") +addCompilerPlugin("com.olegpy" %% "better-monadic-for" % "0.2.4") +addCompilerPlugin("org.scalamacros" % "paradise" % "2.1.1" cross CrossVersion.full) + + +testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-eS") + +addCompilerPlugin("io.tryp" % "splain" % "0.4.1" cross CrossVersion.patch) diff --git a/deliver.sh b/deliver.sh new file mode 100755 index 0000000..f64f668 --- /dev/null +++ b/deliver.sh @@ -0,0 +1,25 @@ +#!/bin/bash + +read -p "Enter your student username (the one you use on badboard): " username +echo "Cleaning your project" + +./sbt.sh clean + +echo "Creating archive" +mkdir wrap +cp -r src ./wrap/ +cp build.sbt ./wrap +cp project/Dependencies.scala ./wrap/project/Dependencies.scala +cp project/build.properties ./wrap/project/build.properties +cp sbt.sh ./wrap +tar czfv $username.gz wrap + +rm -rf ./wrap + +echo "Unwrapping and testing your wrapped package" +mkdir wrapTest +tar -C ./wrapTest -xvf $username.gz +./wrapTest/wrap/sbt.sh test +rm -rf ./wrapTest + +echo "If the test output looked good then you're good to go!" diff --git a/exercise.org b/exercise.org new file mode 100644 index 0000000..7dcfea4 --- /dev/null +++ b/exercise.org @@ -0,0 +1,120 @@ +* Exercise 1 & 2 + The task in this exercise is to implement a 5-stage pipelined processor for + the RISCV32I instruction set. + + You will use the skeleton code which comes with a freebies, namely the registers, + instruction memory and data memory. + + These are contained in the files Registers.scala, Dmem.scala and Imem.scala + +** Getting started + In order to make a correct design in a somewhat expedient fashion you need to be + *methodical!* + + This means you should have a good idea of how your processor should work *before* + you start writing code. While chisel is more pleasent to work with than other HDLs + the bricoleur approach is not recommended. + + My recommended approach is therefore to create a sketch of your processor design. + Start with an overall sketch showing all the components, then drill down. + In your sketch you will eventually add a box for registers, IMEM and DMEM, which + should make it clear how the already finished modules fit into the grander design, + making the skeleton-code less mysterious. + + Next, your focus should be to get the simplest possible program to work, a program + that simply does a single add operation. Info is progressively being omitted in the + later steps, after all brevity is ~~the soul of~~ wit + + Step 0: + In order to verify that the project is set up properly, open sbt in your project root + by typing ./sbt (or simply sbt if you already use scala). + sbt, which stands for scala build tool will provide you with a repl where you can + compile and test your code. + + The initial run will take quite a while to boot as all the necessary stuff is downloaded. + + Step ¼: + In your console, type `compile` to verify that everything compiles correctly. + + Step ½: + In your console, type `test` to verify that the tests run, and that chisel can correctly + build your design. + This command will unleash the full battery of tests on you. + + Step ¾: + In your console, type `testOnly FiveStage.SelectedTests` to run only the tests that you + have defined in the testConf.scala file. + In the skeleton this will run the simple add test only, but you should alter this + manifest as you build your processor to run more complex tests as a stopgap between + running single tests and the full battery. + + Be aware that chisel will make quite a lot of noise during test running. I'm not + aware of a good way to get rid of this sadly. + + Step 1: + In order to do this, your processor must be able to select new instructions, so in + your IF.scala you must increment the PC. + + Step 2: + Next, the instruction must be forwarded to the ID stage, so you will need to add the + instruction to the io part of InstructionFetch as an output. + + Step 3: + Your ID stage must take in an instruction in its io bundle, and decode it. In the + skeleton code a decoder has already been instantiated in the InstructionDecode module, + but it is given a dummy instruction. + Likewise, you must ensure that the register gets the relevant data. + This can be done by using the instruction class methods (TopLevelSignals.scala) which + lets us access the relevant part of the instruction with the dot operator. + For instance: + + #+BEGIN_SRC scala + myModule.io.funct6 := io.instruction.funct6 + #+END_SRC + + drives funct6 of `myModule` with the 26th to 31st bit of `instruction`. + + Step 4: + Your IF should now have an instruction as an OUTPUT, and your ID as an INPUT, however + they are not connected. This must be done in the CPU class where both the ID and IF are + instantiated. + + Step 4½: + You should now verify that the correct control signals are produced. Using printf, ensure + that: + + The program counter is increasing in increments of 4 + + The instruction in ID is as expected + + The decoder output is as expected + + The correct operands are fetched from the registers + + Step 5: + You will now have to create the EX stage. Use the structure of the IF and ID modules to + guide you here. + In your EX stage you should have an ALU, preferrable in its own module a la registers in ID. + While the ALU is hugely complex, it's very easy to describle in hardware design languages! + Using the same approach as in the decoder should be sufficient: + + #+BEGIN_SRC scala + val ALUopMap = Array( + ADD -> (io.op1 + io.op2), + SUB -> (io.op1 - io.op2), + ... + ) + + io.aluResult := MuxLookup(0.U(32.W), io.aluOp, ALUopMap) + #+END_SRC + + Step 6: + Your MEM stage does very little when an ADD instruction is executed, so implementing it should + be easy + + Step 7: + You now need to actually write the result back to your register bank. + This should be handled at the CPU level. + If you sketched your processor already you probably made sure to keep track of the control + signals for the instruction currently in WB, so writing to the correct register address should + be easy for you ;) + + Step 8: + Ensure that the simplest add test works, give yourself a pat on the back, you've just found the + corner pieces of the puzzle, so filling in the rest is "simply" being methodical. diff --git a/instructions.org b/instructions.org new file mode 100644 index 0000000..e323c7c --- /dev/null +++ b/instructions.org @@ -0,0 +1,449 @@ +-------------------------------------------------------------------------- +4.2. Register-Register Arithmetic Instructions +-------------------------------------------------------------------------- + +* ADD + + - Summary : Addition with 3 GPRs, no overflow exception + - Assembly : add rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] + R[rs2] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 000 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +* SUB + + - Summary : Subtraction with 3 GPRs, no overflow exception + - Assembly : sub rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] - R[rs2] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0100000 | rs2 | rs1 | 000 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +* AND + + - Summary : Bitwise logical AND with 3 GPRs + - Assembly : and rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] & R[rs2] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 111 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +* OR + + - Summary : Bitwise logical OR with 3 GPRs + - Assembly : or rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] | R[rs2] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 110 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +* XOR + + - Summary : Bitwise logical XOR with 3 GPRs + - Assembly : xor rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] ^ R[rs2] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 100 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +* SLT + + - Summary : Record result of signed less-than comparison with 2 GPRs + - Assembly : slt rd, rs1, rs2 + - Semantics : R[rd] = ( R[rs1] >> R[rs2][4:0] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0100000 | rs2 | rs1 | 101 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should ensure that the sign-bit of R[rs1] is +extended to the right as it does the right shift. The hardware _must_ +only use the bottom five bits of R[rs2] when performing the shift. + +* SRL + + - Summary : Shift right logical by register value (append zeroes) + - Assembly : srl rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] >> R[rs2][4:0] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 101 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should append zeros to the left as it does the +right shift. The hardware _must_ only use the bottom five bits of R[rs2] +when performing the shift. + +* SLL + + - Summary : Shift left logical by register value (append zeroes) + - Assembly : sll rd, rs1, rs2 + - Semantics : R[rd] = R[rs1] << R[rs2][4:0] + - Format : R-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | rs2 | rs1 | 001 | rd | 0110011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should append zeros to the right as it does the +left shift. The hardware _must_ only use the bottom five bits of R[rs2] +when performing the shift. + + +-------------------------------------------------------------------------- +4.3. Register-Immediate Arithmetic Instructions +-------------------------------------------------------------------------- + +* ADDI + + - Summary : Add constant, no overflow exception + - Assembly : addi rd, rs1, imm + - Semantics : R[rd] = R[rs1] + sext(imm) + - Format : I-type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 000 | rd | 0010011 | + +----------------------+---------+------+---------+-------------+ + +* ANDI + + - Summary : Bitwise logical AND with constant + - Assembly : andi rd, rs1, imm + - Semantics : R[rd] = R[rs1] & sext(imm) + - Format : I-type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 111 | rd | 0010011 | + +----------------------+---------+------+---------+-------------+ + +* ORI + + - Summary : Bitwise logical OR with constant + - Assembly : ori rd, rs1, imm + - Semantics : R[rd] = R[rs1] | sext(imm) + - Format : I-type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 110 | rd | 0010011 | + +----------------------+---------+------+---------+-------------+ + +* XORI + + - Summary : Bitwise logical XOR with constant + - Assembly : xori rd, rs1, imm + - Semantics : R[rd] = R[rs1] ^ sext(imm) + - Format : I-type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 100 | rd | 0010011 | + +----------------------+---------+------+---------+-------------+ + +* SLTI + + - Summary : Set GPR if source GPR < constant, signed comparison + - Assembly : slti rd, rs1, imm + - Semantics : R[rd] = ( R[rs1] >> imm + - Format : I-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0100000 | imm | rs1 | 101 | rd | 0010011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should ensure that the sign-bit of R[rs1] is +extended to the right as it does the right shift. + +* SRLI + + - Summary : Shift right logical by constant (append zeroes) + - Assembly : srli rd, rs1, imm + - Semantics : R[rd] = R[rs1] >> imm + - Format : I-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | imm | rs1 | 101 | rd | 0010011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should append zeros to the left as it does the +right shift. + +* SLLI + + - Summary : Shift left logical constant (append zeroes) + - Assembly : slli rd, rs1, imm + - Semantics : R[rd] = R[rs1] << imm + - Format : I-type + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | 0000000 | imm | rs1 | 001 | rd | 0010011 | + +------------+---------+---------+------+---------+-------------+ + +Note that the hardware should append zeros to the right as it does the +left shift. + +* LUI + + - Summary : Load constant into upper bits of word + - Assembly : lui rd, imm + - Semantics : R[rd] = imm << 12 + - Format : U-type, U-immediate + + 31 11 7 6 0 + +---------------------------------------+---------+-------------+ + | imm | rd | 0110111 | + +---------------------------------------+---------+-------------+ + +* AUIPC + + - Summary : Load PC + constant into upper bits of word + - Assembly : auipc rd, imm + - Semantics : R[rd] = PC + ( imm << 12 ) + - Format : U-type, U-immediate + + 31 11 7 6 0 + +---------------------------------------+---------+-------------+ + | imm | rd | 0010111 | + +---------------------------------------+---------+-------------+ + +-------------------------------------------------------------------------- +4.4. Memory Instructions +-------------------------------------------------------------------------- + +* LW + + - Summary : Load word from memory + - Assembly : lw rd, imm(rs1) + - Semantics : R[rd] = M_4B[ R[rs1] + sext(imm) ] + - Format : I-type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 010 | rd | 0000011 | + +----------------------+---------+------+---------+-------------+ + +All addresses used with LW instructions must be four-byte aligned. This +means the bottom two bits of every effective address (i.e., after the +base address is added to the offset) will always be zero. + +* SW + + - Summary : Store word into memory + - Assembly : sw rs2, imm(rs1) + - Semantics : M_4B[ R[rs1] + sext(imm) ] = R[rs2] + - Format : S-type, S-immediate + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | imm | rs2 | rs1 | 010 | imm | 0100011 | + +------------+---------+---------+------+---------+-------------+ + +All addresses used with SW instructions must be four-byte aligned. This +means the bottom two bits of every effective address (i.e., after the +base address is added to the offset) will always be zero. + +-------------------------------------------------------------------------- +4.5. Unconditional Jump Instructions +-------------------------------------------------------------------------- + +* JAL + + - Summary : Jump to address and place return address in GPR + - Assembly : jal rd, imm + - Semantics : R[rd] = PC + 4; PC = PC + sext(imm) + - Format : U-type, J-immediate + + 31 11 7 6 0 + +---------------------------------------+---------+-------------+ + | imm | rd | 1101111 | + +---------------------------------------+---------+-------------+ + + +* JALR + + - Summary : Jump to address and place return address in GPR + - Assembly : jalr rd, rs1, imm + - Semantics : R[rd] = PC + 4; PC = ( R[rs1] + sext(imm) ) & 0xfffffffe + - Format : I-Type, I-immediate + + 31 20 19 15 14 12 11 7 6 0 + +----------------------+---------+------+---------+-------------+ + | imm | rs1 | 000 | rd | 1100111 | + +----------------------+---------+------+---------+-------------+ + +Note that the target address is obtained by adding the 12-bit signed +I-immediate to the value in register rs1, then setting the +least-significant bit of the result to zero. In other words, the JALR +instruction ignores the lowest bit of the calculated target address. + +JALR is used when we want to call different subroutines. +Consider this jump table: + +mul: +j mulInt +j mulFloat +j mulDouble + +depending on the value in rs1 we can select which subroutine to call + +-------------------------------------------------------------------------- +4.6. Conditional Branch Instructions +-------------------------------------------------------------------------- + +* BEQ + + - Summary : Branch if 2 GPRs are equal + - Assembly : beq rs1, rs2, imm + - Semantics : PC = ( R[rs1] == R[rs2] ) ? PC + sext(imm) : PC + 4 + - Format : S-type, B-immediate + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | imm | rs2 | rs1 | 000 | imm | 1100011 | + +------------+---------+---------+------+---------+-------------+ + +* BNE + + - Summary : Branch if 2 GPRs are not equal + - Assembly : bne rs1, rs2, imm + - Semantics : PC = ( R[rs1] != R[rs2] ) ? PC + sext(imm) : PC + 4 + - Format : S-type, B-immediate + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | imm | rs2 | rs1 | 001 | imm | 1100011 | + +------------+---------+---------+------+---------+-------------+ + +* BLT + + - Summary : Branch based on signed comparison of two GPRs + - Assembly : blt rs1, rs2, imm + - Semantics : PC = ( R[rs1] =s R[rs2] ) ? PC + sext(imm) : PC + 4 + - Format : S-type, B-immediate + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | imm | rs2 | rs1 | 101 | imm | 1100011 | + +------------+---------+---------+------+---------+-------------+ + +This instruction uses a _signed_ comparison. + +* BLTU + + - Summary : Branch based on unsigned comparison of two GPRs + - Assembly : bltu rs1, rs2, imm + - Semantics : PC = ( R[rs1] =u R[rs2] ) ? PC + sext(imm) : PC + 4 + - Format : S-type, B-immediate + + 31 25 24 20 19 15 14 12 11 7 6 0 + +------------+---------+---------+------+---------+-------------+ + | imm | rs2 | rs1 | 111 | imm | 1100011 | + +------------+---------+---------+------+---------+-------------+ + +This instruction uses an _unsigned_ comparison. diff --git a/project/Dependencies.scala b/project/Dependencies.scala new file mode 100644 index 0000000..49489b6 --- /dev/null +++ b/project/Dependencies.scala @@ -0,0 +1,30 @@ +import sbt._ + +object Dependencies { + + val fs2Version = "1.0.0" + val catsVersion = "1.4.0" + val catsEffectVersion = "1.0.0" + + // TODO prune deps + val backendDeps = Def.setting( + Seq( + "com.lihaoyi" %% "sourcecode" % "0.1.4", // expert println debugging + "com.lihaoyi" %% "pprint" % "0.5.3", // pretty print for types and case classes + "com.lihaoyi" %% "fansi" % "0.2.5", + + "org.typelevel" %% "cats-core" % catsVersion, // abstract category dork stuff + "org.typelevel" %% "cats-effect" % catsEffectVersion, // IO monad category wank + + "com.chuusai" %% "shapeless" % "2.3.2", // Abstract level category dork stuff + + "org.tpolecat" %% "atto-core" % "0.6.3", // For parsing asm + "org.tpolecat" %% "atto-refined" % "0.6.3", // For parsing asm + + "com.github.pathikrit" %% "better-files" % "3.7.0", + + "org.atnos" %% "eff" % "5.2.0", + "com.slamdata" %% "matryoshka-core" % "0.21.3" + + )) +} diff --git a/project/build.properties b/project/build.properties new file mode 100644 index 0000000..c4dc11b --- /dev/null +++ b/project/build.properties @@ -0,0 +1 @@ +sbt.version = 1.1.0 diff --git a/sbt.sh b/sbt.sh new file mode 100755 index 0000000..4785673 --- /dev/null +++ b/sbt.sh @@ -0,0 +1,578 @@ +#!/usr/bin/env bash +# +# A more capable sbt runner, coincidentally also called sbt. +# Author: Paul Phillips +# https://github.com/paulp/sbt-extras + +set -o pipefail + +declare -r sbt_release_version="1.2.8" +declare -r sbt_unreleased_version="1.2.8" + +declare -r latest_213="2.13.0-RC1" +declare -r latest_212="2.12.8" +declare -r latest_211="2.11.12" +declare -r latest_210="2.10.7" +declare -r latest_29="2.9.3" +declare -r latest_28="2.8.2" + +declare -r buildProps="project/build.properties" + +declare -r sbt_launch_ivy_release_repo="http://repo.typesafe.com/typesafe/ivy-releases" +declare -r sbt_launch_ivy_snapshot_repo="https://repo.scala-sbt.org/scalasbt/ivy-snapshots" +declare -r sbt_launch_mvn_release_repo="http://repo.scala-sbt.org/scalasbt/maven-releases" +declare -r sbt_launch_mvn_snapshot_repo="http://repo.scala-sbt.org/scalasbt/maven-snapshots" + +declare -r default_jvm_opts_common="-Xms512m -Xss2m" +declare -r noshare_opts="-Dsbt.global.base=project/.sbtboot -Dsbt.boot.directory=project/.boot -Dsbt.ivy.home=project/.ivy" + +declare sbt_jar sbt_dir sbt_create sbt_version sbt_script sbt_new +declare sbt_explicit_version +declare verbose noshare batch trace_level +declare debugUs + +declare java_cmd="java" +declare sbt_launch_dir="$HOME/.sbt/launchers" +declare sbt_launch_repo + +# pull -J and -D options to give to java. +declare -a java_args scalac_args sbt_commands residual_args + +# args to jvm/sbt via files or environment variables +declare -a extra_jvm_opts extra_sbt_opts + +echoerr () { echo >&2 "$@"; } +vlog () { [[ -n "$verbose" ]] && echoerr "$@"; } +die () { echo "Aborting: $*" ; exit 1; } + +setTrapExit () { + # save stty and trap exit, to ensure echo is re-enabled if we are interrupted. + SBT_STTY="$(stty -g 2>/dev/null)" + export SBT_STTY + + # restore stty settings (echo in particular) + onSbtRunnerExit() { + [ -t 0 ] || return + vlog "" + vlog "restoring stty: $SBT_STTY" + stty "$SBT_STTY" + } + + vlog "saving stty: $SBT_STTY" + trap onSbtRunnerExit EXIT +} + +# this seems to cover the bases on OSX, and someone will +# have to tell me about the others. +get_script_path () { + local path="$1" + [[ -L "$path" ]] || { echo "$path" ; return; } + + local -r target="$(readlink "$path")" + if [[ "${target:0:1}" == "/" ]]; then + echo "$target" + else + echo "${path%/*}/$target" + fi +} + +script_path="$(get_script_path "${BASH_SOURCE[0]}")" +declare -r script_path +script_name="${script_path##*/}" +declare -r script_name + +init_default_option_file () { + local overriding_var="${!1}" + local default_file="$2" + if [[ ! -r "$default_file" && "$overriding_var" =~ ^@(.*)$ ]]; then + local envvar_file="${BASH_REMATCH[1]}" + if [[ -r "$envvar_file" ]]; then + default_file="$envvar_file" + fi + fi + echo "$default_file" +} + +sbt_opts_file="$(init_default_option_file SBT_OPTS .sbtopts)" +jvm_opts_file="$(init_default_option_file JVM_OPTS .jvmopts)" + +build_props_sbt () { + [[ -r "$buildProps" ]] && \ + grep '^sbt\.version' "$buildProps" | tr '=\r' ' ' | awk '{ print $2; }' +} + +set_sbt_version () { + sbt_version="${sbt_explicit_version:-$(build_props_sbt)}" + [[ -n "$sbt_version" ]] || sbt_version=$sbt_release_version + export sbt_version +} + +url_base () { + local version="$1" + + case "$version" in + 0.7.*) echo "http://simple-build-tool.googlecode.com" ;; + 0.10.* ) echo "$sbt_launch_ivy_release_repo" ;; + 0.11.[12]) echo "$sbt_launch_ivy_release_repo" ;; + 0.*-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss" + echo "$sbt_launch_ivy_snapshot_repo" ;; + 0.*) echo "$sbt_launch_ivy_release_repo" ;; + *-[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]-[0-9][0-9][0-9][0-9][0-9][0-9]) # ie "*-yyyymmdd-hhMMss" + echo "$sbt_launch_mvn_snapshot_repo" ;; + *) echo "$sbt_launch_mvn_release_repo" ;; + esac +} + +make_url () { + local version="$1" + + local base="${sbt_launch_repo:-$(url_base "$version")}" + + case "$version" in + 0.7.*) echo "$base/files/sbt-launch-0.7.7.jar" ;; + 0.10.* ) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; + 0.11.[12]) echo "$base/org.scala-tools.sbt/sbt-launch/$version/sbt-launch.jar" ;; + 0.*) echo "$base/org.scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; + *) echo "$base/org/scala-sbt/sbt-launch/$version/sbt-launch.jar" ;; + esac +} + +addJava () { vlog "[addJava] arg = '$1'" ; java_args+=("$1"); } +addSbt () { vlog "[addSbt] arg = '$1'" ; sbt_commands+=("$1"); } +addScalac () { vlog "[addScalac] arg = '$1'" ; scalac_args+=("$1"); } +addResidual () { vlog "[residual] arg = '$1'" ; residual_args+=("$1"); } + +addResolver () { addSbt "set resolvers += $1"; } +addDebugger () { addJava "-Xdebug" ; addJava "-Xrunjdwp:transport=dt_socket,server=y,suspend=n,address=$1"; } +setThisBuild () { + vlog "[addBuild] args = '$*'" + local key="$1" && shift + addSbt "set $key in ThisBuild := $*" +} +setScalaVersion () { + [[ "$1" == *"-SNAPSHOT" ]] && addResolver 'Resolver.sonatypeRepo("snapshots")' + addSbt "++ $1" +} +setJavaHome () { + java_cmd="$1/bin/java" + setThisBuild javaHome "_root_.scala.Some(file(\"$1\"))" + export JAVA_HOME="$1" + export JDK_HOME="$1" + export PATH="$JAVA_HOME/bin:$PATH" +} + +getJavaVersion() { + local -r str=$("$1" -version 2>&1 | grep -E -e '(java|openjdk) version' | awk '{ print $3 }' | tr -d '"') + + # java -version on java8 says 1.8.x + # but on 9 and 10 it's 9.x.y and 10.x.y. + if [[ "$str" =~ ^1\.([0-9]+)\..*$ ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ "$str" =~ ^([0-9]+)\..*$ ]]; then + echo "${BASH_REMATCH[1]}" + elif [[ -n "$str" ]]; then + echoerr "Can't parse java version from: $str" + fi +} + +checkJava() { + # Warn if there is a Java version mismatch between PATH and JAVA_HOME/JDK_HOME + + [[ -n "$JAVA_HOME" && -e "$JAVA_HOME/bin/java" ]] && java="$JAVA_HOME/bin/java" + [[ -n "$JDK_HOME" && -e "$JDK_HOME/lib/tools.jar" ]] && java="$JDK_HOME/bin/java" + + if [[ -n "$java" ]]; then + pathJavaVersion=$(getJavaVersion java) + homeJavaVersion=$(getJavaVersion "$java") + if [[ "$pathJavaVersion" != "$homeJavaVersion" ]]; then + echoerr "Warning: Java version mismatch between PATH and JAVA_HOME/JDK_HOME, sbt will use the one in PATH" + echoerr " Either: fix your PATH, remove JAVA_HOME/JDK_HOME or use -java-home" + echoerr " java version from PATH: $pathJavaVersion" + echoerr " java version from JAVA_HOME/JDK_HOME: $homeJavaVersion" + fi + fi +} + +java_version () { + local -r version=$(getJavaVersion "$java_cmd") + vlog "Detected Java version: $version" + echo "$version" +} + +# MaxPermSize critical on pre-8 JVMs but incurs noisy warning on 8+ +default_jvm_opts () { + local -r v="$(java_version)" + if [[ $v -ge 8 ]]; then + echo "$default_jvm_opts_common" + else + echo "-XX:MaxPermSize=384m $default_jvm_opts_common" + fi +} + +build_props_scala () { + if [[ -r "$buildProps" ]]; then + versionLine="$(grep '^build.scala.versions' "$buildProps")" + versionString="${versionLine##build.scala.versions=}" + echo "${versionString%% .*}" + fi +} + +execRunner () { + # print the arguments one to a line, quoting any containing spaces + vlog "# Executing command line:" && { + for arg; do + if [[ -n "$arg" ]]; then + if printf "%s\n" "$arg" | grep -q ' '; then + printf >&2 "\"%s\"\n" "$arg" + else + printf >&2 "%s\n" "$arg" + fi + fi + done + vlog "" + } + + setTrapExit + + if [[ -n "$batch" ]]; then + "$@" < /dev/null + else + "$@" + fi +} + +jar_url () { make_url "$1"; } + +is_cygwin () { [[ "$(uname -a)" == "CYGWIN"* ]]; } + +jar_file () { + is_cygwin \ + && cygpath -w "$sbt_launch_dir/$1/sbt-launch.jar" \ + || echo "$sbt_launch_dir/$1/sbt-launch.jar" +} + +download_url () { + local url="$1" + local jar="$2" + + echoerr "Downloading sbt launcher for $sbt_version:" + echoerr " From $url" + echoerr " To $jar" + + mkdir -p "${jar%/*}" && { + if command -v curl > /dev/null 2>&1; then + curl --fail --silent --location "$url" --output "$jar" + elif command -v wget > /dev/null 2>&1; then + wget -q -O "$jar" "$url" + fi + } && [[ -r "$jar" ]] +} + +acquire_sbt_jar () { + { + sbt_jar="$(jar_file "$sbt_version")" + [[ -r "$sbt_jar" ]] + } || { + sbt_jar="$HOME/.ivy2/local/org.scala-sbt/sbt-launch/$sbt_version/jars/sbt-launch.jar" + [[ -r "$sbt_jar" ]] + } || { + sbt_jar="$(jar_file "$sbt_version")" + download_url "$(make_url "$sbt_version")" "$sbt_jar" + } +} + +usage () { + set_sbt_version + cat < display stack traces with a max of frames (default: -1, traces suppressed) + -debug-inc enable debugging log for the incremental compiler + -no-colors disable ANSI color codes + -sbt-create start sbt even if current directory contains no sbt project + -sbt-dir path to global settings/plugins directory (default: ~/.sbt/) + -sbt-boot path to shared boot directory (default: ~/.sbt/boot in 0.11+) + -ivy path to local Ivy repository (default: ~/.ivy2) + -no-share use all local caches; no sharing + -offline put sbt in offline mode + -jvm-debug Turn on JVM debugging, open at the given port. + -batch Disable interactive mode + -prompt Set the sbt prompt; in expr, 's' is the State and 'e' is Extracted + -script Run the specified file as a scala script + + # sbt version (default: sbt.version from $buildProps if present, otherwise $sbt_release_version) + -sbt-force-latest force the use of the latest release of sbt: $sbt_release_version + -sbt-version use the specified version of sbt (default: $sbt_release_version) + -sbt-dev use the latest pre-release version of sbt: $sbt_unreleased_version + -sbt-jar use the specified jar as the sbt launcher + -sbt-launch-dir directory to hold sbt launchers (default: $sbt_launch_dir) + -sbt-launch-repo repo url for downloading sbt launcher jar (default: $(url_base "$sbt_version")) + + # scala version (default: as chosen by sbt) + -28 use $latest_28 + -29 use $latest_29 + -210 use $latest_210 + -211 use $latest_211 + -212 use $latest_212 + -213 use $latest_213 + -scala-home use the scala build at the specified directory + -scala-version use the specified version of scala + -binary-version use the specified scala version when searching for dependencies + + # java version (default: java from PATH, currently $(java -version 2>&1 | grep version)) + -java-home alternate JAVA_HOME + + # passing options to the jvm - note it does NOT use JAVA_OPTS due to pollution + # The default set is used if JVM_OPTS is unset and no -jvm-opts file is found + $(default_jvm_opts) + JVM_OPTS environment variable holding either the jvm args directly, or + the reference to a file containing jvm args if given path is prepended by '@' (e.g. '@/etc/jvmopts') + Note: "@"-file is overridden by local '.jvmopts' or '-jvm-opts' argument. + -jvm-opts file containing jvm args (if not given, .jvmopts in project root is used if present) + -Dkey=val pass -Dkey=val directly to the jvm + -J-X pass option -X directly to the jvm (-J is stripped) + + # passing options to sbt, OR to this runner + SBT_OPTS environment variable holding either the sbt args directly, or + the reference to a file containing sbt args if given path is prepended by '@' (e.g. '@/etc/sbtopts') + Note: "@"-file is overridden by local '.sbtopts' or '-sbt-opts' argument. + -sbt-opts file containing sbt args (if not given, .sbtopts in project root is used if present) + -S-X add -X to sbt's scalacOptions (-S is stripped) +EOM +} + +process_args () { + require_arg () { + local type="$1" + local opt="$2" + local arg="$3" + + if [[ -z "$arg" ]] || [[ "${arg:0:1}" == "-" ]]; then + die "$opt requires <$type> argument" + fi + } + while [[ $# -gt 0 ]]; do + case "$1" in + -h|-help) usage; exit 0 ;; + -v) verbose=true && shift ;; + -d) addSbt "--debug" && shift ;; + -w) addSbt "--warn" && shift ;; + -q) addSbt "--error" && shift ;; + -x) debugUs=true && shift ;; + -trace) require_arg integer "$1" "$2" && trace_level="$2" && shift 2 ;; + -ivy) require_arg path "$1" "$2" && addJava "-Dsbt.ivy.home=$2" && shift 2 ;; + -no-colors) addJava "-Dsbt.log.noformat=true" && shift ;; + -no-share) noshare=true && shift ;; + -sbt-boot) require_arg path "$1" "$2" && addJava "-Dsbt.boot.directory=$2" && shift 2 ;; + -sbt-dir) require_arg path "$1" "$2" && sbt_dir="$2" && shift 2 ;; + -debug-inc) addJava "-Dxsbt.inc.debug=true" && shift ;; + -offline) addSbt "set offline in Global := true" && shift ;; + -jvm-debug) require_arg port "$1" "$2" && addDebugger "$2" && shift 2 ;; + -batch) batch=true && shift ;; + -prompt) require_arg "expr" "$1" "$2" && setThisBuild shellPrompt "(s => { val e = Project.extract(s) ; $2 })" && shift 2 ;; + -script) require_arg file "$1" "$2" && sbt_script="$2" && addJava "-Dsbt.main.class=sbt.ScriptMain" && shift 2 ;; + + -sbt-create) sbt_create=true && shift ;; + -sbt-jar) require_arg path "$1" "$2" && sbt_jar="$2" && shift 2 ;; + -sbt-version) require_arg version "$1" "$2" && sbt_explicit_version="$2" && shift 2 ;; + -sbt-force-latest) sbt_explicit_version="$sbt_release_version" && shift ;; + -sbt-dev) sbt_explicit_version="$sbt_unreleased_version" && shift ;; + -sbt-launch-dir) require_arg path "$1" "$2" && sbt_launch_dir="$2" && shift 2 ;; + -sbt-launch-repo) require_arg path "$1" "$2" && sbt_launch_repo="$2" && shift 2 ;; + -scala-version) require_arg version "$1" "$2" && setScalaVersion "$2" && shift 2 ;; + -binary-version) require_arg version "$1" "$2" && setThisBuild scalaBinaryVersion "\"$2\"" && shift 2 ;; + -scala-home) require_arg path "$1" "$2" && setThisBuild scalaHome "_root_.scala.Some(file(\"$2\"))" && shift 2 ;; + -java-home) require_arg path "$1" "$2" && setJavaHome "$2" && shift 2 ;; + -sbt-opts) require_arg path "$1" "$2" && sbt_opts_file="$2" && shift 2 ;; + -jvm-opts) require_arg path "$1" "$2" && jvm_opts_file="$2" && shift 2 ;; + + -D*) addJava "$1" && shift ;; + -J*) addJava "${1:2}" && shift ;; + -S*) addScalac "${1:2}" && shift ;; + -28) setScalaVersion "$latest_28" && shift ;; + -29) setScalaVersion "$latest_29" && shift ;; + -210) setScalaVersion "$latest_210" && shift ;; + -211) setScalaVersion "$latest_211" && shift ;; + -212) setScalaVersion "$latest_212" && shift ;; + -213) setScalaVersion "$latest_213" && shift ;; + new) sbt_new=true && : ${sbt_explicit_version:=$sbt_release_version} && addResidual "$1" && shift ;; + *) addResidual "$1" && shift ;; + esac + done +} + +# process the direct command line arguments +process_args "$@" + +# skip #-styled comments and blank lines +readConfigFile() { + local end=false + until $end; do + read -r || end=true + [[ $REPLY =~ ^# ]] || [[ -z $REPLY ]] || echo "$REPLY" + done < "$1" +} + +# if there are file/environment sbt_opts, process again so we +# can supply args to this runner +if [[ -r "$sbt_opts_file" ]]; then + vlog "Using sbt options defined in file $sbt_opts_file" + while read -r opt; do extra_sbt_opts+=("$opt"); done < <(readConfigFile "$sbt_opts_file") +elif [[ -n "$SBT_OPTS" && ! ("$SBT_OPTS" =~ ^@.*) ]]; then + vlog "Using sbt options defined in variable \$SBT_OPTS" + IFS=" " read -r -a extra_sbt_opts <<< "$SBT_OPTS" +else + vlog "No extra sbt options have been defined" +fi + +[[ -n "${extra_sbt_opts[*]}" ]] && process_args "${extra_sbt_opts[@]}" + +# reset "$@" to the residual args +set -- "${residual_args[@]}" +argumentCount=$# + +# set sbt version +set_sbt_version + +checkJava + +# only exists in 0.12+ +setTraceLevel() { + case "$sbt_version" in + "0.7."* | "0.10."* | "0.11."* ) echoerr "Cannot set trace level in sbt version $sbt_version" ;; + *) setThisBuild traceLevel "$trace_level" ;; + esac +} + +# set scalacOptions if we were given any -S opts +[[ ${#scalac_args[@]} -eq 0 ]] || addSbt "set scalacOptions in ThisBuild += \"${scalac_args[*]}\"" + +[[ -n "$sbt_explicit_version" && -z "$sbt_new" ]] && addJava "-Dsbt.version=$sbt_explicit_version" +vlog "Detected sbt version $sbt_version" + +if [[ -n "$sbt_script" ]]; then + residual_args=( "$sbt_script" "${residual_args[@]}" ) +else + # no args - alert them there's stuff in here + (( argumentCount > 0 )) || { + vlog "Starting $script_name: invoke with -help for other options" + residual_args=( shell ) + } +fi + +# verify this is an sbt dir, -create was given or user attempts to run a scala script +[[ -r ./build.sbt || -d ./project || -n "$sbt_create" || -n "$sbt_script" || -n "$sbt_new" ]] || { + cat <` operator to create tuples. + * The reason for this is that it serves as convenient sugar to make maps. + * + * This doesn't matter to you, just fill in the blanks in the style currently + * used, I just want to demystify some of the magic. + * + * `a -> b` == `(a, b)` == `Tuple2(a, b)` + */ + val opcodeMap: Array[(BitPat, List[UInt])] = Array( + + // signal memToReg, regWrite, memRead, memWrite, branch, jump, branchType, Op1Select, Op2Select, ImmSelect, ALUOp + LW -> List(Y, Y, Y, N, N, N, branchType.DC, rs1, imm, ITYPE, ALUOps.ADD), + + SW -> List(N, N, N, Y, N, N, branchType.DC, rs1, imm, STYPE, ALUOps.ADD), + + ADD -> List(N, Y, N, N, N, N, branchType.DC, rs1, rs2, ImmFormat.DC, ALUOps.ADD), + SUB -> List(N, Y, N, N, N, N, branchType.DC, rs1, rs2, ImmFormat.DC, ALUOps.SUB), + + /** + TODO: Fill in the blanks + */ + ) + + + val NOP = List(N, N, N, N, N, N, branchType.DC, rs1, rs2, ImmFormat.DC, ALUOps.DC) + + val decodedControlSignals = ListLookup( + io.instruction.asUInt(), + NOP, + opcodeMap) + + io.controlSignals.memToReg := decodedControlSignals(0) + io.controlSignals.regWrite := decodedControlSignals(1) + io.controlSignals.memRead := decodedControlSignals(2) + io.controlSignals.memWrite := decodedControlSignals(3) + io.controlSignals.branch := decodedControlSignals(4) + io.controlSignals.jump := decodedControlSignals(5) + + io.branchType := decodedControlSignals(6) + io.op1Select := decodedControlSignals(7) + io.op2Select := decodedControlSignals(8) + io.immType := decodedControlSignals(9) + io.ALUop := decodedControlSignals(10) +} diff --git a/src/main/scala/ID.scala b/src/main/scala/ID.scala new file mode 100644 index 0000000..e6df740 --- /dev/null +++ b/src/main/scala/ID.scala @@ -0,0 +1,49 @@ +package FiveStage +import chisel3._ +import chisel3.util.{ BitPat, MuxCase } +import chisel3.experimental.MultiIOModule + + +class InstructionDecode extends MultiIOModule { + + // Don't touch the test harness + val testHarness = IO( + new Bundle { + val registerSetup = Input(new RegisterSetupSignals) + val registerPeek = Output(UInt(32.W)) + + val testUpdates = Output(new RegisterUpdates) + }) + + + val io = IO( + new Bundle { + /** + * TODO: Your code here. + */ + } + ) + + val registers = Module(new Registers) + val decoder = Module(new Decoder).io + + + /** + * Setup. You should not change this code + */ + registers.testHarness.setup := testHarness.registerSetup + testHarness.registerPeek := registers.io.readData1 + testHarness.testUpdates := registers.testHarness.testUpdates + + + /** + * TODO: Your code here. + */ + registers.io.readAddress1 := 0.U + registers.io.readAddress2 := 0.U + registers.io.writeEnable := false.B + registers.io.writeAddress := 0.U + registers.io.writeData := 0.U + + decoder.instruction := 0.U.asTypeOf(new Instruction) +} diff --git a/src/main/scala/IF.scala b/src/main/scala/IF.scala new file mode 100644 index 0000000..73806ce --- /dev/null +++ b/src/main/scala/IF.scala @@ -0,0 +1,59 @@ +package FiveStage +import chisel3._ +import chisel3.experimental.MultiIOModule + +class InstructionFetch extends MultiIOModule { + + // Don't touch + val testHarness = IO( + new Bundle { + val IMEMsetup = Input(new IMEMsetupSignals) + val PC = Output(UInt()) + } + ) + + + /** + * TODO: Add signals for handling events such as jumps + */ + val io = IO( + new Bundle { + val PC = Output(UInt()) + }) + + val IMEM = Module(new IMEM) + val PC = RegInit(UInt(32.W), 0.U) + + + /** + * Setup. You should not change this code + */ + IMEM.testHarness.setupSignals := testHarness.IMEMsetup + testHarness.PC := IMEM.testHarness.requestedAddress + + + /** + * TODO: Your code here. + * + * You should expand on or rewrite the code below. + */ + io.PC := PC + IMEM.io.instructionAddress := PC + + PC := PC + 4.U + + val instruction = Wire(new Instruction) + instruction := IMEM.io.instruction.asTypeOf(new Instruction) + + + + + /** + * Setup. You should not change this code + */ + when(testHarness.IMEMsetup.setup) { + PC := 0.U + // TODO: You must set the instruction to Instruction.NOP here. + // throw new Exception("Just making sure you're seeing the line above") + } +} diff --git a/src/main/scala/IMem.scala b/src/main/scala/IMem.scala new file mode 100644 index 0000000..95d94b7 --- /dev/null +++ b/src/main/scala/IMem.scala @@ -0,0 +1,52 @@ +package FiveStage +import chisel3._ +import chisel3.experimental.MultiIOModule + +/** + * This module is finished and does not need to be modified to complete your fivestage. + * + * When setup is enabled data is written to the instruction memory. + * In normal operation this memory is write only (no self modifying code) + */ +class IMEM() extends MultiIOModule { + + // Don't touch + val testHarness = IO( + new Bundle { + val setupSignals = Input(new IMEMsetupSignals) + val requestedAddress = Output(UInt()) + } + ) + + + val io = IO( + new Bundle { + val instructionAddress = Input(UInt(32.W)) + val instruction = Output(UInt(32.W)) + }) + + + /** + SyncReadMem will output the value of the address signal set in the previous cycle. + */ + val instructions = SyncReadMem(4096, UInt(32.W)) + + // The address we want to read at during operation. During setup it acts as a write address + // leading to the somewhat uninformative name shown here. + val addressSource = Wire(UInt(32.W)) + + testHarness.requestedAddress := io.instructionAddress + + when(testHarness.setupSignals.setup){ + addressSource := testHarness.setupSignals.address + }.otherwise { + addressSource := io.instructionAddress + } + + // For loading data + when(testHarness.setupSignals.setup){ + instructions(addressSource) := testHarness.setupSignals.instruction + } + + io.instruction := instructions(addressSource) +} diff --git a/src/main/scala/MEM.scala b/src/main/scala/MEM.scala new file mode 100644 index 0000000..0038a6d --- /dev/null +++ b/src/main/scala/MEM.scala @@ -0,0 +1,41 @@ +package FiveStage +import chisel3._ +import chisel3.util._ +import chisel3.experimental.MultiIOModule + + +class MemoryFetch() extends MultiIOModule { + + + // Don't touch the test harness + val testHarness = IO( + new Bundle { + val DMEMsetup = Input(new DMEMsetupSignals) + val DMEMpeek = Output(UInt(32.W)) + + val testUpdates = Output(new MemUpdates) + }) + + val io = IO( + new Bundle { + }) + + + val DMEM = Module(new DMEM) + + + /** + * Setup. You should not change this code + */ + DMEM.testHarness.setup := testHarness.DMEMsetup + testHarness.DMEMpeek := DMEM.io.dataOut + testHarness.testUpdates := DMEM.testHarness.testUpdates + + + /** + * Your code here. + */ + DMEM.io.dataIn := 0.U + DMEM.io.dataAddress := 0.U + DMEM.io.writeEnable := false.B +} diff --git a/src/main/scala/Registers.scala b/src/main/scala/Registers.scala new file mode 100644 index 0000000..c93ccd3 --- /dev/null +++ b/src/main/scala/Registers.scala @@ -0,0 +1,81 @@ +package FiveStage +import chisel3._ +import chisel3.experimental.MultiIOModule + + +/** + * This module is already done. Have one on me + * + * When a write and read conflicts this might result in stale data. + * This caveat must be handled using a bypass. + */ +class Registers() extends MultiIOModule { + + + // Don't touch the test harness + val testHarness = IO( + new Bundle { + val setup = Input(new RegisterSetupSignals) + val testUpdates = Output(new RegisterUpdates) + } + ) + + + // You shouldn't really touch these either + val io = IO( + new Bundle { + val readAddress1 = Input(UInt(5.W)) + val readAddress2 = Input(UInt(5.W)) + val writeEnable = Input(Bool()) + val writeAddress = Input(UInt(5.W)) + val writeData = Input(UInt(32.W)) + + val readData1 = Output(UInt(32.W)) + val readData2 = Output(UInt(32.W)) + }) + + + /** + * Mem creates asynchronous read, synchronous write. + * In other words, reading is combinatory. + */ + val registerFile = Mem(32, UInt(32.W)) + + val readAddress1 = Wire(UInt(5.W)) + val readAddress2 = Wire(UInt(5.W)) + val writeAddress = Wire(UInt(5.W)) + val writeData = Wire(UInt(32.W)) + val writeEnable = Wire(Bool()) + + when(testHarness.setup.setup){ + readAddress1 := testHarness.setup.readAddress + readAddress2 := io.readAddress2 + writeData := testHarness.setup.writeData + writeEnable := testHarness.setup.writeEnable + writeAddress := testHarness.setup.readAddress + }.otherwise{ + readAddress1 := io.readAddress1 + readAddress2 := io.readAddress2 + writeData := io.writeData + writeEnable := io.writeEnable + writeAddress := io.writeAddress + } + + + testHarness.testUpdates.writeData := writeData + testHarness.testUpdates.writeEnable := writeEnable + testHarness.testUpdates.writeAddress := writeAddress + + + when(writeEnable){ + when(writeAddress =/= 0.U){ + registerFile(writeAddress) := writeData + } + } + + + io.readData1 := 0.U + io.readData2 := 0.U + when(readAddress1 =/= 0.U){ io.readData1 := registerFile(readAddress1) } + when(readAddress2 =/= 0.U){ io.readData2 := registerFile(readAddress2) } +} diff --git a/src/main/scala/SetupSignals.scala b/src/main/scala/SetupSignals.scala new file mode 100644 index 0000000..0c4c9a3 --- /dev/null +++ b/src/main/scala/SetupSignals.scala @@ -0,0 +1,51 @@ +package FiveStage +import chisel3._ +import chisel3.core.Wire +import chisel3.util.{ BitPat, Cat } + + /** + * Don't touch these + */ +class SetupSignals extends Bundle { + val IMEMsignals = new IMEMsetupSignals + val DMEMsignals = new DMEMsetupSignals + val registerSignals = new RegisterSetupSignals +} + +class IMEMsetupSignals extends Bundle { + val setup = Bool() + val address = UInt(32.W) + val instruction = UInt(32.W) +} + +class DMEMsetupSignals extends Bundle { + val setup = Bool() + val writeEnable = Bool() + val dataIn = UInt(32.W) + val dataAddress = UInt(32.W) +} + +class RegisterSetupSignals extends Bundle { + val setup = Bool() + val readAddress = UInt(5.W) + val writeEnable = Bool() + val writeAddress = UInt(5.W) + val writeData = UInt(32.W) +} + +class TestReadouts extends Bundle { + val registerRead = UInt(32.W) + val DMEMread = UInt(32.W) +} + +class RegisterUpdates extends Bundle { + val writeEnable = Bool() + val writeData = UInt(32.W) + val writeAddress = UInt(5.W) +} + +class MemUpdates extends Bundle { + val writeEnable = Bool() + val writeData = UInt(32.W) + val writeAddress = UInt(32.W) +} diff --git a/src/main/scala/Tile.scala b/src/main/scala/Tile.scala new file mode 100644 index 0000000..0ac57b7 --- /dev/null +++ b/src/main/scala/Tile.scala @@ -0,0 +1,75 @@ +package FiveStage + +import chisel3._ +import chisel3.util._ +import chisel3.core.Input +import chisel3.iotesters.PeekPokeTester + + +/** + * The top level module. You do not have to change anything here, + * however you are free to route out signals as you see fit for debugging. + */ +class Tile() extends Module{ + + val io = IO( + new Bundle { + val DMEMWriteData = Input(UInt(32.W)) + val DMEMAddress = Input(UInt(32.W)) + val DMEMWriteEnable = Input(Bool()) + val DMEMReadData = Output(UInt(32.W)) + + val regsWriteData = Input(UInt(32.W)) + val regsAddress = Input(UInt(5.W)) + val regsWriteEnable = Input(Bool()) + val regsReadData = Output(UInt(32.W)) + + val regsDeviceWriteEnable = Output(Bool()) + val regsDeviceWriteData = Output(UInt(32.W)) + val regsDeviceWriteAddress = Output(UInt(5.W)) + + val memDeviceWriteEnable = Output(Bool()) + val memDeviceWriteData = Output(UInt(32.W)) + val memDeviceWriteAddress = Output(UInt(32.W)) + + val IMEMWriteData = Input(UInt(32.W)) + val IMEMAddress = Input(UInt(32.W)) + + val setup = Input(Bool()) + + val currentPC = Output(UInt()) + }) + + val CPU = Module(new CPU).testHarness + + CPU.setupSignals.IMEMsignals.address := io.IMEMAddress + CPU.setupSignals.IMEMsignals.instruction := io.IMEMWriteData + CPU.setupSignals.IMEMsignals.setup := io.setup + + CPU.setupSignals.DMEMsignals.writeEnable := io.DMEMWriteEnable + CPU.setupSignals.DMEMsignals.dataAddress := io.DMEMAddress + CPU.setupSignals.DMEMsignals.dataIn := io.DMEMWriteData + CPU.setupSignals.DMEMsignals.setup := io.setup + + CPU.setupSignals.registerSignals.readAddress := io.regsAddress + CPU.setupSignals.registerSignals.writeEnable := io.regsWriteEnable + CPU.setupSignals.registerSignals.writeAddress := io.regsAddress + CPU.setupSignals.registerSignals.writeData := io.regsWriteData + CPU.setupSignals.registerSignals.setup := io.setup + + io.DMEMReadData := CPU.testReadouts.DMEMread + io.regsReadData := CPU.testReadouts.registerRead + + io.regsDeviceWriteAddress := CPU.regUpdates.writeAddress + io.regsDeviceWriteEnable := CPU.regUpdates.writeEnable + io.regsDeviceWriteData := CPU.regUpdates.writeData + + io.memDeviceWriteAddress := CPU.memUpdates.writeAddress + io.memDeviceWriteEnable := CPU.memUpdates.writeEnable + io.memDeviceWriteData := CPU.memUpdates.writeData + + io.currentPC := CPU.currentPC +} + + + diff --git a/src/main/scala/ToplevelSignals.scala b/src/main/scala/ToplevelSignals.scala new file mode 100644 index 0000000..02cb6f4 --- /dev/null +++ b/src/main/scala/ToplevelSignals.scala @@ -0,0 +1,105 @@ +package FiveStage +import chisel3._ +import chisel3.core.Wire +import chisel3.util.{ BitPat, Cat } + + +class Instruction extends Bundle(){ + val instruction = UInt(32.W) + + def opcode = instruction(6, 0) + def registerRd = instruction(11, 7) + def funct3 = instruction(14, 12) + def registerRs1 = instruction(19, 15) + def registerRs2 = instruction(24, 20) + def funct7 = instruction(31, 25) + def funct6 = instruction(26, 31) + + def immediateIType = instruction(31, 20).asSInt + def immediateSType = Cat(instruction(31, 25), instruction(11,7)).asSInt + def immediateBType = Cat(instruction(31), instruction(7), instruction(30, 25), instruction(11, 8), 0.U(1.W)).asSInt + def immediateUType = Cat(instruction(31, 12), 0.U(12.W)).asSInt + def immediateJType = Cat(instruction(31), instruction(19, 12), instruction(20), instruction(30, 25), instruction(24, 21), 0.U(1.W)).asSInt + def immediateZType = instruction(19, 15).zext + +} +object Instruction { + def bubble(i: Instruction) = + i.opcode := BitPat.bitPatToUInt(BitPat("b0010011")) + + def default: Instruction = { + val w = Wire(new Instruction) + w.instruction := 0.U + w + } +} + + +class ControlSignals extends Bundle(){ + val memToReg = Bool() + val regWrite = Bool() + val memRead = Bool() + val memWrite = Bool() + val branch = Bool() + val jump = Bool() +} + + +object ControlSignals { + def nop: ControlSignals = { + val b = Wire(new ControlSignals) + b.memToReg := false.B + b.regWrite := false.B + b.memRead := false.B + b.memWrite := false.B + b.branch := false.B + b.jump := false.B + b + } +} + + +object branchType { + val beq = 0.asUInt(3.W) + val neq = 1.asUInt(3.W) + val gte = 2.asUInt(3.W) + val lt = 3.asUInt(3.W) + val gteu = 4.asUInt(3.W) + val ltu = 5.asUInt(3.W) + val jump = 6.asUInt(3.W) + val DC = 0.asUInt(3.W) +} + + +/** + these take the role of the alu source signal. + Used in the decoder. + In the solution manual I use these to select signals at the decode stage. + You can choose to instead do this in the execute stage, and you may forego + using them altogether. + */ +object Op1Select { + val rs1 = 0.asUInt(1.W) + val PC = 1.asUInt(1.W) + val DC = 0.asUInt(1.W) +} + +object Op2Select { + val rs2 = 0.asUInt(1.W) + val imm = 1.asUInt(1.W) + val DC = 0.asUInt(1.W) +} + + +/** + Used in the decoder + */ +object ImmFormat { + val ITYPE = 0.asUInt(3.W) + val STYPE = 1.asUInt(3.W) + val BTYPE = 2.asUInt(3.W) + val UTYPE = 3.asUInt(3.W) + val JTYPE = 4.asUInt(3.W) + val SHAMT = 5.asUInt(3.W) + val DC = 0.asUInt(3.W) +} diff --git a/src/test/resources/tests/basic/arithmetic/arith.s b/src/test/resources/tests/basic/arithmetic/arith.s new file mode 100644 index 0000000..3b288e2 --- /dev/null +++ b/src/test/resources/tests/basic/arithmetic/arith.s @@ -0,0 +1,52 @@ +main: + addi x1, zero, 1 + addi x2, zero, -1 + addi x3, zero, 3 + addi x4, zero, 7 + addi x5, zero, 14 + addi x6, zero, 28 + addi x7, zero, 56 + addi x8, zero, 133 + addi x9, zero, 258 + addi x10, x1, -231 + addi x11, x1, -510 + slt x12, x1, x1 + slt x12, x1, x2 + slt x12, x1, x3 + slt x12, x2, x1 + slt x12, x2, x2 + slt x12, x2, x3 + sltu x12, x1, x1 + sltu x12, x1, x2 + sltu x12, x1, x3 + sltu x12, x2, x1 + sltu x12, x2, x2 + sltu x12, x2, x3 + sll x14, x1, x1 + sll x14, x1, x2 + sll x14, x1, x3 + srl x14, x1, x1 + srl x14, x1, x2 + srl x14, x1, x3 + sra x14, x1, x1 + sra x14, x1, x2 + sra x14, x1, x3 + and x15, x8, x9 + and x15, x9, x10 + or x15, x8, x9 + or x15, x9, x10 + xor x15, x8, x9 + xor x15, x9, x10 + add x16, x1, x5 + add x16, x2, x7 + add x16, x3, x6 + add x16, x4, x10 + add x16, x5, x14 + add x16, x6, x15 + add x16, x7, x7 + add x16, x8, x9 + add x16, x9, x1 + add x16, x10, x8 + add x16, x11, x9 + add x16, x12, x10 + done diff --git a/src/test/resources/tests/basic/forward1.s b/src/test/resources/tests/basic/forward1.s new file mode 100644 index 0000000..205a781 --- /dev/null +++ b/src/test/resources/tests/basic/forward1.s @@ -0,0 +1,205 @@ +main: + ori gp, gp, 0xFFFFFE09 + and gp, gp, ra + xor ra, ra, sp + sra ra, sp, sp + or sp, ra, ra + xori ra, sp, 0x003D + srli sp, sp, 0xFFFFFFF2 + addi ra, sp, 0x0012 + add ra, gp, sp + ori gp, ra, 0xFFFFFFB8 + sll ra, ra, ra + sll sp, gp, ra + slt ra, gp, ra + srai gp, gp, 0xFFFFFFFB + slli ra, ra, 0x0007 + sub sp, gp, sp + andi ra, gp, 0x002D + sub gp, gp, sp + srai ra, gp, 0x0005 + or sp, ra, sp + xori ra, gp, 0xFFFFFF54 + sll ra, sp, ra + sub gp, gp, sp + add ra, sp, sp + sll ra, gp, ra + add gp, sp, sp + ori gp, sp, 0xFFFFFE68 + srli sp, gp, 0xFFFFFFF1 + sltui ra, ra, 0xFFFFFE5D + srli ra, gp, 0x0006 + srai sp, sp, 0x0005 + andi sp, sp, 0x0129 + and sp, sp, sp + slli gp, ra, 0x0001 + or gp, gp, gp + or sp, gp, gp + slli gp, gp, 0x0006 + srl sp, ra, gp + sltui gp, ra, 0xFFFFFEC3 + add sp, gp, sp + xori ra, ra, 0xFFFFFFA2 + or gp, gp, sp + and sp, gp, gp + srai gp, sp, 0x0008 + add sp, sp, ra + slti sp, ra, 0xFFFFFFF7 + srli gp, ra, 0xFFFFFFFD + sll gp, ra, gp + sltu sp, sp, gp + srli gp, ra, 0x0002 + ori gp, sp, 0xFFFFFF28 + srl ra, gp, ra + slti gp, ra, 0x0054 + or gp, ra, ra + ori sp, gp, 0x01D9 + and sp, ra, ra + addi sp, gp, 0x0054 + slli gp, ra, 0xFFFFFFF2 + ori sp, sp, 0x0093 + add gp, gp, gp + and gp, gp, gp + sltui gp, ra, 0x00DE + slli sp, gp, 0x000D + slli sp, ra, 0xFFFFFFF5 + sltui sp, ra, 0xFFFFFF0E + and ra, gp, ra + add gp, sp, sp + slti ra, sp, 0x008C + srli ra, sp, 0x0000 + addi sp, sp, 0x0168 + slli ra, ra, 0xFFFFFFF3 + addi ra, gp, 0x012A + or sp, gp, ra + add ra, sp, gp + and gp, ra, ra + slli ra, gp, 0xFFFFFFF6 + or sp, gp, sp + or gp, ra, ra + ori gp, ra, 0x00EB + or sp, gp, ra + ori gp, sp, 0x01DA + andi ra, ra, 0xFFFFFFE9 + addi gp, sp, 0x00C9 + sltui ra, ra, 0xFFFFFF13 + sltui ra, ra, 0xFFFFFF3A + sltui sp, gp, 0xFFFFFE5A + ori sp, sp, 0xFFFFFFFE + and gp, sp, gp + sltui sp, ra, 0x0034 + srl gp, gp, ra + sll gp, sp, ra + ori ra, gp, 0xFFFFFEB6 + sll ra, sp, ra + sra ra, gp, sp + sub ra, sp, gp + xor gp, gp, sp + sub ra, ra, sp + srl gp, gp, sp + andi ra, ra, 0xFFFFFFCB + ori ra, ra, 0xFFFFFE1B + andi ra, ra, 0xFFFFFEC8 + sltui sp, gp, 0x0108 + sub sp, gp, ra + slti ra, sp, 0x015D + slli sp, sp, 0x0004 + xor gp, sp, ra + srl ra, gp, ra + sltui ra, ra, 0xFFFFFF3C + add sp, sp, sp + add gp, gp, ra + andi sp, sp, 0xFFFFFF3A + srli ra, sp, 0x0004 + ori sp, gp, 0xFFFFFEAB + ori sp, ra, 0xFFFFFE95 + slli sp, sp, 0xFFFFFFF2 + xori gp, sp, 0x0040 + slti gp, sp, 0xFFFFFED1 + or sp, sp, sp + sltui sp, gp, 0x01B4 + addi ra, gp, 0x002D + and sp, gp, gp + or ra, ra, ra + or ra, gp, ra + or ra, gp, ra + sra ra, ra, gp + sra gp, ra, sp + sub ra, sp, ra + srai ra, ra, 0x000F + sltu sp, sp, ra + slli ra, gp, 0xFFFFFFF5 + slti gp, gp, 0x00E0 + addi gp, ra, 0xFFFFFF72 + srl ra, ra, gp + sltui gp, sp, 0xFFFFFEAA + xor sp, ra, gp + and gp, sp, ra + srli gp, gp, 0x0003 + xori ra, ra, 0x01BD + sub ra, gp, sp + sll gp, ra, gp + xori ra, sp, 0x0065 + or ra, sp, ra + slt sp, gp, ra + addi ra, sp, 0xFFFFFE34 + slli gp, sp, 0x0007 + sll ra, sp, gp + sltui gp, gp, 0xFFFFFE62 + slti sp, sp, 0x0019 + xori ra, gp, 0x0092 + sltui gp, sp, 0xFFFFFF29 + srl sp, ra, gp + xori sp, gp, 0xFFFFFF4C + add sp, ra, gp + add sp, gp, ra + sra sp, sp, gp + slli sp, ra, 0x0008 + srl sp, sp, sp + add sp, gp, ra + andi sp, sp, 0x0039 + sll ra, gp, sp + andi gp, ra, 0xFFFFFECC + sll sp, sp, sp + sub sp, sp, ra + srai ra, sp, 0x0008 + xor gp, ra, sp + add sp, sp, sp + sub gp, ra, gp + xori gp, sp, 0x01EE + and ra, ra, ra + ori gp, ra, 0xFFFFFE96 + slli ra, gp, 0x0002 + srli gp, ra, 0x000D + add ra, sp, sp + andi sp, gp, 0xFFFFFEC0 + andi sp, gp, 0xFFFFFE7A + xori ra, sp, 0x0169 + xori gp, sp, 0xFFFFFE02 + andi ra, ra, 0xFFFFFFD1 + xor ra, sp, gp + xori gp, gp, 0x00AB + srl ra, ra, gp + and ra, ra, sp + xori gp, sp, 0x005D + srai sp, sp, 0x000A + addi ra, sp, 0xFFFFFE19 + or sp, ra, ra + addi ra, gp, 0x0084 + ori sp, sp, 0xFFFFFF3D + xor gp, ra, gp + sra ra, ra, gp + xori ra, sp, 0x0040 + srai gp, gp, 0x0002 + xori ra, ra, 0xFFFFFE9A + sra ra, sp, sp + ori gp, sp, 0xFFFFFFB8 + sll sp, ra, ra + sll sp, ra, gp + sll gp, sp, sp + sra gp, ra, ra + srli gp, gp, 0x0001 + done +#regset x1, 123 +#regset x2, -40 +#regset x3, 0xFFEE diff --git a/src/test/resources/tests/basic/forward2.s b/src/test/resources/tests/basic/forward2.s new file mode 100644 index 0000000..9b5e973 --- /dev/null +++ b/src/test/resources/tests/basic/forward2.s @@ -0,0 +1,205 @@ +main: + sltui gp, ra, 0x01AE + srli sp, sp, 0xFFFFFFFB + addi ra, sp, 0x0177 + sub sp, ra, sp + slli sp, ra, 0x000B + add sp, gp, ra + slli gp, sp, 0x0006 + ori sp, ra, 0xFFFFFF64 + and gp, gp, gp + andi gp, gp, 0x0084 + xori ra, ra, 0xFFFFFEB4 + or sp, ra, ra + addi sp, ra, 0x0078 + srli gp, sp, 0x0000 + srl sp, gp, sp + andi ra, gp, 0xFFFFFFF4 + srai ra, ra, 0xFFFFFFFF + sltu gp, sp, gp + or sp, ra, gp + sub ra, gp, ra + addi sp, gp, 0x017C + sltui ra, gp, 0xFFFFFF64 + xori sp, gp, 0x00A1 + xor ra, sp, gp + ori gp, ra, 0x00B6 + add ra, ra, sp + sltui gp, ra, 0xFFFFFFEC + sltu gp, sp, ra + sll sp, ra, gp + add gp, ra, ra + or gp, ra, gp + xor sp, ra, sp + addi sp, gp, 0xFFFFFF4C + xor ra, sp, gp + xori ra, sp, 0xFFFFFF72 + xori gp, sp, 0xFFFFFE95 + or ra, ra, ra + slti ra, gp, 0xFFFFFE75 + slli sp, sp, 0xFFFFFFFC + sltui ra, ra, 0xFFFFFE25 + add sp, ra, gp + sltui gp, gp, 0xFFFFFFDB + addi sp, sp, 0x003D + sll ra, ra, sp + ori ra, ra, 0x012C + add gp, ra, gp + xori sp, sp, 0x0157 + slti gp, sp, 0xFFFFFF2A + and sp, ra, ra + add gp, ra, ra + sltui ra, gp, 0xFFFFFE56 + sra gp, gp, ra + xori sp, gp, 0xFFFFFF0D + sub sp, gp, ra + slti ra, ra, 0x0154 + slli ra, ra, 0x000A + ori ra, gp, 0xFFFFFEC2 + ori ra, sp, 0x0075 + addi gp, sp, 0x0079 + xor gp, ra, sp + srli ra, sp, 0xFFFFFFF8 + slli gp, gp, 0x0006 + sra sp, gp, gp + add sp, sp, sp + slli gp, ra, 0xFFFFFFF0 + add sp, ra, sp + srai ra, sp, 0x0005 + addi ra, gp, 0xFFFFFF83 + xor gp, sp, ra + srli ra, ra, 0x0007 + sll gp, ra, gp + xori gp, gp, 0x0163 + add ra, gp, gp + add sp, ra, ra + sltu ra, ra, sp + sll sp, ra, ra + ori sp, sp, 0xFFFFFF6B + slli gp, gp, 0xFFFFFFFA + xori sp, ra, 0x00A7 + add sp, ra, ra + add ra, gp, gp + addi gp, gp, 0xFFFFFFE9 + sra sp, ra, gp + add gp, ra, ra + ori gp, gp, 0x0002 + addi gp, gp, 0x002F + sll sp, sp, ra + srli sp, ra, 0xFFFFFFF2 + ori gp, sp, 0x00EB + sra gp, ra, sp + sra sp, sp, gp + and gp, ra, ra + sra ra, ra, gp + add sp, gp, ra + srl gp, sp, gp + add ra, ra, sp + srai gp, ra, 0xFFFFFFF2 + srli sp, ra, 0xFFFFFFFC + ori gp, sp, 0xFFFFFE6E + and gp, ra, ra + ori gp, ra, 0xFFFFFFAF + srl ra, ra, ra + or sp, ra, ra + ori gp, sp, 0x0018 + and gp, gp, gp + slti gp, ra, 0x00C6 + sll gp, sp, gp + srli gp, sp, 0xFFFFFFFA + add gp, ra, ra + add gp, ra, sp + sra sp, ra, ra + ori sp, ra, 0x0022 + and gp, gp, gp + add ra, sp, ra + sll sp, gp, gp + ori gp, sp, 0x008E + slti ra, gp, 0x00B5 + add sp, ra, sp + and sp, gp, gp + addi ra, sp, 0x0145 + and sp, gp, gp + sll gp, ra, sp + addi sp, sp, 0xFFFFFFAF + xori sp, ra, 0xFFFFFE2C + srl gp, ra, gp + sub ra, sp, gp + add sp, ra, ra + slli sp, sp, 0x0006 + sub gp, ra, sp + sltu sp, ra, gp + xori ra, ra, 0xFFFFFF9A + addi sp, sp, 0xFFFFFE12 + slli ra, ra, 0xFFFFFFFD + add gp, gp, gp + xori ra, ra, 0xFFFFFED7 + andi gp, gp, 0xFFFFFE05 + and gp, gp, sp + addi gp, gp, 0xFFFFFEE5 + slli ra, gp, 0xFFFFFFF6 + sll sp, gp, gp + and ra, gp, sp + ori ra, gp, 0xFFFFFE22 + srl sp, sp, gp + srli ra, sp, 0x0005 + slli ra, ra, 0xFFFFFFFB + srai ra, ra, 0xFFFFFFF9 + srli ra, gp, 0xFFFFFFF1 + andi gp, gp, 0xFFFFFF96 + sra sp, gp, gp + srai gp, gp, 0x000F + sub sp, sp, gp + sltui ra, ra, 0xFFFFFF41 + and sp, sp, sp + xor gp, sp, ra + srai gp, sp, 0xFFFFFFF6 + xori gp, gp, 0x00D3 + or gp, sp, sp + sltu gp, ra, gp + slli ra, gp, 0x0002 + or sp, gp, ra + addi ra, ra, 0x002B + addi gp, ra, 0x0035 + slli sp, gp, 0x0008 + addi gp, sp, 0x015E + xor ra, gp, sp + or ra, gp, ra + sll ra, gp, ra + sll gp, gp, ra + srli gp, ra, 0xFFFFFFF4 + slt sp, ra, sp + sltui sp, sp, 0xFFFFFE1C + ori sp, ra, 0xFFFFFE83 + andi sp, sp, 0xFFFFFEFC + addi ra, ra, 0xFFFFFF85 + ori gp, ra, 0x0084 + sll gp, gp, ra + xori gp, sp, 0xFFFFFF6D + sll gp, sp, gp + sra ra, sp, ra + xor ra, gp, sp + srl ra, ra, sp + srl ra, ra, sp + andi gp, ra, 0xFFFFFE7B + srai ra, sp, 0xFFFFFFF1 + sub sp, sp, ra + or sp, gp, gp + slt ra, ra, gp + or gp, gp, sp + srli ra, sp, 0xFFFFFFF5 + andi ra, gp, 0xFFFFFFD4 + sra sp, sp, sp + add sp, ra, sp + sub gp, ra, sp + xori ra, gp, 0x0131 + add sp, sp, ra + addi sp, gp, 0x0003 + sll ra, ra, ra + slli gp, ra, 0x000E + andi ra, gp, 0xFFFFFE88 + srai ra, gp, 0xFFFFFFFA + done +#regset x1, 123 +#regset x2, -40 +#regset x3, 0xFFEE diff --git a/src/test/resources/tests/basic/immediate/addi.s b/src/test/resources/tests/basic/immediate/addi.s new file mode 100644 index 0000000..c0b93f0 --- /dev/null +++ b/src/test/resources/tests/basic/immediate/addi.s @@ -0,0 +1,41 @@ +main: + addi x0, x1, 1 + addi x0, x1, 2 + addi x0, x1, 3 + addi x0, x1, 7 + addi x0, x1, 14 + addi x0, x1, 28 + addi x0, x1, 56 + addi x31, x1, 1 + addi x31, x1, 2 + addi x31, x1, 3 + addi x31, x1, 7 + addi x31, x1, 14 + addi x31, x1, 28 + addi x31, x1, 56 + addi x31, x1, 133 + addi x31, x1, 258 + addi x31, x1, 511 + addi x31, x1, -1 + addi x31, x1, -3 + addi x31, x1, -9 + addi x31, x1, -98 + addi x31, x1, -231 + addi x31, x1, -510 + addi x30, x0, 1 + addi x30, x30, 2 + addi x30, x30, 3 + addi x30, x30, 7 + addi x30, x30, 14 + addi x30, x30, 28 + addi x30, x30, 56 + addi x30, x30, 133 + addi x30, x30, 258 + addi x30, x30, 511 + addi x30, x30, -1 + addi x30, x30, -3 + addi x30, x30, -9 + addi x30, x30, -98 + addi x30, x30, -231 + addi x30, x30, -510 + done diff --git a/src/test/resources/tests/basic/immediate/arithImm.s b/src/test/resources/tests/basic/immediate/arithImm.s new file mode 100644 index 0000000..29e2dfa --- /dev/null +++ b/src/test/resources/tests/basic/immediate/arithImm.s @@ -0,0 +1,37 @@ +main: + addi x1, zero, 1 + addi x2, zero, -1 + addi x3, zero, 3 + addi x4, zero, 7 + addi x5, zero, 14 + addi x6, zero, 28 + addi x7, zero, 56 + addi x8, zero, 133 + addi x9, zero, 258 + addi x10, x1, -231 + addi x11, x1, -510 + slti x12, x1, 1 + slti x12, x1, 10 + slti x12, x1, -10 + sltiu x13, x1, 1 + sltiu x13, x1, 10 + sltiu x13, x1, -10 + slti x12, x2, 1 + slti x12, x2, 10 + slti x12, x2, -10 + sltiu x13, x2, 1 + sltiu x13, x2, 10 + sltiu x13, x2, -10 + slli x14, x1, 16 + slli x14, x2, 16 + srai x14, x1, 16 + srai x14, x2, 16 + srli x14, x1, 16 + srli x14, x2, 16 + andi x15, x8, 3 + andi x15, x9, -1 + ori x15, x8, 3 + ori x15, x9, -1 + xori x15, x8, 3 + xori x15, x9, -1 + done diff --git a/src/test/resources/tests/basic/immediate/arithmetic.s b/src/test/resources/tests/basic/immediate/arithmetic.s new file mode 100644 index 0000000..e69de29 diff --git a/src/test/resources/tests/basic/load.s b/src/test/resources/tests/basic/load.s new file mode 100644 index 0000000..c4cb55d --- /dev/null +++ b/src/test/resources/tests/basic/load.s @@ -0,0 +1,35 @@ +main: + addi x1, zero, 4 + addi x2, zero, 4 + addi x3, zero, 4 + addi x4, zero, 4 + lw x3, 0(x3) + nop + nop + lw x3, 0(x3) + nop + nop + lw x3, 0(x3) + nop + nop + lw x3, 0(x3) + nop + nop + lw x2, 0(x2) + nop + lw x2, 0(x2) + nop + lw x2, 0(x2) + nop + lw x2, 0(x2) + nop + lw x1, 0(x1) + lw x1, 0(x1) + lw x1, 0(x1) + lw x1, 0(x1) + done +#memset 0x0, 4 +#memset 0x4, 8 +#memset 0x8, 12 +#memset 0xc, 16 +#memset 0x10, 20 diff --git a/src/test/resources/tests/basic/load2.s b/src/test/resources/tests/basic/load2.s new file mode 100644 index 0000000..9a26690 --- /dev/null +++ b/src/test/resources/tests/basic/load2.s @@ -0,0 +1,20 @@ +main: + addi x1, zero, 4 + addi x2, zero, 4 + addi x3, zero, 4 + addi x4, zero, 4 + lw x1, 0(x1) + add x1, x1, x1 + lw x1, 0(x1) + sw x1, 4(x1) + lw x1, 4(x1) + done +#memset 0x0, 4 +#memset 0x4, 8 +#memset 0x8, 12 +#memset 0xc, 16 +#memset 0x10, 20 +#memset 0x14, 20 +#memset 0x18, 20 +#memset 0x1c, 20 +#memset 0x20, 20 diff --git a/src/test/resources/tests/programs/BTreeManyO3.s b/src/test/resources/tests/programs/BTreeManyO3.s new file mode 100644 index 0000000..ea32532 --- /dev/null +++ b/src/test/resources/tests/programs/BTreeManyO3.s @@ -0,0 +1,180 @@ +main: + addi sp,sp,-16 + sw ra,12(sp) + sw s0,8(sp) + sw s1,4(sp) + sw s2,0(sp) + lw s2,0(zero) + lw a5,0(s2) + blez a5,.L17 + addi s0,s2,4 + slli a5,a5,2 + add s2,a5,s0 + li s1,0 +.L16: + addi s0,s0,4 + lw a0,0(s0) + call find + add s1,s1,a0 + bne s0,s2,.L16 +.L14: + mv a0,s1 + lw ra,12(sp) + lw s0,8(sp) + lw s1,4(sp) + lw s2,0(sp) + addi sp,sp,16 + jr ra +.L17: + li s1,0 + j .L14 +find: + li a5,4 + j .L2 +.L12: + mv a0,a5 + ret +.L13: + lw a5,0(a5) + slli a5,a5,1 + andi a5,a5,508 + addi a5,a5,4 + j .L2 +.L4: + bge a4,a0,.L9 + lw a4,0(a5) + andi a4,a4,256 + beqz a4,.L10 + lw a5,0(a5) + srli a5,a5,7 + andi a5,a5,508 + addi a5,a5,4 +.L2: + lh a4,2(a5) + beq a4,a0,.L12 + ble a4,a0,.L4 + lw a4,0(a5) + andi a4,a4,1 + bnez a4,.L13 + li a0,-1 + ret +.L9: + li a0,-1 + ret +.L10: + li a0,-1 + ret +#memset 0x0, 0x019C +#memset 0x0004, 0x02D49D03 +#memset 0x0008, 0x00912305 +#memset 0x000C, 0x00301307 +#memset 0x0010, 0x001B0D09 +#memset 0x0014, 0x00010B00 +#memset 0x0018, 0x00090000 +#memset 0x001C, 0x001E110F +#memset 0x0020, 0x001D0000 +#memset 0x0024, 0x001E0000 +#memset 0x0028, 0x00782115 +#memset 0x002C, 0x00661D17 +#memset 0x0030, 0x003C1B19 +#memset 0x0034, 0x00300000 +#memset 0x0038, 0x00430000 +#memset 0x003C, 0x00701F00 +#memset 0x0040, 0x00700000 +#memset 0x0044, 0x007B0000 +#memset 0x0048, 0x018E5925 +#memset 0x004C, 0x00A12B27 +#memset 0x0050, 0x009F0029 +#memset 0x0054, 0x00910000 +#memset 0x0058, 0x011D392D +#memset 0x005C, 0x00E22F00 +#memset 0x0060, 0x01190031 +#memset 0x0064, 0x01090033 +#memset 0x0068, 0x00E93500 +#memset 0x006C, 0x00FF3700 +#memset 0x0070, 0x00FF0000 +#memset 0x0074, 0x01213F3B +#memset 0x0078, 0x0120003D +#memset 0x007C, 0x011F0000 +#memset 0x0080, 0x01835741 +#memset 0x0084, 0x016A5543 +#memset 0x0088, 0x012F4745 +#memset 0x008C, 0x012E0000 +#memset 0x0090, 0x014B4F49 +#memset 0x0094, 0x013A4B00 +#memset 0x0098, 0x01424D00 +#memset 0x009C, 0x01460000 +#memset 0x00A0, 0x015C0051 +#memset 0x00A4, 0x014D5300 +#memset 0x00A8, 0x01510000 +#memset 0x00AC, 0x01730000 +#memset 0x00B0, 0x018B0000 +#memset 0x00B4, 0x01BF655B +#memset 0x00B8, 0x01A2615D +#memset 0x00BC, 0x01995F00 +#memset 0x00C0, 0x019F0000 +#memset 0x00C4, 0x01A46300 +#memset 0x00C8, 0x01BD0000 +#memset 0x00CC, 0x027C8967 +#memset 0x00D0, 0x026C8569 +#memset 0x00D4, 0x01CC6D6B +#memset 0x00D8, 0x01C60000 +#memset 0x00DC, 0x021F7D6F +#memset 0x00E0, 0x02070071 +#memset 0x00E4, 0x01D37300 +#memset 0x00E8, 0x01F67B75 +#memset 0x00EC, 0x01EF7977 +#memset 0x00F0, 0x01D70000 +#memset 0x00F4, 0x01F10000 +#memset 0x00F8, 0x02060000 +#memset 0x00FC, 0x0254837F +#memset 0x0100, 0x023D0081 +#memset 0x0104, 0x02200000 +#memset 0x0108, 0x02640000 +#memset 0x010C, 0x026D8700 +#memset 0x0110, 0x026E0000 +#memset 0x0114, 0x02C0008B +#memset 0x0118, 0x0297998D +#memset 0x011C, 0x0289978F +#memset 0x0120, 0x02839591 +#memset 0x0124, 0x027C9300 +#memset 0x0128, 0x027E0000 +#memset 0x012C, 0x02850000 +#memset 0x0130, 0x028D0000 +#memset 0x0134, 0x029E009B +#memset 0x0138, 0x02990000 +#memset 0x013C, 0x03D4C99F +#memset 0x0140, 0x0332ADA1 +#memset 0x0144, 0x02E9A300 +#memset 0x0148, 0x031AABA5 +#memset 0x014C, 0x0305A9A7 +#memset 0x0150, 0x02EC0000 +#memset 0x0154, 0x03090000 +#memset 0x0158, 0x032B0000 +#memset 0x015C, 0x03CC00AF +#memset 0x0160, 0x0365B7B1 +#memset 0x0164, 0x0333B300 +#memset 0x0168, 0x036200B5 +#memset 0x016C, 0x033E0000 +#memset 0x0170, 0x03B6C3B9 +#memset 0x0174, 0x03AA00BB +#memset 0x0178, 0x039700BD +#memset 0x017C, 0x037DC1BF +#memset 0x0180, 0x03750000 +#memset 0x0184, 0x03870000 +#memset 0x0188, 0x03C8C7C5 +#memset 0x018C, 0x03BB0000 +#memset 0x0190, 0x03C90000 +#memset 0x0194, 0x03E0CB00 +#memset 0x0198, 0x03E40000 +#memset 0x019C, 0x0010 +#memset 0x01A0, 0x0264 +#memset 0x01A4, 0x0C0D +#memset 0x01A8, 0x0031 +#memset 0x01AC, 0x0206 +#memset 0x01B0, 0x0891 +#memset 0x01B4, 0x0043 +#memset 0x01B8, 0x029E +#memset 0x01BC, 0x0264 +#memset 0x01C0, 0x0123 +#memset 0x01C4, 0x0264 diff --git a/src/test/resources/tests/programs/BTreeO3.s b/src/test/resources/tests/programs/BTreeO3.s new file mode 100644 index 0000000..ca3a6a5 --- /dev/null +++ b/src/test/resources/tests/programs/BTreeO3.s @@ -0,0 +1,142 @@ +main: + addi sp,sp,-16 + sw ra,12(sp) + lw a0,0(zero) + call find + lw ra,12(sp) + addi sp,sp,16 + jr ra +find: + li a5,4 +.L2: + lh a4,2(a5) + beq a4,a0,.L13 +.L11: + ble a4,a0,.L4 + lw a5,0(a5) + andi a4,a5,1 + bnez a4,.L14 +.L10: + li a0,-1 + ret +.L4: + bge a4,a0,.L10 + lw a4,0(a5) + srli a5,a4,7 + andi a4,a4,256 + andi a5,a5,508 + beqz a4,.L10 + addi a5,a5,4 + lh a4,2(a5) + bne a4,a0,.L11 +.L13: + mv a0,a5 + ret +.L14: + slli a5,a5,1 + andi a5,a5,508 + addi a5,a5,4 + j .L2 +#memset 0x0, 0x013A +#memset 0x0004, 0x02D49D03 +#memset 0x0008, 0x00912305 +#memset 0x000C, 0x00301307 +#memset 0x0010, 0x001B0D09 +#memset 0x0014, 0x00010B00 +#memset 0x0018, 0x00090000 +#memset 0x001C, 0x001E110F +#memset 0x0020, 0x001D0000 +#memset 0x0024, 0x001E0000 +#memset 0x0028, 0x00782115 +#memset 0x002C, 0x00661D17 +#memset 0x0030, 0x003C1B19 +#memset 0x0034, 0x00300000 +#memset 0x0038, 0x00430000 +#memset 0x003C, 0x00701F00 +#memset 0x0040, 0x00700000 +#memset 0x0044, 0x007B0000 +#memset 0x0048, 0x018E5925 +#memset 0x004C, 0x00A12B27 +#memset 0x0050, 0x009F0029 +#memset 0x0054, 0x00910000 +#memset 0x0058, 0x011D392D +#memset 0x005C, 0x00E22F00 +#memset 0x0060, 0x01190031 +#memset 0x0064, 0x01090033 +#memset 0x0068, 0x00E93500 +#memset 0x006C, 0x00FF3700 +#memset 0x0070, 0x00FF0000 +#memset 0x0074, 0x01213F3B +#memset 0x0078, 0x0120003D +#memset 0x007C, 0x011F0000 +#memset 0x0080, 0x01835741 +#memset 0x0084, 0x016A5543 +#memset 0x0088, 0x012F4745 +#memset 0x008C, 0x012E0000 +#memset 0x0090, 0x014B4F49 +#memset 0x0094, 0x013A4B00 +#memset 0x0098, 0x01424D00 +#memset 0x009C, 0x01460000 +#memset 0x00A0, 0x015C0051 +#memset 0x00A4, 0x014D5300 +#memset 0x00A8, 0x01510000 +#memset 0x00AC, 0x01730000 +#memset 0x00B0, 0x018B0000 +#memset 0x00B4, 0x01BF655B +#memset 0x00B8, 0x01A2615D +#memset 0x00BC, 0x01995F00 +#memset 0x00C0, 0x019F0000 +#memset 0x00C4, 0x01A46300 +#memset 0x00C8, 0x01BD0000 +#memset 0x00CC, 0x027C8967 +#memset 0x00D0, 0x026C8569 +#memset 0x00D4, 0x01CC6D6B +#memset 0x00D8, 0x01C60000 +#memset 0x00DC, 0x021F7D6F +#memset 0x00E0, 0x02070071 +#memset 0x00E4, 0x01D37300 +#memset 0x00E8, 0x01F67B75 +#memset 0x00EC, 0x01EF7977 +#memset 0x00F0, 0x01D70000 +#memset 0x00F4, 0x01F10000 +#memset 0x00F8, 0x02060000 +#memset 0x00FC, 0x0254837F +#memset 0x0100, 0x023D0081 +#memset 0x0104, 0x02200000 +#memset 0x0108, 0x02640000 +#memset 0x010C, 0x026D8700 +#memset 0x0110, 0x026E0000 +#memset 0x0114, 0x02C0008B +#memset 0x0118, 0x0297998D +#memset 0x011C, 0x0289978F +#memset 0x0120, 0x02839591 +#memset 0x0124, 0x027C9300 +#memset 0x0128, 0x027E0000 +#memset 0x012C, 0x02850000 +#memset 0x0130, 0x028D0000 +#memset 0x0134, 0x029E009B +#memset 0x0138, 0x02990000 +#memset 0x013C, 0x03D4C99F +#memset 0x0140, 0x0332ADA1 +#memset 0x0144, 0x02E9A300 +#memset 0x0148, 0x031AABA5 +#memset 0x014C, 0x0305A9A7 +#memset 0x0150, 0x02EC0000 +#memset 0x0154, 0x03090000 +#memset 0x0158, 0x032B0000 +#memset 0x015C, 0x03CC00AF +#memset 0x0160, 0x0365B7B1 +#memset 0x0164, 0x0333B300 +#memset 0x0168, 0x036200B5 +#memset 0x016C, 0x033E0000 +#memset 0x0170, 0x03B6C3B9 +#memset 0x0174, 0x03AA00BB +#memset 0x0178, 0x039700BD +#memset 0x017C, 0x037DC1BF +#memset 0x0180, 0x03750000 +#memset 0x0184, 0x03870000 +#memset 0x0188, 0x03C8C7C5 +#memset 0x018C, 0x03BB0000 +#memset 0x0190, 0x03C90000 +#memset 0x0194, 0x03E0CB00 +#memset 0x0198, 0x03E40000 diff --git a/src/test/resources/tests/programs/add.s b/src/test/resources/tests/programs/add.s new file mode 100644 index 0000000..9524ea4 --- /dev/null +++ b/src/test/resources/tests/programs/add.s @@ -0,0 +1,14 @@ +main: + add t0, t0, t1 + add t2, t0, t1 + add zero, t0, t1 + add t2, t2, t1 + add t3, t3, t3 + add t3, t3, t3 + add t3, t3, t3 + nop + done +#regset t0,10 +#regset t1,23 +#regset t2,43 +#regset t3,-11 diff --git a/src/test/resources/tests/programs/memoFib.s b/src/test/resources/tests/programs/memoFib.s new file mode 100644 index 0000000..120303d --- /dev/null +++ b/src/test/resources/tests/programs/memoFib.s @@ -0,0 +1,118 @@ +main: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + addi s0,sp,32 + sw zero,-20(s0) + li a5,100 + sw a5,-24(s0) + lw a1,-20(s0) + li a0,11 + call setupmem + lw a1,-24(s0) + li a0,11 + call setupmem + lw a2,-24(s0) + lw a1,-20(s0) + li a0,10 + call f + li a5,0 + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + addi sp,sp,32 + jr ra +f: + addi sp,sp,-48 + sw ra,44(sp) + sw s0,40(sp) + sw s1,36(sp) + addi s0,sp,48 + sw a0,-36(s0) + sw a1,-40(s0) + sw a2,-44(s0) + lw a5,-36(s0) + slli a5,a5,2 + lw a4,-40(s0) + add a5,a4,a5 + lw a5,0(a5) + beqz a5,.L2 + lw a5,-36(s0) + slli a5,a5,2 + lw a4,-44(s0) + add a5,a4,a5 + lw a5,0(a5) + j .L3 +.L2: + lw a5,-36(s0) + bnez a5,.L4 + li a5,0 + j .L3 +.L4: + lw a4,-36(s0) + li a5,1 + bne a4,a5,.L5 + li a5,1 + j .L3 +.L5: + lw a5,-36(s0) + addi a5,a5,-1 + lw a2,-44(s0) + lw a1,-40(s0) + mv a0,a5 + call f + mv s1,a0 + lw a5,-36(s0) + addi a5,a5,-2 + lw a2,-44(s0) + lw a1,-40(s0) + mv a0,a5 + call f + mv a5,a0 + add a5,s1,a5 + sw a5,-20(s0) + lw a5,-36(s0) + slli a5,a5,2 + lw a4,-40(s0) + add a5,a4,a5 + li a4,1 + sw a4,0(a5) + lw a5,-36(s0) + slli a5,a5,2 + lw a4,-44(s0) + add a5,a4,a5 + lw a4,-20(s0) + sw a4,0(a5) + lw a5,-20(s0) +.L3: + mv a0,a5 + lw ra,44(sp) + lw s0,40(sp) + lw s1,36(sp) + addi sp,sp,48 + jr ra +setupmem: + addi sp,sp,-48 + sw s0,44(sp) + addi s0,sp,48 + sw a0,-36(s0) + sw a1,-40(s0) + sw zero,-20(s0) + j .L7 +.L8: + lw a5,-20(s0) + slli a5,a5,2 + lw a4,-40(s0) + add a5,a4,a5 + sw zero,0(a5) + lw a5,-20(s0) + addi a5,a5,1 + sw a5,-20(s0) +.L7: + lw a4,-20(s0) + lw a5,-36(s0) + blt a4,a5,.L8 + nop + lw s0,44(sp) + addi sp,sp,48 + jr ra diff --git a/src/test/resources/tests/programs/naiveFib.s b/src/test/resources/tests/programs/naiveFib.s new file mode 100644 index 0000000..c0d8590 --- /dev/null +++ b/src/test/resources/tests/programs/naiveFib.s @@ -0,0 +1,49 @@ +main: + addi sp,sp,-16 + sw ra,12(sp) + sw s0,8(sp) + addi s0,sp,16 + li a0,6 + call f + mv a5,a0 + mv a0,a5 + lw ra,12(sp) + lw s0,8(sp) + addi sp,sp,16 + jr ra +f: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + sw s1,20(sp) + addi s0,sp,32 + sw a0,-20(s0) + lw a5,-20(s0) + bnez a5,.L2 + li a5,0 + j .L3 +.L2: + lw a4,-20(s0) + li a5,1 + bne a4,a5,.L4 + li a5,1 + j .L3 +.L4: + lw a5,-20(s0) + addi a5,a5,-1 + mv a0,a5 + call f + mv s1,a0 + lw a5,-20(s0) + addi a5,a5,-2 + mv a0,a5 + call f + mv a5,a0 + add a5,s1,a5 +.L3: + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + lw s1,20(sp) + addi sp,sp,32 + jr ra diff --git a/src/test/resources/tests/programs/palindrome.s b/src/test/resources/tests/programs/palindrome.s new file mode 100644 index 0000000..ddbd0b4 --- /dev/null +++ b/src/test/resources/tests/programs/palindrome.s @@ -0,0 +1,112 @@ +main: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + addi s0,sp,32 + sw zero,-20(s0) + li a5,32 + sw a5,-24(s0) + li a2,7 + li a1,0 + lw a0,-20(s0) + call isPalindrome +.DEBUG_call1_return: + mv a5,a0 + beqz a5,.L2 + li a2,15 + li a1,0 + lw a0,-24(s0) + call isPalindrome +.DEBUG_call2_return: + mv a5,a0 + beqz a5,.L2 + li a5,1 + j .L4 +.L2: + li a5,0 +.L4: + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + addi sp,sp,32 + jr ra +isPalindrome: + addi sp,sp,-48 + sw ra,44(sp) + sw s0,40(sp) + addi s0,sp,48 + sw a0,-36(s0) + sw a1,-40(s0) + sw a2,-44(s0) + lw a4,-40(s0) + lw a5,-44(s0) + blt a4,a5,.L6 + li a5,1 + j .L7 +.L6: + lw a5,-40(s0) + slli a5,a5,2 + lw a4,-36(s0) + add a5,a4,a5 + lw a4,0(a5) + lw a5,-44(s0) + slli a5,a5,2 + lw a3,-36(s0) + add a5,a3,a5 + lw a5,0(a5) + sub a5,a4,a5 + seqz a5,a5 + andi a5,a5,0xff + sw a5,-20(s0) + lw a5,-20(s0) + beqz a5,.CompareFailed + lw a5,-40(s0) + addi a4,a5,1 + lw a5,-44(s0) + addi a5,a5,-1 + mv a2,a5 + mv a1,a4 + lw a0,-36(s0) + call isPalindrome + mv a5,a0 + beqz a5,.CompareFailed + li a5,1 + j .L7 +.CompareFailed: + li a5,0 +.L7: + mv a0,a5 + lw ra,44(sp) + lw s0,40(sp) + addi sp,sp,48 + jr ra + +#memset 0, 10 +#memset 4, -3 +#memset 8, 8 +#memset 12, 0 +#memset 16, 0 +#memset 20, 8 +#memset 24, -3 +#memset 28, 10 + + +#memset 32, 10 +#memset 36, -3 +#memset 40, 8 +#memset 44, 0 +#memset 48, 0 +#memset 52, 10 + +#memset 56, -3 +#memset 60, 8 + +#memset 64, -3 +#memset 68, 8 + +#memset 72, 10 +#memset 76, 0 +#memset 80, 0 +#memset 84, 8 +#memset 88, -3 +#memset 92, 10 diff --git a/src/test/resources/tests/programs/palindromeO3.s b/src/test/resources/tests/programs/palindromeO3.s new file mode 100644 index 0000000..b4cbb85 --- /dev/null +++ b/src/test/resources/tests/programs/palindromeO3.s @@ -0,0 +1,120 @@ +main: + addi sp,sp,-16 + li a2,7 + li a1,0 + li a0,0 + sw ra,12(sp) + call isPalindrome.part.0 +.DEBUG_call1_return: + bnez a0,.L17 +isPalindrome.part.0: + slli a4,a1,2 + slli a3,a2,2 + add a4,a0,a4 + add a5,a0,a3 + lw a4,0(a4) + lw a5,0(a5) + beq a4,a5,.L10 + li a5,0 +.L6: + mv a0,a5 + ret +.L10: + addi a1,a1,1 + addi a2,a2,-1 + li a5,1 + bge a1,a2,.L6 + addi sp,sp,-16 + sw ra,12(sp) + call isPalindrome.part.0 +.DEBUG_call2_return: + lw ra,12(sp) + snez a5,a0 + mv a0,a5 + addi sp,sp,16 + jr ra +.L11: + lw ra,12(sp) + addi sp,sp,16 + jr ra +.L17: + li a2,15 + li a1,0 + li a0,32 + call isPalindrome.part.0 + snez a0,a0 + j .L11 +isPalindrome: + bge a1,a2,.L20 + slli a3,a2,2 + slli a4,a1,2 + add a5,a0,a3 + add a4,a0,a4 + lw a7,0(a4) + lw a6,0(a5) + li a3,0 + bne a7,a6,.L28 + addi a7,a1,1 + addi a6,a2,-1 + li a3,1 + bge a7,a6,.L28 + lw a7,4(a4) + lw a6,-4(a5) + li a3,0 + bne a7,a6,.L28 + addi a7,a1,2 + addi a6,a2,-2 + li a3,1 + bge a7,a6,.L28 + lw a4,8(a4) + lw a5,-8(a5) + li a3,0 + bne a4,a5,.L28 + addi a1,a1,3 + addi a2,a2,-3 + li a3,1 + bge a1,a2,.L28 + addi sp,sp,-16 + sw ra,12(sp) + call isPalindrome.part.0 + lw ra,12(sp) + snez a3,a0 + mv a0,a3 + addi sp,sp,16 + jr ra +.L20: + li a3,1 +.L28: + mv a0,a3 + ret + + +#memset 0, 10 +#memset 4, -3 +#memset 8, 8 +#memset 12, 0 +#memset 16, 0 +#memset 20, 8 +#memset 24, -3 +#memset 28, 10 + + +#memset 32, 10 +#memset 36, -3 +#memset 40, 8 +#memset 44, 0 +#memset 48, 0 +#memset 52, 10 + +#memset 56, -3 +#memset 60, 8 + +#memset 64, -3 +#memset 68, 8 + +#memset 72, 10 +#memset 76, 0 +#memset 80, 0 +#memset 84, 8 +#memset 88, -3 +#memset 92, 10 diff --git a/src/test/resources/tests/programs/searchRegularO0.s b/src/test/resources/tests/programs/searchRegularO0.s new file mode 100644 index 0000000..4096104 --- /dev/null +++ b/src/test/resources/tests/programs/searchRegularO0.s @@ -0,0 +1,187 @@ +main: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + addi s0,sp,32 + li a5,0 + lw a5,0(a5) + sw a5,-20(s0) + lw a0,-20(s0) + call find + mv a5,a0 + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + addi sp,sp,32 + jr ra +find: + addi sp,sp,-64 + sw s0,60(sp) + addi s0,sp,64 + sw a0,-52(s0) + sw zero,-24(s0) + li a5,4 + sw a5,-20(s0) + sw zero,-28(s0) + j .L2 +.WHILE: + lw a5,-20(s0) + lh a5,2(a5) + sw a5,-28(s0) + lw a4,-28(s0) + lw a5,-52(s0) + bne a4,a5,.CHECKLEFT + lw a5,-20(s0) + j .L1 +.CHECKLEFT: + lw a4,-28(s0) + lw a5,-52(s0) + ble a4,a5,.CHECKRIGHT + lw a5,-20(s0) + lw a5,0(a5) + andi a5,a5,1 + beqz a5,.CHECKRIGHT + lw a5,-20(s0) + lw a5,0(a5) + srli a5,a5,1 + andi a5,a5,127 + andi a5,a5,0xff + sw a5,-32(s0) + lw a5,-32(s0) + slli a5,a5,2 + addi a5,a5,4 + sw a5,-20(s0) + j .L2 +.CHECKRIGHT: + lw a4,-28(s0) + lw a5,-52(s0) + bge a4,a5,.L6 + lw a5,-20(s0) + lw a5,0(a5) + andi a5,a5,256 + beqz a5,.L6 + lw a5,-20(s0) + lw a5,0(a5) + srli a5,a5,9 + andi a5,a5,127 + andi a5,a5,0xff + sw a5,-36(s0) + lw a5,-36(s0) + slli a5,a5,2 + addi a5,a5,4 + sw a5,-20(s0) + j .L2 +.L6: + li a5,-1 + j .L1 +.L2: + lw a5,-24(s0) + beqz a5,.WHILE +.L1: + mv a0,a5 + lw s0,60(sp) + addi sp,sp,64 + jr ra +#memset 0x0, 0x013A +#memset 0x0004, 0x02D49D03 +#memset 0x0008, 0x00912305 +#memset 0x000C, 0x00301307 +#memset 0x0010, 0x001B0D09 +#memset 0x0014, 0x00010B00 +#memset 0x0018, 0x00090000 +#memset 0x001C, 0x001E110F +#memset 0x0020, 0x001D0000 +#memset 0x0024, 0x001E0000 +#memset 0x0028, 0x00782115 +#memset 0x002C, 0x00661D17 +#memset 0x0030, 0x003C1B19 +#memset 0x0034, 0x00300000 +#memset 0x0038, 0x00430000 +#memset 0x003C, 0x00701F00 +#memset 0x0040, 0x00700000 +#memset 0x0044, 0x007B0000 +#memset 0x0048, 0x018E5925 +#memset 0x004C, 0x00A12B27 +#memset 0x0050, 0x009F0029 +#memset 0x0054, 0x00910000 +#memset 0x0058, 0x011D392D +#memset 0x005C, 0x00E22F00 +#memset 0x0060, 0x01190031 +#memset 0x0064, 0x01090033 +#memset 0x0068, 0x00E93500 +#memset 0x006C, 0x00FF3700 +#memset 0x0070, 0x00FF0000 +#memset 0x0074, 0x01213F3B +#memset 0x0078, 0x0120003D +#memset 0x007C, 0x011F0000 +#memset 0x0080, 0x01835741 +#memset 0x0084, 0x016A5543 +#memset 0x0088, 0x012F4745 +#memset 0x008C, 0x012E0000 +#memset 0x0090, 0x014B4F49 +#memset 0x0094, 0x013A4B00 +#memset 0x0098, 0x01424D00 +#memset 0x009C, 0x01460000 +#memset 0x00A0, 0x015C0051 +#memset 0x00A4, 0x014D5300 +#memset 0x00A8, 0x01510000 +#memset 0x00AC, 0x01730000 +#memset 0x00B0, 0x018B0000 +#memset 0x00B4, 0x01BF655B +#memset 0x00B8, 0x01A2615D +#memset 0x00BC, 0x01995F00 +#memset 0x00C0, 0x019F0000 +#memset 0x00C4, 0x01A46300 +#memset 0x00C8, 0x01BD0000 +#memset 0x00CC, 0x027C8967 +#memset 0x00D0, 0x026C8569 +#memset 0x00D4, 0x01CC6D6B +#memset 0x00D8, 0x01C60000 +#memset 0x00DC, 0x021F7D6F +#memset 0x00E0, 0x02070071 +#memset 0x00E4, 0x01D37300 +#memset 0x00E8, 0x01F67B75 +#memset 0x00EC, 0x01EF7977 +#memset 0x00F0, 0x01D70000 +#memset 0x00F4, 0x01F10000 +#memset 0x00F8, 0x02060000 +#memset 0x00FC, 0x0254837F +#memset 0x0100, 0x023D0081 +#memset 0x0104, 0x02200000 +#memset 0x0108, 0x02640000 +#memset 0x010C, 0x026D8700 +#memset 0x0110, 0x026E0000 +#memset 0x0114, 0x02C0008B +#memset 0x0118, 0x0297998D +#memset 0x011C, 0x0289978F +#memset 0x0120, 0x02839591 +#memset 0x0124, 0x027C9300 +#memset 0x0128, 0x027E0000 +#memset 0x012C, 0x02850000 +#memset 0x0130, 0x028D0000 +#memset 0x0134, 0x029E009B +#memset 0x0138, 0x02990000 +#memset 0x013C, 0x03D4C99F +#memset 0x0140, 0x0332ADA1 +#memset 0x0144, 0x02E9A300 +#memset 0x0148, 0x031AABA5 +#memset 0x014C, 0x0305A9A7 +#memset 0x0150, 0x02EC0000 +#memset 0x0154, 0x03090000 +#memset 0x0158, 0x032B0000 +#memset 0x015C, 0x03CC00AF +#memset 0x0160, 0x0365B7B1 +#memset 0x0164, 0x0333B300 +#memset 0x0168, 0x036200B5 +#memset 0x016C, 0x033E0000 +#memset 0x0170, 0x03B6C3B9 +#memset 0x0174, 0x03AA00BB +#memset 0x0178, 0x039700BD +#memset 0x017C, 0x037DC1BF +#memset 0x0180, 0x03750000 +#memset 0x0184, 0x03870000 +#memset 0x0188, 0x03C8C7C5 +#memset 0x018C, 0x03BB0000 +#memset 0x0190, 0x03C90000 +#memset 0x0194, 0x03E0CB00 +#memset 0x0198, 0x03E40000 diff --git a/src/test/resources/tests/programs/source/memoFib.c b/src/test/resources/tests/programs/source/memoFib.c new file mode 100644 index 0000000..ba5ba12 --- /dev/null +++ b/src/test/resources/tests/programs/source/memoFib.c @@ -0,0 +1,38 @@ +#include + +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O3" +// rmsbolt-disassemble: nil +// End: + + +int f(int x, int* isMemoized, int* memoizedVal){ + if(isMemoized[x]) + return memoizedVal[x]; + + if (x == 0) return 0; + if (x == 1) return 1; + + int next = f(x-1, isMemoized, memoizedVal) + f(x-2, isMemoized, memoizedVal); + isMemoized[x] = 1; + memoizedVal[x] = next; + + return next; +} + +void setupmem(int n, int* m) { + for(int ii = 0; ii < n; ii++){ + m[ii] = 0; + } +} + +int main() { + int* isMemoized = (int*)0; + int* memoizedVal = (int*)100; + setupmem(11, isMemoized); + setupmem(11, memoizedVal); + int r = f(10, isMemoized, memoizedVal); + return r; +} diff --git a/src/test/resources/tests/programs/source/naiveFib.c b/src/test/resources/tests/programs/source/naiveFib.c new file mode 100644 index 0000000..3c7eeb8 --- /dev/null +++ b/src/test/resources/tests/programs/source/naiveFib.c @@ -0,0 +1,20 @@ + +#include + +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O0" +// rmsbolt-disassemble: nil +// End: + +int f(int x){ + if (x == 0) return 0; + if (x == 1) return 1; + return f(x-1) + f(x-2); +} + + +int main() { + return f(4); +} diff --git a/src/test/resources/tests/programs/source/palindrome.c b/src/test/resources/tests/programs/source/palindrome.c new file mode 100644 index 0000000..f5ea9e3 --- /dev/null +++ b/src/test/resources/tests/programs/source/palindrome.c @@ -0,0 +1,28 @@ +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O3" +// rmsbolt-disassemble: nil +// End: + +int main() { + + /* int palindrome[8]; */ + /* int notAPalindrome[16]; */ + + // Set up "heap" addresses + int palindrome = (int*)0; + int notAPalindrome = (int*)32; + + return isPalindrome(palindrome, 0, 7) && isPalindrome(notAPalindrome, 0, 15); +} + +int isPalindrome(int* word, int start, int stop){ + if(start >= stop){ + return 1; + } + else{ + int currentIsPalindrome = (word[start] == word[stop]); + return currentIsPalindrome && isPalindrome(word, start + 1, stop - 1); + } +} diff --git a/src/test/resources/tests/programs/source/search.c b/src/test/resources/tests/programs/source/search.c new file mode 100644 index 0000000..84e66eb --- /dev/null +++ b/src/test/resources/tests/programs/source/search.c @@ -0,0 +1,50 @@ +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O3" +// rmsbolt-disassemble: nil +// End: + +/** + * Represents a binary tree + */ +typedef struct { + unsigned int hasLeft : 1; + unsigned int leftIndex : 7; + unsigned int hasRight : 1; + unsigned int rightIndex : 7; + int value : 16; +} node; + + +int find(int findMe){ + int found = 0; + node* currentNodeIdx = (node*)4; + int currentValue = 0; + + while(!found){ + currentValue = currentNodeIdx->value; + if(currentValue == findMe){ + return (int)currentNodeIdx; + } + + if((currentValue > findMe) && currentNodeIdx->hasLeft){ + int nextNodeIdx = currentNodeIdx->leftIndex; + currentNodeIdx = (node*)(4 + (nextNodeIdx << 2)); + continue; + } + + if((currentValue < findMe) && currentNodeIdx->hasRight){ + int nextNodeIdx = currentNodeIdx->rightIndex; + currentNodeIdx = (node*)(4 + (nextNodeIdx << 2)); + continue; + } + + return -1; + } +} + +int main() { + int needle = *(int*)0; + return find(needle); +} diff --git a/src/test/resources/tests/programs/source/searchMany.c b/src/test/resources/tests/programs/source/searchMany.c new file mode 100644 index 0000000..a5e1e76 --- /dev/null +++ b/src/test/resources/tests/programs/source/searchMany.c @@ -0,0 +1,64 @@ +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O1" +// rmsbolt-disassemble: nil +// End: + +/** + * Represents a binary tree + */ +typedef struct { + unsigned int hasLeft : 1; + unsigned int leftIndex : 7; + unsigned int hasRight : 1; + unsigned int rightIndex : 7; + int value : 16; +} node; + + +int find(int findMe){ + int found = 0; + node* currentNodeIdx = (node*)4; + int currentValue = 0; + + while(!found){ + currentValue = currentNodeIdx->value; + if(currentValue == findMe){ + return (int)currentNodeIdx; + } + + if((currentValue > findMe) && currentNodeIdx->hasLeft){ + int nextNodeIdx = currentNodeIdx->leftIndex; + currentNodeIdx = (node*)(4 + (nextNodeIdx << 2)); + continue; + } + + if((currentValue < findMe) && currentNodeIdx->hasRight){ + int nextNodeIdx = currentNodeIdx->rightIndex; + currentNodeIdx = (node*)(4 + (nextNodeIdx << 2)); + continue; + } + + return -1; + } +} + +int main() { + // Where does the needle list start? + int needles = *(int*)0; + int sum = 0; + + // How many needles are there? + int numNeedles = *(int*)needles; + int nextNeedle = (int)needles + 4; + + // Some useless calculations to make gcc O3 happy + for(int ii = 0; ii < numNeedles; ii++){ + nextNeedle += 4; + int needle = *(int*)(nextNeedle); + sum += find(needle); + } + + return sum; +} diff --git a/src/test/resources/tests/programs/source/square.c b/src/test/resources/tests/programs/source/square.c new file mode 100644 index 0000000..18ac207 --- /dev/null +++ b/src/test/resources/tests/programs/source/square.c @@ -0,0 +1,35 @@ + +#include + +// C rmsbolt starter file + +// Local Variables: +// rmsbolt-command: "/opt/riscv/bin/riscv32-unknown-elf-gcc -O0" +// rmsbolt-disassemble: nil +// End: + + +int mul(int a, int b) { + int c = 0; + int ii = 0; + for(int ii = 0; ii < a; ii++){ + c += b; + } + return c; +} + +int square(int a){ + return mul(a, a); +} + +int main() { + int a = 6; + int b = 0xFFFFFFFE; // MAXVAL - 2, (a + b) = -2 + int c = -1; //-1 + int d = 7; //0x4D2 (c + d) = 0x4D1 + + if(square(a+b) > square(c + d)) + return a; + else + return c; +} diff --git a/src/test/resources/tests/programs/square.s b/src/test/resources/tests/programs/square.s new file mode 100644 index 0000000..7f049cc --- /dev/null +++ b/src/test/resources/tests/programs/square.s @@ -0,0 +1,80 @@ +main: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + sw s1,20(sp) + addi s0,sp,32 + li a5,6 + sw a5,-20(s0) + li a5,-2 + sw a5,-24(s0) + li a5,-1 + sw a5,-28(s0) + li a5,7 + sw a5,-32(s0) + lw a4,-20(s0) + lw a5,-24(s0) + add a5,a4,a5 + mv a0,a5 + call square + mv s1,a0 + lw a4,-28(s0) + lw a5,-32(s0) + add a5,a4,a5 + mv a0,a5 + call square + mv a5,a0 + ble s1,a5,.L8 + lw a5,-20(s0) + j .L9 +.L8: + lw a5,-28(s0) +.L9: + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + lw s1,20(sp) + addi sp,sp,32 + jr ra +mul: + addi sp,sp,-48 + sw s0,44(sp) + addi s0,sp,48 + sw a0,-36(s0) + sw a1,-40(s0) + sw zero,-20(s0) + sw zero,-28(s0) + sw zero,-24(s0) + j .L2 +.L3: + lw a4,-20(s0) + lw a5,-40(s0) + add a5,a4,a5 + sw a5,-20(s0) + lw a5,-24(s0) + addi a5,a5,1 + sw a5,-24(s0) +.L2: + lw a4,-24(s0) + lw a5,-36(s0) + blt a4,a5,.L3 + lw a5,-20(s0) + mv a0,a5 + lw s0,44(sp) + addi sp,sp,48 + jr ra +square: + addi sp,sp,-32 + sw ra,28(sp) + sw s0,24(sp) + addi s0,sp,32 + sw a0,-20(s0) + lw a1,-20(s0) + lw a0,-20(s0) + call mul + mv a5,a0 + mv a0,a5 + lw ra,28(sp) + lw s0,24(sp) + addi sp,sp,32 + jr ra diff --git a/src/test/scala/Manifest.scala b/src/test/scala/Manifest.scala new file mode 100644 index 0000000..79249fe --- /dev/null +++ b/src/test/scala/Manifest.scala @@ -0,0 +1,78 @@ +package FiveStage +import org.scalatest.{Matchers, FlatSpec} +import cats._ +import cats.implicits._ +import fileUtils._ + +import chisel3.iotesters._ +import scala.collection.mutable.LinkedHashMap + +import fansi.Str + +import Ops._ +import Data._ +import VM._ + +import PrintUtils._ +import LogParser._ + +object Manifest { + + val singleTest = "forward2.s" + + val nopPadded = false + + val singleTestOptions = TestOptions( + printIfSuccessful = true, + printErrors = true, + printParsedProgram = false, + printVMtrace = false, + printVMfinal = false, + printMergedTrace = true, + nopPadded = nopPadded, + breakPoints = Nil, // not implemented + testName = singleTest) + + + val allTestOptions: String => TestOptions = name => TestOptions( + printIfSuccessful = false, + printErrors = false, + printParsedProgram = false, + printVMtrace = false, + printVMfinal = false, + printMergedTrace = false, + nopPadded = nopPadded, + breakPoints = Nil, // not implemented + testName = name) + +} + + +class SingleTest extends FlatSpec with Matchers { + it should "just werk" in { + TestRunner.run(Manifest.singleTestOptions) should be(true) + } +} + + +class AllTests extends FlatSpec with Matchers { + it should "just werk" in { + val werks = getAllTestNames.map{testname => + say(s"testing $testname") + val opts = Manifest.allTestOptions(testname) + (testname, TestRunner.run(opts)) + } + if(werks.foldLeft(true)(_ && _._2)) + say(Console.GREEN + "All tests successful!" + Console.RESET) + else { + val success = werks.map(x => if(x._2) 1 else 0).sum + val total = werks.size + say(s"$success/$total tests successful") + werks.foreach{ case(name, success) => + val msg = if(success) Console.GREEN + s"$name successful" + Console.RESET + else Console.RED + s"$name failed" + Console.RESET + say(msg) + } + } + } +} diff --git a/src/test/scala/RISCV/DataTypes.scala b/src/test/scala/RISCV/DataTypes.scala new file mode 100644 index 0000000..12e91c2 --- /dev/null +++ b/src/test/scala/RISCV/DataTypes.scala @@ -0,0 +1,357 @@ +package FiveStage +import cats.data.Writer +import cats._ +import cats.data._ +import cats.implicits._ + +import fansi._ +import PrintUtils._ + +import fileUtils.say + +/** + * Types and extension methods go here. + * Maybe it's a litte arbitrary to put VM types here, but op types in Ops.scala + * Maybe these could be separated to somewhere else. + */ +object Data { + type Label = String + + case class Reg(value: Int) + case class Imm(value: Int) + case class Addr(value: Int){ + def +(that: Addr) = Addr(value + that.value) + def -(that: Addr) = Addr(value - that.value) + def step = Addr(value + 4) + } + + object Reg{ def apply(s: String): Reg = Reg(lookupReg(s).get) } + + type SourceInfo[A] = Writer[List[String], A] + object SourceInfo { def apply[A](s: String, a: A) = Writer(List(s), a) } + + trait ExecutionEvent + import PrintUtils._ + case class RegUpdate(reg: Reg, word: Int) extends ExecutionEvent + case class MemWrite(addr: Addr, word: Int) extends ExecutionEvent + case class MemRead(addr: Addr, word: Int) extends ExecutionEvent + + // addr is the target address + case class PcUpdateJALR(addr: Addr) extends ExecutionEvent + case class PcUpdateJAL(addr: Addr) extends ExecutionEvent + case class PcUpdateB(addr: Addr) extends ExecutionEvent + case class PcUpdate(addr: Addr) extends ExecutionEvent + + case class ExecutionTraceEvent(pc: Addr, event: ExecutionEvent*){ override def toString(): String = s"$pc: " + event.toList.mkString(", ") } + type ExecutionTrace[A] = Writer[List[ExecutionTraceEvent], A] + + object ExecutionTrace { + def apply(vm: VM, event: ExecutionTraceEvent*) = Writer(event.toList, vm) + } + + + sealed trait ChiselEvent + case class ChiselRegEvent(pcAddr: Addr, reg: Reg, word: Int) extends ChiselEvent + case class ChiselMemWriteEvent(pcAddr: Addr, memAddr: Addr, word: Int) extends ChiselEvent + + type CircuitTrace = (Addr, List[ChiselEvent]) + + + /** + * Not sure these should be defined here instead of in the VM + */ + case class Regs(repr: Map[Reg, Int]) { + def +(a: (Reg, Int)): (Option[RegUpdate], Regs) = + if(a._1.value == 0) (None, this) + else (Some(RegUpdate(a._1, a._2)), copy(repr + a)) + + def arith(rd: Reg, operand1: Reg, operand2: Reg, op: (Int, Int) => Int): (Option[RegUpdate], Regs) = + this + (rd -> op(repr(operand1), repr(operand2))) + + def arithImm(rd: Reg, operand1: Reg, operand2: Imm, op: (Int, Int) => Int): (Option[RegUpdate], Regs) = + this + (rd -> op(repr(operand1), operand2.value)) + + def compare(operand1: Reg, operand2: Reg, comp: (Int, Int) => Boolean): Boolean = + comp(repr(operand1), repr(operand2)) + + def apply(setting: TestSetting): Regs = setting match { + case setting: REGSET => Regs(repr + (setting.rd -> setting.word)) + case _ => this + } + } + + + case class DMem(repr: Map[Addr, Int]) { + def read(addr: Addr): Either[String, (MemRead, Int)] = + if(addr.value >= 4096) + Left(s"attempted to read from illegal address ${addr.show}") + else { + val readResult = repr.lift(addr).getOrElse(0) + Right((MemRead(addr, readResult), readResult)) + } + + def write(addr: Addr, word: Int): Either[String, (MemWrite, DMem)] = + if(addr.value >= 4096) + Left(s"attempted to write to illegal address ${addr.show}") + else { + Right((MemWrite(addr, word)), DMem(repr + (addr -> word))) + } + + def apply(setting: TestSetting): DMem = setting match { + case setting: MEMSET => { + DMem(repr + (setting.addr -> setting.word)) + } + case _ => this + } + } + + object Regs{ + def empty: Regs = Regs((0 to 31).map(x => (Reg(x) -> 0)).toMap) + def apply(settings: List[TestSetting]): Regs = settings.foldLeft(empty){ + case(acc, setting) => acc(setting) + } + } + object DMem{ + def empty: DMem = DMem(Map[Addr, Int]()) + def apply(settings: List[TestSetting]): DMem = settings.foldLeft(empty){ + case(acc, setting) => acc(setting) + } + } + + + + + implicit class IntOps(i: Int) { + // Needs backticks to not conflict with xml + def `u>`(that: Int): Boolean = { + if((i >= 0) && (that >= 0)) + i > that + else if((i < 0) && (that < 0)) + i > that + else if((i < 0)) + true + else + false + } + + def nBitsS: Int = i match { + case i if (i < 0) => (math.log(math.abs(i.toLong))/math.log(2)).ceil.toInt + 1 + case i if (i == 0) => 0 + case i if (i > 0) => (math.log((i + 1).toLong)/math.log(2)).ceil.toInt + 1 + } + + /** + * Yes, a negative number technically has a unsigned size, but that depends on integer width, + * so it is better left as an option + */ + def nBitsU: Option[Int] = i match { + case i if (i < 0) => None + case i if (i == 0) => Some(0) + case i if (i > 0) => Some((math.log(i)/math.log(2)).ceil.toInt) + } + + def field(firstBit: Int, size: Int): Int = { + val bitsLeft = 31 - firstBit + val bitsRight = 32 - size + val leftShifted = i << bitsLeft + val rightShifted = leftShifted >> bitsRight + rightShifted + } + + def splitLoHi(loBits: Int): (Int, Int) = { + val hiBits = 32 - loBits + val sep = 31 - loBits + val lo = i.field(31, loBits) + val hi = i.field(sep, hiBits) + (lo, hi) + } + + def log2: Int = math.ceil(math.log(i.toDouble)/math.log(2.0)).toInt + } + + implicit class StringOps(s: String) { + def binary: Int = { + s.reverse.foldLeft((0, 0)){ + case((acc, pow), char) if char == '0' => (acc, pow + 1) + case((acc, pow), char) if char == '1' => (acc + (1 << pow), pow + 1) + case((acc, pow), char) => assert(false, "malformed binary conversion"); (0, 0) + }._1 + } + } + + implicit class ListOps[A](xs: List[A]) { + def mkStringN = xs.mkString("\n","\n","\n") + def splitAtPred(p: (A, A) => Boolean): List[List[A]] = { + val splitPoints = xs.tail.foldLeft((1, List[Int](), xs.head)){ + case((idx, acc, pA), a) if(p(pA, a)) => (1, idx :: acc, a) + case((idx, acc, pA), a) => (idx + 1, acc, a) + }._2.reverse + + val (blocks, rem) = splitPoints.foldLeft((List[List[A]](), xs)){ + case((acc, rem), point) => { + val(block, remainder) = rem.splitAt(point) + (block :: acc, remainder) + } + } + (rem :: blocks).reverse + } + def showN(sep: String)(implicit ev: Fancy[A]): fansi.Str = + xs.foldLeft(fansi.Str("")){ case(acc, a) => acc ++ a.show ++ fansi.Str(sep) } + def showN(sep1: String, sep2: String, sep3: String)(implicit ev: Fancy[A]): fansi.Str = + Str(sep1) ++ xs.foldLeft(fansi.Str("")){ case(acc, a) => acc ++ a.show ++ fansi.Str(sep2) } ++ Str(sep3) + def shuffle(shuffler: scala.util.Random): List[A] = shuffler.shuffle(xs) + } + implicit class NestedListOps[A](xs: List[List[A]]) { + def zipWithIndexNested: List[List[(A, Int)]] = { + val startingPoints = xs.scanLeft(0){ case(acc, xs) => acc + xs.size } + (xs.map(_.zipWithIndex) zip startingPoints).map{ case(withIndex, offset) => + withIndex.map{ case(a, idx) => (a, idx + offset) } + } + } + } + + + import Ops._ + + sealed trait TestSetting + case class REGSET(rd: Reg, word: Int) extends TestSetting + case class MEMSET(addr: Addr, word: Int) extends TestSetting + + implicit class ListEitherOps[E,A](es: List[Either[E,A]]) { + import cats.data.Validated + def separateXOR: Either[List[E], List[A]] = { + val (errors, as) = es.map(_.toValidated).separate + Either.cond(errors.isEmpty, as, errors) + } + } + + + /** + * Represents the result of parsing a program, with built in convenience methods for running a VM + * and assembling the program. + */ + case class Program( + ops : List[SourceInfo[Op]], + settings : List[TestSetting], + labelMap : Map[Label, Addr], + maxSteps : Int = 5000 + ){ + + def imem: Map[Addr, Op] = + ops.map(_.run._2).zipWithIndex.map{ case(op, idx) => (Addr(idx*4), op) }.toMap + + + /** + * Loads a VM which can be run to get a trace. + */ + def vm: VM = + VM(settings, imem, labelMap) + + + /** + * A convenient lookup for every instruction, allowing the test runner to check what source line + * caused an error to happen. + */ + val sourceMap: Map[Addr, String] = + ops.map(_.run._1).zipWithIndex.map{ case(info, idx) => (Addr(idx*4), info.map(_.trim).mkString(", ")) }.toMap + + + /** + * The assembled program + */ + def machineCode: Either[String, Map[Addr, Int]] = + imem.toList + .sortBy(_._1.value).map{ case(addr, op) => assembler.assembleOp(op, addr, labelMap).map(x => (addr, x)) } + .sequence + .map(_.toMap) + .left.map{ case(error, addr) => s"Assembler error: $error, corresponding to source:\n${sourceMap(addr)}" } + + + /** + * Returns the binary code and the execution trace or an error for convenient error checking. + */ + def validate: Either[String, (Map[Addr, Int], ExecutionTrace[VM])] = machineCode.flatMap{ binary => + val uk = "UNKNOWN" + val (finish, trace) = VM.run(maxSteps, vm) + finish match { + case Failed(s, addr) => Left(s"VM failed with error $s at address $addr\nSource line:\n${sourceMap.lift(addr).getOrElse(uk)}") + case Timeout => Left(s"VM timed out after $maxSteps steps. This should not happen with the supplied tests") + case Success => Right(binary, trace) + } + } + + def labelMapReverse = labelMap.toList.map(_.swap).toMap + } + + + def lookupReg(s: String): Option[Int] = { + val regMap = Map( + "x0" -> 0, + "x1" -> 1, + "x2" -> 2, + "x3" -> 3, + "x4" -> 4, + "x5" -> 5, + "x6" -> 6, + "x7" -> 7, + "x8" -> 8, + "x9" -> 9, + "x10" -> 10, + "x11" -> 11, + "x12" -> 12, + "x13" -> 13, + "x14" -> 14, + "x15" -> 15, + "x16" -> 16, + "x17" -> 17, + "x18" -> 18, + "x19" -> 19, + "x20" -> 20, + "x21" -> 21, + "x22" -> 22, + "x23" -> 23, + "x24" -> 24, + "x25" -> 25, + "x26" -> 26, + "x27" -> 27, + "x28" -> 28, + "x29" -> 29, + "x30" -> 30, + "x31" -> 31, + "zero" -> 0, + "ra" -> 1, + "sp" -> 2, + "gp" -> 3, + "tp" -> 4, + "t0" -> 5, + "t1" -> 6, + "t2" -> 7, + "s0" -> 8, + "fp" -> 8, + "s1" -> 9, + "a0" -> 10, + "a1" -> 11, + "a2" -> 12, + "a3" -> 13, + "a4" -> 14, + "a5" -> 15, + "a6" -> 16, + "a7" -> 17, + "s2" -> 18, + "s3" -> 19, + "s4" -> 20, + "s5" -> 21, + "s6" -> 22, + "s7" -> 23, + "s8" -> 24, + "s9" -> 25, + "s10" -> 26, + "s11" -> 27, + "t3" -> 28, + "t4" -> 29, + "t5" -> 30, + "t6" -> 31) + + regMap.lift(s) + } +} diff --git a/src/test/scala/RISCV/LogParser.scala b/src/test/scala/RISCV/LogParser.scala new file mode 100644 index 0000000..c54c616 --- /dev/null +++ b/src/test/scala/RISCV/LogParser.scala @@ -0,0 +1,184 @@ +package FiveStage +import Data._ +import fileUtils.say +import PrintUtils._ + +/** + * Helpers for comparing VM and chisel logs + */ +object LogParser { + + + /** + * Peeks ahead at the chisel log to see if an expected jump is taken. + */ + def fetchUntilJumpTarget(addr: Addr, chiselLog: List[CircuitTrace]): Option[List[CircuitTrace]] = { + val (head, tail) = chiselLog.splitAt(4) // very arbitrary choice + val pruned: List[CircuitTrace] = head.dropWhile{ case(myAddr, _) => myAddr != addr } + pruned.headOption.map(_ => pruned ::: tail) + } + + + + /** + * Fetches a basic block of VM execution trace + */ + def splitToBlocks(vmTrace: List[ExecutionTraceEvent]): List[List[ExecutionTraceEvent]] = + vmTrace.splitAtPred{ case(current, next) => + !(next.pc == current.pc + Addr(4)) + } + + + /** + * Fetches a basic block of chisel execution trace + */ + def splitToBlocksChisel(chiselTrace: List[CircuitTrace]): List[List[CircuitTrace]] = + chiselTrace.splitAtPred{ case((current, _), (next, _)) => + !((next == current + Addr(4)) || (next == current)) + } + + + def guessBlockName(trace: List[Addr], labelMap: Map[Addr, Label]): String = trace.headOption.map(x => labelMap.lift(x) + .getOrElse(labelMap.lift(trace.head - Addr(4)).getOrElse("UNKNOWN (return jump or misjump)"))).getOrElse("UNKNOWN") + + + /** + * Attempts to merge blocks, patching up when mismatches occur + * Fails when branchpredictor misjumps, feel free to send a PR + */ + type BlockList = List[(List[ExecutionTraceEvent], List[CircuitTrace])] + + def mergeTraces(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): BlockList = { + def helper(acc: BlockList, blocs: (List[List[ExecutionTraceEvent]], List[List[CircuitTrace]])): BlockList = blocs match { + + case (vmBlock :: vmTail, chiselBlock :: chiselTail) if (vmBlock.head.pc == chiselBlock.head._1) => + helper((vmBlock, chiselBlock) :: acc, ((vmTail, chiselTail))) + + case (vmBlock :: vmTail, chiselBlock :: chiselTail) => + helper((Nil, chiselBlock) :: acc, ((vmBlock :: vmTail, chiselTail))) + + case (Nil, chiselBlock :: chiselTail) => + helper((Nil, chiselBlock) :: acc, ((Nil, chiselTail))) + + case (vmBlock :: vmTail, Nil) => + helper((vmBlock, Nil) :: acc, ((vmTail, Nil))) + + case _ => acc.reverse + } + helper(Nil, (splitToBlocks(vmTrace), splitToBlocksChisel(chiselTrace))) + } + + + /** + * Compares register update logs + */ + def compareRegs(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): Option[String] = { + val vmRegUpdates = vmTrace.zipWithIndex + .flatMap{ case (e, step) => e.event.toList.map(y => (step, e.pc, y))} + .collect{ case (step: Int, addr: Addr, x: RegUpdate) => (step, addr, x) } + + val chiselRegUpdates = chiselTrace.zipWithIndex + .flatMap{ case (e, step) => e._2.map(y => (step, e._1, y))} + .collect{ case (step: Int, addr: Addr, x: ChiselRegEvent) => (step, addr, x) } + + val errors = (vmRegUpdates zip chiselRegUpdates).map{ + case((_, _, vmUpdate), (_, _, chiselUpdate)) if ((vmUpdate.reg == chiselUpdate.reg) && (vmUpdate.word == chiselUpdate.word)) => { + None + } + + case((vmStep, vmAddr, vmUpdate), (chiselStep, chiselAddr, chiselUpdate)) => { + val errorString = s"Register update mismatch.\n" ++ + s"VM: At step $vmStep, at address ${vmAddr.show}, the VM got ${vmUpdate.show}\n" ++ + s"Circuit: At step $chiselStep, at address ${chiselAddr.show}, the circuit got ${chiselUpdate.show}" + Some(errorString) + } + } + + val error = errors.collect{ case Some(x) => x }.headOption + + + val lengthMismatch: Option[String] = (vmRegUpdates, chiselRegUpdates) match { + case (h :: t, Nil) => Some(s"Your design performed no reg updates. First expected update was at VM step ${h._1}, PC: ${h._2.show}, ${h._3.show}") + case (Nil, h :: t) => Some(s"Your design performed reg updates, but the VM did not. First update was at step ${h._1}, PC: ${h._2.show}, ${h._3.show}") + case (hVM :: tVM, hC :: tC) if (tVM.size > tC.size) => { + + val VMremainder = tVM.drop(tC.size) + + val errorString = + s"VM performed more reg updates than your design.\n" ++ + s"Your design performed ${chiselRegUpdates.size} updates before terminating, while the VM performed ${vmRegUpdates.size} updates.\n" ++ + s"The first update your design missed happened at VM step ${VMremainder.head._1}, PC: ${VMremainder.head._2.show} and was ${VMremainder.head._3.show}" + + Some(errorString) + } + case (hVM :: tVM, hC :: tC) if (tVM.size < tC.size) => { + val ChiselRemainder = tC.drop(tVM.size) + + val errorString = + s"Your design performed more reg updates than the VM.\n" ++ + s"Your design performed ${chiselRegUpdates.size} updates before terminating, while the VM performed ${vmRegUpdates.size} updates.\n" ++ + s"The first spurious update your design did happened at cycle ${ChiselRemainder.head._1}, PC: ${ChiselRemainder.head._2.show} and was ${ChiselRemainder.head._3.show}" + + Some(errorString) + } + + case _ => None + } + + + (error :: lengthMismatch :: Nil).flatten.headOption + } + + + def compareMem(vmTrace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace]): Option[String] = { + val vmMemUpdates = vmTrace.zipWithIndex + .flatMap{ case (e, step) => e.event.toList.map(y => (step, e.pc, y))} + .collect{ case (step: Int, addr: Addr, x: MemWrite) => (step, addr, x) } + + val chiselMemUpdates = chiselTrace.zipWithIndex + .flatMap{ case (e, step) => e._2.map(y => (step, e._1, y))} + .collect{ case (step: Int, addr: Addr, x: ChiselMemWriteEvent) => (step, addr, x) } + + val error = (vmMemUpdates zip chiselMemUpdates).map{ + case((_, _, vmUpdate), (_, _, chiselUpdate)) if ((vmUpdate.addr == chiselUpdate.memAddr) && (vmUpdate.word == chiselUpdate.word)) => + None + case((vmStep, vmAddr, vmUpdate), (chiselStep, chiselAddr, chiselUpdate)) => { + val errorString = s"Mem update mismatch.\n" ++ + s"VM: At step $vmStep, at address ${vmAddr.show}, the VM got ${vmUpdate.show}\n" ++ + s"Circuit: At step $chiselStep, at address ${chiselAddr.show}, the circuit got ${chiselUpdate.show}" + Some(errorString) + } + }.collect{ case Some(x) => x }.headOption + + + val lengthMismatch = (vmMemUpdates, chiselMemUpdates) match { + case (h :: t, Nil) => Some(s"Your design performed no mem updates. First expected update was at VM step ${h._1}, PC: ${h._2}, ${h._3}") + case (Nil, h :: t) => Some(s"Your design performed mem updates, but the VM did not. First spurious update was at step ${h._1}, PC: ${h._2}, ${h._3}") + case (hVM :: tVM, hC :: tC) if (tVM.size > tC.size) => { + + val VMremainder = tVM.drop(tC.size) + + val errorString = + s"VM performed more mem updates than your design.\n" ++ + s"Your design performed ${chiselMemUpdates.size} updates before terminating, while the VM performed ${vmMemUpdates.size} updates.\n" ++ + s"The first update your design missed happened at VM step ${VMremainder.head._1}, PC: ${VMremainder.head._2} and was ${VMremainder.head._3}" + + Some(errorString) + } + case (hVM :: tVM, hC :: tC) if (tVM.size < tC.size) => { + val ChiselRemainder = tC.drop(tVM.size) + + val errorString = + s"Your design performed more mem updates than the VM.\n" ++ + s"Your design performed ${chiselMemUpdates.size} updates before terminating, while the VM performed ${vmMemUpdates.size} updates.\n" ++ + s"The first spurious update your design did happened at cycle ${ChiselRemainder.head._1}, PC: ${ChiselRemainder.head._2} and was ${ChiselRemainder.head._3}" + + Some(errorString) + } + + case _ => None + } + + (error :: lengthMismatch :: Nil).flatten.headOption + } +} diff --git a/src/test/scala/RISCV/Ops.scala b/src/test/scala/RISCV/Ops.scala new file mode 100644 index 0000000..e627d90 --- /dev/null +++ b/src/test/scala/RISCV/Ops.scala @@ -0,0 +1,124 @@ +package FiveStage +import cats.implicits._ +import fileUtils._ + +import Data._ +import PrintUtils._ + +object Ops { + + sealed trait Op extends RegLayout + + sealed trait RegLayout + sealed trait RType extends RegLayout { def rd: Reg; def rs1: Reg; def rs2: Reg } + sealed trait IType extends RegLayout { def rd: Reg; def rs1: Reg; } + sealed trait SType extends RegLayout { def rs1: Reg; def rs2: Reg } + sealed trait UType extends RegLayout { def rd: Reg; } + + sealed trait ImmType + sealed trait NoImmediate extends ImmType + sealed trait IImmediate extends ImmType + sealed trait SImmediate extends ImmType + sealed trait BImmediate extends ImmType + sealed trait UImmediate extends ImmType + sealed trait JImmediate extends ImmType + sealed trait ShiftImmediate extends ImmType + + + sealed trait Comparison { + def run(rs1Val: Int, rs2Val: Int): Boolean + } + case object EQ extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val == rs2Val } + case object NE extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val != rs2Val } + case object GE extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val >= rs2Val } + case object LT extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val < rs2Val } + case object GEU extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = !(rs1Val `u>` rs2Val) } + case object LTU extends Comparison { def run(rs1Val: Int, rs2Val: Int): Boolean = rs1Val `u>` rs2Val } + + case class Branch(rs1: Reg, rs2: Reg, dst: Label, comp: Comparison) extends Op with SType + object Branch{ + def beq( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, EQ) + def bne( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, NE) + def blt( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, LT) + def bge( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, GE) + def bltu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, LTU) + def bgeu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs1), Reg(rs2), dst, GEU) + + def ble( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, GE) + def bgt( rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, LT) + def bleu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, GEU) + def bgtu(rs1: Int, rs2: Int, dst: Label) = Branch(Reg(rs2), Reg(rs1), dst, LTU) + + def beqz(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, EQ) + def bnez(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, NE) + def blez(rs1: Int, dst: Label) = Branch(Reg(rs1), Reg(0), dst, LT) + } + + sealed trait someDecorator + sealed trait ArithOp { + def run(operand1: Int, operand2: Int): Int + } + case object ADD extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 + operand2 } + case object SUB extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 - operand2 } + case object OR extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 | operand2 } + case object XOR extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 ^ operand2 } + case object AND extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 & operand2 } + case object SLL extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 << operand2 } + case object SRL extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 >>> operand2 } + case object SRA extends ArithOp { def run(operand1: Int, operand2: Int): Int = operand1 >> operand2 } + case object SLT extends ArithOp { def run(operand1: Int, operand2: Int): Int = if(operand2 > operand1) 1 else 0 } + case object SLTU extends ArithOp { def run(operand1: Int, operand2: Int): Int = if(operand2 `u>` operand1) 1 else 0 } + + case class Arith(rd: Reg, rs1: Reg, rs2: Reg, op: ArithOp) extends Op with RType + object Arith { + def add( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), ADD) + def sub( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SUB) + def or( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), OR) + def xor( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), XOR) + def and( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), AND) + def sll( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLL) + def srl( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SRL) + def sra( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SRA) + def slt( rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLT) + def sltu(rd: Int, rs1: Int, rs2: Int) = Arith(Reg(rd), Reg(rs1), Reg(rs2), SLTU) + } + + def NOP = ArithImm.nop + + + + case class ArithImm(rd: Reg, rs1: Reg, imm: Imm, op: ArithOp) extends Op with IType + object ArithImm { + def add( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), ADD) + def or( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), OR) + def xor( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), XOR) + def and( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), AND) + def sll( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLL) + def srl( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SRL) + def sra( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SRA) + def slt( rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLT) + def sltu(rd: Int, rs1: Int, imm: Int) = ArithImm(Reg(rd), Reg(rs1), Imm(imm), SLTU) + def nop = add(0, 0, 0) + } + + + case class LUI(rd: Reg, imm: Imm) extends Op with UType + case class AUIPC(rd: Reg, imm: Imm) extends Op with UType + case class SW(rs2: Reg, rs1: Reg, offset: Imm) extends Op with SType + case class LW(rd: Reg, rs1: Reg, offset: Imm) extends Op with IType + + case class JALR(rd: Reg, rs1: Reg, dst: String) extends Op with IType + case class JAL(rd: Reg, dst: String) extends Op with UType + + + object LUI { def apply(rd: Int, imm: Int): LUI = LUI(Reg(rd), Imm(imm)) } + object AUIPC { def apply(rd: Int, imm: Int): AUIPC = AUIPC(Reg(rd), Imm(imm)) } + object SW { def apply(rs2: Int, rs1: Int, offset: Int): SW = SW(Reg(rs2), Reg(rs1), Imm(offset)) } + object LW { def apply(rd: Int, rs1: Int, offset: Int): LW = LW(Reg(rd), Reg(rs1), Imm(offset)) } + + object JAL{ def apply(rd: Int, dst: String): JAL = JAL(Reg(rd), dst) } + object JALR{ def apply(rd: Int, rs1: Int, dst: String): JALR = JALR(Reg(rd), Reg(rs1), dst) } + + // This op should not be assembled, but will for the sake of simplicity be rendered as a NOP + case object DONE extends Op with IType { val rd = Reg(0); val rs1 = Reg(0) } +} diff --git a/src/test/scala/RISCV/Parser.scala b/src/test/scala/RISCV/Parser.scala new file mode 100644 index 0000000..f987d48 --- /dev/null +++ b/src/test/scala/RISCV/Parser.scala @@ -0,0 +1,357 @@ +package FiveStage +import atto._, Atto._, syntax.refined._ +import eu.timepit.refined.numeric._ +import fileUtils.say + +import Ops._ +import Data._ + +import cats._ +import cats.data.{ Op => _ } +import cats.implicits._ + +object Parser { + + def hex : Parser[Int] = string("0x") ~> many1(hexDigit).map{ ds => + val bi = Integer.parseUnsignedInt(new String(ds.toList.toArray), 16) + bi.toInt + } + + def labelDest : Parser[Label] = (takeWhile(_ != ':') <~ char(':')) + def label : Parser[Label] = takeWhile(_ != ' ') + def reg : Parser[Int] = takeWhile(x => (x != ',' && x != ')')).map(lookupReg).attempt + def sep : Parser[Unit] = many(whitespace) *> char(',') *> many(whitespace).void + + def branch : (Parser[Int], Parser[Int], Parser[String]) = (reg <~ sep, reg <~ sep, label) + def branchZ : (Parser[Int], Parser[String]) = (reg <~ sep, label) + + def arith : (Parser[Int], Parser[Int], Parser[Int]) = (reg <~ sep, reg <~ sep, reg) + def arithImm : (Parser[Int], Parser[Int], Parser[Int]) = (reg <~ sep, reg <~ sep, hex | int) + + def stringWs(s: String) : Parser[String] = many(whitespace) ~> string(s) <~ many1(whitespace) + + val singleInstruction: Parser[Op] = List( + //////////////////////////////////////////// + //// Branches + stringWs("beq") ~> branch.mapN{Branch.beq}, + stringWs("bne") ~> branch.mapN{Branch.bne}, + stringWs("blt") ~> branch.mapN{Branch.blt}, + stringWs("bge") ~> branch.mapN{Branch.bge}, + stringWs("bltu") ~> branch.mapN{Branch.bltu}, + stringWs("bgeu") ~> branch.mapN{Branch.bgtu}, + + // pseudos: + stringWs("ble") ~> branch.mapN{Branch.ble}, + stringWs("bgt") ~> branch.mapN{Branch.bgt}, + stringWs("bleu") ~> branch.mapN{Branch.bleu}, + stringWs("bgtu") ~> branch.mapN{Branch.bgtu}, + + // Introduce zero + stringWs("bnez") ~> branchZ.mapN{Branch.bnez}, + stringWs("beqz") ~> branchZ.mapN{Branch.beqz}, + stringWs("blez") ~> branchZ.mapN{Branch.blez}, + + + + //////////////////////////////////////////// + //// Arith + stringWs("add") ~> arith.mapN{Arith.add}, + stringWs("sub") ~> arith.mapN{Arith.sub}, + stringWs("or") ~> arith.mapN{Arith.or}, + stringWs("xor") ~> arith.mapN{Arith.xor}, + stringWs("and") ~> arith.mapN{Arith.and}, + + stringWs("sll") ~> arith.mapN{Arith.sll}, + stringWs("srl") ~> arith.mapN{Arith.srl}, + stringWs("sra") ~> arith.mapN{Arith.sra}, + + stringWs("slt") ~> arith.mapN{Arith.slt}, + stringWs("sltu") ~> arith.mapN{Arith.sltu}, + + // pseudos + stringWs("mv") ~> (reg <~ sep, reg, ok(0)).mapN{Arith.add}, + stringWs("nop") ~> (ok(0), ok(0), ok(0)).mapN{Arith.add}, + + // Check if rs1 is not equal to 0. + // snez rd, rs1 => sltu rd, zero, rs1 + stringWs("snez") ~> (reg <~ sep, ok(0), reg).mapN{Arith.sltu}, + + + //////////////////////////////////////////// + //// Arith Imm + stringWs("addi") ~> arithImm.mapN{ArithImm.add}, + stringWs("ori") ~> arithImm.mapN{ArithImm.or}, + stringWs("xori") ~> arithImm.mapN{ArithImm.xor}, + stringWs("andi") ~> arithImm.mapN{ArithImm.and}, + + stringWs("slli") ~> arithImm.mapN{ArithImm.sll}, + stringWs("srli") ~> arithImm.mapN{ArithImm.srl}, + stringWs("srai") ~> arithImm.mapN{ArithImm.sra}, + + stringWs("slti") ~> arithImm.mapN{ArithImm.slt}, + stringWs("sltui") ~> arithImm.mapN{ArithImm.sltu}, + + // pseudos + stringWs("not") ~> (reg <~ sep, reg, ok(-1)).mapN{ArithImm.xor}, + + // Check if rs1 is less than 1. Only 0 is less than 1 when using unsigned comparison + // seqz rd, rs1 => sltiu rd, rs1, 1 + stringWs("seqz") ~> (reg <~ sep, reg, ok(1)).mapN{ArithImm.sltu}, + + stringWs("li") ~> (reg ~ sep ~ int).collect{ + case((a, b), c) if (c.nBitsS <= 12) => ArithImm.add(a, 0, c) + }, + + + //////////////////////////////////////////// + //// Jumps + stringWs("jalr") ~> (reg <~ sep, reg <~ sep, label).mapN{JALR.apply}, + stringWs("jal") ~> (reg <~ sep, label).mapN{JAL.apply}, + + // pseudos + // JAL with ra as rd automatically chosen. + stringWs("call") ~> label.map(label => JAL(regNames.ra, label)), + + // For jr we don't care about where we jumped from. + stringWs("jr") ~> reg.map(r => JALR(0, r, "zero")), + + // As jr, but with a label rather than a register. + stringWs("j") ~> label.map(label => JAL(0, label)), + many(whitespace) ~> string("ret") ~> ok(JALR(0, regNames.ra, "zero")), + + + //////////////////////////////////////////// + //// load/store + stringWs("sw") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{case (rs2, offset, rs1) => SW(rs2, rs1, offset)}, + stringWs("lw") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{case (rd, offset, rs1) => LW(rd, rs1, offset)}, + + + + + //////////////////////////////////////////// + //// others + stringWs("auipc") ~> (reg <~ sep, int).mapN{AUIPC.apply}, + stringWs("lui") ~> (reg <~ sep, int).mapN{LUI.apply}, + + many(whitespace) ~> string("nop") ~> ok(Arith.add(0, 0, 0)), + many(whitespace) ~> string("done") ~> ok(DONE), + // stringWs("done") ~> ok(DONE), + + ).map(_.widen[Op]).reduce(_|_) + + + // def getShiftsHalfWord(offset: Int): (Int, Int) = (offset % 4) match { + // case 0 => (16, 16) + // case 1 => ( + // } + + val multipleInstructions: Parser[List[Op]] = List( + stringWs("li") ~> (reg <~ sep, int.map(_.splitLoHi(12))).mapN{ case(rd, (lo, hi)) => List( + LUI(rd, hi), + ArithImm.add(rd, 0, lo) + )}.map(_.widen[Op]), + + // NOTE: THESE ARE NOT PSEUDO-OPS IN RISV32I! + // NOTE: USES A SPECIAL REGISTER + stringWs("lh") ~> (reg <~ sep, int <~ char('('), reg <~ char(')')).mapN{ + case (rd, offset, rs1) if (offset % 4 == 3) => { + val placeHolder = if(rd == Reg("a0").value) Reg("a1").value else Reg("a0").value + List( + SW(placeHolder, 0, 2048), + LW(placeHolder, rs1.value, (offset & 0xFFFFFF1C)), + LW(rd.value, rs1.value, (offset & 0xFFFFFF1C) + 4), + ArithImm.sra(placeHolder, placeHolder, 24), + ArithImm.sll(rd.value, rd.value, 24), + ArithImm.sra(rd.value, rd.value, 16), + Arith.add(rd, rd, placeHolder), + LW(placeHolder, 0, 2048)).reverse + } + case (rd, offset, rs1) if (offset % 4 == 2) => { + List( + LW(rd, rs1, (offset & 0xFFFFFF1C)), + ArithImm.sra(rd, rd, 16) + ).reverse + } + + case (rd, offset, rs1) => { + val leftShift = if((offset % 4) == 0) 16 else 8 + List( + LW(rd, rs1, (offset & 0xFFFFFF1C)), + ArithImm.sll(rd, rd, leftShift), + ArithImm.sra(rd, rd, 16), + ).reverse + } + }.map(_.widen[Op]), + ).reduce(_|_) + + + val instruction = singleInstruction.map(List(_)) | multipleInstructions + + val setting = List( + char('#') ~> string("regset") ~> many1(whitespace) ~> (reg.map(Reg.apply) <~ sep, hex | int).mapN{REGSET.apply}, + char('#') ~> string("memset") ~> many1(whitespace) ~> ((hex | int).map(Addr.apply) <~ sep, hex | int).mapN{MEMSET.apply} + ).map(_.widen[TestSetting]).reduce(_|_) + + + def parseProgram(p: List[String], testOptions: TestOptions): Either[String, Program] = { + + val all = setting || (instruction || labelDest) + + /** + * The foldhelper represents a traversal through a RISC-V program. + * + * When it sees an op it records the operation and appends the source line and its location. + * If it is in nopPad mode it will also insert NOPs between the parsed ops. + * After appending ops the address counter is bumbed accordingly + * + * When it sees a label destination it checks what the current addres counter is at and creates + * a link to this address. + * + * When it sees a parse error it simply stores the error and keeps going, allowing you to get every error + * (This works for an ASM program since each line is independent) + * + * Lastly, when it sees a test setting it appends that test setting. + * + * The reason everything is treated all-in-one is to make it easier to ensure that everything is parsed. + * If there were separate parsers for ops, labels and settings it would be difficult to find out if errors + * were simply of the wrong type or a legit error. + * This is not set in stone, if you're re-architecturing the code maybe it's better to separate parsers? + * Or maybe have multiple passes? Up to you! + */ + case class FoldHelper( + settings : List[TestSetting], + ops : List[SourceInfo[Op]], + labelMap : Map[Label, Addr], + errors : List[String], + addrCount : Int){ + def addSettings (t: TestSetting): FoldHelper = copy(settings = t :: settings) + def addErrors (t: String): FoldHelper = copy(errors = t :: errors) + def addLabelMap (t: Label): FoldHelper = copy(labelMap = labelMap + (t -> Addr(addrCount))) + def addOps (t: List[SourceInfo[Op]]): FoldHelper = { + if(testOptions.nopPadded){ + copy( + ops = t.flatMap(x => (x :: List.fill(4)(SourceInfo("inserted NOP", NOP).widen[Op]))).reverse ::: ops, + addrCount = addrCount + t.size*4*5) + } + else { + copy(ops = t ::: ops, addrCount = addrCount + t.size*4) + } + } + def program: Either[String, (List[TestSetting], List[SourceInfo[Op]], Map[Label, Addr])] = { + /** + * There are two possible ways for a program to successfully terminate, either by explicitly executing + * a DONE instruction, or by returning from the main method. + * In the latter case it is necessary to preload the return address register such that the return instruction + * jumps to a predetermined special done address + */ + val hasDONEinstruction = ops.map(_.run._2).contains(DONE) + val done = copy(settings = REGSET(Reg("sp"), 1024) :: settings, ops = ops.reverse, errors = errors.reverse) + + val withReturnAddress = if(hasDONEinstruction){ + done + } + else + done.copy(settings = REGSET(Reg("ra"), 0xEB1CEB1C) :: done.settings) // now that's what I call EPIC + + Either.cond(errors.isEmpty, (withReturnAddress.settings, done.ops, labelMap), done.errors).left.map(errors => + s"Parser errors in ${testOptions.testName}:\n" + errors.mkString("\n")) + } + } + + + def foldHelper( + acc: FoldHelper, + program: List[(Int, String)]): FoldHelper = program match { + case Nil => acc + case (lineNo, line) :: t => { + if(line.isEmpty) + foldHelper(acc, t) + else { + val next = all.parse(line).done.either match { + case Left(parseError) => acc.addErrors(f"$lineNo%3d" +s":$line\t$parseError") + case Right(Left(setting)) => acc.addSettings(setting) + case Right(Right(Right(label))) => acc.addLabelMap(label) + case Right(Right(Left(ops))) => acc.addOps(ops.map(op => SourceInfo(s"${lineNo.toString.padTo(3, ' ')}:\t$line", op))) + } + foldHelper(next, t) + } + } + } + + val results = foldHelper(FoldHelper(Nil, Nil, Map("zero" -> Addr(0)), Nil, 0), p.zipWithIndex.map(_.swap)) + + results.program.map{ case(settings, ops, labelMap) => Program(ops, settings, labelMap) } + } + + + def lookupReg(s: String): Int = { + val regMap = Map( + "x0" -> 0, + "x1" -> 1, + "x2" -> 2, + "x3" -> 3, + "x4" -> 4, + "x5" -> 5, + "x6" -> 6, + "x7" -> 7, + "x8" -> 8, + "x9" -> 9, + "x10" -> 10, + "x11" -> 11, + "x12" -> 12, + "x13" -> 13, + "x14" -> 14, + "x15" -> 15, + "x16" -> 16, + "x17" -> 17, + "x18" -> 18, + "x19" -> 19, + "x20" -> 20, + "x21" -> 21, + "x22" -> 22, + "x23" -> 23, + "x24" -> 24, + "x25" -> 25, + "x26" -> 26, + "x27" -> 27, + "x28" -> 28, + "x29" -> 29, + "x30" -> 30, + "x31" -> 31, + "zero" -> 0, + "ra" -> 1, + "sp" -> 2, + "gp" -> 3, + "tp" -> 4, + "t0" -> 5, + "t1" -> 6, + "t2" -> 7, + "s0" -> 8, + "fp" -> 8, + "s1" -> 9, + "a0" -> 10, + "a1" -> 11, + "a2" -> 12, + "a3" -> 13, + "a4" -> 14, + "a5" -> 15, + "a6" -> 16, + "a7" -> 17, + "s2" -> 18, + "s3" -> 19, + "s4" -> 20, + "s5" -> 21, + "s6" -> 22, + "s7" -> 23, + "s8" -> 24, + "s9" -> 25, + "s10" -> 26, + "s11" -> 27, + "t3" -> 28, + "t4" -> 29, + "t5" -> 30, + "t6" -> 31) + + regMap(s) + } +} diff --git a/src/test/scala/RISCV/VM.scala b/src/test/scala/RISCV/VM.scala new file mode 100644 index 0000000..fe545bd --- /dev/null +++ b/src/test/scala/RISCV/VM.scala @@ -0,0 +1,171 @@ +package FiveStage +import cats._ +import cats.data.{ Op => _ } +import cats.implicits._ +import fileUtils._ + +import Data._ +import Ops._ + +import PrintUtils._ + +sealed trait Finished +case object Success extends Finished +case object Timeout extends Finished +case class Failed(s: String, addr: Addr) extends Finished + +case class VM( + dmem : DMem, + imem : Map[Addr, Op], + regs : Regs, + pc : Addr, + labelMap : Map[Label, Addr]){ + def stepInstruction: Either[Finished, ExecutionTrace[VM]] = { + if (pc.value == 0xEB1CEB1C) Left(Success) + else getOp flatMap { + case op: Branch => executeBranch(op) + case op: Arith => executeArith(op) + case op: ArithImm => executeArithImm(op) + case op: AUIPC => executeAUIPC(op) + case op: LUI => executeLUI(op) + case op: JALR => executeJALR(op) + case op: JAL => executeJAL(op) + case op: LW => executeLW(op) + case op: SW => executeSW(op) + case DONE => Left(Success) + } + } + + + + private def executeBranch(op: Branch) = { + getAddr(op.dst).map{ addr => + val takeBranch = regs.compare(op.rs1, op.rs2, op.comp.run) + if(takeBranch){ + val nextVM = copy(pc = addr) + jump(nextVM, PcUpdateB(nextVM.pc)) + } + else { + step(this) + } + } + } + + + /** + * The weird :_* syntax is simply a way to pass a list to a varArgs function. + * + * To see why, consider def printMany(a: Any*); printMany(List(1,2,3)) + * It is now ambiguous if it should print "1, 2, 3" or List(1, 2, 3) + * + * This is disambiguated by appending :_*, thus + * printMany(List(1,2,3):_*) == printMany(1, 2, 3) + */ + private def executeArith(op: Arith) = { + val (regUpdate, nextRegs) = regs.arith(op.rd, op.rs1, op.rs2, op.op.run) + val nextVM = this.copy(regs = nextRegs) + Right(step(nextVM, regUpdate.toList:_*)) + } + + + private def executeArithImm(op: ArithImm) = { + val (regUpdate, nextRegs) = regs.arithImm(op.rd, op.rs1, op.imm, op.op.run) + val nextVM = this.copy(regs = nextRegs) + Right(step(nextVM, regUpdate.toList:_*)) + } + + + private def executeLUI(op: LUI) = { + val (regUpdate, nextRegs) = regs + (op.rd -> (op.imm.value << 12)) + val nextVM = this.copy(regs = nextRegs) + Right(step(nextVM, regUpdate.toList:_*)) + } + + + private def executeAUIPC(op: AUIPC) = { + val (regUpdate, nextRegs) = regs + (op.rd -> (pc.value << 12)) + val nextVM = this.copy(regs = nextRegs) + Right(step(nextVM, regUpdate.toList:_*)) + } + + + + private def executeJALR(op: JALR) = getAddr(op.dst).map{ targetAddr => + val nextPc = Addr((regs.repr(op.rs1).value + targetAddr.value) & 0xFFFFFFFE) + val (regUpdate, nextRegs) = regs + (op.rd -> (pc.step.value)) + val nextVM = this.copy(regs = nextRegs, pc = nextPc) + jump(nextVM, (PcUpdateJALR(nextPc) :: regUpdate.toList):_*) + } + + + private def executeJAL(op: JAL) = getAddr(op.dst).map{ targetAddr => + val nextPc = targetAddr + val (regUpdate, nextRegs) = regs + (op.rd -> (pc.step.value)) + val nextVM = this.copy(regs = nextRegs, pc = nextPc) + jump(nextVM, (PcUpdateJAL(nextPc) :: regUpdate.toList):_*) + } + + + private def executeLW(op: LW) = dmem.read(Addr(regs.repr(op.rs1) + op.offset.value)).map{ case(event, result) => + val (regUpdate, nextRegs) = regs + (op.rd -> result) + val nextVM = this.copy(regs = nextRegs) + step(nextVM, (event :: regUpdate.toList):_*) + }.left.map(x => Failed(x, pc)) + + private def executeSW(op: SW) = { + val writeAddress = Addr(regs.repr(op.rs1) + op.offset.value) + val writeData = regs.repr(op.rs2) + dmem.write(writeAddress, writeData).map{ case(event, nextDmem) => + val nextVM = this.copy(dmem = nextDmem) + step(nextVM, event) + } + }.left.map(x => Failed(x, pc)) + + private def step(nextVM: VM, event: ExecutionEvent*) = + ExecutionTrace(nextVM.stepPC, ExecutionTraceEvent(pc, event:_*)) + + // Same as above, but no stepping + private def jump(nextVM: VM, event: ExecutionEvent*) = + ExecutionTrace(nextVM, ExecutionTraceEvent(pc, event:_*)) + + + private def stepPC: VM = copy(pc = this.pc.step) + + private def getAddr(dst: Label): Either[Failed, Addr] = + labelMap.lift(dst).toRight(Failed(s"Label $dst missing", pc)) + + private def getOp: Either[Failed, Op] = + imem.lift(pc).toRight(Failed(s"Attempted to fetch instruction at illegal address ${pc.show}", pc)) +} + + +object VM { + + val init = VM(DMem.empty, Map[Addr, Op](), Regs.empty, Addr(0), Map[Label, Addr]()) + + def apply(settings: List[TestSetting], imem: Map[Addr, Op], labelMap: Map[Label, Addr]): VM = { + val (dmem, regs) = settings.foldLeft((DMem.empty, Regs.empty)){ case((dmem, regs), setting) => setting match { + case setting: REGSET => (dmem, regs(setting)) + case setting: MEMSET => (dmem(setting), regs) + } + } + VM(dmem, imem, regs, Addr(0), labelMap) + } + + def run(maxSteps: Int, vm: VM) = { + def helper(state: ExecutionTrace[VM], step: Int): (Finished, ExecutionTrace[VM]) = { + if(step > 0){ + val (log, vm) = state.run + val next = vm.stepInstruction + next match { + case Left(stopped) => (stopped, state) + case Right(trace) => helper((state >> trace), step - 1) + } + } + else{ + (Timeout, state) + } + } + helper(ExecutionTrace(vm), maxSteps) + } +} diff --git a/src/test/scala/RISCV/assembler.scala b/src/test/scala/RISCV/assembler.scala new file mode 100644 index 0000000..298a85b --- /dev/null +++ b/src/test/scala/RISCV/assembler.scala @@ -0,0 +1,269 @@ +package FiveStage +import cats.implicits._ + +import Data._ +import Ops._ +import fileUtils._ +import PrintUtils._ + +object assembler { + + type InstructionFragment = Either[(String, Addr), Int] + + /** + * Will only be called by applyImmedate, thus error propagation is not needed + * Kind of a kludge, but it works, don't touch! + */ + def setField(firstBit: Int, size: Int, field: Int): Int => Int = instruction => { + val shiftedField = field << firstBit + val mask = (1 << size) - 1 + val shiftedMask = (mask << firstBit) + val masked = ((~instruction) | shiftedMask) + val maskedInv = ~(masked) + + val ret = (shiftedField & shiftedMask) | maskedInv + + ret + } + + + def getSubField(firstBit: Int, size: Int): Int => Int = word => { + val bitsLeft = 32 - firstBit + val bitsRight = 32 - size + val leftShifted = word << bitsLeft + val rightShifted = leftShifted >> bitsRight + rightShifted + } + + + /** + Splits the immediate value into fields given by points. + The order of points is important! + points is of type idx, size + */ + def applyImmediate(immediateBits: Int, immediate: Int, points: List[(Int, Int)]): Int => Int = instruction => { + + def go(instruction: Int, immediateIndex: Int, points: List[(Int,Int)]): Int = points match { + case h :: t => { + val (immFirstBit, size) = h + val firstBit = (immFirstBit - size) + 1 + val immSubField = getSubField(immediateIndex, size)(immediate) + val nextImmIndex = immediateIndex - size + val nextInstruction = setField(firstBit, size, immSubField)(instruction) + go(nextInstruction, nextImmIndex, points.tail) + } + case _ => { + instruction + } + } + + go(instruction, immediateBits, points) + } + + + def applyImmediateU(immediate: Int, points: List[(Int, Int)], addr: Addr): Int => InstructionFragment = instruction => { + def totalBits = points.foldLeft(0){ case(acc, (first, size)) => acc + size } + totalBits.nBitsU.toRight("Negative number used as unsigned immediate", addr).flatMap { bits => + Either.cond(bits < totalBits, applyImmediate(totalBits, immediate, points)(instruction), ("Immediate unsigned too large", addr)) + } + } + + def applyImmediateS(immediate: Int, points: List[(Int, Int)], addr: Addr): Int => InstructionFragment = instruction => { + def totalBits = points.foldLeft(0){ case(acc, (first, size)) => acc + size } + if(totalBits < immediate.nBitsS) Left((s"Immediate signed too large. immedate: $immediate, immediate.nBitsS = ${immediate.nBitsS}, total bits: $totalBits", addr)) + else Right(applyImmediate(totalBits, immediate, points)(instruction)) + } + + + /** + * Used by JALR, LW and arithmetic immediate ops. + * JALR is sort of the odd man out here as it should be unsigned. + * This issue should not surface at the very limited address space + * for your design. (I hope) + */ + def setItypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = { + val points = List((31, 12)) + val withField = applyImmediateS(immediate, points, addr) + withField + } + + /** + * Used by SW + */ + def setStypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = { + val points = List((31, 7), (11, 5)) + val withField = applyImmediateS(immediate, points, addr) + withField + } + + /** + * Used by Branches. PC relative jump, thus signed + * Last bit is not used, hence the shift + */ + def setBtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = { + val points = List((31, 1), (7, 1), (30, 6), (11, 4)) + val withField = applyImmediateS((immediate >> 1), points, addr) + withField + } + + /** + * Used by LUI and AUIPC. Unsigned + */ + def setUtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = { + val points = List((31, 20)) + val withField = applyImmediateU(immediate, points, addr) + withField + } + + /** + * Used by JAL. PC relative jump, thus signed + * The last bit is not used, hence the shift + */ + def setJtypeImmediate(immediate: Int, addr: Addr): Int => InstructionFragment = { + val points = List((31, 1), (19, 8), (20, 1), (30, 10)) + applyImmediateU((immediate >> 1), points, addr) + } + + /** + * Currently not used, thus we allow too larg shifts. + */ + def setShiftTypeImmediate(instruction: Int, immediate: Int): Int = { + val points = List((24, 5)) + val withField = applyImmediate(5, immediate, points)(instruction) + withField + } + + def setOpCode(opcode: Int): Int => Int = setField(0, 7, opcode) + def setFunct7(funct7: Int): Int => Int = setField(25, 7, funct7) + def setFunct3(funct3: Int): Int => Int = setField(12, 3, funct3) + def setRs1(rs1: Int): Int => Int = setField(15, 5, rs1) + def setRs2(rs2: Int): Int => Int = setField(20, 5, rs2) + def setRd(rd: Int): Int => Int = setField(7, 5, rd) + + + def setOpCode(op: Op): Int => Int = op match { + case x: Branch => setOpCode("1100011".binary) + case x: Arith => setOpCode("0110011".binary) + case x: ArithImm => setOpCode("0010011".binary) + case x: LW => setOpCode("0000011".binary) + case x: SW => setOpCode("0100011".binary) + case x: JALR => setOpCode("1100111".binary) + case x: JAL => setOpCode("1101111".binary) + case x: AUIPC => setOpCode("0110111".binary) + case x: LUI => setOpCode("0010111".binary) + case DONE => setOpCode("0010011".binary) // done is turned into a NOP in the assembler. + } + + def setComparisonFunct(cmp: Comparison): Int => Int = cmp match { + case EQ => setFunct3("000".binary) + case NE => setFunct3("001".binary) + case GE => setFunct3("101".binary) + case LT => setFunct3("100".binary) + case GEU => setFunct3("111".binary) + case LTU => setFunct3("110".binary) + } + + def setBranchDestination(labelMap: Map[Label, Addr], op: Branch, opAddr: Addr): Int => InstructionFragment = instruction => { + labelMap.lift(op.dst).toRight((s"destination ${op.dst} not found", opAddr)).flatMap{ dstAddr => + setBtypeImmediate(dstAddr.value - opAddr.value, opAddr)(instruction) + } + } + + def setArithFunct(op: ArithOp): Int => Int = op match { + case ADD => setFunct7("0000000".binary) andThen setFunct3("000".binary) + case SUB => setFunct7("0100000".binary) andThen setFunct3("000".binary) + case SLL => setFunct7("0000000".binary) andThen setFunct3("001".binary) + case SLT => setFunct7("0000000".binary) andThen setFunct3("010".binary) + case SLTU => setFunct7("0000000".binary) andThen setFunct3("011".binary) + case XOR => setFunct7("0000000".binary) andThen setFunct3("100".binary) + case SRL => setFunct7("0000000".binary) andThen setFunct3("101".binary) + case SRA => setFunct7("0100000".binary) andThen setFunct3("101".binary) + case OR => setFunct7("0000000".binary) andThen setFunct3("110".binary) + case AND => setFunct7("0000000".binary) andThen setFunct3("111".binary) + } + + + def assembleRegLayout(op: RegLayout): Int => Int = { + + def assembleRType(op: RType): Int => Int = + setRd(op.rd.value) andThen + setRs1(op.rs1.value) andThen + setRs2(op.rs2.value) + + def assembleIType(op: IType): Int => Int = + setRd(op.rd.value) andThen + setRs1(op.rs1.value) + + def assembleSType(op: SType): Int => Int = { + // say("stype") + instruction => + (setRs1(op.rs1.value) andThen + setRs2(op.rs2.value))(instruction) + } + + def assembleUType(op: UType): Int => Int = + setRd(op.rd.value) + + op match { + case op: RType => assembleRType(op) + case op: IType => assembleIType(op) + case op: SType => assembleSType(op) + case op: UType => assembleUType(op) + } + } + + + def assembleImmediate(op: Op, addr: Addr, labelMap: Map[Label, Addr]): Int => Either[(String, Addr), Int] = op match { + case DONE => instruction => Right(instruction) + case op: Arith => instruction => Right(instruction) + case op: ArithImm => setItypeImmediate(op.imm.value, addr) + case op: Branch => setBranchDestination(labelMap, op, addr) + case op: JALR => instruction => labelMap.lift(op.dst).toRight(s"label ${op.dst} not found", addr).flatMap(addr => setItypeImmediate(addr.value, addr)(instruction)) + case op: AUIPC => setUtypeImmediate(op.imm.value, addr) + case op: LUI => setUtypeImmediate(op.imm.value, addr) + case op: LW => setItypeImmediate(op.offset.value, addr) + case op: SW => setStypeImmediate(op.offset.value, addr) + case op: JAL => instruction => { + val addressDistance = labelMap + .lift(op.dst).toRight(s"label ${op.dst} not found", addr) + .map(absoluteAddr => absoluteAddr - addr) + + import PrintUtils._ + + addressDistance.flatMap(distance => + setJtypeImmediate(distance.value, addr)(instruction)) + } + } + + + def assembleOp( + op : Op with RegLayout, + opAddr : Addr, + labelMap : Map[Label, Addr]): Either[(String, Addr), Int] = { + + val layout = assembleRegLayout(op) + val immediate = assembleImmediate(op, opAddr, labelMap) + val opcode = setOpCode(op) + + val extras: Int => Int = (instruction: Int) => op match { + case op: Branch => setComparisonFunct(op.comp)(instruction) + case op: ArithImm => setArithFunct(op.op)(instruction) + case op: Arith => setArithFunct(op.op)(instruction) + case op: JALR => setFunct3("000".binary)(instruction) + case op: LW => setFunct3("010".binary)(instruction) + case op: SW => setFunct3("010".binary)(instruction) + case DONE => (setFunct3("000".binary) andThen setFunct7("0000000".binary))(instruction) + case _ => instruction + } + + val withOp = opcode(0) + val withLayout = layout(withOp) + val withImmediates = immediate(withLayout) + val withExtras = withImmediates.map(extras) + + + val finalOp = (opcode andThen layout andThen extras andThen immediate)(0) + + finalOp + } +} diff --git a/src/test/scala/RISCV/deleteMe.scala b/src/test/scala/RISCV/deleteMe.scala new file mode 100644 index 0000000..0ed9892 --- /dev/null +++ b/src/test/scala/RISCV/deleteMe.scala @@ -0,0 +1,178 @@ +package FiveStage +import cats.data.Writer +import cats._ +import cats.data.{ Op => _ } +import cats.implicits._ + +object DTree { + + // opaques WHEN + type Feature = String + type Value = String + type Cls = String + + case class TrainingData(values: Map[Feature, Value], cls: Cls) + + type TestData = Map[Feature, Value] + + // Base + // def predict(data: TestData): Cls = "Died" + + // Gendered + def predictStatic(data: TestData): Cls = + if(data("gender") == "female") + "survived" + else "died" + + + sealed trait Tree + case class Node(feature: Feature, children: Map[Value, Tree]) extends Tree + case class Leaf(cls: Cls) extends Tree + + val genderBased: Tree = Node( + "gender", Map( + "female" -> Leaf("survived"), + "male" -> Leaf("died"), + )) + + + def predict(data: TestData)(tree: Tree): Cls = + tree match { + case Leaf(cls) => cls + case Node(feature, children) => predict(data)(children(data(feature))) + } + + val data = Map("gender" -> "female", "family size" -> "0", "ticket" -> "1") + + predict(data)(genderBased) // true + + + def entropy(classes: List[Cls]): Double = { + val total = classes.size + classes.groupBy(identity) + .mapValues { group => + val prop = group.size / total + prop * math.log(1.0 / prop) + }.values.sum + } + + def bucketedEntropy(data: List[TrainingData], feature: Feature): Double = { + val total = data.size + val bucketed = data.groupBy(_.values(feature)) + .mapValues(_.map(_.cls)) + .toMap + bucketed.values.map { classes => + val prop = classes.size / total + prop * entropy(classes) + }.sum + } + + def best(data: List[TrainingData], features: Set[Feature]): Feature = features.minBy(bucketedEntropy(data, _)) + + def mostCommonCls(data: List[TrainingData]): Cls = ??? + + def build(data: List[TrainingData], features: Set[Feature]): Tree = { + if(features.nonEmpty) { + val feature = best(data, features) + val buckets = data.groupBy(_.values(feature)) + Node(feature, buckets.mapValues(build(_, features - feature))) + } else { + Leaf(mostCommonCls(data)) + } + } + + def withHKT { + + sealed trait Tree[A] + case class Node[A](feature: Feature, children: Map[Value, A]) extends Tree[A] + case class Leaf[A](cls: Cls) extends Tree[A] + + // import matryoshka._ + // import matryoshka.data.Fix + // import matryoshka.implicits._ + + case class Fix[F[_]](unfix: F[Fix[F]]) + case class Cofree[F[_], A](head: A, tail: F[Cofree[F, A]]){ + def counit: A = head + def map[B](f: A => B)(implicit ev: Functor[F]): Cofree[F, B] = Cofree(f(head), tail.map(_.map(f))) + + /** + * Coflatmap alters the value of the node based on its context, then recursively + * alters its tail independently (which makes sense as it's the only thing Cofree[F, A] => B can do. + */ + def coflatMap[B](fa: Cofree[F, A] => B)(implicit ev: Functor[F]): Cofree[F, B] = { + val b = fa(this) + val fb = tail.map(_.coflatMap(fa)) + Cofree(b, fb) + } + } + + implicit val treeFunctor: Functor[Tree] = new Functor[Tree] { + def map[A, B](fa: Tree[A])(f: A => B): Tree[B] = fa match { + case Node(name, children) => Node(name, children.mapValues(f)) + case Leaf(cls) => Leaf(cls) + } + } + + val genderBased: Fix[Tree] = + Fix(Node( + "gender", + Map( + "female" -> Fix(Leaf[Fix[Tree]]("survived")), + "male" -> Fix(Leaf[Fix[Tree]]("died")) + ))) + + def build: ((List[TrainingData], Set[Feature])) => Tree[(List[TrainingData], Set[Feature])] = { + case (data, features) => + if(features.nonEmpty) { + val feature = best(data, features) + val buckets = data.groupBy(_.values(feature)) + val next = buckets.mapValues { subset => (subset, features - feature) } + Node(feature, next) + } else { + Leaf(mostCommonCls(data)) + } + } + + def explore(testData: TestData): Fix[Tree] => Cls Either Fix[Tree] = + fix => fix.unfix match { + case Leaf(cls) => Left(cls) + case Node(feature, children) => Right(children.get(testData(feature)).get) + } + + // Anamorphism: Generalized unfold, builds structures + def ana[F[_]: Functor, A](f: A => F[A])(a: A): Fix[F] = + Fix( (f(a)).map(ana(f)) ) + + // Catamorphism: Generalized fold, tears structures down. + def cata[F[_]: Functor, A](fa: F[A] => A)(f: Fix[F]): A = { + fa(f.unfix.map(cata(fa))) + } + + // def hyloSimple[F[_] : Functor, A, B](f: F[B] => B)(g: A => F[A]): A => B + def hyloSimple[F[_]: Functor, A, B](f: F[B] => B)(g: A => F[A])(a: A): B = + cata(f)(ana(g)(a)) + + // A more powerful cata + def para[F[_]: Functor, A](f: F[(Fix[F], A)] => A)(fa: Fix[F]): A = + f(fa.unfix.map(x => (x, para(f)(x)))) + + // A more powerful ana + def apo[F[_]: Functor, A](f: A => F[Either[Fix[F], A]])(a: A): Fix[F] = { + Fix(f(a).map{ + case Right(a) => apo(f)(a) + case Left(fix) => fix + }) + } + + // When we have cofree + def histo[F[_]: Functor, A](f: F[Cofree[F, A]] => A)(fix: Fix[F]): A = { + def toCofree(fix: Fix[F]): Cofree[F, A] = + Cofree(histo(f)(fix), fix.unfix.map(toCofree)) + + f(fix.unfix.map(toCofree)) + } + + + } +} diff --git a/src/test/scala/RISCV/deleteMe2.scala b/src/test/scala/RISCV/deleteMe2.scala new file mode 100644 index 0000000..74662cb --- /dev/null +++ b/src/test/scala/RISCV/deleteMe2.scala @@ -0,0 +1,164 @@ +package FiveStage +import cats.data.Writer +import cats._ +import cats.data.{ Op => _ } +import cats.implicits._ + +import fileUtils.say + +object DeletDis { + + def delet = { + + case class Fix[F[_]](unfix: F[Fix[F]]) + case class Cofree[F[_], A](head: A, tail: F[Cofree[F, A]]){ + def counit: A = head + def map[B](f: A => B)(implicit ev: Functor[F]): Cofree[F, B] = Cofree(f(head), tail.map(_.map(f))) + + /** + * Coflatmap alters the value of the node based on its context, then recursively + * alters its tail independently (which makes sense as it's the only thing Cofree[F, A] => B can do. + */ + def coflatMap[B](fa: Cofree[F, A] => B)(implicit ev: Functor[F]): Cofree[F, B] = { + val b = fa(this) + val fb = tail.map(_.coflatMap(fa)) + Cofree(b, fb) + } + } + + // Anamorphism: Generalized unfold, builds structures + def ana[F[_]: Functor, A](f: A => F[A])(a: A): Fix[F] = + Fix( (f(a)).map(ana(f)) ) + + // Catamorphism: Generalized fold, tears structures down. + def cata[F[_]: Functor, A](fa: F[A] => A)(f: Fix[F]): A = { + fa(f.unfix.map(cata(fa))) + } + + // def hyloSimple[F[_] : Functor, A, B](f: F[B] => B)(g: A => F[A]): A => B + def hylo[F[_]: Functor, A, B](f: F[B] => B)(g: A => F[A])(a: A): B = + cata(f)(ana(g)(a)) + + // A more powerful cata + def para[F[_]: Functor, A](f: F[(Fix[F], A)] => A)(fa: Fix[F]): A = + f(fa.unfix.map(x => (x, para(f)(x)))) + + // A more powerful ana + def apo[F[_]: Functor, A](f: A => F[Either[Fix[F], A]])(a: A): Fix[F] = { + Fix(f(a).map{ + case Right(a) => apo(f)(a) + case Left(fix) => fix + }) + } + + // When we have cofree + def histo[F[_]: Functor, A](f: F[Cofree[F, A]] => A)(fix: Fix[F]): A = { + def toCofree(fix: Fix[F]): Cofree[F, A] = + Cofree(histo(f)(fix), fix.unfix.map(toCofree)) + + f(fix.unfix.map(toCofree)) + } + + sealed trait StackR + final case class DoneR(result: Int = 1) extends StackR + final case class MoreR(stack: StackR, next: Int) extends StackR + + def unfoldStackR(n: Int): StackR = + if(n > 0) MoreR(unfoldStackR(n-1), n) else DoneR() + + say(unfoldStackR(5)) + + sealed trait Stack[A] + final case class Done[A](result: Int) extends Stack[A] + final case class More[A](a: A, next: Int) extends Stack[A] + + object Stack { + implicit val stackFunctor: Functor[Stack] = new Functor[Stack] { + def map[A, B](fa: Stack[A])(f: A => B): Stack[B] = fa match { + case Done(result) => Done(result) + case More(a, next) => More(f(a), next) + } + } + + def done[A](result: Int = 1): Stack[A] = Done(result) + def more[A](a: A, next: Int): Stack[A] = More(a, next) + } + + import Stack._ + + val stackCoalgebra: Int => Stack[Int] = + n => if(n > 0) more(n - 1, n) else done() + + say(ana(stackCoalgebra)(5)) + + val stackAlgebra: Stack[Int] => Int = { + case Done(result) => result + case More(acc, next) => acc * next + } + + say(cata(stackAlgebra)(ana(stackCoalgebra)(5))) + say(hylo(stackAlgebra)(stackCoalgebra)(5)) + + + sealed trait Nat[A] + final case class Zero[A]() extends Nat[A] + final case class Succ[A](prev: A) extends Nat[A] + + object Nat { + implicit val natFunctor: Functor[Nat] = new Functor[Nat] { + override def map[A, B](na: Nat[A])(f: A => B): Nat[B] = + na match { + case Zero() => Zero() + case Succ(a) => Succ(f(a)) + } + } + } + + val natAlgebra: Nat[Int] => Int = { + case Zero() => 1 + case Succ(n) => { + say(s"nat alg succ $n") + n + 1 + } + } + + val natAlgebraS: Nat[String] => String = { + case Zero() => "N" + case Succ(n) => n match { + case "N" => "NI" + case "NI" => "NIG" + case "NIG" => "NIGG" + case "NIGG" => "NIGGE" + case "NIGGE" => "NIGGER :-" + case s => s + "D" + } + } + + val natCoalgebra: Int => Nat[Int] = + n => if (n == 0) Zero() else Succ(n - 1) + + val b = ana(natCoalgebra)(9) + val c = cata(natAlgebraS)(b) + say(c) + + + val natAlgebraPara: Nat[(Fix[Nat], Int)] => Int = { + case Zero() => 1 + case Succ((fix, acc)) => { + say(s"nat para alg succ $fix, $acc") + cata(natAlgebra)(fix) * acc + } + } + + val build = ana(natCoalgebra)(_) + say("built") + val tear = para(natAlgebraPara)(_) + say(tear(build(5))) + // say(ana(natCoalgebra)(5)) + + val lastThreeSteps: Fix[Stack] = Fix(More(Fix(More(Fix(More(Fix(Done(1)),1)),2)),3)) + + val stackCoalgebraApo: Int => Stack[Either[Fix[Stack], Int]] = + n => if(n > 3) more(n - 1, n).map(_.asRight) else lastThreeSteps.unfix.map(_.asLeft) + } +} diff --git a/src/test/scala/RISCV/printUtils.scala b/src/test/scala/RISCV/printUtils.scala new file mode 100644 index 0000000..77fe516 --- /dev/null +++ b/src/test/scala/RISCV/printUtils.scala @@ -0,0 +1,352 @@ +package FiveStage +import cats.data.Writer +import cats._ +import cats.data.{ Op => _ } +import cats.implicits._ + +import fansi._ + +import Ops._ +import Data._ +import VM._ +import fileUtils._ + +object PrintUtils { + + // Minimal typeclass def for being fancy + trait Fancy[-A] { def s(a: A): fansi.Str } + implicit class FancyOps[-A](a: A)(implicit ev: Fancy[A]){ def show: fansi.Str = ev.s(a) } + implicit val DoubleFancy = new Fancy[fansi.Str]{ def s(a: fansi.Str) = a } + implicit val FancyMonoid = new Monoid[fansi.Str] { + def empty: fansi.Str = fansi.Str("") + def combine(self: fansi.Str, that: fansi.Str): fansi.Str = self ++ that + } + + implicit class ExtraFancy(a: fansi.Str) { + def leftPad(length: Int): fansi.Str = Str((0 until length).map(_ => ' ').mkString) ++ a + def replace(from: Char, to: Char): fansi.Str = fansi.Str(a.toString.replace(from, to)) + def padTo(length: Int, pad: Char = ' '): fansi.Str = a ++ (0 until length - a.length).map(_ => pad).mkString + def trim: fansi.Str = fansi.Str(a.toString.trim) + } + + implicit val RegFancy = new Fancy[Reg] { def s(r: Reg) = fansi.Str(regNames.canonicalName.lift(r.value).getOrElse("ERROR")) } + implicit val ImmFancy = new Fancy[Imm] { def s(r: Imm) = Str(r.value.hs) } + implicit val AddrFancy = new Fancy[Addr]{ def s(r: Addr) = Str(r.value.hs) } + + implicit val ExecutionEventFancy = new Fancy[ExecutionEvent]{ + def s(a: ExecutionEvent): fansi.Str = a match { + case RegUpdate(reg, word) => Str(s"R(${reg.show}) <- 0x${word.hs}") + case MemWrite(addr, word) => fansi.Color.Blue(s"M[${addr.show}] <- 0x${word.hs}") + case MemRead(addr, word) => fansi.Color.Red(f"M[${addr.show}] -> 0x${word.hs}") + + // addr is the target address + case PcUpdateJALR(addr) => fansi.Color.Green(s"PC updated to ${addr.show} via JALR") + case PcUpdateJAL(addr) => fansi.Color.Magenta(s"PC updated to ${addr.show} via JAL") + case PcUpdateB(addr) => fansi.Color.Yellow(s"PC updated to ${addr.show} via Branch") + } + } + + implicit val ExecutionTraceEventFancy = new Fancy[ExecutionTraceEvent]{ + def s(a: ExecutionTraceEvent): fansi.Str = a.event.toList match { + case (h: ExecutionEvent) :: (t: RegUpdate) :: Nil => t.show.padTo(25) ++ h.show + case (h: MemWrite) :: t :: Nil => t.show.padTo(25) ++ h.show + case (h: MemRead) :: t :: Nil => t.show.padTo(25) ++ h.show + case (h: MemWrite) :: Nil => h.show.leftPad(25) + case (h: MemRead) :: Nil => h.show.leftPad(25) + case h :: t :: Nil => h.show ++ t.show + case (h: RegUpdate) :: Nil => h.show + case h :: Nil => h.show.leftPad(25) + case _ => Str("") + } + } + + implicit val ChiselEventFancy = new Fancy[ChiselEvent]{ + def s(e: ChiselEvent) = e match { + case ChiselRegEvent(addr, reg, word) => fansi.Str(s"R(${reg.show}) <- ${word.hs}") + case ChiselMemWriteEvent(addr, memAddr, word) => fansi.Str(s"M[${memAddr.show}] <- ${word.hs}") + } + } + + implicit val RegsFancy = new Fancy[Regs]{ + def s(e: Regs) = { + (0 to 9).map{ idx => + val cols = List.range(idx, 32, 10) + cols.map{ reg => + (fansi.Color.Yellow(Reg(reg).show.padTo(5)) ++ Str(e.repr.lift(Reg(reg)).getOrElse(0).hs)).padTo(16) + }.showN("| ") + }.toList.showN("\n", "\n", "\n") + } + } + + val UNKNOWN = "UNKNOWN" + + def printInstruction(op: Ops.Op, labelMap: Map[Label, Addr]): fansi.Str = op match { + case op: Branch => fansi.Color.Red(s"B${op.comp}\t${op.rs1.show}, ${op.rs2.show}, ${op.dst.show}\t[${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]") + case op: Arith => s"${op.op}\t${op.rd.show}, ${op.rs1.show}, ${op.rs2.show}" + case op: ArithImm => s"${op.op}I\t${op.rd.show}, ${op.rs1.show}, ${op.imm.show}" + case op: JALR => fansi.Color.Green(s"JALR\t${op.rd.show}, ${op.rs1.show}, ${op.dst.show}\t[${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]") + case op: JAL => fansi.Color.Magenta(s"JAL\t${op.rd.show}, ${op.dst.show} [${labelMap.lift(op.dst).getOrElse(UNKNOWN)}]") + case op: LW => s"LW\t${op.rd.show}, ${op.offset.show}(${op.rs1.show})" + case op: SW => s"SW\t${op.rs2.show}, ${op.offset.show}(${op.rs1.show})" + case op: LUI => s"LUI\t${op.rd.show}, ${op.imm.show}" + case op: AUIPC => s"AUIPC\t${op.rd.show}, ${op.imm.show}" + case DONE => s"DONE" + } + + + implicit class IntPrinters(i: Int) { + def hs: String = if(i > 65535) f"0x$i%08X" else f"0x$i%04X" + def binary: String = String.format("%" + 32 + "s", i.toBinaryString) + .replace(' ', '0').grouped(4) + .map(x => x + " ").mkString + } + + + + def printProgram(vm: VM): String = { + val withLabels: List[Either[(Addr, Op), (Label, Addr)]] = (vm.imem.toList.map(Left(_)) ::: vm.labelMap.toList.map(Right(_))) + .sortBy { case Left(x) => x._1.value.toDouble + 0.1; case Right(x) => x._2.value.toDouble } + + withLabels.map{ + case Left((addr, op)) => s"$addr:\t\t${printInstruction(op, vm.labelMap)}" + case Right((label, addr)) => s"$addr:\t$label:" + }.mkString("\n","\n","\n") + } + + + def printProgram(p: Program): String = printProgram(p.vm) + + + def printBinary(bin: Map[Addr, Int]): String = { + bin.toList.sortBy(_._1.value).map{ case(addr, op) => s"$addr: ${op.hs}" }.mkString("\n","\n","\n") + } + + + def printChiselLogEntry(event: (Addr, List[ChiselEvent])): fansi.Str = { + val (addr, log) = event + val events = log match { + case (h: ChiselRegEvent) :: (t: ChiselMemWriteEvent) :: Nil => h.show.padTo(25, ' ') ++ t.show + case (h: ChiselMemWriteEvent) :: Nil => h.show.leftPad(25) + case (h: ChiselRegEvent) :: Nil => h.show + case _ => fansi.Str("") + } + events + } + + + def printVMtrace(trace: List[ExecutionTraceEvent], program: Program): String = { + val blocks: List[List[(ExecutionTraceEvent, Int)]] = LogParser.splitToBlocks(trace).zipWithIndexNested + blocks.map{ block => + val name = LogParser.guessBlockName(block.map(_._1.pc), program.labelMapReverse) + val blockString = block.map{ case(event, idx) => + Str(s"Step: $idx,").padTo(12) ++ + Str("PC: ") ++ + event.pc.show ++ + Str("\t") ++ + event.show.padTo(70) ++ + printSourceLine(event.pc, program) + } + fansi.Color.Yellow(name) ++ Str("\n") ++ blockString.showN("\n") + }.mkStringN + } + + def printSourceLine(addr: Addr, program: Program): fansi.Str = program.sourceMap.lift(addr).map(fansi.Str(_)).getOrElse(fansi.Str("???")) + + def printMergedTraces( + mt: (List[ExecutionTraceEvent], List[CircuitTrace]), + program: Program + ): List[String] = { + + + /** + * For branch predicting processors we may end up with a spurious block causing a transient + * desynchronization. Therefore it is necessary to have a certain tolerance before calling a divergence. + */ + // TODO: Implement a synchronization test to pinpoint where a desync happens + // This is not straight forward in the case of processors which perform branch prediction. + def isSynchronized(desyncs: Int, traces: (List[ExecutionTraceEvent], List[CircuitTrace]) ): Boolean = ??? + + + def helper(mt: (List[ExecutionTraceEvent], List[CircuitTrace])): List[List[fansi.Str]] = mt match { + /** + * VM trace and execution log synchronized, step both + */ + case (hVM :: tVM, hC :: tC) if (hVM.pc == hC._1) => { + val address = hVM.pc + val chiselLogEntry = printChiselLogEntry(hC) + val vmEntry = hVM.show + val sourceLine = printSourceLine(address, program) + + val line = List(address.show, chiselLogEntry, vmEntry, sourceLine) + + line :: helper((tVM, tC)) + } + + + /** + * VM trace and execution log unsynchronized, step only chisel trace + * Helper is called with (hVM :: tVM) to only step the chisel log. + */ + case (hVM :: tVM, hC :: tC) => { + val address = hC._1 + val chiselLogEntry = printChiselLogEntry(hC) + val vmEntry = Str("") + val sourceLine = printSourceLine(address, program) + + val line = List(address.show, chiselLogEntry, vmEntry, sourceLine) + + line :: helper((hVM :: tVM, tC)) + } + + + /** + * The Block contains no more instructions performed by the VM. This + * happens naturally since 5-stage pipelines will fetch spurious instructions. + */ + case (Nil, hC :: tC) => { + val address = hC._1 + val chiselLogEntry = printChiselLogEntry(hC) + val vmEntry = fansi.Str("") + val sourceLine = printSourceLine(address, program) + + val line = List(fansi.Color.LightBlue(address.show), chiselLogEntry, vmEntry, sourceLine) + + line :: helper((Nil, tC)) + } + + + /** + * The Block contains no more instructions performed by the circuit. + * This happens when the circuit takes a jump the VM does not. + * This is an error *unless* the circuit has a branch predictor. + * + * If you want to you can splice the logs when this happens, but it's typically easy to spot. + */ + case (hVM :: tVM, Nil) => { + val address = hVM.pc + val chiselLogEntry = fansi.Str("") + val vmEntry = hVM.show + val sourceLine = printSourceLine(address, program) + + val line = List(fansi.Color.LightRed(address.show), chiselLogEntry, vmEntry, sourceLine) + + line :: helper((tVM, Nil)) + } + + case (Nil, Nil) => Nil + } + + + def format(triplet: List[fansi.Str]): String = { + // Ahh, the GNU toolchain and its tabs + val annoyingTabCharacter = ' ' + triplet match { + case address :: ch :: vm :: source :: Nil => { + val vmLine: fansi.Str = vm.padTo(60, ' ') + val chiselLine: fansi.Str = ch.padTo(50, ' ') + val sourceLines: fansi.Str = source + ((address ++ Str(":")).padTo(14, ' ') ++ vmLine ++ fansi.Str("| ") ++ chiselLine ++ Str("| ") ++ sourceLines).toString + } + case _ => "" + } + } + + val blockName = LogParser.guessBlockName(mt._1.map(_.pc), program.labelMapReverse) + + (fansi.Color.Yellow(s"$blockName").padTo(74, ' ').toString ++ "|".padTo(55, ' ') ++ "|") :: helper(mt).map(format) + } + + + def printChiselError(e: Throwable): String = { + val rawError = """ + |This typically occurs when you forget to wrap a module + |e.g + | + |class MyBundle extends Bundle { + | val signal = UInt(32.W) + |} + |val mySignal = new MyBundle + | ^^^^ Wrong! + |should be + |val mySignal = Wire(new MyBundle) + """.stripMargin + + def getFiveStageStacktrace(strace: Array[StackTraceElement]): String = + strace.map(_.toString).filter(_.contains("FiveStage")).toList.take(5).mkStringN + + e match { + case e: firrtl.passes.CheckInitialization.RefNotInitializedException => { + s""" + |########################################################## + |########################################################## + |########################################################## + |Your design has unconnected wires!" + |error:\n" + |${e.getMessage} + | + | + |########################################################## + |########################################################## + |########################################################## + """.stripMargin + } + case e: chisel3.core.Binding.ExpectedHardwareException => { + s""" + |########################################################## + |########################################################## + |########################################################## + |Your design is using raw chisel types! + |error:\n + |${e.getMessage} + | + |$rawError + |The error should be here somewhere: + |${getFiveStageStacktrace(e.getStackTrace)} + |########################################################## + |########################################################## + |########################################################## + """.stripMargin + } + case e: firrtl.passes.PassExceptions => { + val rawErrorMsg = if(e.getMessage.contains("raw")) + s"One of the errors was use of raw chisel types. $rawError" + else + "" + + s""" + |########################################################## + |########################################################## + |########################################################## + |Your design has multiple errors! + |error: + |${e.getMessage} + |$rawErrorMsg + |########################################################## + |########################################################## + |########################################################## + """.stripMargin + } + case e: Exception => { + s""" + |########################################################## + |########################################################## + |########################################################## + |Unexpected Chisel tester error (could be a chisel error I havent accounted for, or a bug in the tester like index out of bounds.) + |error: + |${e.getMessage} + |reduced stacktrace: + |${getFiveStageStacktrace(e.getStackTrace)} + |########################################################## + |########################################################## + |########################################################## + """.stripMargin + } + } + } + + def printLogSideBySide(trace: List[ExecutionTraceEvent], chiselTrace: List[CircuitTrace], program: Program): String = { + import LogParser._ + val traces = mergeTraces(trace, chiselTrace).map(x => printMergedTraces((x), program)) + traces.map(_.mkString("\n")).mkString("\n", "\n--------------------------------------------------------------------------+------------------------------------------------------+-------------------------------\n", "\n") + } +} diff --git a/src/test/scala/RISCV/regNames.scala b/src/test/scala/RISCV/regNames.scala new file mode 100644 index 0000000..753c0bb --- /dev/null +++ b/src/test/scala/RISCV/regNames.scala @@ -0,0 +1,114 @@ +package FiveStage +object regNames { + val x0 = 0 + val x1 = 1 + val x2 = 2 + val x3 = 3 + val x4 = 4 + val x5 = 5 + val x6 = 6 + val x7 = 7 + val x8 = 8 + val x9 = 9 + + val x10 = 10 + val x11 = 11 + val x12 = 12 + val x13 = 13 + val x14 = 14 + val x15 = 15 + val x16 = 16 + val x17 = 17 + val x18 = 18 + val x19 = 19 + + val x20 = 20 + val x21 = 21 + val x22 = 22 + val x23 = 23 + val x24 = 24 + val x25 = 25 + val x26 = 26 + val x27 = 27 + val x28 = 28 + val x29 = 29 + + val x30 = 30 + val x31 = 31 + + + val zero = 0 + val ra = 1 + val sp = 2 + val gp = 3 + val tp = 4 + val t0 = 5 + val t1 = 6 + val t2 = 7 + val s0 = 8 + val fp = 8 + val s1 = 9 + + val a0 = 10 + val a1 = 11 + val a2 = 12 + val a3 = 13 + val a4 = 14 + val a5 = 15 + val a6 = 16 + val a7 = 17 + val s2 = 18 + val s3 = 19 + + val s4 = 20 + val s5 = 21 + val s6 = 22 + val s7 = 23 + val s8 = 24 + val s9 = 25 + val s10 = 26 + val s11 = 27 + val t3 = 28 + val t4 = 29 + + val t5 = 30 + val t6 = 31 + + val canonicalName = Map( + 0 -> "zero", + 1 -> "ra", + 2 -> "sp", + 3 -> "gp", + 4 -> "tp", + 5 -> "t0", + 6 -> "t1", + 7 -> "t2", + 8 -> "s0", + 8 -> "fp", + 9 -> "s1", + 10 -> "a0", + 11 -> "a1", + 12 -> "a2", + 13 -> "a3", + 14 -> "a4", + 15 -> "a5", + 16 -> "a6", + 17 -> "a7", + 18 -> "s2", + 19 -> "s3", + 20 -> "s4", + 21 -> "s5", + 22 -> "s6", + 23 -> "s7", + 24 -> "s8", + 25 -> "s9", + 26 -> "s10", + 27 -> "s11", + 28 -> "t3", + 29 -> "t4", + 30 -> "t5", + 31 -> "t6", + ) + +} + diff --git a/src/test/scala/RISCV/testRunner.scala b/src/test/scala/RISCV/testRunner.scala new file mode 100644 index 0000000..0e25da6 --- /dev/null +++ b/src/test/scala/RISCV/testRunner.scala @@ -0,0 +1,103 @@ +package FiveStage +import org.scalatest.{Matchers, FlatSpec} +import cats._ +import cats.implicits._ +import fileUtils._ + +import chisel3.iotesters._ +import scala.collection.mutable.LinkedHashMap + +import fansi.Str + +import Ops._ +import Data._ +import VM._ + +import PrintUtils._ +import LogParser._ + +case class TestOptions( + printIfSuccessful : Boolean, + printErrors : Boolean, + printParsedProgram : Boolean, + printVMtrace : Boolean, + printVMfinal : Boolean, + printMergedTrace : Boolean, + nopPadded : Boolean, + breakPoints : List[Int], // Not implemented + testName : String +) + +case class TestResult( + regError : Option[String], + memError : Option[String], + program : String, + vmTrace : String, + vmFinal : String, + sideBySide : String +) + +object TestRunner { + + def run(testOptions: TestOptions): Boolean = { + + val testResults = for { + lines <- fileUtils.readTest(testOptions) + program <- FiveStage.Parser.parseProgram(lines, testOptions) + (binary, (trace, finalVM)) <- program.validate.map(x => (x._1, x._2.run)) + (termitationCause, chiselTrace) <- ChiselTestRunner( + binary.toList.sortBy(_._1.value).map(_._2), + program.settings, + finalVM.pc, + 1500) + } yield { + val traces = mergeTraces(trace, chiselTrace).map(x => printMergedTraces((x), program)) + + val programString = printProgram(program) + val vmTraceString = printVMtrace(trace, program) + val vmFinalState = finalVM.regs.show + val traceString = printLogSideBySide(trace, chiselTrace, program) + + val regError = compareRegs(trace, chiselTrace) + val memError = compareMem(trace, chiselTrace) + + TestResult( + regError, + memError, + programString, + vmTraceString, + vmFinalState.toString, + traceString) + } + + testResults.left.foreach{ error => + say(s"Test was unable to run due to error: $error") + } + + testResults.map{ testResults => + val successful = List(testResults.regError, testResults.memError).flatten.headOption.map(_ => false).getOrElse(true) + if(successful) + say(s"${testOptions.testName} succesful") + else + say(s"${testOptions.testName} failed") + + if(testOptions.printIfSuccessful && successful){ + if(testOptions.printParsedProgram) say(testResults.program) + if(testOptions.printVMtrace) say(testResults.vmTrace) + if(testOptions.printVMfinal) say(testResults.vmFinal) + if(testOptions.printMergedTrace) say(testResults.sideBySide) + } + else{ + if(testOptions.printErrors){ + say(testResults.regError.map(_.show.toString).getOrElse("no reg errors")) + say(testResults.memError.map(_.show.toString).getOrElse("no mem errors")) + } + if(testOptions.printParsedProgram) say(testResults.program) + if(testOptions.printVMtrace) say(testResults.vmTrace) + if(testOptions.printVMfinal) say(testResults.vmFinal) + if(testOptions.printMergedTrace) say(testResults.sideBySide) + } + successful + }.toOption.getOrElse(false) + } +} diff --git a/src/test/scala/TestUtils.scala b/src/test/scala/TestUtils.scala new file mode 100644 index 0000000..5943fd7 --- /dev/null +++ b/src/test/scala/TestUtils.scala @@ -0,0 +1,166 @@ +package FiveStage + +import fileUtils._ +import Data._ +import PrintUtils._ + +object TestUtils { + + /** + * Generate and serialize BTrees for the test runner + */ + def generateBTree: Unit = { + + import cats._ + import cats.implicits._ + + case class AnnotatedNode(value: Int, index: Int, left: Option[AnnotatedNode], right: Option[AnnotatedNode]){ + def find(key: Int): Unit = { + say(s"at $index (${(4 + (index << 2)).hs})-> ${value.hs}, looking for ${key.hs}") + if(value == key) + say("found it") + else if(key < value) + left.map{x => say("going left\n"); x.find(key)}.getOrElse("gave up") + else + right.map{x => say("going right\n"); x.find(key)}.getOrElse("gave up") + } + } + + def printAnnoTree(tree: AnnotatedNode, depth: Int): String = { + val ls = tree.left.map(n => printAnnoTree(n, depth + 2)).getOrElse("left empty") + val rs = tree.right.map(n => printAnnoTree(n, depth + 2)).getOrElse("right empty") + val pads = "|".padTo(2, ' ')*depth + s"${tree.value.hs} at ${tree.index.hs}\n$pads$ls\n$pads$rs" + } + + case class Node(value: Int, left: Option[Node], right: Option[Node]){ + def append(v: Int): Node = if(v < value) + copy(left = left.map(_.append(v)).orElse(Some(Node(v)))) + else + copy(right = right.map(_.append(v)).orElse(Some(Node(v)))) + } + + object Node { + def apply(v: Int): Node = Node(v, None, None) + } + + def annotate(n: Int, root: Node): (AnnotatedNode, Int) = { + (root.left, root.right) match { + case (None, None) => { + (AnnotatedNode(root.value, n, None, None), n + 1) + } + case (Some(node), None) => { + val (annotated, next) = annotate(n+1, node) + (AnnotatedNode(root.value, n, Some(annotated), None), next) + } + case (None, Some(node)) => { + val (annotated, next) = annotate(n+1, node) + (AnnotatedNode(root.value, n, None, Some(annotated)), next) + } + case (Some(left), Some(right)) => { + val (leftAnno, leftNext) = annotate(n+1, left) + val (rightAnno, rightNext) = annotate(leftNext, right) + (AnnotatedNode(root.value, n, Some(leftAnno), Some(rightAnno)), rightNext) + } + } + } + + def foldAnno(root: Option[AnnotatedNode]): List[Int] = { + root.map{ root => + val leftIndex = root.left.map(_.index).getOrElse(0) + val hasLeft = root.left.map(_ => 1).getOrElse(0) + + val rightIndex = root.right.map(_.index).getOrElse(0) + val hasRight = root.right.map(_ => 1).getOrElse(0) + + val entry = hasLeft + (leftIndex << 1) + (hasRight << 8) + (rightIndex << 9) + (root.value << 16) + + say(s"with leftIndex: ${leftIndex.hs}, rightIndex: ${rightIndex.hs}, value: ${root.value.hs} we got ${entry.hs}") + + entry :: foldAnno(root.left) ::: foldAnno(root.right) + }.getOrElse(Nil) + } + + import scala.util.Random + val r = new scala.util.Random(0xF01D1EF7) + def randInt = r.nextInt(1000) + + val seed = (0 to 100).map(_ => randInt).toList + val btree = seed.foldLeft(Node(randInt, None, None)){ case(acc, n) => acc.append(n)} + + val annoTree = annotate(0, btree) + say(foldAnno(Some(annoTree._1)).zipWithIndex.map{case(m, idx) => s"#memset ${(4 + (idx*4)).hs}, ${m.hs}"}.mkStringN) + } + + + /** + * Generates a random program filled with hazards + */ + def generateHazardsForward(steps: Int) : Unit = { + + // val r = new scala.util.Random(0xF01D1EF7) + val r = new scala.util.Random(0xF01D1EF8) + import Ops._ + + val active = List(1, 2, 3) + + val initVM = { + val init = VM.init + val init1 = init.copy(regs = (init.regs + (Reg(1) -> 123))._2) + val init2 = init.copy(regs = (init1.regs + (Reg(2) -> -40))._2) + val init3 = init.copy(regs = (init2.regs + (Reg(3) -> 0xFFEE))._2) + init3 + } + + + def generateInstruction: (Int, (String, Op)) = { + val rd = active.shuffle(r).head + val rs1 = active.shuffle(r).head + val rs2 = active.shuffle(r).head + val imm = r.nextInt(1024) - 512 + val shift = r.nextInt(32) - 16 + + val choices = List( + (s"add ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.add(rd, rs1, rs2)), + (s"sub ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.sub(rd, rs1, rs2)), + (s"or ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.or(rd, rs1, rs2)), + (s"xor ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.xor(rd, rs1, rs2)), + (s"and ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.and(rd, rs1, rs2)), + (s"sll ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.sll(rd, rs1, rs2)), + (s"srl ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.srl(rd, rs1, rs2)), + (s"sra ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.sra(rd, rs1, rs2)), + (s"slt ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.slt(rd, rs1, rs2)), + (s"sltu ${Reg(rd).show}, ${Reg(rs1).show}, ${Reg(rs2).show}", Arith.sltu(rd, rs1, rs2)), + + (s"addi ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.add(rd, rs1, imm)), + (s"ori ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.or(rd, rs1, imm)), + (s"xori ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.xor(rd, rs1, imm)), + (s"andi ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.and(rd, rs1, imm)), + (s"slli ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(shift).show}", ArithImm.sll(rd, rs1, shift)), + (s"srli ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(shift).show}", ArithImm.srl(rd, rs1, shift)), + (s"srai ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(shift).show}", ArithImm.sra(rd, rs1, shift)), + (s"slti ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.slt(rd, rs1, imm)), + (s"sltiu ${Reg(rd).show}, ${Reg(rs1).show}, ${Imm(imm).show}", ArithImm.sltu(rd, rs1, imm))) + (rd, choices.shuffle(r).head) + } + + def helper(attempts: Int, steps: Int, vm: VM): Unit = { + if((attempts > 10) || (steps == 0)) + () + else{ + val (nextRd, (nS, nextOp)) = generateInstruction + val withOp = vm.copy(imem = vm.imem + (vm.pc -> nextOp)) + val nextVmE = withOp.stepInstruction + val nextVm = nextVmE.toOption.get.run._2 + if(nextVm.regs.repr(Reg(nextRd)) == 0) + helper(attempts + 1, steps, vm) + else { + say(nS) + helper(0, steps - 1, nextVm) + } + } + } + + helper(0, steps, initVM) + } +} diff --git a/src/test/scala/chiselTestRunner.scala b/src/test/scala/chiselTestRunner.scala new file mode 100644 index 0000000..cdd83a8 --- /dev/null +++ b/src/test/scala/chiselTestRunner.scala @@ -0,0 +1,188 @@ +package FiveStage +import chisel3.iotesters._ +import scala.collection.mutable.LinkedHashMap + +import fileUtils.say + +import Data._ +import PrintUtils._ + +private class ChiselTestRunner ( + instructions : List[Int], + settings : List[TestSetting], + c : Tile, + d : PeekPokeTester[Tile], + terminalAddress : Addr, + maxSteps : Int) { + + + /** + * Currently unused as it is quite nontrivial to figure out whether read data is actually used. + * Still, with the necessary scaffolding these can be correlated with reg writes to figure out + * if they are valid or not. + */ + case class ChiselMemReadEvent(pcAddr: Addr, memAddr: Addr, word: Int) + + private def setup = { + + d.poke(d.dut.io.setup, 1) + + val initDMem = DMem(settings).repr.toList + val initRegs = Regs(settings).repr.toList + + setupImem(instructions) + setupRegs(initRegs) + setupDmem(initDMem) + + // flush + d.step(1) + disableTestSignals + d.step(5) + d.poke(d.dut.io.setup, 0) + + def disableTestSignals: Unit = { + d.poke(d.dut.io.DMEMWriteData, 0) + d.poke(d.dut.io.DMEMAddress, 0) + d.poke(d.dut.io.DMEMWriteEnable, 0) + d.poke(d.dut.io.regsWriteData, 0) + d.poke(d.dut.io.regsAddress, 0) + d.poke(d.dut.io.regsWriteEnable, 0) + d.poke(d.dut.io.IMEMWriteData, 0) + d.poke(d.dut.io.IMEMAddress, 4092) + } + + def setupRegs(regs: List[(Reg, Int)]) = { + regs.foreach{ case(reg, word) => + d.poke(d.dut.io.setup, 1) + d.poke(d.dut.io.regsWriteEnable, 1) + d.poke(d.dut.io.regsWriteData, BigInt(word)) + d.poke(d.dut.io.regsAddress, reg.value) + d.step(1) + } + d.poke(d.dut.io.regsWriteEnable, 0) + d.poke(d.dut.io.regsWriteData, 0) + d.poke(d.dut.io.regsAddress, 0) + } + + def setupDmem(mem: List[(Addr, Int)]) = { + mem.foreach { case (addr, word) => + d.poke(d.dut.io.setup, 1) + d.poke(d.dut.io.DMEMWriteEnable, 1) + d.poke(d.dut.io.DMEMWriteData, word) + d.poke(d.dut.io.DMEMAddress, addr.value) + d.step(1) + } + d.poke(d.dut.io.DMEMWriteEnable, 0) + d.poke(d.dut.io.DMEMWriteData, 0) + d.poke(d.dut.io.DMEMAddress, 0) + + } + + def setupImem(instructions: List[Int]) = { + (0 until instructions.length).foreach{ ii => + d.poke(d.dut.io.IMEMAddress, ii*4) + d.poke(d.dut.io.IMEMWriteData, instructions(ii).toInt) + d.step(1) + } + d.poke(d.dut.io.IMEMAddress, 4092) // Ensures that we don't overwrite an instruction. Bandaid for lack of IMEM.writeEnable + d.poke(d.dut.io.IMEMWriteData, 0) + } + } + + + private def getPC = Addr(d.peek(d.dut.io.currentPC).toInt) + private def getDMemWriteAddress = Addr(d.peek(d.dut.io.memDeviceWriteAddress).toInt) + private def getRegWriteAddress = Reg(d.peek(d.dut.io.regsDeviceWriteAddress).toInt) + + private def getRegUpdate: Option[ChiselRegEvent] = { + if( + (d.peek(d.dut.io.regsDeviceWriteEnable) == 1) && + (getRegWriteAddress.value != 0) + ){ + val regWriteAddress = getRegWriteAddress + val regWriteData = d.peek(d.dut.io.regsDeviceWriteData).toInt + val regUpdate = ChiselRegEvent(getPC, regWriteAddress, regWriteData) + Some(regUpdate) + } + else + None + } + + private def getMemWrite: Option[ChiselMemWriteEvent] = { + if(d.peek(d.dut.io.memDeviceWriteEnable) == 1) { + val memWriteAddress = getDMemWriteAddress + val memWriteData = d.peek(d.dut.io.memDeviceWriteData).toInt + val memUpdate = ChiselMemWriteEvent(getPC, memWriteAddress, memWriteData) + Some(memUpdate) + } + else + None + } + + private def stepOne: CircuitTrace = { + val state = (getPC, List(getRegUpdate, getMemWrite).flatten) + // say(d.peek(d.dut.io).toList.mkStringN) + d.step(1) + state + } + + + private def go(log: List[CircuitTrace], timeOut: Int): (Option[String], List[CircuitTrace]) = { + if(timeOut == 0){ + (Some("Chisel tester timed out before reaching termination address"), log.reverse) + } + else if(getPC == terminalAddress){ + (None, (flush ::: log).reverse) + } + else { + val step = stepOne + go(step :: log, timeOut - 1) + } + } + + // After finishing, let the circuit run until all updates can be committed. + private def flush: List[CircuitTrace] = + (0 to 3).map(_ => stepOne).reverse.toList + + /** + * Run the entire shebang + */ + def run: (Option[String], List[CircuitTrace]) = { + setup + go(Nil, maxSteps) + } +} + +object ChiselTestRunner { + + def apply( + binary : List[Int], + settings : List[TestSetting], + terminalAddress : Addr, + maxSteps : Int): Either[String, (Option[String], List[CircuitTrace])] = { + + var sideEffectExtravaganza: Option[(Option[String], List[CircuitTrace])] = None + + val error: Either[String, Boolean] = scala.util.Try { + chisel3.iotesters.Driver(() => new Tile(), "treadle") { c => + new PeekPokeTester(c) { + val testRunner = new ChiselTestRunner( + binary, + settings, + c, + this, + terminalAddress, + maxSteps + ) + + val log = testRunner.run + sideEffectExtravaganza = Some(log) + } + } + }.toEither.left.map{printChiselError} + + error.flatMap{ e => + sideEffectExtravaganza.toRight("Unknown test failure, please let me know") + } + } +} diff --git a/src/test/scala/fileUtils.scala b/src/test/scala/fileUtils.scala new file mode 100644 index 0000000..cfff3fa --- /dev/null +++ b/src/test/scala/fileUtils.scala @@ -0,0 +1,94 @@ +package FiveStage +import chisel3.iotesters._ +import java.io.File +import java.nio.file.Path +import scala.collection.mutable.LinkedHashMap +// import cats.effect.ContextShift + +import cats.implicits._ +import cats._ +import cats.syntax._ +import cats.Applicative._ +import atto._, Atto._ + +object fileUtils { + + def say(word: Any)(implicit filename: sourcecode.File, line: sourcecode.Line): Unit = { + val fname = filename.value.split("/").last + println(Console.YELLOW + s"[${fname}: ${sourcecode.Line()}]" + Console.RESET + s" - $word") + } + + def sayRed(word: Any)(implicit filename: sourcecode.File, line: sourcecode.Line): Unit = { + val fname = filename.value.split("/").last + println(Console.YELLOW + s"[${fname}: ${sourcecode.Line()}]" + Console.RED + s" - $word") + } + def sayGreen(word: Any)(implicit filename: sourcecode.File, line: sourcecode.Line): Unit = { + val fname = filename.value.split("/").last + println(Console.YELLOW + s"[${fname}: ${sourcecode.Line()}]" + Console.GREEN + s" - $word") + } + + def getListOfFiles(dir: String): List[File] = + (new File(dir)).listFiles.filter(_.isFile).toList + + def getListOfFiles(dir: Path): List[File] = + dir.toFile().listFiles.filter(_.isFile).toList + + + def getListOfFolders(dir: String): List[File] = + (new File(dir)).listFiles.filter(_.isDirectory).toList + + def getListOfFolders(dir: Path): List[File] = + dir.toFile().listFiles.filter(_.isDirectory).toList + + def getListOfFilesRecursive(dir: String): List[File] = { + getListOfFiles(dir) ::: getListOfFolders(dir).flatMap(f => + getListOfFilesRecursive(f.getPath) + ) + } + + import cats.implicits._ + import java.nio.file.Paths + import java.util.concurrent.Executors + import scala.concurrent.ExecutionContext + + def relativeFile(name: String) = { + new File(getClass.getClassLoader.getResource(name).getPath) + } + + def getTestDir: File = + new File(getClass.getClassLoader.getResource("tests").getPath) + + def getAllTests: List[File] = getListOfFilesRecursive(getTestDir.getPath) + .filter( f => f.getPath.endsWith(".s") ) + + def getAllTestNames: List[String] = getAllTests.map(_.toString.split("/").takeRight(1).mkString) + + def clearTestResults = { + try { + val testResults = relativeFile("/testResults") + testResults.delete() + } + catch { + case _:Throwable => () + } + } + + /** + * Read an assembly file. + */ + def readTest(testOptions: TestOptions): Either[String, List[String]] = { + + // Ahh, the GNU toolchain and its tabs + val annoyingTabCharacter = ' ' + + getAllTests.filter(_.getName.contains(testOptions.testName)).headOption.toRight(s"File not found: ${testOptions.testName}").flatMap{ filename => + import scala.io.Source + scala.util.Try( + Source.fromFile(filename) + .getLines.toList + .map(_.replace(annoyingTabCharacter, ' '))) + .toOption + .toRight(s"Error reading $filename") + } + } +} diff --git a/src/test/scala/testConf.scala b/src/test/scala/testConf.scala new file mode 100644 index 0000000..721cc38 --- /dev/null +++ b/src/test/scala/testConf.scala @@ -0,0 +1,43 @@ +// package FiveStage +// import chisel3._ +// import chisel3.iotesters._ +// import org.scalatest.{Matchers, FlatSpec} +// import spire.math.{UInt => Uint} +// import fileUtils._ +// import cats.implicits._ + +// import RISCVutils._ +// import RISCVasm._ +// import riscala._ + +// import utilz._ + +// class AllTests extends FlatSpec with Matchers { + +// val results = fileUtils.getAllTests.map{f => +// val result = TestRunner.runTest(f.getPath, false) +// (f.getName, result) +// } + +// makeReport(results) +// } + + +// /** +// This is for you to run more verbose testing. +// */ +// class SelectedTests extends FlatSpec with Matchers { + +// val tests = List( +// "matMul.s" +// ) + +// if(!tests.isEmpty){ +// val results = fileUtils.getAllTests.filter(f => tests.contains(f.getName)).map{ f => +// val result = TestRunner.runTest(f.getPath, true) +// (f.getName, result) +// } + +// makeReport(results) +// } +// }