From 64aee9ae21ed59694fcb079a7e0bcbaf1ffaf87f Mon Sep 17 00:00:00 2001
From: xinyangli <lixinyang411@gmail.com>
Date: Wed, 14 Aug 2024 17:01:37 +0800
Subject: [PATCH] npc,ci: add npc to nix packages

---
 ...achine-build.yml => build-nix-package.yml} |  19 +--
 .gitea/workflows/build-npc.yml                |  18 +++
 .gitea/workflows/npc-test.yml                 |  51 -------
 npc/CMakeLists.txt                            |  20 +--
 .../src/main/scala/components/RV32Inst.scala  | 106 ++++++++++++++
 npc/core/src/main/scala/top/Config.scala      |   1 +
 npc/core/src/main/scala/top/FlowMain.scala    |  98 ++-----------
 npc/csrc/Flow/CMakeLists.txt                  |   8 +-
 npc/flake.lock                                | 136 ++++++++++++++++++
 npc/flake.nix                                 |  41 ++++++
 npc/flow-simlib.nix                           |  47 ++++++
 npc/flow.nix                                  |  35 +++++
 12 files changed, 423 insertions(+), 157 deletions(-)
 rename .gitea/workflows/{abstract-machine-build.yml => build-nix-package.yml} (58%)
 create mode 100644 .gitea/workflows/build-npc.yml
 delete mode 100644 .gitea/workflows/npc-test.yml
 create mode 100644 npc/core/src/main/scala/components/RV32Inst.scala
 create mode 100644 npc/flake.lock
 create mode 100644 npc/flake.nix
 create mode 100644 npc/flow-simlib.nix
 create mode 100644 npc/flow.nix

diff --git a/.gitea/workflows/abstract-machine-build.yml b/.gitea/workflows/build-nix-package.yml
similarity index 58%
rename from .gitea/workflows/abstract-machine-build.yml
rename to .gitea/workflows/build-nix-package.yml
index 3a4547c..86bd52d 100644
--- a/.gitea/workflows/abstract-machine-build.yml
+++ b/.gitea/workflows/build-nix-package.yml
@@ -8,20 +8,21 @@ on:
       - master
 
 jobs:
-  build-abstract-machine:
+  build-packages:
     runs-on: nix
+    strategy:
+      matrix:
+        package:
+          - "abstract-machine"
+          - "nemu"
+          - "nemu-lib"
+          - "rv32Cross.abstract-machine"
     steps:
       - uses: https://github.com/cachix/cachix-action@v14
         with:
           name: ysyx
           authToken: '${{ secrets.CACHIX_SIGNING_KEY }}'
       - uses: actions/checkout@v4
-        with:
-          submodules: true
-      - name: Build abstract-machine
+      - name: Build package
         run: |
