Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 7 additions & 3 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
name: Continuous Integration

on:
pull_request:
push:

env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

jobs:
run:
name: test
name: Compile
strategy:
matrix:
java-version: [17]
Expand All @@ -27,5 +26,10 @@ jobs:
java-version: ${{ matrix.java-version }}
cache: sbt

- name: Run the unit tests
# cache sbt dependencies
- uses: coursier/cache-action@v6

- uses: sbt/setup-sbt@v1

- name: unit test
run: sbt test
34 changes: 25 additions & 9 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,24 @@
# dependentChisel
This is the impl for Master thesis : Dependent Chisel: Statically-checked hardware designs based on Chisel and Scala 3, by Yuchen du
This is an ongoing research project to bring dependent type features to hardware design in Chisel-like syntax, as an embedded DSL(eDSL) in Scala 3.

It originally started as Master thesis "Dependent Chisel: Statically-checked hardware designs based on Chisel and Scala 3", by Yuchen du in 2023
https://github.com/doofin/dependentChisel/blob/master/Msc_Thesis_yuchen.pdf

It uses partial dependent types in Scala 3 to provide early error message for Chisel, a hardware description language embedded in Scala.
This allows you to identify bitwidth mismatch at compile time where IDE can show errors instantly in the editor.

## Design
The main philosophy is to make it clean and simple, and avoid complex language features. The core intermediate representation is an algebraic AST, which is easy to analyze and transform.

**algebraic AST**

The layer of DSL translation:

Chisel-like syntax -> list of commands as stack data structure -> Algebraic AST (FIRRTL like IR in Scala ADT)

when we have the algebraic AST, it's convenient to do various analysis, transformation and finally code generation to FIRRTL. This algebraic AST is similar to FIRRTL IR in Chisel, but Chisel seems to discourage direct manipulation of FIRRTL IR AST, while we embrace it.

**dependent types**

It uses partial dependent types in Scala 3 to provide early error message, which can be directly shown in IDE when writing code, allowing you to catch bitwidth mismatch at compile time.
## examples
examples like adder, etc. can be found in src/test/scala/dependentChisel/

Expand Down Expand Up @@ -55,13 +69,12 @@ Instantiate those modules :


## Interop with chisel
The current implementation is based on Chisel 3.5.1, which is used internally. There's no direct interop with the original Chisel, but you can probably use the generated FIRRTL for that.
The current implementation is based on Chisel 3.5.1, which is used internally. There's no direct interop with the original Chisel, but you can probably use the generated FIRRTL for interop.


Although scala 3 can invoke scala 2.13 libraries,chisel uses scala 2 macros different from scala 3 ,making it partially incompatible.
Although scala 3 can invoke scala 2.13 libraries,chisel uses scala 2 macros which is different from scala 3 ,making it incompatible.

To fix the mismatch, there are several possible ways:

- Rewrite all macros and make everything compatible.
- Rewrite some macros and extend some base class.
- Write a new frontend and emit FIRRTL.
Expand All @@ -78,18 +91,21 @@ many tests under src/test can be run by

### IDE support

recommended IDEs:
[Metals](https://scalameta.org/metals/) with vscode

[IntelliJ](https://blog.jetbrains.com/scala/)

## theories and related work
related work : https://github.com/doofin/dependentChisel/blob/master/resources.md

similar projects:
- [zaozi] : https://github.com/sequencer/zaozi

and more are listed in the thesis pdf.

## misc

with

git ls-files | grep '\.scala$' | xargs wc -l

chisel has 60927 total loc
chisel has 60927 total loc, while dependentChisel only has 3765 total loc, so it's a good idea to understand dependentChisel before diving into chisel codebase.
2 changes: 1 addition & 1 deletion src/main/scala/dependentChisel/algo/Tree.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,6 @@ import scala.collection.mutable.ArrayBuffer
object Tree {
case class TreeNode[t](
val value: t,
val cld: ArrayBuffer[TreeNode[t]] = ArrayBuffer[TreeNode[t]]()
val children: ArrayBuffer[TreeNode[t]] = ArrayBuffer[TreeNode[t]]()
)
}
Original file line number Diff line number Diff line change
@@ -1,23 +1,27 @@
package dependentChisel.algo

import scala.util.*
import scala.collection.mutable.Stack

import Tree.*
import com.doofin.stdScalaCross.*

import dependentChisel.codegen.seqCommands.*
import dependentChisel.codegen.sequentialCommands.*

/** algorithm to convert sequential commands to AST */
object seqCmd2tree {
type AST = TreeNode[NewInstStmt | FirStmt | Ctrl | VarDecls]
/** algorithm to convert sequential commands to AST in tree structure
*/
object stackList2tree {
type AST = TreeNode[NewInstance | WeakStmt | Ctrl | VarDecls]

/** convert sequential commands to AST. multiple stmt is appended as multiple nodes
/** convert sequential commands to AST.
*
* @param cmdList
* list of sequential commands which implicitly has stack structure
* @return
* AST tree structure where parent node has multiple children nodes
*/
def list2tree(cmdList: List[Cmds]): AST = {
import scala.collection.mutable.Stack
val parents: Stack[AST] = Stack(TreeNode(Ctrl.Top())) // new Stack[AST]
// parents.push(TreeNode(Ctrl.Top()))
// ppc(cmdList)

cmdList.foreach { cmd =>
// dbg(cmd)
Expand All @@ -27,15 +31,15 @@ object seqCmd2tree {
then push new node into parent stack as new top elem*/
val newParNode: AST = TreeNode(ctrl) // new parent node
// add this newParNode as child
parents.top.cld += newParNode
parents.top.children += newParNode
parents push newParNode
case End(ctrl, uid) =>
// end of block, pop out one parent
parents.pop()
// for other stmt,just append
case stmt: (FirStmt | NewInstStmt | VarDecls) =>
case stmt: (WeakStmt | NewInstance | VarDecls) =>
val newNd: AST = TreeNode(stmt)
parents.top.cld += newNd
parents.top.children += newNd
case _ =>
}

Expand Down
46 changes: 46 additions & 0 deletions src/main/scala/dependentChisel/algo/treeTraverse.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package dependentChisel.algo

import scala.collection.mutable.ArrayBuffer

/** tree traversal algorithms
*/
object treeTraverse {

/** imperitive pre-order traversal
*
* @param node
* @param visit
*/
def preOrder[t](visit: t => Unit, node: Tree.TreeNode[t]): Unit = {
visit(node.value)
node.children.foreach(preOrder(visit, _))
}

/** filter tree nodes based on predicate
*
* TODO: test this function
* @param predicate
* @param node
* @return
* tree with nodes filtered
*/
def filter[t](
predicate: t => Boolean,
node: Tree.TreeNode[t]
): Tree.TreeNode[t] = {
val flag @ (yes, hasChild) = (predicate(node.value), node.children.nonEmpty)

// if children is empty, it's leaf node, just return itself if satisfies predicate
if hasChild then {
// recursively filter its children
val filteredChildren = node.children
.map(child => filter(predicate, child))
.filter(child => predicate(child.value) || child.children.nonEmpty)
Tree.TreeNode(node.value, filteredChildren)
} else {
// leaf node, just return itself if satisfies predicate
if yes then node else Tree.TreeNode(node.value, ArrayBuffer())
}

}
}
Loading