From 4466cf0c11de8f42173b177132616417e541b2ec Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 15:42:17 -0700 Subject: [PATCH 1/6] chore(ci): run sanity checks in CI --- .github/workflows/ci.yml | 118 +++++++++++++++++++++++++++++++++++++++ .gitignore | 10 +++- README.md | 37 +++++++++++- project/build.properties | 1 - 4 files changed, 159 insertions(+), 7 deletions(-) create mode 100644 .github/workflows/ci.yml delete mode 100644 project/build.properties diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..37305ea --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,118 @@ +name: ci + +on: + pull_request: + push: + branches: [main] + +permissions: {} + +concurrency: + group: ci-${{ github.ref }} + # Superseded pull request pushes are not worth finishing; every commit that lands on + # main is, since a release can be cut from any of them. + cancel-in-progress: ${{ github.event_name == 'pull_request' }} + +jobs: + # A release is tagged by release-please the moment its PR merges, so a publish failure + # afterwards leaves a tagged version that was never published. This job runs the same + # command the release job does, on every pull request and push, so that never happens. + publish: + runs-on: ubuntu-latest + permissions: + contents: read + steps: + - uses: actions/checkout@v7 + with: + # rocket-chip and friends are submodules, and rocket-chip has its own + # nested submodules (cde, diplomacy, hardfloat) that the build compiles. + submodules: recursive + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '21' + + - uses: actions/cache@v6 + with: + path: | + ~/.cache/coursier + ~/.cache/mill + key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} + restore-keys: mill-${{ runner.os }}- + + # Also generates the POM, the source jar and the Scaladoc jar, which is most of + # what publishing can break. + - name: Publish every package + run: ./mill __.publishM2Local --m2RepoPath "$PWD/m2" + + # Only ever consumed by the examples job below, and only for a version that is not + # published yet. See the comment there. + - name: Upload the Maven repository + uses: actions/upload-artifact@v7 + with: + name: maven-repo + path: m2 + retention-days: 1 + + # The examples are external consumers of the released artifacts (see the README), so + # they check that what a user of Chippy actually gets is usable. + examples: + needs: [publish] + runs-on: ubuntu-latest + permissions: + contents: read + env: + # Mill resolves the repositories a build lists — for the examples, the released + # repository on GitHub Pages — before any repository configured here, so the + # released artifacts are what gets tested. The repository built by the publish job + # is only reached for a version GitHub Pages does not have, which is exactly the + # case on a release PR: it bumps the examples to the version it is about to + # publish. `central` has to be spelled out because this replaces coursier's + # default repositories rather than adding to them. + COURSIER_REPOSITORIES: ivy2Local|central|file://${{ github.workspace }}/m2 + # A runner defaults to a quarter of its 16 GB for the heap, which is not much for + # elaborating a chip top. The publish job deliberately gets no such override: it is + # meant to fail wherever the release job would. + JAVA_TOOL_OPTIONS: -Xmx6g -Xss8m + steps: + - uses: actions/checkout@v7 + with: + submodules: recursive + + - uses: actions/setup-java@v5 + with: + distribution: temurin + java-version: '21' + + - uses: actions/cache/restore@v6 + with: + path: | + ~/.cache/coursier + ~/.cache/mill + key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} + restore-keys: mill-${{ runner.os }}- + + - name: Download the Maven repository + uses: actions/download-artifact@v8 + with: + name: maven-repo + path: m2 + + - name: Compile every example + run: ./mill examples.__.compile + + # Everything else the examples' tests do needs EDA tools that are not available + # here — VCS, or a Verilator build plus RISC-V binaries from examples/software. + # Elaborating the two chip tops needs neither and still exercises the artifacts at + # run time, which is where diplomacy and CDE errors surface. + # + # A `-z` filter that matches nothing still exits 0, so the summary is checked as + # well; otherwise renaming a test would leave this step passing having run nothing. + - name: Elaborate the chip tops + run: | + set -euo pipefail + for module in examples.rocket-config examples.sky130-chip.digital-chip; do + ./mill "$module.test" -z "should generate valid System Verilog" | tee elaborate.log + grep -q "Tests: succeeded 1, failed 0" elaborate.log + done diff --git a/.gitignore b/.gitignore index 0e146e0..4b4c3e9 100644 --- a/.gitignore +++ b/.gitignore @@ -83,11 +83,15 @@ $RECYCLE.BIN/ *.lnk # End of https://www.toptal.com/developers/gitignore/api/scala,macos,linux,windows +### Tooling ### +# Metals / BSP .metals/ .bsp/ +.bloop/ +# IntelliJ +.idea/ +# Build output +out/ build/ target/ -project/metals.sbt -project/project/ -out/ .scala-build/ diff --git a/README.md b/README.md index 5157aa7..6c920ad 100644 --- a/README.md +++ b/README.md @@ -31,17 +31,47 @@ libraryDependencies += "edu.berkeley.cs" %% "diplomacy" % "0.1.1" ``` -Alternatively, you can build against unreleased changes by publishing to your local Ivy repository: +Alternatively, you can build against unreleased changes by publishing Chippy into a directory of +your own: ``` git clone https://github.com/ucb-substrate/chippy.git cd chippy git submodule update --init --recursive -./mill __.publishLocal +./mill __.publishM2Local --m2RepoPath /path/to/chippy-repo ``` +and resolving from that directory instead of the released repository: + +```scala +def repositories = Seq("file:///path/to/chippy-repo") +``` + +`./mill __.publishLocal` publishes to your local Ivy repository, which Mill also resolves, but only +after the repositories a build lists explicitly — so a version that has already been released still +comes from GitHub Pages, and listing your own repository is what makes local changes take effect. + Usage examples can be found in the `examples/` folder. +## Continuous integration + +`.github/workflows/ci.yml` runs on every pull request and every push to `main`: + +- **publish** runs `./mill __.publishM2Local`, the command the release job uses. Merging a release PR + tags the release before anything is published, so a break in publishing has to be caught before it + lands on `main` rather than after a version has already been tagged. +- **examples** compiles every module under `examples/` and elaborates the two chip tops. The rest of + the examples' tests need VCS, or Verilator plus the RISC-V binaries built by + `examples/software/Makefile`, so those only run locally. + +The examples are checked against the *released* artifacts, since that is what a user of Chippy gets. +A release PR is the exception: it bumps the examples to the version it is about to publish, which +does not exist yet. So the examples job also puts the repository the publish job produced *behind* +the released one, using `COURSIER_REPOSITORIES`. Mill resolves the repositories a build lists before +any repository configured that way, so the released artifacts win whenever they exist, and the ones +built from source are reached only for a version that has not been published — a release PR, or a +push to `main` before the release job has finished deploying. + ## Releasing Versions are managed by [release-please](https://github.com/googleapis/release-please) and are shared @@ -81,4 +111,5 @@ in prose — an unmatched opening marker turns the rest of the file into a repla Because the release PR bumps the examples to the version it is about to publish, the examples briefly reference a version that does not exist yet — from the moment the release PR is opened until the -publish job finishes after it is merged. +publish job finishes after it is merged. CI covers that window by falling back to the repository it +builds itself; see [Continuous integration](#continuous-integration). diff --git a/project/build.properties b/project/build.properties deleted file mode 100644 index 01a16ed..0000000 --- a/project/build.properties +++ /dev/null @@ -1 +0,0 @@ -sbt.version=1.11.7 From 7fd462859c7d942de368318070863d93a1c80745 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 16:37:02 -0700 Subject: [PATCH 2/6] test examples using released code --- .github/workflows/ci.yml | 59 +++++++++++++++++++++++----------------- README.md | 37 ++----------------------- 2 files changed, 37 insertions(+), 59 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 37305ea..b16a115 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -16,8 +16,16 @@ concurrency: jobs: # A release is tagged by release-please the moment its PR merges, so a publish failure # afterwards leaves a tagged version that was never published. This job runs the same - # command the release job does, on every pull request and push, so that never happens. + # command the release job does, ahead of every merge, so that never happens. + # + # The one push it skips is the merge of a release PR: release.yml publishes that exact + # commit for real moments later, and the release PR itself has already run this check. + # Spelled out per event rather than relying on `head_commit` being empty off a push — + # this silently skipping everywhere would cost exactly the protection it exists for. publish: + if: >- + github.event_name != 'push' || + !startsWith(github.event.head_commit.message, 'chore(main): release') runs-on: ubuntu-latest permissions: contents: read @@ -46,31 +54,15 @@ jobs: - name: Publish every package run: ./mill __.publishM2Local --m2RepoPath "$PWD/m2" - # Only ever consumed by the examples job below, and only for a version that is not - # published yet. See the comment there. - - name: Upload the Maven repository - uses: actions/upload-artifact@v7 - with: - name: maven-repo - path: m2 - retention-days: 1 - # The examples are external consumers of the released artifacts (see the README), so - # they check that what a user of Chippy actually gets is usable. + # they resolve from like any other project + # and check that what a user of Chippy actually gets is usable. Nothing here depends + # on the job above; the two run in parallel. examples: - needs: [publish] runs-on: ubuntu-latest permissions: contents: read env: - # Mill resolves the repositories a build lists — for the examples, the released - # repository on GitHub Pages — before any repository configured here, so the - # released artifacts are what gets tested. The repository built by the publish job - # is only reached for a version GitHub Pages does not have, which is exactly the - # case on a release PR: it bumps the examples to the version it is about to - # publish. `central` has to be spelled out because this replaces coursier's - # default repositories rather than adding to them. - COURSIER_REPOSITORIES: ivy2Local|central|file://${{ github.workspace }}/m2 # A runner defaults to a quarter of its 16 GB for the heap, which is not much for # elaborating a chip top. The publish job deliberately gets no such override: it is # meant to fail wherever the release job would. @@ -93,11 +85,28 @@ jobs: key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} restore-keys: mill-${{ runner.os }}- - - name: Download the Maven repository - uses: actions/download-artifact@v8 - with: - name: maven-repo - path: m2 + # The one case where the released artifacts cannot be used: a release bumps the + # examples to the version it is about to publish, so that version is not on GitHub + # Pages yet. Build it from source instead — on release-please's own PR, which it + # always opens from this branch, and on the commit that lands when that PR is + # merged, which is titled `chore(main): release X.Y.Z`. Its `autorelease:` label is + # not used for this: it is attached after the PR is created and flips to + # `autorelease: tagged` once the release exists. + # + # Deliberately conditional rather than a repository that is always available as a + # fallback: a package missing from GitHub Pages has to fail here, not be quietly + # supplied by this build. + - name: Build Chippy from source for a release + if: >- + github.head_ref == 'release-please--branches--main' || + startsWith(github.event.head_commit.message, 'chore(main): release') + run: | + ./mill __.publishM2Local --m2RepoPath "$PWD/m2" + # `central` has to be spelled out because this replaces coursier's default + # repositories rather than adding to them. The examples list GitHub Pages + # themselves, and Mill resolves a build's own repositories first, so the + # released artifacts still win for anything already published. + echo "COURSIER_REPOSITORIES=ivy2Local|central|file://$PWD/m2" >> "$GITHUB_ENV" - name: Compile every example run: ./mill examples.__.compile diff --git a/README.md b/README.md index 6c920ad..5157aa7 100644 --- a/README.md +++ b/README.md @@ -31,47 +31,17 @@ libraryDependencies += "edu.berkeley.cs" %% "diplomacy" % "0.1.1" ``` -Alternatively, you can build against unreleased changes by publishing Chippy into a directory of -your own: +Alternatively, you can build against unreleased changes by publishing to your local Ivy repository: ``` git clone https://github.com/ucb-substrate/chippy.git cd chippy git submodule update --init --recursive -./mill __.publishM2Local --m2RepoPath /path/to/chippy-repo +./mill __.publishLocal ``` -and resolving from that directory instead of the released repository: - -```scala -def repositories = Seq("file:///path/to/chippy-repo") -``` - -`./mill __.publishLocal` publishes to your local Ivy repository, which Mill also resolves, but only -after the repositories a build lists explicitly — so a version that has already been released still -comes from GitHub Pages, and listing your own repository is what makes local changes take effect. - Usage examples can be found in the `examples/` folder. -## Continuous integration - -`.github/workflows/ci.yml` runs on every pull request and every push to `main`: - -- **publish** runs `./mill __.publishM2Local`, the command the release job uses. Merging a release PR - tags the release before anything is published, so a break in publishing has to be caught before it - lands on `main` rather than after a version has already been tagged. -- **examples** compiles every module under `examples/` and elaborates the two chip tops. The rest of - the examples' tests need VCS, or Verilator plus the RISC-V binaries built by - `examples/software/Makefile`, so those only run locally. - -The examples are checked against the *released* artifacts, since that is what a user of Chippy gets. -A release PR is the exception: it bumps the examples to the version it is about to publish, which -does not exist yet. So the examples job also puts the repository the publish job produced *behind* -the released one, using `COURSIER_REPOSITORIES`. Mill resolves the repositories a build lists before -any repository configured that way, so the released artifacts win whenever they exist, and the ones -built from source are reached only for a version that has not been published — a release PR, or a -push to `main` before the release job has finished deploying. - ## Releasing Versions are managed by [release-please](https://github.com/googleapis/release-please) and are shared @@ -111,5 +81,4 @@ in prose — an unmatched opening marker turns the rest of the file into a repla Because the release PR bumps the examples to the version it is about to publish, the examples briefly reference a version that does not exist yet — from the moment the release PR is opened until the -publish job finishes after it is merged. CI covers that window by falling back to the repository it -builds itself; see [Continuous integration](#continuous-integration). +publish job finishes after it is merged. From c7c78a8285f723e9285cc602bf56a9b345d84c6f Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 17:09:59 -0700 Subject: [PATCH 3/6] fix ordering and install deps --- .github/workflows/ci.yml | 26 ++++++++++++++++++++++++++ .github/workflows/release.yml | 24 ++++++++++++++++++++---- 2 files changed, 46 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index b16a115..d883f18 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -118,6 +118,32 @@ jobs: # # A `-z` filter that matches nothing still exits 0, so the summary is checked as # well; otherwise renaming a test would leave this step passing having run nothing. + # + # Elaborating a subsystem shells out to the device tree compiler, which rocket-chip + # runs to build the DTB the boot ROM embeds. It is not on the runner image. + - name: Install the device tree compiler + run: | + sudo apt-get update + sudo apt-get install -y device-tree-compiler + dtc --version + + # Saturn's instruction decoder goes through Chisel's default minimizer, which uses + # espresso when it is on the PATH and otherwise falls back to Quine-McCluskey with + # nothing but a logged error — exponential, on a vector ISA's decode table, so the + # job would crawl or hang rather than fail. rocket-chip pins QMCMinimizer itself, + # so only the sky130 example needs this. + - name: Install espresso + env: + ESPRESSO_SHA256: 7683c4315e1c9cec293c194eb6a1ed716d22a9952a96bd88bff62ecf57df6e2f + run: | + set -euo pipefail + mkdir -p "$HOME/.local/bin" + curl -fsSL -o "$HOME/.local/bin/espresso" \ + https://github.com/chipsalliance/espresso/releases/download/v2.4/x86_64-linux-gnu-espresso + echo "$ESPRESSO_SHA256 $HOME/.local/bin/espresso" | sha256sum --check + chmod +x "$HOME/.local/bin/espresso" + echo "$HOME/.local/bin" >> "$GITHUB_PATH" + - name: Elaborate the chip tops run: | set -euo pipefail diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 7b996db..8b300d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -1,7 +1,15 @@ name: release on: - push: + # Runs once ci has finished for a commit on main, rather than on the push itself, so + # nothing is ever tagged or published on top of a failing build. This event also fires + # for ci runs on pull requests — including a fork branch that happens to be named + # `main` — so the release-please job below checks that the run it is reacting to was a + # push. Being a `workflow_run` trigger, the copy of this file on the default branch is + # the one that runs. + workflow_run: + workflows: [ci] + types: [completed] branches: [main] # Escape hatch: re-publish the version currently in version.txt # (useful if the publish job failed after the release was already tagged). @@ -14,10 +22,13 @@ concurrency: cancel-in-progress: false jobs: - # Maintains the release PR ("chore(main): release X.Y.Z") on every push to main. - # Merging that PR is what produces a tag + GitHub release, which gates the publish job below. + # Maintains the release PR ("chore(main): release X.Y.Z") for every push to main that + # passes ci. Merging that PR is what produces a tag + GitHub release, which gates the + # publish job below. release-please: - if: github.event_name == 'push' + if: >- + github.event.workflow_run.event == 'push' && + github.event.workflow_run.conclusion == 'success' runs-on: ubuntu-latest permissions: contents: write @@ -50,6 +61,11 @@ jobs: steps: - uses: actions/checkout@v7 with: + # A workflow_run event does not check out the commit that triggered ci, so pin + # this to the tag release-please just created — the artifacts published then + # match the release exactly, even if main has moved on since. The dispatch path + # has no tag and re-publishes whatever main currently holds. + ref: ${{ needs.release-please.outputs.tag_name || github.ref }} # rocket-chip and friends are submodules, and rocket-chip has its own # nested submodules (cde, diplomacy, hardfloat) that the build compiles. submodules: recursive From cc001d81224c22defb7613eecd90c588b654e756 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 17:40:06 -0700 Subject: [PATCH 4/6] cache build output --- .github/workflows/ci.yml | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index d883f18..7ade14e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -49,6 +49,24 @@ jobs: key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} restore-keys: mill-${{ runner.os }}- + # Mill's build output, so a run only recompiles what changed rather than all of + # rocket-chip, chipyard and constellation from cold. The submodules are pinned by + # commit and rarely move, so most of this survives from run to run. Safe to reuse: + # Mill decides what to re-run by hashing task inputs, so stale entries are ignored + # rather than trusted. The key is rolled per commit and matched by prefix, which + # makes every run save a fresh entry and restore the most recent one. + # + # Deliberately not done in release.yml: what that job publishes should come from a + # cold build of the tag, not from whatever a previous run left behind. + - uses: actions/cache@v6 + with: + path: | + out + !out/mill-daemon + !out/mill-out-lock + key: mill-out-publish-${{ runner.os }}-${{ github.sha }} + restore-keys: mill-out-publish-${{ runner.os }}- + # Also generates the POM, the source jar and the Scaladoc jar, which is most of # what publishing can break. - name: Publish every package From 4735064d629de1b154c6cdc51723d2211426fea8 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 18:13:56 -0700 Subject: [PATCH 5/6] additional speed optimizations --- .github/workflows/ci.yml | 29 ++++++++++++++++++++++++++--- 1 file changed, 26 insertions(+), 3 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 7ade14e..56f9311 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -87,8 +87,16 @@ jobs: JAVA_TOOL_OPTIONS: -Xmx6g -Xss8m steps: - uses: actions/checkout@v7 - with: - submodules: recursive + + # Only the two submodules the examples actually compile. rocket-chip, chipyard, + # constellation and DRAMSim2 are not in their dependency graph — the examples + # resolve Chippy from the published repository — and fetching them all is most of + # this job's checkout. A release run does need them, and initialises them itself. + - name: Check out the submodules the examples build + run: | + git submodule update --init --depth 1 \ + examples/sky130-chip/digital-chip/shuttle \ + examples/sky130-chip/digital-chip/saturn-vectors - uses: actions/setup-java@v5 with: @@ -103,6 +111,18 @@ jobs: key: mill-${{ runner.os }}-${{ hashFiles('build.mill', 'mill') }} restore-keys: mill-${{ runner.os }}- + # Same rolling scheme as the publish job, under its own key: the two jobs build + # different modules into `out`, and a shared key would leave one of them restoring + # the other's tree and saving nothing. + - uses: actions/cache@v6 + with: + path: | + out + !out/mill-daemon + !out/mill-out-lock + key: mill-out-examples-${{ runner.os }}-${{ github.sha }} + restore-keys: mill-out-examples-${{ runner.os }}- + # The one case where the released artifacts cannot be used: a release bumps the # examples to the version it is about to publish, so that version is not on GitHub # Pages yet. Build it from source instead — on release-please's own PR, which it @@ -119,6 +139,7 @@ jobs: github.head_ref == 'release-please--branches--main' || startsWith(github.event.head_commit.message, 'chore(main): release') run: | + git submodule update --init --recursive --depth 1 ./mill __.publishM2Local --m2RepoPath "$PWD/m2" # `central` has to be spelled out because this replaces coursier's default # repositories rather than adding to them. The examples list GitHub Pages @@ -141,8 +162,10 @@ jobs: # runs to build the DTB the boot ROM embeds. It is not on the runner image. - name: Install the device tree compiler run: | + # The runner image ships no package lists, so the index refresh is not + # skippable — it is most of this step, and its cost is mostly network variance. sudo apt-get update - sudo apt-get install -y device-tree-compiler + sudo apt-get install -y --no-install-recommends device-tree-compiler dtc --version # Saturn's instruction decoder goes through Chisel's default minimizer, which uses From 1e33f0e558377b82a9d914b8e111ab3a30032086 Mon Sep 17 00:00:00 2001 From: Rohan Kumar Date: Mon, 17 Aug 2026 18:31:37 -0700 Subject: [PATCH 6/6] pin dtc to improve installation speed --- .github/workflows/ci.yml | 28 ++++++++++++++++++++++++---- 1 file changed, 24 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 56f9311..d7b62d1 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -160,12 +160,32 @@ jobs: # # Elaborating a subsystem shells out to the device tree compiler, which rocket-chip # runs to build the DTB the boot ROM embeds. It is not on the runner image. + # dtc is a 223 KB package, but installing it through apt costs an `apt-get update` + # first, which refreshes every source the runner image carries — tens of MB of + # indexes, and minutes of it when a mirror is slow. Fetch the package and its two + # dependencies straight from the archive pool instead. Pinned by version and + # checksum, which is also what makes the plain-HTTP fetch safe; apt verifies its + # downloads the same way. If the pool ever drops these versions the curl 404s and + # the apt path takes over. - name: Install the device tree compiler run: | - # The runner image ships no package lists, so the index refresh is not - # skippable — it is most of this step, and its cost is mostly network variance. - sudo apt-get update - sudo apt-get install -y --no-install-recommends device-tree-compiler + set -euo pipefail + pool=http://archive.ubuntu.com/ubuntu/pool/main + cd "$(mktemp -d)" + if curl -fsSLO "$pool/d/device-tree-compiler/device-tree-compiler_1.7.0-2build1_amd64.deb" \ + && curl -fsSLO "$pool/d/device-tree-compiler/libfdt1_1.7.0-2build1_amd64.deb" \ + && curl -fsSLO "$pool/liby/libyaml/libyaml-0-2_0.2.5-1build1_amd64.deb"; then + sha256sum --check <<'SUMS' + b2c1e8c86f18b6bda26408f92bfb9ec1a1e40bfdc41f1034600ccd68e82d2ed7 device-tree-compiler_1.7.0-2build1_amd64.deb + 274d20dfab9d6b216b5de85446a93f6ce5b2cd82c847b8dfdc508577f76eb96a libfdt1_1.7.0-2build1_amd64.deb + f5271b120d936dcc7ddf17b9e718df41d55386a6075555d0c634925eaef0b2ac libyaml-0-2_0.2.5-1build1_amd64.deb + SUMS + sudo dpkg -i ./*.deb + else + echo "Archive pool no longer serves the pinned versions; falling back to apt." + sudo apt-get update + sudo apt-get install -y --no-install-recommends device-tree-compiler + fi dtc --version # Saturn's instruction decoder goes through Chisel's default minimizer, which uses