-          nix build .?submodules=1#abstract-machine
-      - name: Build nemu
-        run: |
-          nix build .?submodules=1#nemu
-
+          nix build -L .#${{ matrix.package }}
diff --git a/.gitea/workflows/build-npc.yml b/.gitea/workflows/build-npc.yml
new file mode 100644
index 0000000..ec2a518
--- /dev/null
+++ b/.gitea/workflows/build-npc.yml
@@ -0,0 +1,18 @@
+name: Run CTests within npc
+on: [push]
+
+jobs:
+  npc-test:
+    strategy:
+      matrix:
+        package: [ "flow", "flow-simlib"]
+    runs-on: nix
+    steps:
+      - uses: https://github.com/cachix/cachix-action@v14
+        with:
+          name: ysyx
+          authToken: '${{ secrets.CACHIX_SIGNING_KEY }}'
+      - uses: actions/checkout@v4
+      - name: Build package
+        run: |
+          nix build -L .#${{ matrix.package }}
diff --git a/.gitea/workflows/npc-test.yml b/.gitea/workflows/npc-test.yml
deleted file mode 100644
index e71108a..0000000
--- a/.gitea/workflows/npc-test.yml
+++ /dev/null
@@ -1,51 +0,0 @@
-name: Run CTests within npc
-on: [push]
-
-jobs:
-  npc-test:
-    runs-on: nix
-    steps:
-      - uses: https://github.com/cachix/cachix-action@v14
-        with:
-          name: ysyx
-          authToken: '${{ secrets.CACHIX_SIGNING_KEY }}'
-      - uses: actions/checkout@v4
-        with:
-          submodules: true
-      - name: Cache develop environment
-        id: cache-nix-develop
-        uses: actions/cache@v4
-        with:
-          path: |
-            /nix/store
-            /nix/var/nix/db
-          key: nix-develop-${{ hashFiles('flake.*') }}
-      - name: Fetch nix store
-        if: steps.cache-nix-develop.outputs.cache-hit != 'true'
-        run: nix develop .#npc --command true
-      - name: Use develop environment
-        uses: https://git.xinyang.life/xin/nix-develop@main
-        with:
-          arguments: .#npc
-      - name: Cache sbt dependencies
-        id: cache-sbt-dependency
-        uses: actions/cache@v4
-        with:
-          path: |
-            npc/core
-            ~/.cache/coursier
-            ~/.ivy2/cache
-            ~/.sbt
-          key: core-${{ hashFiles('npc/core/build.sbt') }}
-      - name: Fetch sbt dependencies
-        if: steps.cache-sbt-dependency.outputs.cache-hit != 'true'
-        run: |
-          cd npc/core
-          sbt update
-      - name: Run difftests
-        run: |
-          mkdir -p npc/build
-          cd npc/build
-          cmake $cmakeFlags ../
-          make -j8
-          ctest -V
diff --git a/npc/CMakeLists.txt b/npc/CMakeLists.txt
index 2903475..22da348 100644
--- a/npc/CMakeLists.txt
+++ b/npc/CMakeLists.txt
@@ -6,10 +6,12 @@ cmake_policy(SET CMP0144 NEW)
 
 include(CMakeDependentOption)
 include(CTest)
+include(GNUInstallDirs)
 enable_testing()
 list(APPEND CMAKE_MODULE_PATH ${PROJECT_SOURCE_DIR}/cmake)
 
 # -- Build options
+option(BUILD_CHISEL_EMIT_TARGET "Emit verilog file with chisel" ON)
 option(BUILD_USE_BLOOP "Whether to use bloop to speed up elaborate" OFF)
 option(BUILD_SIM_TARGET "Whether to build verilator simulation binary" ON)
 cmake_dependent_option(
@@ -48,16 +50,18 @@ option(ENABLE_SDB "Enable simple debugger" OFF)
 find_library(NVBOARD_LIBRARY NAMES nvboard)
 find_path(NVBOARD_INCLUDE_DIR NAMES nvboard.h)
 
-# FIXME: all scala source file are tracked here, cause all files to rebuild
-# after a source update.
-set(SCALA_CORE "${CMAKE_CURRENT_SOURCE_DIR}/core")
-set(CHISEL_MODULE_CLASS "${CMAKE_PROJECT_NAME}.${TOPMODULE}")
+if(BUILD_CHISEL_EMIT_TARGET)
+  # FIXME: all scala source file are tracked here, cause all files to rebuild
+  # after a source update.
+  set(SCALA_CORE "${CMAKE_CURRENT_SOURCE_DIR}/core")
+  set(CHISEL_MODULE_CLASS "${CMAKE_PROJECT_NAME}.${TOPMODULE}")
 
-set(CHISEL_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TOPMODULE}/vsrc)
+  set(CHISEL_OUTPUT_DIR ${CMAKE_CURRENT_BINARY_DIR}/${TOPMODULE}/vsrc)
 
