A RISC-V assembler library for Scala/Chisel HDL projects

RISCVAssembler

A RISC-V assembler library for Scala/Chisel HDL projects. For details, check the scaladoc.

riscvassembler Scala version support riscvassembler Scala version support riscvassembler Scala version support Sonatype Snapshots

Scala CI codecov Scala Steward badge Mergify Status Scaladoc

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.

Using in your project

SBT

When using SBT, add the following lines to your build.sbt file.

// Import libraries
libraryDependencies += "com.carlosedp" %% "riscvassembler" % "1.6.0"  //ReleaseVerSBT

Mill

If you use mill build tool, add the following dep to your build.sc:

// Add to your ivyDeps
def ivyDeps = Agg(
  ivy"com.carlosedp::riscvassembler:1.6.0"  //ReleaseVerMill
  ...
)

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.

Library Description and Sample Code

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 or https://riscvasm.lucasteske.dev web app.

The library can be seen in use in ChiselV, my RV32I core written in Chisel. The core tests use the library to generate test data.

What the library can and can not do:

  • The library can generate hex(machine code) for most RV32 instructions;
  • It can accept either offsets or labels (in the same or previous line) for jump/branch instructions;
  • It can implement some pseudo-instructions (more to come soon);
  • It can generate one machine code for each input asm instruction;
  • It can not decompose one pseudo-instruction to multiple instructions. Eg. li x1, 0x80000000 to addiw ra,zero,1 + ra,ra,0x1f as gcc;
  • It can not validate your input asm code. Imm values might get truncated if not proper used;
  • It can not support assembler relocation functions;
  • The library ignores all asm directives.

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.

Examples

Reading from file:

# Sample file "input.asm":
addi x0, x0, 0
addi x1, x1, 1
addi x2, x2, 2
// Using the lib
val outputHex = RISCVAssembler.fromFile("input.asm")

// outputHex will be:
//    00000013
//    00108093
//    00210113

From a multiline string:

// Sample input:
val input =
      """addi x0, x0, 0
         addi x1, x1, 1
         addi x2, x2, 2
        """.stripMargin
val output = RISCVAssembler.fromString(input)

// outputHex will be:
//    00000013
//    00108093
//    00210113

Which can be used as the input to tests in a RISC-V Core.

Command line tool

The library also contains a command line tool that generates the machine code from strings or input files.

❯ rvasmcli --help
RISC-V Assembler for Scala
main
This tool parses input strings or files in RISC-V assembly language generating hexadecimal machine
code.
  -a --assembly <str>  Assembly instruction string in quotes(can be multiple instructions separated
                       by `\n`
  -f --file-in <str>   Assembly file input
  -o --file-out <str>  If defined, output will be redirected to this file (overwrite if exists)

❯ rvasmcli -a "addi x1, x2, 32\njal x0, 128"
RISC-V Assembler for Scala
Generated Output:

02010093
0800006F

To generate the tool binary yourself, use ./mill bin and the native executable will be generated and it's name printed on screen.

Native binaries for major OS/Arch combinations will be published soon.

Using Snapshot versions

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.

SBT

Add the new Sonatype repository to your build.sbt resolvers and change the library import name:

resolvers ++= Seq(
  Resolver.sonatypeRepo("snapshots"),
  Resolver.sonatypeRepo("releases"),
  "Sonatype New OSS Snapshots" at "https://s01.oss.sonatype.org/content/repositories/snapshots"
)

// and change the dependency to latest SNAPSHOT as:
libraryDependencies += "com.carlosedp" %% "riscvassembler" % "1.7-SNAPSHOT"  //SnapshotVerSBT

Confirm the latest versions displayed on the badges at the top of this readme for both stable and snapshot (without the leading "v").

Mill

If you use mill build tool, add the following dep to your build.sc:

import coursier.MavenRepository

...

// Inside your project `object`:
// And add the snapshot resolver if using it
def repositoriesTask = T.task { super.repositoriesTask() ++ Seq(
  MavenRepository("https://s01.oss.sonatype.org/content/repositories/snapshots")
) }

def ivyDeps = Agg(
  ivy"com.carlosedp::riscvassembler:1.7-SNAPSHOT"  //SnapshotVerMill
  ...
)

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.

Development and Testing

All build processes are integrated into mill build.sc. There are tasks for linting, code coverage, publishing and binary generation.

To locally test and build the library for Scala.js, it's required to have nodejs. After install, run npm install so dependencies are installed as well.

To test and generate the Scala Native binaries, the LLVM toolchain is required.

Publishing flow:

  1. Commit and push latest changes to main (generates SNAPSHOT)
  2. Git tag new version
  3. Push tag to origin (generates a new release)
  4. Check if readme was updated

The library has been published to Maven Central thru Sonatype: