commit 932413bb3dc28b13dfde01c572e3b4918f44e508 Author: peteraa Date: Fri Jun 7 17:43:33 2019 +0200 Nuke 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) +// } +// }