-set(CHISEL_OUTPUT_VERILATOR_CONF ${CHISEL_OUTPUT_DIR}/conf.vlt)
-set(CHISEL_OUTPUT_TOPMODULE ${CHISEL_OUTPUT_DIR}/${TOPMODULE}.sv)
-set(CHISEL_EMIT_ARGS "--target-dir ${CHISEL_OUTPUT_DIR}")
+  set(CHISEL_OUTPUT_VERILATOR_CONF ${CHISEL_OUTPUT_DIR}/conf.vlt)
+  set(CHISEL_OUTPUT_TOPMODULE ${CHISEL_OUTPUT_DIR}/${TOPMODULE}.sv)
+  set(CHISEL_EMIT_ARGS "--target-dir ${CHISEL_OUTPUT_DIR}")
+endif()
 
 # -- Build NVBoard executable
 if(BUILD_SIM_NVBOARD_TARGET)
diff --git a/npc/core/src/main/scala/components/RV32Inst.scala b/npc/core/src/main/scala/components/RV32Inst.scala
new file mode 100644
index 0000000..ddb06f9
--- /dev/null
+++ b/npc/core/src/main/scala/components/RV32Inst.scala
@@ -0,0 +1,106 @@
+package flow.components
+
+import chisel3.util.BitPat
+
+object RV32Inst {
+  private val bp = BitPat
+
+  // format: off
+  val lui    = this.bp("b???????_?????_?????_???_?????_01101_11")
+  val auipc  = this.bp("b???????_?????_?????_???_?????_00101_11")
+
+  val jal    = this.bp("b???????_?????_?????_???_?????_11011_11")
+  val jalr   = this.bp("b???????_?????_?????_???_?????_11001_11")
+  val beq    = this.bp("b???????_?????_?????_000_?????_11000_11")
+  val bne    = this.bp("b???????_?????_?????_001_?????_11000_11")
+  val blt    = this.bp("b???????_?????_?????_100_?????_11000_11")
+  val bge    = this.bp("b???????_?????_?????_101_?????_11000_11")
+  val bltu   = this.bp("b???????_?????_?????_110_?????_11000_11")
+  val bgeu   = this.bp("b???????_?????_?????_111_?????_11000_11")
+
+  val lb     = this.bp("b???????_?????_?????_000_?????_00000_11")
+  val lh     = this.bp("b???????_?????_?????_001_?????_00000_11")
+  val lw     = this.bp("b???????_?????_?????_010_?????_00000_11")
+  val lbu    = this.bp("b???????_?????_?????_100_?????_00000_11")
+  val lhu    = this.bp("b???????_?????_?????_101_?????_00000_11")
+  val sb     = this.bp("b???????_?????_?????_000_?????_01000_11")
+  val sh     = this.bp("b???????_?????_?????_001_?????_01000_11")
+  val sw     = this.bp("b???????_?????_?????_010_?????_01000_11")
+
+  val addi   = this.bp("b???????_?????_?????_000_?????_00100_11")
+  val slti   = this.bp("b???????_?????_?????_010_?????_00100_11")
+  val sltiu  = this.bp("b???????_?????_?????_011_?????_00100_11")
+  val xori   = this.bp("b???????_?????_?????_100_?????_00100_11")
+  val ori    = this.bp("b???????_?????_?????_110_?????_00100_11")
+  val andi   = this.bp("b???????_?????_?????_111_?????_00100_11")
+  val slli   = this.bp("b0000000_?????_?????_001_?????_00100_11")
+  val srli   = this.bp("b0000000_?????_?????_101_?????_00100_11")
+  val srai   = this.bp("b0100000_?????_?????_101_?????_00100_11")
+  val add    = this.bp("b0000000_?????_?????_000_?????_01100_11")
+  val sub    = this.bp("b0100000_?????_?????_000_?????_01100_11")
+  val sll    = this.bp("b0000000_?????_?????_001_?????_01100_11")
+  val slt    = this.bp("b0000000_?????_?????_010_?????_01100_11")
+  val sltu   = this.bp("b0000000_?????_?????_011_?????_01100_11")
+  val xor    = this.bp("b0000000_?????_?????_100_?????_01100_11")
+  val srl    = this.bp("b0000000_?????_?????_101_?????_01100_11")
+  val sra    = this.bp("b0100000_?????_?????_101_?????_01100_11")
+  val or     = this.bp("b0000000_?????_?????_110_?????_01100_11")
+  val and    = this.bp("b0000000_?????_?????_111_?????_01100_11")
+
+  val ebreak = this.bp("b0000000_00001_00000_000_00000_11100_11")
+
+  val csrrw  = this.bp("b???????_?????_?????_001_?????_11100_11")
+
+
+  val mul    = this.bp("b0000001_?????_?????_000_?????_01100_11")
+  val mulh   = this.bp("b0000001_?????_?????_001_?????_01100_11")
+  val mulhsu = this.bp("b0000001_?????_?????_010_?????_01100_11")
+  val mulhu  = this.bp("b0000001_?????_?????_011_?????_01100_11")
+  val div    = this.bp("b0000001_?????_?????_100_?????_01100_11")
+  val divu   = this.bp("b0000001_?????_?????_101_?????_01100_11")
+  val rem    = this.bp("b0000001_?????_?????_110_?????_01100_11")
+  val remu   = this.bp("b0000001_?????_?????_111_?????_01100_11")
+
+  val inv    = this.bp("b???????_?????_?????_???_?????_?????_??")
+  // format: on
+}
+
+import chisel3._
+import chisel3.util._
+import flow.Params
+object RV32InstSubfields {
+  implicit class ImmDecoder(val inst: UInt) extends AnyVal {
+    def immB: UInt = Cat(
+      Fill(20, inst(31)),
+      inst(7),
+      inst(30, 25),
+      inst(11, 8),
+      0.U(1.W)
+    )
+
+    def immI: UInt = Cat(Fill(20, inst(31)), inst(31, 20))
+
+    def immJ: UInt = Cat(
+      Fill(12, inst(31)),
+      inst(19, 12),
+      inst(20),
+      inst(30, 25),
+      inst(24, 21),
+      0.U(1.W)
+    )
+
+    def immS: UInt = Cat(
+      Fill(20, inst(31)),
+      inst(31),
+      inst(30, 25),
+      inst(11, 8),
+      inst(7)
+    )
+
+    def immU: UInt = Cat(inst(31, 12), 0.U(12.W))
+
+    def rd: UInt = inst(11, 7)
+    def rs1: UInt = inst(19, 15)
+    def rs2: UInt = inst(24, 20)
+  }
+}
diff --git a/npc/core/src/main/scala/top/Config.scala b/npc/core/src/main/scala/top/Config.scala
index d2f3a2e..4c21a25 100644
--- a/npc/core/src/main/scala/top/Config.scala
+++ b/npc/core/src/main/scala/top/Config.scala
@@ -18,5 +18,6 @@ import io.circe.generic.JsonCodec
 import chisel3._
 case class Params(
     XLEN: Width,
+    arch: String,
     csrAddrWidth: Width = 12.W
 )
