├── .codecov.yml ├── .github ├── FUNDING.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md └── workflows │ ├── generate-readme.yml │ └── scala.yml ├── .scala-steward.conf ├── .git-blame-ignore-revs ├── package.json ├── .mergify.yml ├── .scalafix.conf ├── LICENSE ├── .gitignore ├── scripts └── updateReadme.sh ├── .scalafmt.conf ├── riscvassembler ├── test │ ├── jvm │ │ └── RISCVAssemblerJVMSpec.scala │ └── src │ │ ├── ObjectUtilsSpec.scala │ │ ├── internal │ │ ├── InternalMethodsSpec.scala │ │ └── InstructionsSpec.scala │ │ └── RISCVAssemblerSpec.scala └── src │ ├── ObjectUtils.scala │ ├── RISCVAssembler.scala │ └── internal │ ├── Instructions.scala │ └── InternalMethods.scala ├── rvasmcli ├── test │ └── src │ │ └── MainSpec.scala └── src │ └── Main.scala ├── Readme.md └── mill /.codecov.yml: -------------------------------------------------------------------------------- 1 | coverage: 2 | status: 3 | project: 4 | default: 5 | threshold: 3% -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | # These are supported funding model platforms 2 | 3 | github: carlosedp 4 | patreon: carlosedp 5 | -------------------------------------------------------------------------------- /.scala-steward.conf: -------------------------------------------------------------------------------- 1 | updatePullRequests = "always" 2 | updates.ignore = [ { groupId = "org.scala-lang", artifactId = "scala-library" }, { groupId = "org.scala-lang", artifactId = "scala3-library" } ] 3 | -------------------------------------------------------------------------------- /.git-blame-ignore-revs: -------------------------------------------------------------------------------- 1 | # Scala Steward: Reformat with scalafmt 3.7.2 2 | 40e8279b2408d14ce7b4abed4659eceec5302036 3 | 4 | # Scala Steward: Reformat with scalafmt 3.7.4 5 | 7eb59e891f715c6266fe8c2d4415f4cfc4d1cd85 6 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | # Set update schedule for GitHub Actions 2 | 3 | version: 2 4 | updates: 5 | 6 | - package-ecosystem: "github-actions" 7 | directory: "/" 8 | schedule: 9 | # Check for updates to GitHub Actions every weekday 10 | interval: "daily" 11 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "riscvassembler", 3 | "description": "A RISC-V assembler library for Scala/Chisel HDL projects", 4 | "author": "Carlos Eduardo de Paula", 5 | "license": "MIT", 6 | "version": "0.0.1", 7 | "dependencies": { 8 | "jsdom": "^22.1.0" 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '[Bug] ' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Describe the bug** 11 | A clear and concise description of what the bug is. 12 | 13 | **Code sample** 14 | Please paste below a code sample that demonstrates the problem: 15 | 16 | ```scala 17 | // Yout code here 18 | ``` 19 | 20 | **Expected behavior** 21 | A clear and concise description of what you expected to happen. 22 | 23 | **Error Logs** 24 | If applicable, add logs from the operator and your Kubernetes deployment related to the error. 25 | 26 | **Additional context** 27 | Add any other context about the problem here. 28 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '[Feature] ' 5 | labels: 'feature' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.mergify.yml: -------------------------------------------------------------------------------- 1 | pull_request_rules: 2 | - name: Automatic merge on approval and CI success 3 | conditions: 4 | - base=main 5 | - label="please merge" 6 | - status-success=test (11) 7 | - status-success=test (17) 8 | - "#review-requested=0" 9 | actions: 10 | merge: 11 | method: squash 12 | 13 | - name: Automatic merge Dependabot PRs (for actions) on CI success 14 | conditions: 15 | - base=main 16 | - author=dependabot[bot] 17 | - files~=^.github/workflows/ 18 | - status-success=test (11) 19 | - status-success=test (17) 20 | - "#review-requested=0" 21 | actions: 22 | merge: 23 | method: squash 24 | 25 | - name: Label lib PRs 26 | conditions: 27 | - base=main 28 | - author=scala-steward 29 | actions: 30 | label: 31 | add: 32 | - dependencies 33 | -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | # RemoveUnused, 3 | DisableSyntax, 4 | LeakingImplicitClassVal, 5 | NoAutoTupling, 6 | NoValInForComprehension, 7 | OrganizeImports, 8 | # ProcedureSyntax, 9 | ] 10 | 11 | # DisableSyntax.noVars = true 12 | DisableSyntax.noThrows = false 13 | DisableSyntax.noNulls = true 14 | # DisableSyntax.noReturns = true 15 | DisableSyntax.noXml = true 16 | DisableSyntax.noFinalVal = true 17 | DisableSyntax.noFinalize = true 18 | DisableSyntax.noValPatterns = true 19 | 20 | OrganizeImports.blankLines = Auto 21 | OrganizeImports.groupedImports = Merge, 22 | OrganizeImports.groups = ["javax?\\.", "scala.", "*"] 23 | OrganizeImports.coalesceToWildcardImportThreshold = 5 24 | OrganizeImports.expandRelative = true 25 | OrganizeImports.removeUnused = false // Disabled until supported for Scala 3.3 26 | OrganizeImports.importSelectorsOrder = Ascii 27 | 28 | RemoveUnused.imports = false // handled by OrganizeImports 29 | -------------------------------------------------------------------------------- /.github/workflows/generate-readme.yml: -------------------------------------------------------------------------------- 1 | name: Regenerate Readme (on new tags) 2 | 3 | on: 4 | push: 5 | branches: [ main ] 6 | tags: ["*"] 7 | 8 | concurrency: 9 | group: generate-readme 10 | cancel-in-progress: true 11 | 12 | jobs: 13 | regenerate-readme: 14 | runs-on: ubuntu-latest 15 | steps: 16 | - name: Sleep for 30 minutes while repository metadata is updated 17 | run: sleep 30m 18 | shell: bash 19 | 20 | - name: Checkout 21 | uses: actions/checkout@v6 22 | with: 23 | fetch-depth: 0 24 | 25 | - name: Generate Readme from template 26 | run: | 27 | chmod +x ./scripts/updateReadme.sh 28 | ./scripts/updateReadme.sh 29 | 30 | - name: Add and Commit file 31 | uses: EndBug/add-and-commit@v9 32 | with: 33 | default_author: github_actions 34 | message: 'Update library versions on Readme' 35 | push: origin HEAD:main 36 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Carlos Eduardo 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Scala .gitignore 2 | # 3 | # JARs aren't checked in, they are fetched by sbt 4 | # 5 | /lib/*.jar 6 | /test/files/codelib/*.jar 7 | /test/files/lib/*.jar 8 | /test/files/speclib/instrumented.jar 9 | /tools/*.jar 10 | 11 | # Developer specific properties 12 | /build.properties 13 | /buildcharacter.properties 14 | 15 | # might get generated when testing Jenkins scripts locally 16 | /jenkins.properties 17 | 18 | # target directory for build 19 | /build/ 20 | 21 | # other 22 | /out/ 23 | /bin/ 24 | /sandbox/ 25 | 26 | # intellij 27 | /src/intellij*/*.iml 28 | /src/intellij*/*.ipr 29 | /src/intellij*/*.iws 30 | **/.cache 31 | /.idea 32 | /.settings 33 | 34 | # vscode 35 | /.vscode 36 | 37 | # Standard symbolic link to build/quick/bin 38 | /qbin 39 | 40 | # sbt's target directories 41 | /target/ 42 | /project/**/target/ 43 | /test/macro-annot/target/ 44 | /test/files/target/ 45 | /test/target/ 46 | /build-sbt/ 47 | local.sbt 48 | jitwatch.out 49 | 50 | # Used by the restarr/restarrFull commands as target directories 51 | /build-restarr/ 52 | /target-restarr/ 53 | 54 | # metals 55 | .metals 56 | .bloop 57 | project/**/metals.sbt 58 | project/metals.sbt 59 | 60 | # custom 61 | .bsp 62 | .addons-dont-touch 63 | .DS_Store 64 | generated 65 | docs/generated 66 | test_run_dir 67 | tmpasm 68 | node_modules -------------------------------------------------------------------------------- /scripts/updateReadme.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | GROUPID=com.carlosedp 4 | ARTIFACT=riscvassembler_2.13 5 | 6 | REPO=$(echo ${GROUPID} | sed "s/\./\//g")/${ARTIFACT} 7 | 8 | echo "Fetching release and snapshot versions for ${REPO}" 9 | 10 | # Fetch latest release and snapshot versions 11 | LASTRELEASE=$(curl -sL https://repo1.maven.org/maven2/"${REPO}"/maven-metadata.xml |grep latest |head -1 |sed -e 's/<[^>]*>//g' |tr -d " ") 12 | LASTSNAPSHOT=$(curl -sL "https://s01.oss.sonatype.org/service/local/lucene/search?g=${GROUPID}&a=${ARTIFACT}" |grep ""|head -1 |sed -e 's/<[^>]*>//g' |tr -d " ") 13 | 14 | echo "Latest library release: ${LASTRELEASE}" 15 | echo "Latest library snapshot: ${LASTSNAPSHOT}" 16 | 17 | # Update Readme 18 | echo "Updating readme versions..." 19 | ## Release 20 | sed -i "s/ivy.*ReleaseVerMill/ivy\"com.carlosedp::riscvassembler:${LASTRELEASE}\" \/\/ReleaseVerMill/" Readme.md 21 | sed -i "s/libraryDependencies.*ReleaseVerSBT/libraryDependencies += \"com.carlosedp\" %% \"riscvassembler\" % \"${LASTRELEASE}\" \/\/ReleaseVerSBT/" Readme.md 22 | 23 | ## Snapshots 24 | sed -i "s/ivy.*SnapshotVerMill/ivy\"com.carlosedp::riscvassembler:${LASTSNAPSHOT}\" \/\/SnapshotVerMill/" Readme.md 25 | sed -i "s/libraryDependencies.*SnapshotVerSBT/libraryDependencies += \"com.carlosedp\" %% \"riscvassembler\" % \"${LASTSNAPSHOT}\" \/\/SnapshotVerSBT/" Readme.md 26 | 27 | echo "Finished" -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.9.8 2 | runner.dialect = scala213 3 | project.git = true 4 | 5 | fileOverride { 6 | "glob:**/rvasmcli/src/**" { 7 | runner.dialect = scala3 8 | } 9 | "glob:**/rvasmcli/test/src/**" { 10 | runner.dialect = scala3 11 | } 12 | "glob:**/build.mill" { 13 | runner.dialect = scala3 14 | } 15 | } 16 | 17 | 18 | maxColumn = 120 19 | align.preset = more 20 | assumeStandardLibraryStripMargin = true 21 | docstrings.style = Asterisk 22 | docstrings.wrapMaxColumn = 80 23 | lineEndings = preserve 24 | danglingParentheses.preset = true 25 | danglingParentheses.exclude = [ 26 | "`trait`" 27 | ] 28 | align.tokens."+" = [ 29 | {code = ":"} 30 | {code = "=", owner = "(Enumerator.Val|Defn.(Va(l|r)|Def|Type|GivenAlias)|Term.Assign)"} 31 | ] 32 | newlines.source = keep 33 | newlines.beforeCurlyLambdaParams = false 34 | newlines.implicitParamListModifierForce = [before] 35 | rewrite.trailingCommas.style = "multiple" 36 | rewrite.trailingCommas.allowFolding = true 37 | rewrite.scala3.convertToNewSyntax = true 38 | rewrite.scala3.removeOptionalBraces = true 39 | 40 | rewrite.rules = [ 41 | RedundantBraces, 42 | RedundantParens, 43 | PreferCurlyFors, 44 | ] 45 | 46 | verticalMultiline.atDefnSite = true 47 | verticalMultiline.arityThreshold = 3 48 | 49 | rewrite.redundantBraces.generalExpressions = false 50 | rewriteTokens = { 51 | "⇒": "=>" 52 | "→": "->" 53 | "←": "<-" 54 | } 55 | -------------------------------------------------------------------------------- /riscvassembler/test/jvm/RISCVAssemblerJVMSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import java.io.{File, FileWriter} 4 | import scala.util.Try 5 | import org.scalatest.flatspec._ 6 | import org.scalatest.matchers.should._ 7 | import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} 8 | 9 | class RISCVAssemblerJVMSpec extends AnyFlatSpec with BeforeAndAfterEach with BeforeAndAfterAll with Matchers { 10 | 11 | it should "generate hex output from file source" in { 12 | val prog = """ 13 | main: lui x1, 0xfffff 14 | addi x2, x0, 1 15 | wait: lw x3, 0(x1) 16 | bne x2, x3, wait 17 | cont: sw x0, 0(x1) 18 | wait2: lw x3, 0(x1) 19 | bne x2, x3, wait2 20 | cont2: addi x3, x0, 2 21 | """.stripMargin 22 | 23 | val file = File.createTempFile("hextemp", ".asm") 24 | val fileWriter = new FileWriter(new File(file.getAbsolutePath())) 25 | fileWriter.write(prog) 26 | fileWriter.close() 27 | val output = RISCVAssembler.fromFile(file.getAbsolutePath()).trim 28 | 29 | val correct = 30 | """ 31 | |fffff0b7 32 | |00100113 33 | |0000a183 34 | |fe311ee3 35 | |0000a023 36 | |0000a183 37 | |fe311ee3 38 | |00200193 39 | |""".stripMargin.toUpperCase.trim 40 | 41 | output should be(correct) 42 | Try { 43 | file.delete() 44 | } 45 | } 46 | 47 | } 48 | -------------------------------------------------------------------------------- /riscvassembler/test/src/ObjectUtilsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import ObjectUtils._ 4 | import org.scalatest.flatspec._ 5 | import org.scalatest.matchers.should._ 6 | 7 | class ObjectUtilsSpec extends AnyFlatSpec with Matchers { 8 | 9 | behavior of "Padding" 10 | 11 | it should "pad binary string with zeros" in { 12 | val myBinary = "100" 13 | val output = myBinary.padZero(12) 14 | output should be("000000000100") 15 | } 16 | 17 | behavior of "Truncating" 18 | 19 | it should "truncate long 1L to 32 bits" in { 20 | val myLong = 1L 21 | val output = myLong.to32Bit 22 | output should be(1L) 23 | } 24 | 25 | it should "truncate long 0xffffffff to 32 bits" in { 26 | val myLong = 0xffffffffL 27 | val output = myLong.to32Bit 28 | output should be(0xffffffffL) 29 | } 30 | 31 | it should "truncate long bigger than 0xffffffff to 32 bits" in { 32 | val myLong = 0xaaffffffffL 33 | val output = myLong.to32Bit 34 | output should be(0xffffffffL) 35 | } 36 | 37 | it should "truncate int 0xffffffff to 32 bits" in { 38 | val myInt = 0xffffffff 39 | val output = myInt.to32Bit 40 | output should be(0xffffffffL) 41 | } 42 | 43 | it should "truncate BigInt bigger than 0xffffffff to 32 bits" in { 44 | val myBigInt = BigInt("aaffffffff", 16) 45 | val output = myBigInt.to32Bit 46 | output should be(0xffffffffL) 47 | } 48 | 49 | behavior of "Converting" 50 | 51 | it should "convert a binary string to Long" in { 52 | val output = "101010".b 53 | output should be(42) 54 | } 55 | 56 | it should "convert an hex string to Long" in { 57 | val output = "abc".h 58 | output should be(2748) 59 | } 60 | 61 | it should "convert an oct string to Long" in { 62 | val output = "567".o 63 | output should be(375) 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /rvasmcli/test/src/MainSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.rvasmcli 2 | 3 | import org.scalatest.flatspec.* 4 | import org.scalatest.matchers.should.* 5 | 6 | class MainSpec extends AnyFlatSpec with Matchers: 7 | 8 | behavior of "rvasmcli" 9 | 10 | it should "help message if no parameters are passed" in { 11 | val res = Main.run() 12 | res should include("--help") 13 | } 14 | 15 | it should "generate hex output for single instruction passed as argument" in { 16 | val res = Main.run(assembly = "addi x0, x1, 10") 17 | res should include("00A08013") 18 | } 19 | 20 | it should "generate hex output for multiple instruction passed as argument" in { 21 | val res = Main.run(assembly = "addi x0, x1, 10\njal x0, 128") 22 | res should include("00A08013\n0800006F") 23 | } 24 | 25 | it should "generate hex output for single instruction with file output" in { 26 | val testfile = os.pwd / "testfile.hex" 27 | val res = Main.run(assembly = "addi x0, x1, 10", fileOut = "testfile.hex") 28 | res should include("testfile.hex") 29 | val filecontents = os.read(testfile) 30 | filecontents should include("00A08013") 31 | var _ = os.remove(testfile) 32 | } 33 | 34 | it should "generate hex output from file input" in { 35 | val testfile = os.pwd / "testfile.asm" 36 | os.write.over(testfile, "addi x0, x1, 10") 37 | val res = Main.run(fileIn = testfile.toString()) 38 | res should include("00A08013") 39 | var _ = os.remove(testfile) 40 | } 41 | 42 | it should "generate hex file output from file input" in { 43 | val testfilein = os.pwd / "testfile.asm" 44 | val testfileout = os.pwd / "testfile.hex" 45 | os.write.over(testfilein, "addi x0, x1, 10") 46 | val res = Main.run(fileIn = testfilein.toString(), fileOut = "testfile.hex") 47 | res should include("testfile.hex") 48 | val filecontents = os.read(testfileout) 49 | filecontents should include("00A08013") 50 | var _ = os.remove(testfilein) 51 | var _ = os.remove(testfileout) 52 | } 53 | -------------------------------------------------------------------------------- /rvasmcli/src/Main.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp 2 | package rvasmcli 3 | 4 | import com.carlosedp.riscvassembler.RISCVAssembler 5 | import mainargs.{ParserForMethods, arg, main} 6 | 7 | object Main: 8 | @main( 9 | name = "main", 10 | doc = "This tool parses input strings or files in RISC-V assembly language generating hexadecimal machine code.", 11 | ) 12 | def run( 13 | @arg( 14 | name = "assembly", 15 | short = 'a', 16 | doc = "Assembly instruction string in quotes(can be multiple instructions separated by `\\n`", 17 | ) 18 | assembly: String = "", 19 | @arg(name = "file-in", short = 'f', doc = "Assembly file input") 20 | fileIn: String = "", 21 | @arg( 22 | name = "file-out", 23 | short = 'o', 24 | doc = "If defined, output will be redirected to this file (overwrite if exists)", 25 | ) 26 | fileOut: String = "", 27 | ): String = 28 | var output = "" 29 | var hex = "" 30 | if !assembly.isEmpty() then 31 | assembly.split("\\\\n").foreach { l => 32 | hex += RISCVAssembler.fromString(l.trim) 33 | } 34 | if fileOut.isEmpty() then 35 | output += "Generated Output: \n" 36 | output += hex 37 | else 38 | os.write.over(os.pwd / fileOut, hex) 39 | output = s"Generated $fileOut" 40 | else if !fileIn.isEmpty() then 41 | hex = RISCVAssembler.fromFile(fileIn) 42 | if fileOut.isEmpty() then 43 | output += "Generated Output: \n" 44 | output += hex 45 | else 46 | os.write.over(os.pwd / fileOut, hex) 47 | output = s"Generated $fileOut" 48 | else 49 | output = s""" 50 | |RISCVAssembler version ${RISCVAssembler.AppInfo.appVersion} 51 | |Revision ${RISCVAssembler.AppInfo.revision} 52 | |Commit: ${RISCVAssembler.AppInfo.buildCommit} 53 | |Commit Date: ${RISCVAssembler.AppInfo.commitDate} 54 | |Build Date: ${RISCVAssembler.AppInfo.buildDate} 55 | |Run tool with --help for options 56 | |""".stripMargin 57 | output 58 | 59 | def main(args: Array[String]): Unit = 60 | println("RISC-V Assembler for Scala") 61 | val out = ParserForMethods(this).runOrExit(args.toIndexedSeq) 62 | println(out) 63 | -------------------------------------------------------------------------------- /riscvassembler/src/ObjectUtils.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | object ObjectUtils { 4 | implicit class StringWithPad(s: String) { 5 | 6 | /** 7 | * Left-pads a string to a specified length with a specified character. 8 | * @param length 9 | * The length to pad to. 10 | * @param padChar 11 | * The character to pad with. 12 | */ 13 | def padStr(length: Int, padChar: Char): String = 14 | s.reverse.padTo(length, padChar).reverse 15 | 16 | /** 17 | * Left-pads a string to a certain length with zero (mostly used for binary 18 | * strings). 19 | * @param length 20 | * The length to pad to. 21 | */ 22 | def padZero(length: Int): String = 23 | s.padStr(length, '0') 24 | } 25 | 26 | /** 27 | * Number manipulation functions 28 | */ 29 | implicit class NumericManipulation[T: Numeric]( 30 | x: T 31 | )( 32 | implicit n: Numeric[T] 33 | ) { 34 | 35 | /** 36 | * Truncates a number to 32-bit and returns a Long. 37 | * @return 38 | * The 32-bit long. 39 | */ 40 | def to32Bit: Long = 41 | n.toLong(x) & 0xffffffffL 42 | } 43 | 44 | /** 45 | * Convert a string in a specified base to a Long 46 | * @return 47 | * The Long converted from the string. 48 | */ 49 | implicit class StringToLong(digits: String) { 50 | 51 | /** 52 | * Convert a string in a specified base to a Long 53 | * @param b 54 | * The base of the string. 55 | * @return 56 | * The Long converted from the string. 57 | */ 58 | private def base(b: Int): Long = 59 | BigInt(digits, b).toLong 60 | 61 | /** 62 | * Convert a string to a base 2 (binary) Long 63 | * @param b 64 | * The base of the string. 65 | * @return 66 | * The Long converted from the string. 67 | */ 68 | def b = base(2) 69 | 70 | /** 71 | * Convert a string to a base 8 (octal) Long 72 | * @param b 73 | * The base of the string. 74 | * @return 75 | * The Long converted from the string. 76 | */ 77 | def o = base(8) 78 | 79 | /** 80 | * Convert a string to a base 16 (hex) Long 81 | * @param b 82 | * The base of the string. 83 | * @return 84 | * The Long converted from the string. 85 | */ 86 | def h = base(16) 87 | } 88 | } 89 | -------------------------------------------------------------------------------- /riscvassembler/src/RISCVAssembler.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import scala.io.Source 4 | 5 | import com.carlosedp.riscvassembler.ObjectUtils._ 6 | 7 | object RISCVAssembler { 8 | 9 | /** 10 | * AppInfo contains version and build information for the library 11 | * 12 | * Fields: 13 | * - `appName`: artifactName 14 | * - `appVersion`: Version derived from git tag or git `tag+1-SNAPSHOT` 15 | * - `revision`: Generated revision based on tag, commit and number of 16 | * commits after last tag 17 | * - `buildCommit`: Commit ID 18 | * - `commitDate`: Last commit date 19 | * - `buildDate`: Build date 20 | */ 21 | val AppInfo = BuildInfo 22 | 23 | /** 24 | * Generate an hex string output fom the assembly source file 25 | * 26 | * Usage: 27 | * 28 | * {{{ 29 | * val outputHex = RISCVAssembler.fromFile("input.asm") 30 | * }}} 31 | * 32 | * @param fileName 33 | * the assembly source file 34 | * @return 35 | * the output hex string 36 | */ 37 | def fromFile(filename: String): String = 38 | fromString(Source.fromFile(filename).getLines().mkString("\n")) 39 | 40 | /** 41 | * Generate an hex string output fom the assembly string 42 | * 43 | * Usage: 44 | * 45 | * {{{ 46 | * val input = 47 | * """ 48 | * addi x1 , x0, 1000 49 | * addi x2 , x1, 2000 50 | * addi x3 , x2, -1000 51 | * addi x4 , x3, -2000 52 | * addi x5 , x4, 1000 53 | * """.stripMargin 54 | * val outputHex = RISCVAssembler.fromString(input) 55 | * }}} 56 | * 57 | * @param input 58 | * input assembly string to assemble (multiline string) 59 | * @return 60 | * the assembled hex string 61 | */ 62 | def fromString(input: String): String = { 63 | val (instructions, addresses, labels) = LineParser(input) 64 | (instructions zip addresses).map { case (i: String, a: String) => { binOutput(i, a, labels) } } 65 | .map(hexOutput(_)) 66 | .mkString("\n") + "\n" 67 | } 68 | 69 | /** 70 | * Generate the binary output for the input instruction 71 | * @param input 72 | * the input instruction (eg. "add x1, x2, x3") 73 | * @return 74 | * the binary output in string 75 | */ 76 | def binOutput( 77 | instruction: String, 78 | address: String = "0", 79 | labelIndex: Map[String, String] = Map[String, String](), 80 | width: Int = 32, 81 | ): String = { 82 | val cleanInst = "\\/\\*.*\\*\\/".r.replaceAllIn(instruction, "").toLowerCase.trim 83 | 84 | InstructionParser(cleanInst, address, labelIndex) match { 85 | case Some((op, opdata)) => FillInstruction(op, opdata).takeRight(width) 86 | case _ => "0" * width 87 | } 88 | 89 | } 90 | 91 | /** 92 | * Generate the hex string of the instruction from binary 93 | * 94 | * @param input 95 | * the binary string of the instruction 96 | * @return 97 | * the hex string of the instruction in string 98 | */ 99 | def hexOutput(input: String): String = { 100 | val x = input.b 101 | f"0x$x%08X".toString.takeRight(8) 102 | } 103 | } 104 | -------------------------------------------------------------------------------- /.github/workflows/scala.yml: -------------------------------------------------------------------------------- 1 | name: Scala CI 2 | 3 | on: 4 | push: 5 | branches: [main] 6 | tags: ["*"] 7 | pull_request: 8 | branches: [main] 9 | 10 | concurrency: 11 | group: ${{ github.ref }} 12 | cancel-in-progress: true 13 | 14 | jobs: 15 | test: 16 | strategy: 17 | matrix: 18 | jvm: ["11", "17", "21"] 19 | runs-on: ubuntu-latest 20 | 21 | steps: 22 | - name: Checkout 23 | uses: actions/checkout@v6 24 | with: 25 | fetch-depth: 0 26 | 27 | - name: Setup Java 28 | uses: actions/setup-java@v5 29 | with: 30 | distribution: "temurin" 31 | java-version: ${{ matrix.jvm }} 32 | 33 | - name: Setup Nodejs 34 | uses: actions/setup-node@v6 35 | with: 36 | node-version: 19 37 | 38 | - name: Install dependencies 39 | run: npm install 40 | 41 | - name: Cache packages 42 | uses: coursier/cache-action@v7 43 | id: coursier-cache 44 | 45 | - name: Check Formatting 46 | run: ./mill Alias/run checkfmt 47 | if: startsWith(matrix.jvm, '17') 48 | 49 | - name: Run tests for all versions 50 | run: ./mill Alias/run testall 51 | 52 | code-coverage: 53 | needs: [test] 54 | runs-on: ubuntu-latest 55 | 56 | steps: 57 | - name: Checkout 58 | uses: actions/checkout@v6 59 | with: 60 | fetch-depth: 0 61 | 62 | - name: Setup Java 63 | uses: actions/setup-java@v5 64 | with: 65 | distribution: "temurin" 66 | java-version: 17 67 | 68 | - name: Cache packages 69 | uses: coursier/cache-action@v7 70 | id: coursier-cache 71 | 72 | - name: Run code coverage 73 | run: ./mill Alias/run coverage 74 | 75 | - name: Upload coverage to Codecov 76 | uses: codecov/codecov-action@v5 77 | 78 | publish-snapshot: 79 | needs: [test] 80 | runs-on: ubuntu-latest 81 | if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/main' }} 82 | 83 | steps: 84 | - name: Checkout 85 | uses: actions/checkout@v6 86 | with: 87 | fetch-depth: 0 88 | 89 | - name: Setup Java 90 | uses: actions/setup-java@v5 91 | with: 92 | distribution: "temurin" 93 | java-version: "17" 94 | 95 | - name: Cache packages 96 | uses: coursier/cache-action@v7 97 | id: coursier-cache 98 | 99 | - name: Publish 100 | run: ./mill Alias/run pub 101 | env: 102 | MILL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 103 | MILL_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 104 | MILL_PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 105 | MILL_PGP_SECRET_BASE64: ${{ secrets.PGP_SECRET }} 106 | 107 | publish-release: 108 | needs: [test] 109 | runs-on: ubuntu-latest 110 | permissions: 111 | contents: write 112 | if: contains(github.ref, 'refs/tags/') 113 | steps: 114 | - name: Checkout 115 | uses: actions/checkout@v6 116 | with: 117 | fetch-depth: 0 118 | 119 | - name: Setup Java 120 | uses: actions/setup-java@v5 121 | with: 122 | distribution: "temurin" 123 | java-version: "17" 124 | 125 | - name: Cache packages 126 | uses: coursier/cache-action@v7 127 | id: coursier-cache 128 | 129 | - name: Publish 130 | run: ./mill Alias/run pub 131 | env: 132 | MILL_SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 133 | MILL_SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 134 | MILL_PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 135 | MILL_PGP_SECRET_BASE64: ${{ secrets.PGP_SECRET }} 136 | 137 | - name: Publish release from tag 138 | uses: ghalactic/github-release-from-tag@v6 139 | with: 140 | generateReleaseNotes: "true" 141 | prerelease: "false" 142 | draft: "false" 143 | -------------------------------------------------------------------------------- /riscvassembler/test/src/internal/InternalMethodsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import org.scalatest.flatspec._ 4 | import org.scalatest.matchers.should._ 5 | 6 | class RISCVAssemblerInternalSpec extends AnyFlatSpec with Matchers { 7 | behavior of "InstructionFiller" 8 | 9 | it should "fill R-type instruction" in { 10 | val i = Instruction( 11 | name = "ADD", 12 | funct7 = "0000000", 13 | funct3 = "000", 14 | opcode = "0110011", 15 | instType = InstType.R, 16 | ) 17 | val opdata = Map("rd" -> 1.toLong, "rs1" -> 2.toLong, "rs2" -> 3.toLong) 18 | val output = FillInstruction(i, opdata) 19 | output should be("00000000001100010000000010110011") 20 | } 21 | 22 | it should "fill I-type instruction" in { 23 | val i = Instruction( 24 | name = "ADDI", 25 | funct3 = "000", 26 | opcode = "0010011", 27 | instType = InstType.I, 28 | ) 29 | val opdata = Map("rd" -> 1.toLong, "rs1" -> 2.toLong, "imm" -> 4095.toLong) 30 | val output = FillInstruction(i, opdata) 31 | output should be("11111111111100010000000010010011") 32 | } 33 | 34 | it should "fill B-type instruction" in { 35 | val i = Instruction( 36 | name = "BEQ", 37 | funct3 = "000", 38 | opcode = "1100011", 39 | instType = InstType.B, 40 | ) 41 | val opdata = Map("rs1" -> 1L, "rs2" -> 2L, "imm" -> 4094L) 42 | val output = FillInstruction(i, opdata) 43 | output should be("01111110001000001000111111100011") 44 | } 45 | 46 | it should "fill S-type instruction" in { 47 | val i = Instruction( 48 | name = "SB", 49 | funct3 = "000", 50 | opcode = "0100011", 51 | instType = InstType.S, 52 | ) 53 | val opdata = Map("rs1" -> 2L, "rs2" -> 3L, "imm" -> 1024L) 54 | val output = FillInstruction(i, opdata) 55 | output should be("01000000001100010000000000100011") 56 | } 57 | 58 | it should "fill U-type instruction" in { 59 | val i = Instruction( 60 | name = "LUI", 61 | opcode = "0110111", 62 | instType = InstType.U, 63 | ) 64 | val opdata = Map("rd" -> 2L, "imm" -> 0xfffffL) 65 | val output = FillInstruction(i, opdata) 66 | output should be("11111111111111111111000100110111") 67 | } 68 | 69 | it should "fill J-type instruction" in { 70 | val i = Instruction( 71 | name = "JAL", 72 | opcode = "1101111", 73 | instType = InstType.J, 74 | ) 75 | val opdata = Map("rd" -> 1L, "imm" -> 2048L) 76 | val output = FillInstruction(i, opdata) 77 | output should be("00000000000100000000000011101111") 78 | } 79 | 80 | // ------------------------------------------------------------ 81 | 82 | behavior of "InstructionParser" 83 | 84 | it should "parse R-type instruction" in { 85 | val (inst, instData) = InstructionParser("add x1, x2, x3").get 86 | val d = Instruction( 87 | name = "ADD", 88 | funct7 = "0000000", 89 | funct3 = "000", 90 | opcode = "0110011", 91 | instType = InstType.R, 92 | ) 93 | inst should be(d) 94 | instData should be(Map("rd" -> 1, "rs1" -> 2, "rs2" -> 3)) 95 | } 96 | 97 | it should "parse I-type instruction" in { 98 | val (inst, instData) = InstructionParser("addi x1, x2, 1024").get 99 | val d = Instruction( 100 | name = "ADDI", 101 | funct3 = "000", 102 | opcode = "0010011", 103 | instType = InstType.I, 104 | ) 105 | inst should be(d) 106 | instData should be(Map("rd" -> 1, "rs1" -> 2, "imm" -> 1024)) 107 | } 108 | 109 | it should "parse I-type instruction with imm in hex" in { 110 | val (inst, instData) = InstructionParser("addi x1, x2, 0x400").get 111 | val d = Instruction( 112 | name = "ADDI", 113 | funct3 = "000", 114 | opcode = "0010011", 115 | instType = InstType.I, 116 | ) 117 | inst should be(d) 118 | instData should be(Map("rd" -> 1, "rs1" -> 2, "imm" -> 1024)) 119 | } 120 | 121 | it should "parse I-type instruction with offset in hex" in { 122 | val (inst, instData) = InstructionParser("lb x1, 0x400(x2)").get 123 | val d = Instruction( 124 | name = "LB", 125 | funct3 = "000", 126 | opcode = "0000011", 127 | hasOffset = true, 128 | instType = InstType.I, 129 | ) 130 | inst should be(d) 131 | instData should be(Map("rd" -> 1, "rs1" -> 2, "imm" -> 1024)) 132 | } 133 | 134 | it should "parse S-type instruction" in { 135 | val (inst, instData) = InstructionParser("sb x3, 1024(x2)").get 136 | val d = Instruction( 137 | name = "SB", 138 | funct3 = "000", 139 | opcode = "0100011", 140 | instType = InstType.S, 141 | ) 142 | inst should be(d) 143 | instData should be(Map("rs1" -> 2, "rs2" -> 3, "imm" -> 1024)) 144 | } 145 | 146 | it should "parse S-type instruction with offset in hex" in { 147 | val (inst, instData) = InstructionParser("sb x3, 0x400(x2)").get 148 | val d = Instruction( 149 | name = "SB", 150 | funct3 = "000", 151 | opcode = "0100011", 152 | instType = InstType.S, 153 | ) 154 | inst should be(d) 155 | instData should be(Map("rs1" -> 2, "rs2" -> 3, "imm" -> 1024)) 156 | } 157 | 158 | it should "parse B-type instruction" in { 159 | val (inst, instData) = InstructionParser("beq x3, x0, +16").get 160 | val d = Instruction( 161 | name = "BEQ", 162 | funct3 = "000", 163 | opcode = "1100011", 164 | instType = InstType.B, 165 | ) 166 | inst should be(d) 167 | instData should be(Map("rs1" -> 3, "rs2" -> 0, "imm" -> 16)) 168 | } 169 | 170 | it should "parse B-type instruction with offset in hex" in { 171 | val (inst, instData) = InstructionParser("beq x3, x0, 0x10").get 172 | val d = Instruction( 173 | name = "BEQ", 174 | funct3 = "000", 175 | opcode = "1100011", 176 | instType = InstType.B, 177 | ) 178 | inst should be(d) 179 | instData should be(Map("rs1" -> 3, "rs2" -> 0, "imm" -> 16)) 180 | } 181 | 182 | it should "parse U-type instruction with hex input" in { 183 | val (inst, instData) = InstructionParser("lui x2, 0xc0000000").get 184 | val d = Instruction( 185 | name = "LUI", 186 | opcode = "0110111", 187 | instType = InstType.U, 188 | ) 189 | inst should be(d) 190 | println(instData) 191 | instData should be(Map("rd" -> 2L, "imm" -> 0xc0000000L)) 192 | } 193 | 194 | it should "parse U-type instruction with dec input" in { 195 | val (inst, instData) = InstructionParser("lui x2, 32").get 196 | val d = Instruction( 197 | name = "LUI", 198 | opcode = "0110111", 199 | instType = InstType.U, 200 | ) 201 | inst should be(d) 202 | instData should be(Map("rd" -> 2, "imm" -> 32)) 203 | } 204 | 205 | it should "parse J-type instruction" in { 206 | val (inst, instData) = InstructionParser("jal x0, -16").get 207 | val d = Instruction( 208 | name = "JAL", 209 | opcode = "1101111", 210 | instType = InstType.J, 211 | ) 212 | inst should be(d) 213 | instData should be(Map("rd" -> 0, "imm" -> -16)) 214 | } 215 | 216 | // ------------------------------------------------------------ 217 | 218 | behavior of "RegMap" 219 | it should "map registers using name" in { 220 | for (i <- 0 to 31) { 221 | val output = RegMap("x" + i.toString) 222 | output should be(i) 223 | } 224 | } 225 | 226 | it should "map registers using ABI name" in { 227 | val output = RegMap("a0") 228 | output should be(10) 229 | } 230 | 231 | it should "map registers using secondary ABI name" in { 232 | val output = RegMap("fp") 233 | output should be(8) 234 | } 235 | } 236 | -------------------------------------------------------------------------------- /riscvassembler/src/internal/Instructions.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | object InstType extends Enumeration { 4 | type Type = Value 5 | val I, R, B, S, U, J = Value 6 | } 7 | 8 | case class Instruction( 9 | name: String, 10 | realName: String = "", 11 | instType: InstType.Type, 12 | funct3: String = "", 13 | funct7: String = "", 14 | opcode: String, 15 | hasOffset: Boolean = false, 16 | isCsr: Boolean = false, 17 | hasImm: Boolean = false, 18 | fixed: String = "", 19 | ) 20 | 21 | protected object Instructions { 22 | def apply(instruction: String): Option[Instruction] = 23 | instructions.find(_.name == instruction.toUpperCase) 24 | 25 | // scalafmt: { maxColumn = 130} 26 | private val instructions = List( 27 | Instruction(name = "LUI", instType = InstType.U, opcode = "0110111"), 28 | Instruction(name = "AUIPC", instType = InstType.U, opcode = "0010111", funct3 = "001"), 29 | Instruction(name = "JAL", instType = InstType.J, opcode = "1101111"), 30 | Instruction(name = "JALR", instType = InstType.I, opcode = "1100111", funct3 = "000"), 31 | Instruction(name = "BEQ", instType = InstType.B, opcode = "1100011", funct3 = "000"), 32 | Instruction(name = "BNE", instType = InstType.B, opcode = "1100011", funct3 = "001"), 33 | Instruction(name = "BLT", instType = InstType.B, opcode = "1100011", funct3 = "100"), 34 | Instruction(name = "BGE", instType = InstType.B, opcode = "1100011", funct3 = "101"), 35 | Instruction(name = "BLTU", instType = InstType.B, opcode = "1100011", funct3 = "110"), 36 | Instruction(name = "BGEU", instType = InstType.B, opcode = "1100011", funct3 = "111"), 37 | Instruction(name = "LB", instType = InstType.I, opcode = "0000011", funct3 = "000", hasOffset = true), 38 | Instruction(name = "LH", instType = InstType.I, opcode = "0000011", funct3 = "001", hasOffset = true), 39 | Instruction(name = "LW", instType = InstType.I, opcode = "0000011", funct3 = "010", hasOffset = true), 40 | Instruction(name = "LBU", instType = InstType.I, opcode = "0000011", funct3 = "100", hasOffset = true), 41 | Instruction(name = "LHU", instType = InstType.I, opcode = "0000011", funct3 = "101", hasOffset = true), 42 | Instruction(name = "SB", instType = InstType.S, opcode = "0100011", funct3 = "000"), 43 | Instruction(name = "SH", instType = InstType.S, opcode = "0100011", funct3 = "001"), 44 | Instruction(name = "SW", instType = InstType.S, opcode = "0100011", funct3 = "010"), 45 | Instruction(name = "ADDI", instType = InstType.I, opcode = "0010011", funct3 = "000"), 46 | Instruction(name = "SLTI", instType = InstType.I, opcode = "0010011", funct3 = "010"), 47 | Instruction(name = "SLTIU", instType = InstType.I, opcode = "0010011", funct3 = "011"), 48 | Instruction(name = "XORI", instType = InstType.I, opcode = "0010011", funct3 = "100"), 49 | Instruction(name = "ORI", instType = InstType.I, opcode = "0010011", funct3 = "110"), 50 | Instruction(name = "ANDI", instType = InstType.I, opcode = "0010011", funct3 = "111"), 51 | Instruction(name = "SLLI", instType = InstType.I, opcode = "0010011", funct3 = "001", fixed = "0000000"), 52 | Instruction(name = "SRLI", instType = InstType.I, opcode = "0010011", funct3 = "101", fixed = "0000000"), 53 | Instruction(name = "SRAI", instType = InstType.I, opcode = "0010011", funct3 = "101", fixed = "0100000"), 54 | Instruction(name = "ADD", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "000"), 55 | Instruction(name = "SUB", instType = InstType.R, opcode = "0110011", funct7 = "0100000", funct3 = "000"), 56 | Instruction(name = "SLL", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "001"), 57 | Instruction(name = "SLT", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "010"), 58 | Instruction(name = "SLTU", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "011"), 59 | Instruction(name = "XOR", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "100"), 60 | Instruction(name = "SRL", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "101"), 61 | Instruction(name = "SRA", instType = InstType.R, opcode = "0110011", funct7 = "0100000", funct3 = "101"), 62 | Instruction(name = "OR", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "110"), 63 | Instruction(name = "AND", instType = InstType.R, opcode = "0110011", funct7 = "0000000", funct3 = "111"), 64 | Instruction(name = "FENCE", instType = InstType.I, opcode = "0001111", funct3 = "000"), 65 | Instruction(name = "FENCE.I", instType = InstType.I, opcode = "0001111", funct3 = "001", fixed = "000000000000"), 66 | Instruction(name = "ECALL", instType = InstType.I, opcode = "1110011", funct3 = "000", fixed = "000000000000"), 67 | Instruction(name = "EBREAK", instType = InstType.I, opcode = "1110011", funct3 = "000", fixed = "000000000001"), 68 | // Instructions below are still not implemented 69 | Instruction(name = "CSRRW", instType = InstType.I, opcode = "1110011", funct3 = "001", isCsr = true, hasImm = false), 70 | Instruction(name = "CSRRS", instType = InstType.I, opcode = "1110011", funct3 = "010", isCsr = true, hasImm = false), 71 | Instruction(name = "CSRRC", instType = InstType.I, opcode = "1110011", funct3 = "011", isCsr = true, hasImm = false), 72 | Instruction(name = "CSRRWI", instType = InstType.I, opcode = "1110011", funct3 = "101", isCsr = true, hasImm = true), 73 | Instruction(name = "CSRRSI", instType = InstType.I, opcode = "1110011", funct3 = "110", isCsr = true, hasImm = true), 74 | Instruction(name = "CSRRCI", instType = InstType.I, opcode = "1110011", funct3 = "111", isCsr = true, hasImm = true), 75 | ) 76 | } 77 | 78 | /** 79 | * This function transforms a pseudo instruction to it's real conterpart 80 | */ 81 | protected object PseudoInstructions { 82 | def apply(instructionData: Array[String]): Option[Array[String]] = 83 | instructionData(0).toUpperCase match { 84 | // Map received params to the corresponding RISC-V instruction 85 | case "NOP" => { Some(Array("addi", "x0", "x0", "0")) } 86 | case "MV" => { Some(Array("addi", instructionData(1), instructionData(2), "0")) } 87 | case "NOT" => { Some(Array("xori", instructionData(1), instructionData(2), "-1")) } 88 | case "NEG" => { Some(Array("sub", instructionData(1), "x0", instructionData(2))) } 89 | case "SEQZ" => { Some(Array("sltiu", instructionData(1), instructionData(2), "1")) } 90 | case "SNEZ" => { Some(Array("sltu", instructionData(1), "x0", instructionData(2))) } 91 | case "SLTZ" => { Some(Array("slt", instructionData(1), instructionData(2), "x0")) } 92 | case "SGTZ" => { Some(Array("slt", instructionData(1), "x0", instructionData(2))) } 93 | case "BEQZ" => { Some(Array("beq", instructionData(1), "x0", instructionData(2))) } 94 | case "BNEZ" => { Some(Array("bne", instructionData(1), "x0", instructionData(2))) } 95 | case "BLEZ" => { Some(Array("bge", "x0", instructionData(1), instructionData(2))) } 96 | case "BGEZ" => { Some(Array("bge", instructionData(1), "x0", instructionData(2))) } 97 | case "BLTZ" => { Some(Array("blt", instructionData(1), "x0", instructionData(2))) } 98 | case "BGTZ" => { Some(Array("blt", "x0", instructionData(1), instructionData(2))) } 99 | case "BGT" => { Some(Array("blt", instructionData(2), instructionData(1), instructionData(3))) } 100 | case "BLE" => { Some(Array("bge", instructionData(2), instructionData(1), instructionData(3))) } 101 | case "BGTU" => { Some(Array("bltu", instructionData(2), instructionData(1), instructionData(3))) } 102 | case "BLEU" => { Some(Array("bgeu", instructionData(2), instructionData(1), instructionData(3))) } 103 | case "J" => { Some(Array("jal", "x0", instructionData(1))) } 104 | case "JR" => { Some(Array("jalr", "x0", instructionData(1), "0")) } 105 | case "RET" => { Some(Array("jalr", "x0", "x1", "0")) } 106 | case _ => None 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /riscvassembler/test/src/RISCVAssemblerSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import org.scalatest.flatspec._ 4 | import org.scalatest.matchers.should._ 5 | import org.scalatest.{BeforeAndAfterAll, BeforeAndAfterEach} 6 | 7 | class RISCVAssemblerSpec extends AnyFlatSpec with BeforeAndAfterEach with BeforeAndAfterAll with Matchers { 8 | 9 | behavior of "RISCVAssembler" 10 | 11 | it should "generate binary output for I-type instructions" in { 12 | val input = "addi x1, x2, 10" 13 | val output = RISCVAssembler.binOutput(input) 14 | 15 | val correct = "00000000101000010000000010010011" 16 | output should be(correct) 17 | } 18 | 19 | it should "generate binary output for I-type instructions with max IMM" in { 20 | val input = "addi x1, x2, -1" 21 | val output = RISCVAssembler.binOutput(input) 22 | 23 | val correct = "11111111111100010000000010010011" 24 | output should be(correct) 25 | } 26 | 27 | it should "convert binary instruction to hex" in { 28 | val output = RISCVAssembler.hexOutput("11111111111111111111000001101111") 29 | output should be("FFFFF06F") 30 | } 31 | 32 | it should "generate hex output for single I-type instruction" in { 33 | val input = 34 | """ 35 | addi x0, x0, 0 36 | """.stripMargin 37 | val output = RISCVAssembler.fromString(input).trim 38 | 39 | val correct = 40 | """ 41 | |00000013 42 | | 43 | """.stripMargin.trim 44 | 45 | output should be(correct) 46 | } 47 | 48 | it should "generate hex output for multiple I-type instructions" in { 49 | val input = 50 | """addi x0, x0, 0 51 | addi x1, x1, 1 52 | addi x2, x2, 2 53 | """.stripMargin 54 | val output = RISCVAssembler.fromString(input).trim 55 | 56 | val correct = 57 | """ 58 | |00000013 59 | |00108093 60 | |00210113 61 | """.stripMargin.trim 62 | 63 | output should be(correct) 64 | } 65 | 66 | it should "generate hex output for multiple instructions" in { 67 | val input = 68 | """ 69 | addi x0, x0, 0 70 | addi x1, x1, 1 71 | addi x2, x2, 2 72 | """.stripMargin 73 | val output = RISCVAssembler.fromString(input).trim 74 | 75 | val correct = 76 | """ 77 | |00000013 78 | |00108093 79 | |00210113 80 | """.stripMargin.trim 81 | output should be(correct) 82 | } 83 | 84 | it should "generate blanks for invalid instructions" in { 85 | val input = 86 | """ 87 | addi x0, x0, 0 88 | addi x1, x1 89 | addi x2, x2, 2 90 | """.stripMargin 91 | val output = RISCVAssembler.fromString(input).trim 92 | 93 | val correct = 94 | """ 95 | |00000013 96 | |00000000 97 | |00210113 98 | """.stripMargin.trim 99 | output should be(correct) 100 | } 101 | 102 | it should "generate blanks for bogus data" in { 103 | val input = 104 | """ 105 | blabla 106 | addi x1, x1 107 | wrong info 108 | """.stripMargin 109 | val output = RISCVAssembler.fromString(input).trim 110 | val correct = 111 | """ 112 | |00000000 113 | |00000000 114 | |00000000 115 | """.stripMargin.trim 116 | output should be(correct) 117 | } 118 | 119 | it should "generate hex output for multiple instructions with /* */ comments" in { 120 | val input = 121 | """ 122 | addi x1 , x0, 1000 /* x1 = 1000 0x3E8 */ 123 | addi x2 , x1, 2000 /* x2 = 3000 0xBB8 */ 124 | addi x3 , x2, -1000 /* x3 = 2000 0x7D0 */ 125 | addi x4 , x3, -2000 /* x4 = 0 0x000 */ 126 | addi x5 , x4, 1000 /* x5 = 1000 0x3E8 */ 127 | """.stripMargin 128 | val output = RISCVAssembler.fromString(input).trim 129 | 130 | val correct = 131 | """ 132 | |3e800093 133 | |7d008113 134 | |c1810193 135 | |83018213 136 | |3e820293 137 | |""".stripMargin.toUpperCase.trim 138 | 139 | output should be(correct) 140 | } 141 | 142 | it should "generate hex output for multiple instructions with // comments" in { 143 | val input = 144 | """ 145 | addi x1, x0, 1000 146 | sw x3, 48(x1) // With single line comment 147 | addi x1, x0, 1000 148 | """.stripMargin 149 | val output = RISCVAssembler.fromString(input).trim 150 | 151 | val correct = 152 | """ 153 | |3e800093 154 | |0230A823 155 | |3e800093 156 | |""".stripMargin.toUpperCase.trim 157 | 158 | output should be(correct) 159 | } 160 | 161 | it should "generate hex output for pseudo-instructions" in { 162 | val input = 163 | """ 164 | addi x0, x0, 0 165 | nop 166 | beqz x0, +4 167 | """.stripMargin 168 | val output = RISCVAssembler.fromString(input).trim 169 | val correct = 170 | """ 171 | |00000013 172 | |00000013 173 | |00000263 174 | |""".stripMargin.toUpperCase.trim 175 | 176 | output should be(correct) 177 | } 178 | 179 | it should "generate hex output with directives" in { 180 | val prog = """ 181 | .global _boot 182 | .text 183 | 184 | _boot: /* x0 = 0 0x000 */ 185 | /* Test ADDI */ 186 | addi x1 , x0, 1000 /* x1 = 1000 0x3E8 */ 187 | addi x2 , x1, 2000 /* x2 = 3000 0xBB8 */ 188 | addi x3 , x2, -1000 /* x3 = 2000 0x7D0 */ 189 | addi x4 , x3, -2000 /* x4 = 0 0x000 */ 190 | addi x5 , x4, 1000 /* x5 = 1000 0x3E8 */ 191 | """.stripMargin 192 | val output = RISCVAssembler.fromString(prog).trim 193 | 194 | val correct = 195 | """ 196 | |3e800093 197 | |7d008113 198 | |c1810193 199 | |83018213 200 | |3e820293 201 | |""".stripMargin.toUpperCase.trim 202 | 203 | output should be(correct) 204 | } 205 | 206 | it should "generate hex output with labels" in { 207 | val prog = """ 208 | main: addi x1, x0, 1000 209 | wait: lw x3, 0(x1) 210 | jal x0, -4 211 | """.stripMargin 212 | val output = RISCVAssembler.fromString(prog).trim 213 | 214 | val correct = 215 | """ 216 | |3e800093 217 | |0000a183 218 | |ffdff06f 219 | |""".stripMargin.toUpperCase.trim 220 | 221 | output should be(correct) 222 | } 223 | 224 | it should "generate hex output using labels inline" in { 225 | val prog = """ 226 | main: addi x1, x0, 1000 227 | wait: lw x3, 0(x1) 228 | jal x0, wait 229 | """.stripMargin 230 | val output = RISCVAssembler.fromString(prog).trim 231 | 232 | val correct = 233 | """ 234 | |3e800093 235 | |0000a183 236 | |ffdff06f 237 | |""".stripMargin.toUpperCase.trim 238 | 239 | output should be(correct) 240 | } 241 | 242 | it should "generate hex output using labels on previous line" in { 243 | val prog = """ 244 | main: addi x1, x0, 1000 245 | wait: 246 | lw x3, 0(x1) 247 | jal x0, wait 248 | """.stripMargin 249 | val output = RISCVAssembler.fromString(prog).trim 250 | 251 | val correct = 252 | """ 253 | |3e800093 254 | |0000a183 255 | |ffdff06f 256 | |""".stripMargin.toUpperCase.trim 257 | 258 | output should be(correct) 259 | } 260 | 261 | it should "generate hex output using labels in same line" in { 262 | val prog = """ 263 | main: lui x1, 0xfffff 264 | addi x2, x0, 1 265 | wait: lw x3, 0(x1) 266 | bne x2, x3, wait 267 | cont: sw x0, 0(x1) 268 | wait2: lw x3, 0(x1) 269 | bne x2, x3, wait2 270 | cont2: addi x3, x0, 2 271 | """.stripMargin 272 | val output = RISCVAssembler.fromString(prog).trim 273 | 274 | val correct = 275 | """ 276 | |fffff0b7 277 | |00100113 278 | |0000a183 279 | |fe311ee3 280 | |0000a023 281 | |0000a183 282 | |fe311ee3 283 | |00200193 284 | |""".stripMargin.toUpperCase.trim 285 | 286 | output should be(correct) 287 | } 288 | 289 | it should "generate hex output with jumps forward and backward" in { 290 | val prog = """ 291 | tgt1: addi x1, x2, 10 292 | nop 293 | jal x4, tgt1 294 | jal x4, tgt2 295 | nop 296 | tgt2: addi x1, x2, 10 297 | """.stripMargin 298 | val output = RISCVAssembler.fromString(prog).trim 299 | 300 | val correct = 301 | """ 302 | |00a10093 303 | |00000013 304 | |ff9ff26f 305 | |0080026f 306 | |00000013 307 | |00a10093 308 | |""".stripMargin.toUpperCase.trim 309 | 310 | output should be(correct) 311 | } 312 | } 313 | -------------------------------------------------------------------------------- /Readme.md: -------------------------------------------------------------------------------- 1 | # RISCVAssembler 2 | 3 | A RISC-V assembler library for Scala/Chisel HDL projects. For details, check the [scaladoc](https://www.javadoc.io/doc/com.carlosedp/riscvassembler_2.13/latest/com/carlosedp/riscvassembler/index.html). 4 | 5 | [![riscvassembler Scala version support](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler/latest-by-scala-version.svg?platform=jvm)](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler) 6 | [![riscvassembler Scala version support](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler/latest-by-scala-version.svg?platform=native0.4)](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler) 7 | [![riscvassembler Scala version support](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler/latest-by-scala-version.svg?platform=sjs1)](https://index.scala-lang.org/carlosedp/riscvassembler/riscvassembler) 8 | [![Sonatype Snapshots](https://img.shields.io/nexus/s/com.carlosedp/riscvassembler_2.13?server=https%3A%2F%2Fs01.oss.sonatype.org)](https://s01.oss.sonatype.org/content/repositories/snapshots/com/carlosedp/) 9 | 10 | 11 | [![Scala CI](https://github.com/carlosedp/riscvassembler/actions/workflows/scala.yml/badge.svg)](https://github.com/carlosedp/riscvassembler/actions/workflows/scala.yml) 12 | [![codecov](https://codecov.io/gh/carlosedp/riscvassembler/branch/main/graph/badge.svg?token=YNEKF3OO04)](https://codecov.io/gh/carlosedp/riscvassembler) 13 | [![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-green.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org) 14 | [![Mergify Status](https://img.shields.io/endpoint.svg?url=https://api.mergify.com/v1/badges/carlosedp/riscvassembler&style=flat)](https://mergify.com) 15 | [![Scaladoc](https://www.javadoc.io/badge/com.carlosedp/riscvassembler_2.13.svg?color=blue&label=Scaladoc)](https://javadoc.io/doc/com.carlosedp/riscvassembler_2.13/latest) 16 | 17 | Leveraging the library, I have a Web application built in Scala.js that runs in the browser to assemble web instructions to hex. Check it out at https://carlosedp.github.io/rvasmweb/. The source code for the site is [here](https://github.com/carlosedp/rvasmweb/). 18 | 19 | ## Using in your project 20 | 21 | ### SBT 22 | 23 | When using SBT, add the following lines to your `build.sbt` file. 24 | 25 | ```scala 26 | // Import libraries 27 | libraryDependencies += "com.carlosedp" %% "riscvassembler" % "1.10.0" //ReleaseVerSBT 28 | ``` 29 | 30 | ### Mill 31 | 32 | If you use `mill` build tool, add the following dep to your `build.sc`: 33 | 34 | ```scala 35 | // Add to your ivyDeps 36 | def ivyDeps = Agg( 37 | ivy"com.carlosedp::riscvassembler:1.10.0" //ReleaseVerMill 38 | ... 39 | ) 40 | ``` 41 | 42 | If using the library for Scala.js or Scala Native projects, remember to use "::" (double colon) on Mill between the library name and version and on SBT use "%%%" (triple percent) between organization and the library name. 43 | 44 | ## Library Description and Sample Code 45 | 46 | The library is pure Scala and provides methods to generate hexadecimal machine code (like memory files to be consumed by `readmemh` statements) from assembly input. It does not depend on Chisel or other libs and intent to work similarly to a simpler `gcc + ld + objcopy + hexdump` flow as used on this [`Makefile`](https://github.com/carlosedp/chiselv/gcc/test/Makefile) or web app. 47 | 48 | The library can be seen in use in [ChiselV](https://github.com/carlosedp/chiselv), my RV32I core written in Chisel. The core tests use the library to generate [test data](https://github.com/carlosedp/chiselv/blob/e014da49ace5d5dd917eac3e3bf8ca6bbeadc244/chiselv/test/src/CPUSingleCycleInstructionSpec.scala#L71). 49 | 50 | What the library **can and can not do**: 51 | 52 | - The library **can** generate hex(machine code) for most RV32 instructions; 53 | - It **can** accept either offsets or [labels](https://github.com/riscv-non-isa/riscv-asm-manual/blob/master/riscv-asm.md#labels) (in the same or previous line) for jump/branch instructions; 54 | - It **can** implement [some](./riscvassembler/src/internal/Instructions.scala#73) pseudo-instructions (more to come soon); 55 | - It **can** generate one machine code for each input asm instruction; 56 | - It **can not** decompose one pseudo-instruction to multiple instructions. Eg. `li x1, 0x80000000` to `addiw ra,zero,1` + `ra,ra,0x1f` as gcc does; 57 | - It **can not** validate your input asm code. Imm values might get truncated if not proper used; 58 | - It **can not** support [assembler relocation functions](https://github.com/riscv-non-isa/riscv-asm-manual/blob/main/src/asm-manual.adoc#assembler-relocation-functions); 59 | - The library ignores all [asm directives](https://github.com/riscv-non-isa/riscv-asm-manual/blob/main/src/asm-manual.adoc#pseudo-ops). 60 | 61 | The program can be a single line or multi-line statements(supports inline or full-line comments) and can be generated from a simple string, multi-line string or loaded from a file. 62 | 63 | ### Examples 64 | 65 | **Reading from file:** 66 | 67 | ```asm 68 | ; Sample file "input.asm": 69 | addi x0, x0, 0 70 | addi x1, x1, 1 71 | addi x2, x2, 2 72 | ``` 73 | 74 | ```scala 75 | // Using the lib 76 | val outputHex = RISCVAssembler.fromFile("input.asm") 77 | 78 | // outputHex will be: 79 | // 00000013 80 | // 00108093 81 | // 00210113 82 | ``` 83 | 84 | **From a multiline string:** 85 | 86 | ```scala 87 | // Sample input: 88 | val input = 89 | """addi x0, x0, 0 90 | addi x1, x1, 1 91 | addi x2, x2, 2 92 | """.stripMargin 93 | val output = RISCVAssembler.fromString(input) 94 | 95 | // outputHex will be: 96 | // 00000013 97 | // 00108093 98 | // 00210113 99 | ``` 100 | 101 | Which can be used as the input to tests in a RISC-V Core. 102 | 103 | ## Command line tool 104 | 105 | The library also contains a command line tool that generates the machine code from strings or input files. 106 | 107 | ```sh 108 | ❯ rvasmcli --help 109 | RISC-V Assembler for Scala 110 | main 111 | This tool parses input strings or files in RISC-V assembly language generating hexadecimal machine 112 | code. 113 | -a --assembly Assembly instruction string in quotes(can be multiple instructions separated 114 | by `\n` 115 | -f --file-in Assembly file input 116 | -o --file-out If defined, output will be redirected to this file (overwrite if exists) 117 | 118 | ❯ rvasmcli -a "addi x1, x2, 32\njal x0, 128" 119 | RISC-V Assembler for Scala 120 | Generated Output: 121 | 122 | 02010093 123 | 0800006F 124 | ``` 125 | 126 | To generate the tool binary yourself, use `./mill Alias/run bin` and the native executable will be generated and it's name printed on screen. 127 | 128 | Native binaries for major OS/Arch combinations will be published soon. 129 | 130 | ### Using Snapshot versions 131 | 132 | Snapshot versions are released on every commit to main branch and might be broken (check CI). If you want to use it, configure as follows. 133 | 134 | #### SBT 135 | 136 | Add the new Sonatype repository to your `build.sbt` resolvers and change the library import name: 137 | 138 | ```scala 139 | resolvers ++= Seq( 140 | Resolver.sonatypeRepo("snapshots"), 141 | Resolver.sonatypeRepo("releases"), 142 | "Sonatype New OSS Snapshots" at "https://s01.oss.sonatype.org/content/repositories/snapshots" 143 | ) 144 | 145 | // and change the dependency to latest SNAPSHOT as: 146 | libraryDependencies += "com.carlosedp" %% "riscvassembler" % "1.11-SNAPSHOT" //SnapshotVerSBT 147 | ``` 148 | 149 | Confirm the latest versions displayed on the badges at the top of this readme for both stable and snapshot (without the leading "v"). 150 | 151 | #### Mill 152 | 153 | If you use `mill` build tool, add the following dep to your `build.sc`: 154 | 155 | ```scala 156 | import coursier.MavenRepository 157 | 158 | ... 159 | 160 | // Inside your project `object`: 161 | // And add the snapshot resolver if using it 162 | def repositoriesTask = T.task { super.repositoriesTask() ++ Seq( 163 | MavenRepository("https://s01.oss.sonatype.org/content/repositories/snapshots") 164 | ) } 165 | 166 | def ivyDeps = Agg( 167 | ivy"com.carlosedp::riscvassembler:1.11-SNAPSHOT" //SnapshotVerMill 168 | ... 169 | ) 170 | ``` 171 | 172 | If using the library for Scala.js or Scala Native projects, remember to use "::" (double colon) on Mill between the library name and version and on SBT use "%%%" (triple percent) between organization and the library name. 173 | 174 | ### Development and Testing 175 | 176 | All build processes are integrated into mill `build.sc`. There are tasks for linting, code coverage, publishing and binary generation. 177 | 178 | To locally test and build the library for Scala.js, it's required to have [nodejs](nodejs.org/). After install, run `npm install` so dependencies are installed as well. 179 | 180 | To test and generate the Scala Native binaries, the [LLVM toolchain](https://scala-native.org/en/stable/user/setup.html#installing-clang-and-runtime-dependencies) is required. 181 | 182 | Publishing flow: 183 | 184 | 1. Commit and push latest changes to `main` (generates SNAPSHOT) 185 | 2. Git tag new version 186 | 3. Push tag to origin (generates a new release) 187 | 4. Check if readme was updated 188 | 189 | The library has been published to Maven Central thru Sonatype: 190 | 191 | - 192 | - 193 | -------------------------------------------------------------------------------- /riscvassembler/test/src/internal/InstructionsSpec.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import org.scalatest.flatspec._ 4 | import org.scalatest.matchers.should._ 5 | 6 | import scala.{Tuple2 => &} 7 | 8 | class InstructionsSpec extends AnyFlatSpec with Matchers { 9 | behavior of "Instructions" 10 | 11 | it should "fetch R-type instructions" in { 12 | val insts = List("ADD", "SUB", "SLL", "SRL", "SRA", "XOR", "OR", "AND", "SLT", "SLTU") 13 | val funct3s = List("000", "000", "001", "101", "101", "100", "110", "111", "010", "011") 14 | val funct7s = 15 | List("0000000", "0100000", "0000000", "0000000", "0100000", "0000000", "0000000", "0000000", "0000000", "0000000") 16 | for (i & f3 & f7 <- insts zip funct3s zip funct7s) { 17 | val inst = Instructions(i).get 18 | inst.name should be(i) 19 | inst.funct7 should be(f7) 20 | inst.funct3 should be(f3) 21 | inst.opcode should be("0110011") 22 | inst.instType should be(InstType.R) 23 | } 24 | } 25 | 26 | it should "fetch I-type instructions" in { 27 | val insts = List("ADDI", "XORI", "ORI", "ANDI", "SLTI", "SLTIU") 28 | val funct3s = List("000", "100", "110", "111", "010", "011") 29 | for (i & f3 <- insts zip funct3s) { 30 | val inst = Instructions(i).get 31 | inst.name should be(i) 32 | inst.funct3 should be(f3) 33 | inst.opcode should be("0010011") 34 | inst.instType should be(InstType.I) 35 | } 36 | } 37 | 38 | it should "fetch I-type CSR instructions" in { 39 | val insts = List("CSRRW", "CSRRS", "CSRRC", "CSRRWI", "CSRRSI", "CSRRCI") 40 | val funct3s = List("001", "010", "011", "101", "110", "111") 41 | for (i & f3 <- insts zip funct3s) { 42 | val inst = Instructions(i).get 43 | inst.name should be(i) 44 | inst.funct3 should be(f3) 45 | inst.opcode should be("1110011") 46 | inst.isCsr should be(true) 47 | inst.instType should be(InstType.I) 48 | } 49 | } 50 | 51 | it should "fetch I-type Load instructions" in { 52 | val insts = List("LB", "LH", "LBU", "LHU", "LW") 53 | val funct3s = List("000", "001", "100", "101", "010") 54 | for (i & f3 <- insts zip funct3s) { 55 | val inst = Instructions(i).get 56 | inst.name should be(i) 57 | inst.funct3 should be(f3) 58 | inst.opcode should be("0000011") 59 | inst.instType should be(InstType.I) 60 | } 61 | } 62 | 63 | it should "fetch I-type Shift instructions" in { 64 | val insts = List("SLLI", "SRLI", "SRAI") 65 | val funct3s = List("001", "101", "101") 66 | val fixs = List("0000000", "0000000", "0100000") 67 | for (i & f3 & f <- insts zip funct3s zip fixs) { 68 | val inst = Instructions(i).get 69 | inst.name should be(i) 70 | inst.funct3 should be(f3) 71 | inst.fixed should be(f) 72 | inst.opcode should be("0010011") 73 | inst.instType should be(InstType.I) 74 | } 75 | } 76 | 77 | it should "fetch JALR instruction" in { 78 | val inst = Instructions("JALR").get 79 | inst.name should be("JALR") 80 | inst.funct3 should be("000") 81 | inst.opcode should be("1100111") 82 | inst.instType should be(InstType.I) 83 | } 84 | 85 | it should "fetch JAL instruction" in { 86 | val inst = Instructions("JAL").get 87 | inst.name should be("JAL") 88 | inst.opcode should be("1101111") 89 | inst.instType should be(InstType.J) 90 | } 91 | 92 | it should "fetch LUI instruction" in { 93 | val inst = Instructions("LUI").get 94 | inst.name should be("LUI") 95 | inst.opcode should be("0110111") 96 | inst.instType should be(InstType.U) 97 | } 98 | 99 | it should "fetch AUIPC instruction" in { 100 | val inst = Instructions("AUIPC").get 101 | inst.name should be("AUIPC") 102 | inst.opcode should be("0010111") 103 | inst.instType should be(InstType.U) 104 | } 105 | 106 | it should "fetch B-type Branch instructions" in { 107 | val insts = List("BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU") 108 | val funct3s = List("000", "001", "100", "101", "110", "111") 109 | for ((i, f3) <- insts zip funct3s) { 110 | // for ((i, f3) <- insts.lazyZip(funct3s).toList) { 111 | val inst = Instructions(i).get 112 | inst.name should be(i) 113 | inst.funct3 should be(f3) 114 | inst.opcode should be("1100011") 115 | inst.instType should be(InstType.B) 116 | } 117 | } 118 | 119 | it should "fetch S-type Store instructions" in { 120 | val insts = List("SB", "SH", "SW") 121 | val funct3s = List("000", "001", "010") 122 | for ((i, f3) <- insts zip funct3s) { 123 | val inst = Instructions(i).get 124 | inst.name should be(i) 125 | inst.funct3 should be(f3) 126 | inst.opcode should be("0100011") 127 | inst.instType should be(InstType.S) 128 | } 129 | } 130 | 131 | it should "fetch a invalid instruction" in { 132 | val inst = Instructions("invalid") 133 | inst should be(None) 134 | } 135 | 136 | behavior of "Instructions" 137 | 138 | it should "assemble SRAI Instruction" in { 139 | val i = RISCVAssembler.binOutput("srai x0, x0, 0") 140 | i should be( 141 | "01000000000000000101000000010011" 142 | ) 143 | } 144 | 145 | it should "assemble SRAI Instruction with max shamt" in { 146 | val i = RISCVAssembler.binOutput("srai x0, x0, 63") 147 | i should be( 148 | "01000001111100000101000000010011" 149 | ) 150 | } 151 | 152 | it should "assemble SRAI Instruction with invalid shamt" in { 153 | val i = RISCVAssembler.binOutput("srai x0, x0, 64") 154 | i should be( 155 | "00000000000000000000000000000000" 156 | ) 157 | } 158 | 159 | it should "assemble FENCE Instruction" in { 160 | val i = RISCVAssembler.binOutput("fence iorw, iorw") 161 | i should be( 162 | "00001111111100000000000000001111" 163 | ) 164 | } 165 | 166 | it should "assemble FENCE Instruction with input read" in { 167 | val i = RISCVAssembler.binOutput("fence ir, ir") 168 | i should be( 169 | "00001010101000000000000000001111" 170 | ) 171 | } 172 | 173 | it should "assemble FENCE Instruction with no arguments" in { 174 | val i = RISCVAssembler.binOutput("fence") 175 | i should be( 176 | "00001111111100000000000000001111" 177 | ) 178 | } 179 | 180 | it should "assemble FENCE.i Instruction" in { 181 | val i = RISCVAssembler.binOutput("fence.i") 182 | i should be( 183 | "00000000000000000001000000001111" 184 | ) 185 | } 186 | 187 | it should "assemble ECALL Instruction" in { 188 | val i = RISCVAssembler.binOutput("ecall") 189 | i should be( 190 | "00000000000000000000000001110011" 191 | ) 192 | } 193 | 194 | it should "assemble EBREAK Instruction" in { 195 | val i = RISCVAssembler.binOutput("ebreak") 196 | i should be( 197 | "00000000000100000000000001110011" 198 | ) 199 | } 200 | 201 | behavior of "Pseudo-Instructions" 202 | 203 | it should "map NOP Pseudo Instruction" in { 204 | val i = PseudoInstructions(Array("nop")).get 205 | i should be( 206 | Array("addi", "x0", "x0", "0") 207 | ) 208 | } 209 | 210 | it should "map MV Pseudo Instruction" in { 211 | val i = PseudoInstructions(Array("mv", "x1", "x2")).get 212 | i should be( 213 | Array("addi", "x1", "x2", "0") 214 | ) 215 | } 216 | 217 | it should "map NOT Pseudo Instruction" in { 218 | val i = PseudoInstructions(Array("not", "x1", "x2")).get 219 | i should be( 220 | Array("xori", "x1", "x2", "-1") 221 | ) 222 | } 223 | it should "map NEG Pseudo Instruction" in { 224 | val i = PseudoInstructions(Array("neg", "x1", "x2")).get 225 | i should be( 226 | Array("sub", "x1", "x0", "x2") 227 | ) 228 | } 229 | it should "map SEQZ Pseudo Instruction" in { 230 | val i = PseudoInstructions(Array("seqz", "x1", "x2")).get 231 | i should be( 232 | Array("sltiu", "x1", "x2", "1") 233 | ) 234 | } 235 | it should "map SNEZ Pseudo Instruction" in { 236 | val i = PseudoInstructions(Array("snez", "x1", "x2")).get 237 | i should be( 238 | Array("sltu", "x1", "x0", "x2") 239 | ) 240 | } 241 | it should "map SLTZ Pseudo Instruction" in { 242 | val i = PseudoInstructions(Array("sltz", "x1", "x2")).get 243 | i should be( 244 | Array("slt", "x1", "x2", "x0") 245 | ) 246 | } 247 | it should "map SGTZ Pseudo Instruction" in { 248 | val i = PseudoInstructions(Array("sgtz", "x1", "x2")).get 249 | i should be( 250 | Array("slt", "x1", "x0", "x2") 251 | ) 252 | } 253 | it should "map BEQZ Pseudo Instruction" in { 254 | val i = PseudoInstructions(Array("beqz", "x1", "4")).get 255 | i should be( 256 | Array("beq", "x1", "x0", "4") 257 | ) 258 | } 259 | it should "map BNEZ Pseudo Instruction" in { 260 | val i = PseudoInstructions(Array("bnez", "x1", "4")).get 261 | i should be( 262 | Array("bne", "x1", "x0", "4") 263 | ) 264 | } 265 | it should "map BLEZ Pseudo Instruction" in { 266 | val i = PseudoInstructions(Array("blez", "x1", "4")).get 267 | i should be( 268 | Array("bge", "x0", "x1", "4") 269 | ) 270 | } 271 | it should "map BGEZ Pseudo Instruction" in { 272 | val i = PseudoInstructions(Array("bgez", "x1", "4")).get 273 | i should be( 274 | Array("bge", "x1", "x0", "4") 275 | ) 276 | } 277 | it should "map BLTZ Pseudo Instruction" in { 278 | val i = PseudoInstructions(Array("bltz", "x1", "4")).get 279 | i should be( 280 | Array("blt", "x1", "x0", "4") 281 | ) 282 | } 283 | it should "map BGTZ Pseudo Instruction" in { 284 | val i = PseudoInstructions(Array("bgtz", "x1", "4")).get 285 | i should be( 286 | Array("blt", "x0", "x1", "4") 287 | ) 288 | } 289 | it should "map BGT Pseudo Instruction" in { 290 | val i = PseudoInstructions(Array("bgt", "x1", "x2", "4")).get 291 | i should be( 292 | Array("blt", "x2", "x1", "4") 293 | ) 294 | } 295 | it should "map BLE Pseudo Instruction" in { 296 | val i = PseudoInstructions(Array("ble", "x1", "x2", "4")).get 297 | i should be( 298 | Array("bge", "x2", "x1", "4") 299 | ) 300 | } 301 | it should "map BGTU Pseudo Instruction" in { 302 | val i = PseudoInstructions(Array("bgtu", "x1", "x2", "4")).get 303 | i should be( 304 | Array("bltu", "x2", "x1", "4") 305 | ) 306 | } 307 | it should "map BLEU Pseudo Instruction" in { 308 | val i = PseudoInstructions(Array("bleu", "x1", "x2", "4")).get 309 | i should be( 310 | Array("bgeu", "x2", "x1", "4") 311 | ) 312 | } 313 | it should "map J Pseudo Instruction" in { 314 | val i = PseudoInstructions(Array("j", "4")).get 315 | i should be( 316 | Array("jal", "x0", "4") 317 | ) 318 | } 319 | it should "map JR Pseudo Instruction" in { 320 | val i = PseudoInstructions(Array("jr", "x2")).get 321 | i should be( 322 | Array("jalr", "x0", "x2", "0") 323 | ) 324 | } 325 | it should "map RET Pseudo Instruction" in { 326 | val i = PseudoInstructions(Array("ret")).get 327 | i should be( 328 | Array("jalr", "x0", "x1", "0") 329 | ) 330 | } 331 | it should "map an invalid Pseudo Instruction to None" in { 332 | val i = PseudoInstructions(Array("bogus")) 333 | i should be(None) 334 | } 335 | } 336 | -------------------------------------------------------------------------------- /mill: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | 3 | # This is a wrapper script, that automatically selects or downloads Mill from Maven Central or GitHub release pages. 4 | # 5 | # This script determines the Mill version to use by trying these sources 6 | # - env-variable `MILL_VERSION` 7 | # - local file `.mill-version` 8 | # - local file `.config/mill-version` 9 | # - `mill-version` from YAML fronmatter of current buildfile 10 | # - if accessible, find the latest stable version available on Maven Central (https://repo1.maven.org/maven2) 11 | # - env-variable `DEFAULT_MILL_VERSION` 12 | # 13 | # If a version has the suffix '-native' a native binary will be used. 14 | # If a version has the suffix '-jvm' an executable jar file will be used, requiring an already installed Java runtime. 15 | # If no such suffix is found, the script will pick a default based on version and platform. 16 | # 17 | # Once a version was determined, it tries to use either 18 | # - a system-installed mill, if found and it's version matches 19 | # - an already downloaded version under ~/.cache/mill/download 20 | # 21 | # If no working mill version was found on the system, 22 | # this script downloads a binary file from Maven Central or Github Pages (this is version dependent) 23 | # into a cache location (~/.cache/mill/download). 24 | # 25 | # Mill Project URL: https://github.com/com-lihaoyi/mill 26 | # Script Version: 1.0.0-M1-21-7b6fae-DIRTY892b63e8 27 | # 28 | # If you want to improve this script, please also contribute your changes back! 29 | # This script was generated from: dist/scripts/src/mill.sh 30 | # 31 | # Licensed under the Apache License, Version 2.0 32 | 33 | set -e 34 | 35 | if [ "$1" = "--setup-completions" ] ; then 36 | # Need to preserve the first position of those listed options 37 | MILL_FIRST_ARG=$1 38 | shift 39 | fi 40 | 41 | if [ -z "${DEFAULT_MILL_VERSION}" ] ; then 42 | DEFAULT_MILL_VERSION="0.12.10" 43 | fi 44 | 45 | 46 | if [ -z "${GITHUB_RELEASE_CDN}" ] ; then 47 | GITHUB_RELEASE_CDN="" 48 | fi 49 | 50 | 51 | MILL_REPO_URL="https://github.com/com-lihaoyi/mill" 52 | 53 | if [ -z "${CURL_CMD}" ] ; then 54 | CURL_CMD=curl 55 | fi 56 | 57 | # Explicit commandline argument takes precedence over all other methods 58 | if [ "$1" = "--mill-version" ] ; then 59 | echo "The --mill-version option is no longer supported." 1>&2 60 | fi 61 | 62 | MILL_BUILD_SCRIPT="" 63 | 64 | if [ -f "build.mill" ] ; then 65 | MILL_BUILD_SCRIPT="build.mill" 66 | elif [ -f "build.mill.scala" ] ; then 67 | MILL_BUILD_SCRIPT="build.mill.scala" 68 | elif [ -f "build.sc" ] ; then 69 | MILL_BUILD_SCRIPT="build.sc" 70 | fi 71 | 72 | # Please note, that if a MILL_VERSION is already set in the environment, 73 | # We reuse it's value and skip searching for a value. 74 | 75 | # If not already set, read .mill-version file 76 | if [ -z "${MILL_VERSION}" ] ; then 77 | if [ -f ".mill-version" ] ; then 78 | MILL_VERSION="$(tr '\r' '\n' < .mill-version | head -n 1 2> /dev/null)" 79 | elif [ -f ".config/mill-version" ] ; then 80 | MILL_VERSION="$(tr '\r' '\n' < .config/mill-version | head -n 1 2> /dev/null)" 81 | elif [ -n "${MILL_BUILD_SCRIPT}" ] ; then 82 | MILL_VERSION="$(cat ${MILL_BUILD_SCRIPT} | grep '//[|] *mill-version: *' | sed 's;//| *mill-version: *;;')" 83 | fi 84 | fi 85 | 86 | MILL_USER_CACHE_DIR="${XDG_CACHE_HOME:-${HOME}/.cache}/mill" 87 | 88 | if [ -z "${MILL_DOWNLOAD_PATH}" ] ; then 89 | MILL_DOWNLOAD_PATH="${MILL_USER_CACHE_DIR}/download" 90 | fi 91 | 92 | # If not already set, try to fetch newest from Github 93 | if [ -z "${MILL_VERSION}" ] ; then 94 | # TODO: try to load latest version from release page 95 | echo "No mill version specified." 1>&2 96 | echo "You should provide a version via a '//| mill-version: ' comment or a '.mill-version' file." 1>&2 97 | 98 | mkdir -p "${MILL_DOWNLOAD_PATH}" 99 | LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 2>/dev/null || ( 100 | # we might be on OSX or BSD which don't have -d option for touch 101 | # but probably a -A [-][[hh]mm]SS 102 | touch "${MILL_DOWNLOAD_PATH}/.expire_latest"; touch -A -010000 "${MILL_DOWNLOAD_PATH}/.expire_latest" 103 | ) || ( 104 | # in case we still failed, we retry the first touch command with the intention 105 | # to show the (previously suppressed) error message 106 | LANG=C touch -d '1 hour ago' "${MILL_DOWNLOAD_PATH}/.expire_latest" 107 | ) 108 | 109 | # POSIX shell variant of bash's -nt operator, see https://unix.stackexchange.com/a/449744/6993 110 | # if [ "${MILL_DOWNLOAD_PATH}/.latest" -nt "${MILL_DOWNLOAD_PATH}/.expire_latest" ] ; then 111 | if [ -n "$(find -L "${MILL_DOWNLOAD_PATH}/.latest" -prune -newer "${MILL_DOWNLOAD_PATH}/.expire_latest")" ]; then 112 | # we know a current latest version 113 | MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) 114 | fi 115 | 116 | if [ -z "${MILL_VERSION}" ] ; then 117 | # we don't know a current latest version 118 | echo "Retrieving latest mill version ..." 1>&2 119 | LANG=C ${CURL_CMD} -s -i -f -I ${MILL_REPO_URL}/releases/latest 2> /dev/null | grep --ignore-case Location: | sed s'/^.*tag\///' | tr -d '\r\n' > "${MILL_DOWNLOAD_PATH}/.latest" 120 | MILL_VERSION=$(head -n 1 "${MILL_DOWNLOAD_PATH}"/.latest 2> /dev/null) 121 | fi 122 | 123 | if [ -z "${MILL_VERSION}" ] ; then 124 | # Last resort 125 | MILL_VERSION="${DEFAULT_MILL_VERSION}" 126 | echo "Falling back to hardcoded mill version ${MILL_VERSION}" 1>&2 127 | else 128 | echo "Using mill version ${MILL_VERSION}" 1>&2 129 | fi 130 | fi 131 | 132 | MILL_NATIVE_SUFFIX="-native" 133 | MILL_JVM_SUFFIX="-jvm" 134 | FULL_MILL_VERSION=$MILL_VERSION 135 | ARTIFACT_SUFFIX="" 136 | set_artifact_suffix(){ 137 | if [ "$(expr substr $(uname -s) 1 5 2>/dev/null)" = "Linux" ]; then 138 | if [ "$(uname -m)" = "aarch64" ]; then 139 | ARTIFACT_SUFFIX="-native-linux-aarch64" 140 | else 141 | ARTIFACT_SUFFIX="-native-linux-amd64" 142 | fi 143 | elif [ "$(uname)" = "Darwin" ]; then 144 | if [ "$(uname -m)" = "arm64" ]; then 145 | ARTIFACT_SUFFIX="-native-mac-aarch64" 146 | else 147 | ARTIFACT_SUFFIX="-native-mac-amd64" 148 | fi 149 | else 150 | echo "This native mill launcher supports only Linux and macOS." 1>&2 151 | exit 1 152 | fi 153 | } 154 | 155 | case "$MILL_VERSION" in 156 | *"$MILL_NATIVE_SUFFIX") 157 | MILL_VERSION=${MILL_VERSION%"$MILL_NATIVE_SUFFIX"} 158 | set_artifact_suffix 159 | ;; 160 | 161 | *"$MILL_JVM_SUFFIX") 162 | MILL_VERSION=${MILL_VERSION%"$MILL_JVM_SUFFIX"} 163 | ;; 164 | 165 | *) 166 | case "$MILL_VERSION" in 167 | 0.1.*) ;; 168 | 0.2.*) ;; 169 | 0.3.*) ;; 170 | 0.4.*) ;; 171 | 0.5.*) ;; 172 | 0.6.*) ;; 173 | 0.7.*) ;; 174 | 0.8.*) ;; 175 | 0.9.*) ;; 176 | 0.10.*) ;; 177 | 0.11.*) ;; 178 | 0.12.*) ;; 179 | *) 180 | set_artifact_suffix 181 | esac 182 | ;; 183 | esac 184 | 185 | MILL="${MILL_DOWNLOAD_PATH}/$MILL_VERSION$ARTIFACT_SUFFIX" 186 | 187 | try_to_use_system_mill() { 188 | if [ "$(uname)" != "Linux" ]; then 189 | return 0 190 | fi 191 | 192 | MILL_IN_PATH="$(command -v mill || true)" 193 | 194 | if [ -z "${MILL_IN_PATH}" ]; then 195 | return 0 196 | fi 197 | 198 | SYSTEM_MILL_FIRST_TWO_BYTES=$(head --bytes=2 "${MILL_IN_PATH}") 199 | if [ "${SYSTEM_MILL_FIRST_TWO_BYTES}" = "#!" ]; then 200 | # MILL_IN_PATH is (very likely) a shell script and not the mill 201 | # executable, ignore it. 202 | return 0 203 | fi 204 | 205 | SYSTEM_MILL_PATH=$(readlink -e "${MILL_IN_PATH}") 206 | SYSTEM_MILL_SIZE=$(stat --format=%s "${SYSTEM_MILL_PATH}") 207 | SYSTEM_MILL_MTIME=$(stat --format=%y "${SYSTEM_MILL_PATH}") 208 | 209 | if [ ! -d "${MILL_USER_CACHE_DIR}" ]; then 210 | mkdir -p "${MILL_USER_CACHE_DIR}" 211 | fi 212 | 213 | SYSTEM_MILL_INFO_FILE="${MILL_USER_CACHE_DIR}/system-mill-info" 214 | if [ -f "${SYSTEM_MILL_INFO_FILE}" ]; then 215 | parseSystemMillInfo() { 216 | LINE_NUMBER="${1}" 217 | # Select the line number of the SYSTEM_MILL_INFO_FILE, cut the 218 | # variable definition in that line in two halves and return 219 | # the value, and finally remove the quotes. 220 | sed -n "${LINE_NUMBER}p" "${SYSTEM_MILL_INFO_FILE}" |\ 221 | cut -d= -f2 |\ 222 | sed 's/"\(.*\)"/\1/' 223 | } 224 | 225 | CACHED_SYSTEM_MILL_PATH=$(parseSystemMillInfo 1) 226 | CACHED_SYSTEM_MILL_VERSION=$(parseSystemMillInfo 2) 227 | CACHED_SYSTEM_MILL_SIZE=$(parseSystemMillInfo 3) 228 | CACHED_SYSTEM_MILL_MTIME=$(parseSystemMillInfo 4) 229 | 230 | if [ "${SYSTEM_MILL_PATH}" = "${CACHED_SYSTEM_MILL_PATH}" ] \ 231 | && [ "${SYSTEM_MILL_SIZE}" = "${CACHED_SYSTEM_MILL_SIZE}" ] \ 232 | && [ "${SYSTEM_MILL_MTIME}" = "${CACHED_SYSTEM_MILL_MTIME}" ]; then 233 | if [ "${CACHED_SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then 234 | MILL="${SYSTEM_MILL_PATH}" 235 | return 0 236 | else 237 | return 0 238 | fi 239 | fi 240 | fi 241 | 242 | SYSTEM_MILL_VERSION=$(${SYSTEM_MILL_PATH} --version | head -n1 | sed -n 's/^Mill.*version \(.*\)/\1/p') 243 | 244 | cat < "${SYSTEM_MILL_INFO_FILE}" 245 | CACHED_SYSTEM_MILL_PATH="${SYSTEM_MILL_PATH}" 246 | CACHED_SYSTEM_MILL_VERSION="${SYSTEM_MILL_VERSION}" 247 | CACHED_SYSTEM_MILL_SIZE="${SYSTEM_MILL_SIZE}" 248 | CACHED_SYSTEM_MILL_MTIME="${SYSTEM_MILL_MTIME}" 249 | EOF 250 | 251 | if [ "${SYSTEM_MILL_VERSION}" = "${MILL_VERSION}" ]; then 252 | MILL="${SYSTEM_MILL_PATH}" 253 | fi 254 | } 255 | try_to_use_system_mill 256 | 257 | # If not already downloaded, download it 258 | if [ ! -s "${MILL}" ] || [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then 259 | case $MILL_VERSION in 260 | 0.0.* | 0.1.* | 0.2.* | 0.3.* | 0.4.* ) 261 | DOWNLOAD_SUFFIX="" 262 | DOWNLOAD_FROM_MAVEN=0 263 | ;; 264 | 0.5.* | 0.6.* | 0.7.* | 0.8.* | 0.9.* | 0.10.* | 0.11.0-M* ) 265 | DOWNLOAD_SUFFIX="-assembly" 266 | DOWNLOAD_FROM_MAVEN=0 267 | ;; 268 | *) 269 | DOWNLOAD_SUFFIX="-assembly" 270 | DOWNLOAD_FROM_MAVEN=1 271 | ;; 272 | esac 273 | case $MILL_VERSION in 274 | 0.12.0 | 0.12.1 | 0.12.2 | 0.12.3 | 0.12.4 | 0.12.5 | 0.12.6 | 0.12.7 | 0.12.8 | 0.12.9 | 0.12.10 | 0.12.11 ) 275 | DOWNLOAD_EXT="jar" 276 | ;; 277 | 0.12.* ) 278 | DOWNLOAD_EXT="exe" 279 | ;; 280 | 0.* ) 281 | DOWNLOAD_EXT="jar" 282 | ;; 283 | *) 284 | DOWNLOAD_EXT="exe" 285 | ;; 286 | esac 287 | 288 | DOWNLOAD_FILE=$(mktemp mill.XXXXXX) 289 | if [ "$DOWNLOAD_FROM_MAVEN" = "1" ] ; then 290 | DOWNLOAD_URL="https://repo1.maven.org/maven2/com/lihaoyi/mill-dist${ARTIFACT_SUFFIX}/${MILL_VERSION}/mill-dist${ARTIFACT_SUFFIX}-${MILL_VERSION}.${DOWNLOAD_EXT}" 291 | else 292 | MILL_VERSION_TAG=$(echo "$MILL_VERSION" | sed -E 's/([^-]+)(-M[0-9]+)?(-.*)?/\1\2/') 293 | DOWNLOAD_URL="${GITHUB_RELEASE_CDN}${MILL_REPO_URL}/releases/download/${MILL_VERSION_TAG}/${MILL_VERSION}${DOWNLOAD_SUFFIX}" 294 | unset MILL_VERSION_TAG 295 | fi 296 | 297 | if [ "$MILL_TEST_DRY_RUN_LAUNCHER_SCRIPT" = "1" ] ; then 298 | echo $DOWNLOAD_URL 299 | echo $MILL 300 | exit 0 301 | fi 302 | # TODO: handle command not found 303 | echo "Downloading mill ${MILL_VERSION} from ${DOWNLOAD_URL} ..." 1>&2 304 | ${CURL_CMD} -f -L -o "${DOWNLOAD_FILE}" "${DOWNLOAD_URL}" 305 | chmod +x "${DOWNLOAD_FILE}" 306 | mkdir -p "${MILL_DOWNLOAD_PATH}" 307 | mv "${DOWNLOAD_FILE}" "${MILL}" 308 | 309 | unset DOWNLOAD_FILE 310 | unset DOWNLOAD_SUFFIX 311 | fi 312 | 313 | if [ -z "$MILL_MAIN_CLI" ] ; then 314 | MILL_MAIN_CLI="${0}" 315 | fi 316 | 317 | MILL_FIRST_ARG="" 318 | if [ "$1" = "--bsp" ] || [ "${1#"-i"}" != "$1" ] || [ "$1" = "--interactive" ] || [ "$1" = "--no-server" ] || [ "$1" = "--no-daemon" ] || [ "$1" = "--repl" ] || [ "$1" = "--help" ] ; then 319 | # Need to preserve the first position of those listed options 320 | MILL_FIRST_ARG=$1 321 | shift 322 | fi 323 | 324 | unset MILL_DOWNLOAD_PATH 325 | unset MILL_OLD_DOWNLOAD_PATH 326 | unset OLD_MILL 327 | unset MILL_VERSION 328 | unset MILL_REPO_URL 329 | 330 | # -D mill.main.cli is for compatibility with Mill 0.10.9 - 0.13.0-M2 331 | # We don't quote MILL_FIRST_ARG on purpose, so we can expand the empty value without quotes 332 | # shellcheck disable=SC2086 333 | exec "${MILL}" $MILL_FIRST_ARG -D "mill.main.cli=${MILL_MAIN_CLI}" "$@" 334 | -------------------------------------------------------------------------------- /riscvassembler/src/internal/InternalMethods.scala: -------------------------------------------------------------------------------- 1 | package com.carlosedp.riscvassembler 2 | 3 | import scala.collection.mutable.ArrayBuffer 4 | import scala.util.Try 5 | 6 | import com.carlosedp.riscvassembler.ObjectUtils._ 7 | 8 | protected object LineParser { 9 | 10 | /** 11 | * Parses input string lines to generate the list of instructions, addresses 12 | * and label addresses 13 | * 14 | * @param input 15 | * input multiline assembly string 16 | * @return 17 | * a tuple containing: 18 | * - `ArrayBuffer[String]` with the assembly instruction 19 | * - `ArrayBuffer[String]` with the assembly instruction address 20 | * - `Map[String, String]` with the assembly label addresses 21 | */ 22 | def apply(input: String): (ArrayBuffer[String], ArrayBuffer[String], Map[String, String]) = { 23 | val instList = input.split("\n").toList.map(_.trim).filter(_.nonEmpty) 24 | val ignores = Seq(".", "/") 25 | 26 | // Filter lines which begin with characters from `ignores` 27 | val instListFilter = instList.filterNot(l => ignores.contains(l.trim().take(1))).toIndexedSeq 28 | 29 | // Remove inline comments 30 | val instListNocomment = instListFilter.map(_.split("/")(0).trim).toIndexedSeq 31 | 32 | var idx = 0 33 | val instructions = ArrayBuffer.empty[String] 34 | val instructionsAddr = ArrayBuffer.empty[String] 35 | val labelIndex = scala.collection.mutable.Map[String, String]() 36 | 37 | instListNocomment.foreach { data => 38 | // That's an ugly parser, but works for now :) 39 | // println(s"-- Processing line: $data, address: ${(idx * 4L).toHexString}") 40 | val hasLabel = data.indexOf(":") 41 | if (hasLabel != -1) { 42 | if (""".+:\s*(\/.*)?$""".r.findFirstIn(data).isDefined) { 43 | // Has label without code, this label points to next address 44 | labelIndex(data.split(":")(0).replace(":", "")) = ((idx + 1) * 4L).toHexString 45 | idx += 1 46 | } else { 47 | // Has label and code in the same line, this label points to this address 48 | labelIndex(data.split(':')(0).replace(":", "").trim) = (idx * 4L).toHexString 49 | instructions.append(data.split(':')(1).trim) 50 | instructionsAddr.append((idx * 4L).toHexString) 51 | idx += 1 52 | } 53 | } else { 54 | instructions.append(data.trim) 55 | instructionsAddr.append((idx * 4L).toHexString) 56 | idx += 1 57 | } 58 | } 59 | (instructions, instructionsAddr, labelIndex.toMap) 60 | } 61 | } 62 | 63 | protected object InstructionParser { 64 | 65 | /** 66 | * Parse an assembly instruction and return the opcode and opdata 67 | * 68 | * @param input 69 | * the assembly instruction string 70 | * @param addr 71 | * the assembly instruction address 72 | * @param labelIndex 73 | * the index containing the label addresses 74 | * @return 75 | * a tuple containing the Instruction and opdata 76 | */ 77 | def apply( 78 | input: String, 79 | addr: String = "0", 80 | labelIndex: Map[String, String] = Map[String, String](), 81 | ): Option[ 82 | (Instruction, Map[String, Long]) 83 | ] = { 84 | // The regex splits the input into groups (dependind on type): 85 | // (0) - Instruction name 86 | // (1) - Instruction rd 87 | // (2) - Instruction rs1/imm 88 | // (3) - Instruction rs2/rs 89 | val parsed = input.trim.split("[\\s,\\(\\)]+").filter(_.nonEmpty) 90 | 91 | // Check if it's a pseudo-instruction 92 | val instructionParts = PseudoInstructions(parsed).getOrElse(parsed) 93 | 94 | val inst = Instructions(instructionParts(0)) match { 95 | case Some(i) => i 96 | case _ => return None 97 | } 98 | 99 | inst.instType match { 100 | case InstType.R => 101 | if (instructionParts.length != 4) return None 102 | Some( 103 | ( 104 | inst, 105 | Map( 106 | "rd" -> RegMap(instructionParts(1)), 107 | "rs1" -> RegMap(instructionParts(2)), 108 | "rs2" -> RegMap(instructionParts(3)), 109 | ), 110 | ) 111 | ) 112 | case InstType.I => { 113 | // First check if instruction has appropriate arguments 114 | if ( 115 | instructionParts.length != 4 && !Seq("ECALL", "EBREAK", "FENCE.I", "FENCE").contains( 116 | instructionParts(0).toUpperCase 117 | ) 118 | ) 119 | return None 120 | // Treat instructions that contains offsets (Loads) 121 | if (inst.hasOffset) { 122 | val imm = 123 | if (instructionParts(2).startsWith("0x")) instructionParts(2).substring(2).h 124 | else instructionParts(2).toLong 125 | Some( 126 | ( 127 | inst, 128 | Map( 129 | "rd" -> RegMap(instructionParts(1)), 130 | "rs1" -> RegMap(instructionParts(3)), 131 | "imm" -> imm, 132 | ), 133 | ) 134 | ) 135 | } else { 136 | // Treat instructions with no arguments 137 | if (Seq("ECALL", "EBREAK", "FENCE.I").contains(instructionParts(0).toUpperCase)) { 138 | val imm = inst.fixed.b 139 | Some( 140 | ( 141 | inst, 142 | Map( 143 | "rd" -> 0, 144 | "rs1" -> 0, 145 | "imm" -> imm, 146 | ), 147 | ) 148 | ) 149 | } else if (Seq("FENCE").contains(instructionParts(0).toUpperCase)) { 150 | // Treat FENCE instruction 151 | val imm = if (instructionParts.length == 3) { 152 | val pred = instructionParts(1) 153 | .map(_ match { 154 | case bit if bit.toLower == 'i' => 8 155 | case bit if bit.toLower == 'o' => 4 156 | case bit if bit.toLower == 'r' => 2 157 | case bit if bit.toLower == 'w' => 1 158 | case _ => 0 159 | }) 160 | .sum 161 | .toBinaryString 162 | val succ = instructionParts(2) 163 | .map(_ match { 164 | case bit if bit.toLower == 'i' => 8 165 | case bit if bit.toLower == 'o' => 4 166 | case bit if bit.toLower == 'r' => 2 167 | case bit if bit.toLower == 'w' => 1 168 | case _ => 0 169 | }) 170 | .sum 171 | .toBinaryString 172 | ("0000" + pred + succ).b 173 | } else { 174 | "000011111111".b 175 | } 176 | Some( 177 | ( 178 | inst, 179 | Map( 180 | "rd" -> 0, 181 | "rs1" -> 0, 182 | "imm" -> imm, 183 | ), 184 | ) 185 | ) 186 | } else { 187 | // Treat other I instructions (Shifts) 188 | val shamt = 189 | if (instructionParts(3).startsWith("0x")) instructionParts(3).substring(2).h 190 | else instructionParts(3).toLong 191 | val imm = if (inst.fixed != "") { 192 | if (shamt >= 64) return None // Shamt has 5 bits 193 | // If instruction contains fixed imm (like SRAI, SRLI, SLLI), use the fixed imm padded right to fill 12 bits 194 | (inst.fixed + shamt.toBinaryString.padZero(5).takeRight(5)).b 195 | } else { 196 | if (instructionParts(3).startsWith("0x")) instructionParts(3).substring(2).h 197 | else instructionParts(3).toLong 198 | } 199 | Some( 200 | ( 201 | inst, 202 | Map( 203 | "rd" -> RegMap(instructionParts(1)), 204 | "rs1" -> RegMap(instructionParts(2)), 205 | "imm" -> imm, 206 | ), 207 | ) 208 | ) 209 | } 210 | } 211 | } 212 | case InstType.S => { 213 | if (instructionParts.length != 4) return None 214 | val imm = 215 | if (instructionParts(2).startsWith("0x")) instructionParts(2).substring(2).h 216 | else instructionParts(2).toLong 217 | Some( 218 | ( 219 | inst, 220 | Map( 221 | "rs2" -> RegMap(instructionParts(1)), 222 | "rs1" -> RegMap(instructionParts(3)), 223 | "imm" -> imm, 224 | ), 225 | ) 226 | ) 227 | } 228 | case InstType.B => { 229 | if (instructionParts.length != 4) return None 230 | val imm = instructionParts(3) match { 231 | case i if i.startsWith("0x") => i.substring(2).h 232 | case i if Try(i.toLong).isFailure => labelIndex(i).h - addr.h 233 | case i => i.toLong 234 | } 235 | Some( 236 | ( 237 | inst, 238 | Map( 239 | "rs1" -> RegMap(instructionParts(1)), 240 | "rs2" -> RegMap(instructionParts(2)), 241 | "imm" -> imm, 242 | ), 243 | ) 244 | ) 245 | } 246 | case InstType.U | InstType.J => { 247 | if (instructionParts.length != 3) return None 248 | val imm = instructionParts(2) match { 249 | case i if i.startsWith("0x") => i.substring(2).h 250 | case i if Try(i.toLong).isFailure => labelIndex(i).h - addr.h 251 | case i => i.toLong 252 | } 253 | Some((inst, Map("rd" -> RegMap(instructionParts(1)), "imm" -> imm))) 254 | } 255 | case _ => 256 | None 257 | } 258 | } 259 | } 260 | 261 | protected object FillInstruction { 262 | 263 | /** 264 | * Fills the instruction arguments based on instruction type 265 | * 266 | * @param op 267 | * the instruction opcode and type 268 | * @param data 269 | * the received instruction arguments 270 | * @return 271 | * the filled instruction binary 272 | */ 273 | def apply(op: Instruction, data: Map[String, Long]): String = 274 | op.instType match { 275 | case InstType.R => { 276 | val rd = data("rd").toBinaryString.padZero(5) 277 | val rs1 = data("rs1").toBinaryString.padZero(5) 278 | val rs2 = data("rs2").toBinaryString.padZero(5) 279 | op.funct7 + rs2 + rs1 + op.funct3 + rd + op.opcode 280 | } 281 | 282 | case InstType.I => { 283 | val rd = data("rd").toBinaryString.padZero(5) 284 | val rs1 = data("rs1").toBinaryString.padZero(5) 285 | val imm = data("imm").to32Bit.toBinaryString.padZero(12) 286 | imm + rs1 + op.funct3 + rd + op.opcode 287 | } 288 | 289 | case InstType.S => { 290 | val rs1 = data("rs1").toBinaryString.padZero(5) 291 | val rs2 = data("rs2").toBinaryString.padZero(5) 292 | val imm = data("imm").to32Bit.toBinaryString.padZero(12).reverse // reverse to have binary in little endian 293 | imm.slice(5, 12).reverse + rs2 + rs1 + op.funct3 + imm.slice(0, 5).reverse + op.opcode 294 | } 295 | 296 | case InstType.B => { 297 | val rs1 = data("rs1").toBinaryString.padZero(5) 298 | val rs2 = data("rs2").toBinaryString.padZero(5) 299 | val imm = data("imm").to32Bit.toBinaryString.padZero(13).reverse // reverse to have binary in little endian 300 | imm.slice(12, 13).reverse + imm.slice(5, 11).reverse + rs2 + rs1 + op.funct3 + 301 | imm.slice(1, 5).reverse + imm.slice(11, 12).reverse + op.opcode 302 | } 303 | 304 | case InstType.U => { 305 | val rd = data("rd").toBinaryString.padZero(5) 306 | val imm = data("imm").to32Bit.toBinaryString.padZero(20) 307 | imm + rd + op.opcode 308 | } 309 | 310 | case InstType.J => { 311 | val rd = data("rd").toBinaryString.padZero(5) 312 | val imm = data("imm").to32Bit.toBinaryString.padZero(21).reverse // reverse to have binary in little endian 313 | imm.slice(20, 21).reverse + imm.slice(1, 11).reverse + imm.slice(11, 12).reverse + imm 314 | .slice(12, 20) 315 | .reverse + rd + op.opcode 316 | } 317 | } 318 | } 319 | 320 | protected object RegMap { 321 | 322 | /** 323 | * Maps the register name or ABI name to the register number 324 | * 325 | * @param regName 326 | * the register name 327 | * @return 328 | * the register number 329 | */ 330 | def apply(input: String): Long = input.toLowerCase match { 331 | case "x0" | "zero" => 0 332 | case "x1" | "ra" => 1 333 | case "x2" | "sp" => 2 334 | case "x3" | "gp" => 3 335 | case "x4" | "tp" => 4 336 | case "x5" | "t0" => 5 337 | case "x6" | "t1" => 6 338 | case "x7" | "t2" => 7 339 | case "x8" | "s0" | "fp" => 8 340 | case "x9" | "s1" => 9 341 | case "x10" | "a0" => 10 342 | case "x11" | "a1" => 11 343 | case "x12" | "a2" => 12 344 | case "x13" | "a3" => 13 345 | case "x14" | "a4" => 14 346 | case "x15" | "a5" => 15 347 | case "x16" | "a6" => 16 348 | case "x17" | "a7" => 17 349 | case "x18" | "s2" => 18 350 | case "x19" | "s3" => 19 351 | case "x20" | "s4" => 20 352 | case "x21" | "s5" => 21 353 | case "x22" | "s6" => 22 354 | case "x23" | "s7" => 23 355 | case "x24" | "s8" => 24 356 | case "x25" | "s9" => 25 357 | case "x26" | "s10" => 26 358 | case "x27" | "s11" => 27 359 | case "x28" | "t3" => 28 360 | case "x29" | "t4" => 29 361 | case "x30" | "t5" => 30 362 | case "x31" | "t6" => 31 363 | } 364 | } 365 | --------------------------------------------------------------------------------