diff --git a/npc/core/src/main/scala/top/FlowMain.scala b/npc/core/src/main/scala/top/FlowMain.scala
index edce4d4..01b1972 100644
--- a/npc/core/src/main/scala/top/FlowMain.scala
+++ b/npc/core/src/main/scala/top/FlowMain.scala
@@ -14,67 +14,8 @@ import chisel3.experimental.Trace
 import scala.collection.IndexedSeqView
 import shapeless.Poly1
 import flow.components.RamControlInterface
-
-object RV32Inst {
-  private val bp = BitPat
-
-  // format: off
-  val lui    = this.bp("b???????_?????_?????_???_?????_01101_11")
-  val auipc  = this.bp("b???????_?????_?????_???_?????_00101_11")
-
-  val jal    = this.bp("b???????_?????_?????_???_?????_11011_11")
-  val jalr   = this.bp("b???????_?????_?????_???_?????_11001_11")
-  val beq    = this.bp("b???????_?????_?????_000_?????_11000_11")
-  val bne    = this.bp("b???????_?????_?????_001_?????_11000_11")
-  val blt    = this.bp("b???????_?????_?????_100_?????_11000_11")
-  val bge    = this.bp("b???????_?????_?????_101_?????_11000_11")
-  val bltu   = this.bp("b???????_?????_?????_110_?????_11000_11")
-  val bgeu   = this.bp("b???????_?????_?????_111_?????_11000_11")
-
-  val lb     = this.bp("b???????_?????_?????_000_?????_00000_11")
-  val lh     = this.bp("b???????_?????_?????_001_?????_00000_11")
-  val lw     = this.bp("b???????_?????_?????_010_?????_00000_11")
-  val lbu    = this.bp("b???????_?????_?????_100_?????_00000_11")
-  val lhu    = this.bp("b???????_?????_?????_101_?????_00000_11")
-  val sb     = this.bp("b???????_?????_?????_000_?????_01000_11")
-  val sh     = this.bp("b???????_?????_?????_001_?????_01000_11")
-  val sw     = this.bp("b???????_?????_?????_010_?????_01000_11")
-
-  val addi   = this.bp("b???????_?????_?????_000_?????_00100_11")
-  val slti   = this.bp("b???????_?????_?????_010_?????_00100_11")
-  val sltiu  = this.bp("b???????_?????_?????_011_?????_00100_11")
-  val xori   = this.bp("b???????_?????_?????_100_?????_00100_11")
-  val ori    = this.bp("b???????_?????_?????_110_?????_00100_11")
-  val andi   = this.bp("b???????_?????_?????_111_?????_00100_11")
-  val slli   = this.bp("b0000000_?????_?????_001_?????_00100_11")
-  val srli   = this.bp("b0000000_?????_?????_101_?????_00100_11")
-  val srai   = this.bp("b0100000_?????_?????_101_?????_00100_11")
-  val add    = this.bp("b0000000_?????_?????_000_?????_01100_11")
-  val sub    = this.bp("b0100000_?????_?????_000_?????_01100_11")
-  val sll    = this.bp("b0000000_?????_?????_001_?????_01100_11")
-  val slt    = this.bp("b0000000_?????_?????_010_?????_01100_11")
-  val sltu   = this.bp("b0000000_?????_?????_011_?????_01100_11")
-  val xor    = this.bp("b0000000_?????_?????_100_?????_01100_11")
-  val srl    = this.bp("b0000000_?????_?????_101_?????_01100_11")
-  val sra    = this.bp("b0100000_?????_?????_101_?????_01100_11")
-  val or     = this.bp("b0000000_?????_?????_110_?????_01100_11")
-  val and    = this.bp("b0000000_?????_?????_111_?????_01100_11")
-
-  val ebreak = this.bp("b0000000_00001_00000_000_00000_11100_11")
-
-
-  val mul    = this.bp("b0000001_?????_?????_000_?????_01100_11")
-  val mulh   = this.bp("b0000001_?????_?????_001_?????_01100_11")
-  val mulhsu = this.bp("b0000001_?????_?????_010_?????_01100_11")
-  val mulhu  = this.bp("b0000001_?????_?????_011_?????_01100_11")
-  val div    = this.bp("b0000001_?????_?????_100_?????_01100_11")
-  val divu   = this.bp("b0000001_?????_?????_101_?????_01100_11")
-  val rem    = this.bp("b0000001_?????_?????_110_?????_01100_11")
-  val remu   = this.bp("b0000001_?????_?????_111_?????_01100_11")
-
-  val inv    = this.bp("b???????_?????_?????_???_?????_?????_??")
-  // format: on
-}
+import flow.components.RV32Inst
+import flow.components.RV32InstSubfields._
 
 import flow.components.{RegControl, PcControlInterface, ALUControlInterface}
 class Control(width: Int) extends RawModule {
@@ -447,13 +388,7 @@ class Flow extends Module {
   val npc = Wire(dataType)
   npc := pc.out + 4.U
   pc.in.exeOut := alu.out.result
-  pc.in.immB := Cat(
-    Fill(20, inst(31)),
-    inst(7),
-    inst(30, 25),
-    inst(11, 8),
-    0.U(1.W)
-  )
+  pc.in.immB := inst.immB
 
   control.inst := inst
   reg.control <> control.reg
@@ -499,9 +434,9 @@ class Flow extends Module {
   // printf(cf"maskedData = ${maskedData}, writeData = ${reg.in.writeData(lit(rMemOut))}\n")
   reg.in.writeData(lit(rNpc)) := npc
 
-  reg.in.writeAddr := inst(11, 7)
-  reg.in.rs(0) := inst(19, 15) // rs1
-  reg.in.rs(1) := inst(24, 20) // rs2
+  reg.in.writeAddr := inst.rd
+  reg.in.rs(0) := inst.rs1
+  reg.in.rs(1) := inst.rs2
 
   // TODO: Bulk connection here
   ram.io.clock := clock
@@ -521,23 +456,10 @@ class Flow extends Module {
 
   alu.in.b(lit(aSrcBRs2)) := reg.out.src(1)
   // alu.in.b(lit(aSrcBImmI)) := inst(31, 20).pad(aSrcBImmI.getWidth)
-  alu.in.b(lit(aSrcBImmI)) := Cat(Fill(20, inst(31)), inst(31, 20))
-  alu.in.b(lit(aSrcBImmJ)) := Cat(
-    Fill(12, inst(31)),
-    inst(19, 12),
-    inst(20),
-    inst(30, 25),
-    inst(24, 21),
-    0.U(1.W)
-  )
-  alu.in.b(lit(aSrcBImmS)) := Cat(
-    Fill(20, inst(31)),
-    inst(31),
-    inst(30, 25),
-    inst(11, 8),
-    inst(7)
-  )
-  alu.in.b(lit(aSrcBImmU)) := Cat(inst(31, 12), 0.U(12.W))
+  alu.in.b(lit(aSrcBImmI)) := inst.immI
+  alu.in.b(lit(aSrcBImmJ)) := inst.immJ
+  alu.in.b(lit(aSrcBImmS)) := inst.immS
+  alu.in.b(lit(aSrcBImmU)) := inst.immU
 
   Trace.traceName(pc.out)
   dontTouch(control.out)
diff --git a/npc/csrc/Flow/CMakeLists.txt b/npc/csrc/Flow/CMakeLists.txt
index a322d0e..4ee7fe9 100644
--- a/npc/csrc/Flow/CMakeLists.txt
+++ b/npc/csrc/Flow/CMakeLists.txt
@@ -1,4 +1,6 @@
-include(ChiselBuild)
+if(BUILD_CHISEL_EMIT_TARGET)
+  include(ChiselBuild)
+endif()
 add_executable(V${TOPMODULE} config.cpp gdbstub_wrapper.cpp main.cpp)
 target_link_libraries(V${TOPMODULE} PRIVATE devices gdbstub spdlog::spdlog)
 target_include_directories(V${TOPMODULE} PRIVATE ${CMAKE_SOURCE_DIR}/include)
@@ -12,6 +14,8 @@ verilate(
   INCLUDE_DIRS ${CHISEL_OUTPUT_DIR}
   VERILATOR_ARGS "--vpi" "-Wno-UNOPTFLAT")
 
+install(TARGETS V${TOPMODULE})
+
 foreach(DIFFTEST_BINARY_FILE IN LISTS DIFFTEST_BINARY_FILES)
   get_filename_component(FILENAME ${DIFFTEST_BINARY_FILE} NAME_WE)
   add_test(NAME V${TOPMODULE}.${FILENAME}
@@ -34,3 +38,5 @@ verilate(
   SOURCES ${CHISEL_OUTPUT_TOPMODULE} ${CHISEL_OUTPUT_VERILATOR_CONF}
   INCLUDE_DIRS ${CHISEL_OUTPUT_DIR}
   VERILATOR_ARGS "--vpi" "-Wno-UNOPTFLAT")
+
+install(TARGETS ${TOPMODULE})
diff --git a/npc/flake.lock b/npc/flake.lock
new file mode 100644
index 0000000..b473f33
--- /dev/null
+++ b/npc/flake.lock
@@ -0,0 +1,136 @@
+{
+  "nodes": {
+    "flake-utils": {
+      "inputs": {
+        "systems": "systems"
+      },
+      "locked": {
+        "lastModified": 1710146030,
+        "narHash": "sha256-SZ5L6eA7HJ/nmkzGG7/ISclqe6oZdOZTNoesiInkXPQ=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "b1d9ab70662946ef0850d488da1c9019f3a9752a",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "flake-utils_2": {
+      "locked": {
+        "lastModified": 1667395993,
+        "narHash": "sha256-nuEHfE/LcWyuSWnS8t12N1wc105Qtau+/OdUAjtQ0rA=",
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "rev": "5aed5285a952e0b949eb3ba02c12fa4fcfef535f",
+        "type": "github"
+      },
+      "original": {
+        "owner": "numtide",
+        "repo": "flake-utils",
+        "type": "github"
+      }
+    },
+    "nixpkgs": {
+      "locked": {
+        "lastModified": 1723362943,
+        "narHash": "sha256-dFZRVSgmJkyM0bkPpaYRtG/kRMRTorUIDj8BxoOt1T4=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "a58bc8ad779655e790115244571758e8de055e3d",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "ref": "nixos-unstable",
+        "repo": "nixpkgs",
+        "type": "github"
+      }
+    },
+    "nixpkgs-circt162": {
+      "locked": {
+        "lastModified": 1705645507,
+        "narHash": "sha256-tX3vipIAmNDBA8WNWG4oY4KyTfnm2YieTHO2BhG8ISA=",
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "7995cae3ad60e3d6931283d650d7f43d31aaa5c7",
+        "type": "github"
+      },
+      "original": {
+        "owner": "NixOS",
+        "repo": "nixpkgs",
+        "rev": "7995cae3ad60e3d6931283d650d7f43d31aaa5c7",
+        "type": "github"
+      }
+    },
+    "nur-xin": {
+      "inputs": {
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1721891452,
+        "narHash": "sha256-2c9nDuXXARzoRXE67lte5kKBeFb1XmTNsvdiIbRUEgE=",
+        "ref": "refs/heads/master",
+        "rev": "de8ad578fc4fe527772cec23a7f660bde14c8570",
+        "revCount": 152,
+        "type": "git",
+        "url": "https://git.xinyang.life/xin/nur.git"
+      },
+      "original": {
+        "type": "git",
+        "url": "https://git.xinyang.life/xin/nur.git"
+      }
+    },
+    "root": {
+      "inputs": {
+        "flake-utils": "flake-utils",
+        "nixpkgs": "nixpkgs",
+        "nixpkgs-circt162": "nixpkgs-circt162",
+        "nur-xin": "nur-xin",
+        "sbt-derivation": "sbt-derivation"
+      }
+    },
+    "sbt-derivation": {
+      "inputs": {
+        "flake-utils": "flake-utils_2",
+        "nixpkgs": [
+          "nixpkgs"
+        ]
+      },
+      "locked": {
+        "lastModified": 1698464090,
+        "narHash": "sha256-Pnej7WZIPomYWg8f/CZ65sfW85IfIUjYhphMMg7/LT0=",
+        "owner": "zaninime",
+        "repo": "sbt-derivation",
+        "rev": "6762cf2c31de50efd9ff905cbcc87239995a4ef9",
+        "type": "github"
+      },
+      "original": {
+        "owner": "zaninime",
+        "repo": "sbt-derivation",
+        "type": "github"
+      }
+    },
+    "systems": {
+      "locked": {
+        "lastModified": 1681028828,
+        "narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
+        "owner": "nix-systems",
+        "repo": "default",
+        "rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
+        "type": "github"
+      },
+      "original": {
+        "owner": "nix-systems",
+        "repo": "default",
+        "type": "github"
+      }
+    }
+  },
+  "root": "root",
+  "version": 7
+}
diff --git a/npc/flake.nix b/npc/flake.nix
new file mode 100644
index 0000000..d725b57
--- /dev/null
+++ b/npc/flake.nix
@@ -0,0 +1,41 @@
+{
+  inputs = {
+    nixpkgs.url = "github:NixOS/nixpkgs/nixos-unstable";
+    nixpkgs-circt162.url = "github:NixOS/nixpkgs/7995cae3ad60e3d6931283d650d7f43d31aaa5c7";
+    nur-xin = {
+      url = "git+https://git.xinyang.life/xin/nur.git";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+    flake-utils.url = "github:numtide/flake-utils";
+    sbt-derivation = {
+      url = "github:zaninime/sbt-derivation";
+      inputs.nixpkgs.follows = "nixpkgs";
+    };
+  };
+
+  outputs = { self, nixpkgs, flake-utils, nur-xin, nixpkgs-circt162, sbt-derivation }:
+    flake-utils.lib.eachDefaultSystem (system:
+      let
+        pkgs = import nixpkgs {
+          inherit system;
+          config.allowUnfree = true;
+          overlays = [
+            (self: super: {
+              nvboard = nur-xin.legacyPackages.${system}.nvboard;
+              mini-gdbstub = nur-xin.legacyPackages.${system}.mini-gdbstub;
+            })
+          ];
+        };
+      in
+      {
+        packages = rec {
+          flow = pkgs.callPackage ./flow.nix {
+            inherit (sbt-derivation.lib) mkSbtDerivation;
+          };
+
+          flow-simlib = pkgs.callPackage ./flow-simlib.nix {
+            inherit flow;
+          };
+        };
+      });
+}
diff --git a/npc/flow-simlib.nix b/npc/flow-simlib.nix
new file mode 100644
index 0000000..5127421
--- /dev/null
+++ b/npc/flow-simlib.nix
@@ -0,0 +1,47 @@
+{ flow
+, cmake
+, ninja
+, nvboard
+, cli11
+, flex
+, bison
+, verilator
+, spdlog
+, mini-gdbstub
+, stdenv
+, lib
+, topmodule ? "Flow"
+}:
+stdenv.mkDerivation {
+  pname = "npc-flow-simlib";
+  version = "0.0.0";
+
+  src = ./.;
+
+  nativeBuildInputs = [
+    cmake
+    ninja
+    flex
+    bison
+    nvboard
+    verilator
+    flow
+  ];
+
+  buildInputs = [
+    cli11
+    spdlog
+    mini-gdbstub
+  ];
+
+  cmakeFlags = with lib; [
+    (cmakeBool "ENABLE_YSYX_GIT_TRACKER" false)
+    (cmakeBool "BUILD_CHISEL_EMIT_TARGET" false)
+    (cmakeOptionType "string" "TOPMODULE" "${topmodule}")
+    (cmakeOptionType "string" "CHISEL_OUTPUT_VERILATOR_CONF" "${flow}/share/conf.vlt")
+    (cmakeOptionType "string" "CHISEL_OUTPUT_TOPMODULE" "${flow}/share/${topmodule}.sv")
+    (cmakeOptionType "string" "CHISEL_OUTPUT_DIR" "${flow}/share")
+  ];
+
+  enableParallelBuilding = true;
+}
diff --git a/npc/flow.nix b/npc/flow.nix
new file mode 100644
index 0000000..b12b416
--- /dev/null
+++ b/npc/flow.nix
@@ -0,0 +1,35 @@
+{ mkSbtDerivation
+, pkgs
+, circt
+}: mkSbtDerivation
+{
+  pname = "npc-flow";
+  version = "0.0.0";
+
+  inherit pkgs;
+
+  src = ./core;
+
+  nativeBuildInputs = [
+    circt
+  ];
+  CHISEL_FIRTOOL_PATH = "${circt}/bin";
+
+  depsWarmupCommand = ''
+    CHISEL_FIRTOOL_PATH="${circt}/bin" sbt run
+  '';
+  depsSha256 = "sha256-FsiEEEcv43lAz4RHO1CuCSj8syWdqnl602t7xnlkMrw=";
+
+  buildPhase =
+    let
+      emitArgs = "--target-dir build";
+    in
+    ''
+      sbt "run ${emitArgs}"
+    '';
+
+  installPhase = ''
+    mkdir -p $out/share
+    cp build/* $out/share
+  '';
+}