From 1c71c5595880c7944ccf0d4dcabe72a56abc7e22 Mon Sep 17 00:00:00 2001 From: Quinn Unger Date: Wed, 18 Mar 2026 16:21:09 +0000 Subject: [PATCH] Full rework of fusesoc tooling (#1) * initial commit of reworked fusesoc nix tooling * add devShell output to mkFusesocCore * fix core output folder inference when running a tool flow * bump yosys-slang version --- flake.lock | 48 +++-------------- flake.nix | 37 +++++++++---- flakeModule.nix | 85 ----------------------------- lib/fusesocLib.nix | 34 ++++++++++++ lib/mkFusesocCore.nix | 114 +++++++++++++++++++++++++++++++++++++++ lib/runFusesocCore.nix | 119 +++++++++++++++++++++++++++++++++++++++++ pkgs/edalize.nix | 14 +++++ pkgs/fusesoc.nix | 11 ++++ pkgs/yosys-slang.nix | 41 ++++++++++++++ 9 files changed, 366 insertions(+), 137 deletions(-) delete mode 100644 flakeModule.nix create mode 100644 lib/fusesocLib.nix create mode 100644 lib/mkFusesocCore.nix create mode 100644 lib/runFusesocCore.nix create mode 100644 pkgs/edalize.nix create mode 100644 pkgs/fusesoc.nix create mode 100644 pkgs/yosys-slang.nix diff --git a/flake.lock b/flake.lock index e86af28..d00f69c 100644 --- a/flake.lock +++ b/flake.lock @@ -1,60 +1,24 @@ { "nodes": { - "flake-parts": { - "inputs": { - "nixpkgs-lib": "nixpkgs-lib" - }, - "locked": { - "lastModified": 1763759067, - "narHash": "sha256-LlLt2Jo/gMNYAwOgdRQBrsRoOz7BPRkzvNaI/fzXi2Q=", - "owner": "hercules-ci", - "repo": "flake-parts", - "rev": "2cccadc7357c0ba201788ae99c4dfa90728ef5e0", - "type": "github" - }, - "original": { - "owner": "hercules-ci", - "repo": "flake-parts", - "type": "github" - } - }, - "nixpkgs-lib": { - "locked": { - "lastModified": 1761765539, - "narHash": "sha256-b0yj6kfvO8ApcSE+QmA6mUfu8IYG6/uU28OFn4PaC8M=", - "owner": "nix-community", - "repo": "nixpkgs.lib", - "rev": "719359f4562934ae99f5443f20aa06c2ffff91fc", - "type": "github" - }, - "original": { - "owner": "nix-community", - "repo": "nixpkgs.lib", - "type": "github" - } - }, - "nixpkgs-lib_2": { + "nixpkgs": { "locked": { - "dir": "lib", - "lastModified": 1765311797, - "narHash": "sha256-mSD5Ob7a+T2RNjvPvOA1dkJHGVrNVl8ZOrAwBjKBDQo=", + "lastModified": 1773734432, + "narHash": "sha256-IF5ppUWh6gHGHYDbtVUyhwy/i7D261P7fWD1bPefOsw=", "owner": "Nixos", "repo": "nixpkgs", - "rev": "09eb77e94fa25202af8f3e81ddc7353d9970ac1b", + "rev": "cda48547b432e8d3b18b4180ba07473762ec8558", "type": "github" }, "original": { - "dir": "lib", "owner": "Nixos", - "ref": "nixos-25.11", + "ref": "nixos-unstable", "repo": "nixpkgs", "type": "github" } }, "root": { "inputs": { - "flake-parts": "flake-parts", - "nixpkgs-lib": "nixpkgs-lib_2" + "nixpkgs": "nixpkgs" } } }, diff --git a/flake.nix b/flake.nix index 5f97580..82c6ce5 100644 --- a/flake.nix +++ b/flake.nix @@ -1,21 +1,38 @@ { description = "Helper for a declarative fusesoc environment"; - # See: https://github.com/VTimofeenko/writing-flake-modules/blob/master/example-1-configurable-inputs-bumper/provider/flake.nix inputs = { - nixpkgs-lib.url = "github:Nixos/nixpkgs/nixos-25.11?dir=lib"; - flake-parts.url = "github:hercules-ci/flake-parts"; + nixpkgs.url = "github:Nixos/nixpkgs/nixos-unstable"; }; outputs = - inputs@{ flake-parts, ... }: - flake-parts.lib.mkFlake { inherit inputs; } ( - { flake-parts-lib, ... }: + inputs@{ self, nixpkgs, ... }: + let + inherit (nixpkgs) lib; + forEachSystem = systems: f: builtins.foldl' (lib.recursiveUpdate) { } (builtins.map (f) systems); + in + (forEachSystem [ "x86_64-linux" ] ( + system: + let + pkgs = nixpkgs.legacyPackages.${system}; + in { - flake.flakeModule = flake-parts-lib.importApply ./flakeModule.nix { - inherit flake-parts-lib; - inherit (inputs) nixpkgs-lib; + packages.${system} = rec { + yosys-slang = pkgs.callPackage ./pkgs/yosys-slang.nix { }; + + edalize = pkgs.callPackage ./pkgs/edalize.nix { }; + + fusesoc = pkgs.callPackage ./pkgs/fusesoc.nix { inherit edalize; }; + + fusesocLib = pkgs.callPackage ./lib/fusesocLib.nix { }; + + runFusesocCore = pkgs.callPackage ./lib/runFusesocCore.nix { inherit fusesoc fusesocLib; }; + + mkFusesocCore = pkgs.callPackage ./lib/mkFusesocCore.nix { + inherit fusesocLib runFusesocCore; + }; }; } - ); + )) + // { }; } diff --git a/flakeModule.nix b/flakeModule.nix deleted file mode 100644 index a6fb5ce..0000000 --- a/flakeModule.nix +++ /dev/null @@ -1,85 +0,0 @@ -{ nixpkgs-lib, flake-parts-lib, ... }: -let - inherit (flake-parts-lib) mkPerSystemOption; - inherit (nixpkgs-lib) lib; -in -{ - config, - self, - inputs, - ... -}: -{ - options.perSystem = mkPerSystemOption ( - { config, pkgs, ... }: - let - cfg = config.fusesoc-project; - in - { - # TODO: multi-project with .. pattern mapping to devShell. - options.fusesoc-project = - let - inherit (lib) types; - in - { - fusesocPkg = lib.mkOption { - type = types.package; - default = pkgs.fusesoc; - }; - withVerilator = lib.mkEnableOption "Include verilator"; - verilatorPkg = lib.mkOption { - type = types.package; - default = pkgs.verilator; - }; - withCcache = lib.mkEnableOption "Use ccache with verilator"; - ccachePkg = lib.mkOption { - type = types.package; - default = pkgs.ccache; - }; - sources = lib.mkOption { - type = types.attrsOf (types.either types.path types.str); - default = { }; - }; - extraPackages = lib.mkOption { - type = types.listOf types.package; - default = [ ]; - }; - }; - config = - let - mkFusesocLib = name: path: '' - [library.${name}] - location = ${path} - sync-uri = ${path} - sync-type = local - auto-sync = false - ''; - mkFusesocConf = sources: builtins.concatStringsSep "\n" (lib.mapAttrsToList (mkFusesocLib) sources); - in - { - devShells.default = - let - fusesocConf = pkgs.writeTextFile { - name = "fusesoc.conf"; - text = mkFusesocConf cfg.sources; - }; - fusesocWrapped = pkgs.writeShellScriptBin "fusesoc" '' - exec ${cfg.fusesocPkg}/bin/fusesoc --config ${fusesocConf} $@ - ''; - in - pkgs.mkShell { - packages = lib.concatLists [ - [ fusesocWrapped ] - (lib.optionals cfg.withVerilator [ - cfg.verilatorPkg - pkgs.zlib.dev # needed for generating fst traces - ]) - (lib.optional cfg.withCcache cfg.ccachePkg) - cfg.extraPackages - ]; - shellHook = lib.optionalString cfg.withCcache "export OBJCACHE=ccache"; - }; - }; - } - ); -} diff --git a/lib/fusesocLib.nix b/lib/fusesocLib.nix new file mode 100644 index 0000000..41a7576 --- /dev/null +++ b/lib/fusesocLib.nix @@ -0,0 +1,34 @@ +{ + lib, + remarshal, + runCommand, +}: +{ + parseVlnv = + vlnv: + let + split = lib.splitString ":" vlnv; + in + if builtins.length split == 4 then + { + vendor = builtins.elemAt split 0; + library = builtins.elemAt split 1; + name = builtins.elemAt split 2; + version = builtins.elemAt split 3; + } + else + builtins.throw "Unable to parse vlnv ${vlnv}. Expected format: `vendor:library:name:version`. If the core has no version use 0."; + readYAML = + path: + let + jsonFile = + runCommand "from-yaml-${builtins.baseNameOf path}" + { + nativeBuildInputs = [ remarshal ]; + } + '' + remarshal -k -if yaml -i "${path}" -of json -o "$out" + ''; + in + builtins.fromJSON (builtins.readFile jsonFile); +} diff --git a/lib/mkFusesocCore.nix b/lib/mkFusesocCore.nix new file mode 100644 index 0000000..e304c50 --- /dev/null +++ b/lib/mkFusesocCore.nix @@ -0,0 +1,114 @@ +{ + lib, + callPackage, + symlinkJoin, + runCommand, + writeText, + formats, + fusesocLib, + runFusesocCore, +}: +lib.extendMkDerivation { + constructDrv = symlinkJoin; + excludeDrvArgNames = [ + "core" + "corePath" + "coreName" + "buildInputs" + "dependencies" + "rtl" + ]; + excludeFunctionArgNames = [ ]; + extendDrvArgs = + let + yamlFormat = formats.yaml { }; + in + finalAttrs: + { + core, + corePath ? "", + coreName ? (fusesocLib.parseVlnv core.name).name, + buildInputs ? [ ], + dependencies ? [ ], + rtl ? [ ], + passthru ? { }, + ... + }@args: + let + parsed = fusesocLib.parseVlnv finalAttrs.passthru.core.name; + finalCore = + let + coreFile = yamlFormat.generate "${coreName}.core" finalAttrs.passthru.core; + in + runCommand "writeCore" { } '' + mkdir -p $out/${finalAttrs.passthru.corePath} + cp ${coreFile} $out/${finalAttrs.passthru.corePath}/${finalAttrs.passthru.coreName}.core + ''; + in + { + pname = + with parsed; + lib.concatStringsSep "_" [ + vendor + library + name + ]; + inherit (parsed) version; + passthru = passthru // { + inherit + core + corePath + coreName + buildInputs + dependencies + parsed + rtl + ; + run = lib.mapAttrs ( + n: v: + runFusesocCore ( + _: + let + inherit (finalAttrs.passthru) buildInputs dependencies; + in + { + inherit (finalAttrs.passthru) core; + target = n; + dependencies = dependencies ++ [ "${finalAttrs.finalPackage}" ]; + nativeBuildInputs = + if builtins.isList buildInputs then + buildInputs + else + buildInputs."${n}" or buildInputs.default or [ ]; + } + ) + ) finalAttrs.passthru.core.targets; + devShell = runFusesocCore ( + _: + let + inherit (finalAttrs.passthru) buildInputs dependencies; + in + { + inherit (finalAttrs.passthru) core; + target = + let + targets = builtins.attrNames finalAttrs.passthru.core.targets; + hasDefault = builtins.hasAttr "default" finalAttrs.passthru.core.targets; + in + if hasDefault then "default" else builtins.head (builtins.attrNames targets); + dependencies = dependencies ++ [ "${finalAttrs.finalPackage}" ]; + nativeBuildInputs = + if builtins.isList buildInputs then + buildInputs + else + builtins.concatMap (x: x.value) (lib.attrsToList buildInputs); + } + ); + }; + paths = + let + inherit (finalAttrs.passthru) rtl; + in + (if builtins.isList rtl then rtl else [ rtl ]) ++ [ finalCore ]; + }; +} diff --git a/lib/runFusesocCore.nix b/lib/runFusesocCore.nix new file mode 100644 index 0000000..103b567 --- /dev/null +++ b/lib/runFusesocCore.nix @@ -0,0 +1,119 @@ +{ + lib, + callPackage, + stdenv, + writeText, + writeScriptBin, + fusesoc, + fusesocLib, +}: +lib.extendMkDerivation { + constructDrv = stdenv.mkDerivation; + excludeDrvArgNames = [ + "core" + "target" + "extraArgs" + "dependencies" + "version" + "pname" + ]; + extendDrvArgs = + let + sanitize = + regex: str: + lib.concatStrings ( + builtins.map (x: if (builtins.match regex x) != null then x else "_") (lib.stringToCharacters str) + ); + mkLib = name: path: '' + [library.${sanitize "([a-zA-Z0-9])" name}] + location = ${path} + sync-uri = ${path} + sync-type = local + auto-sync = false + ''; + mkFusesocConf = + sources: + writeText "fusesoc.conf" ( + lib.concatLines + (builtins.foldl' + (acc: elem: { + count = acc.count + 1; + libs = acc.libs ++ [ + (mkLib "${builtins.baseNameOf "${elem}"}-${builtins.toString acc.count}" elem) + ]; + }) + { + count = 0; + libs = [ ]; + } + sources + ).libs + ); + in + finalAttrs: + { + core, + target ? "default", + extraArgs ? "", + dependencies, + nativeBuildInputs ? [ ], + passthru ? { }, + }@args: + let + parsed = fusesocLib.parseVlnv finalAttrs.passthru.core.name; + fusesocWrapped = + let + conf = mkFusesocConf finalAttrs.passthru.dependencies; + in + writeScriptBin "fusesoc" '' + exec ${fusesoc}/bin/fusesoc --config ${conf} $@ + ''; + in + { + pname = + with parsed; + lib.concatStringsSep "_" [ + vendor + library + "${name}-${finalAttrs.passthru.target}" + ]; + inherit (parsed) version; + passthru = passthru // { + inherit + dependencies + core + target + extraArgs + parsed + ; + }; + nativeBuildInputs = nativeBuildInputs ++ [ fusesocWrapped ]; + phases = [ + "buildPhase" + "installPhase" + ]; + buildPhase = + let + buildCmd = lib.concatStringsSep " " [ + "HOME=$TEMP" + "${fusesocWrapped}/bin/fusesoc" + "run --target ${finalAttrs.passthru.target}" + finalAttrs.passthru.extraArgs + finalAttrs.passthru.core.name + ]; + in + buildCmd; + installPhase = + let + vlnvDir = sanitize "([a-zA-Z0-9\.])" finalAttrs.passthru.core.name; + toolName = lib.flatten [ + finalAttrs.passthru.core.targets.${finalAttrs.passthru.target}.default_tool or [ ] + ]; + buildFolder = lib.concatStringsSep "-" ([ finalAttrs.passthru.target ] ++ toolName); + in + '' + mkdir -p $out + cp -r ./build/${vlnvDir}/${buildFolder}/* $out/ + ''; + }; +} diff --git a/pkgs/edalize.nix b/pkgs/edalize.nix new file mode 100644 index 0000000..2fe5c53 --- /dev/null +++ b/pkgs/edalize.nix @@ -0,0 +1,14 @@ +{ + python3Packages, + fetchFromGitHub, +}: +python3Packages.edalize.overridePythonAttrs (old: { + version = "0.6.4"; + src = fetchFromGitHub { + owner = "Mop-u"; + repo = "edalize"; + rev = "55b31ed3910c412c0a79e44d49136a435eb933f8"; + hash = "sha256-ub6Lrb5OXdPzv1hH/XFa/Wy6IEWjVBPUPPw5FXTYtUk="; + }; + dependencies = (old.dependencies or [ ]) ++ [ python3Packages.cocotb ]; +}) diff --git a/pkgs/fusesoc.nix b/pkgs/fusesoc.nix new file mode 100644 index 0000000..5aa36d9 --- /dev/null +++ b/pkgs/fusesoc.nix @@ -0,0 +1,11 @@ +{ + fusesoc, + edalize, + python3Packages, + fetchFromGithub, +}: +fusesoc.override { + python3Packages = python3Packages // { + inherit edalize; + }; +} diff --git a/pkgs/yosys-slang.nix b/pkgs/yosys-slang.nix new file mode 100644 index 0000000..7532788 --- /dev/null +++ b/pkgs/yosys-slang.nix @@ -0,0 +1,41 @@ +{ + lib, + fetchFromGitHub, + stdenv, + gnumake, + cmake, + python3, + yosys, +}: +stdenv.mkDerivation (finalAttrs: { + pname = "yosys-slang"; + version = "0-unstable-2026-03-16"; + plugin = "slang"; + + src = fetchFromGitHub { + owner = "povik"; + repo = "yosys-slang"; + rev = "8c82a4b7c4dff83d1d446610d8ee582aa094fd9d"; + hash = "sha256-wpOeskpD9kK0d163G1zn5atO4An80xNREaahmHFGPsY="; + fetchSubmodules = true; # Needed to fetch the pinned slang source + }; + + nativeBuildInputs = [ + gnumake + cmake + python3 + yosys + ]; + + installPhase = '' + mkdir -p $out/share/yosys/plugins + cp slang.so $out/share/yosys/plugins/slang.so + ''; + + meta = { + description = "Slang plugin for Yosys providing a SystemVerilog frontend."; + homepage = "https://github.com/povik/yosys-slang"; + license = lib.licenses.isc; + platforms = lib.platforms.all; + }; +}) -- 2.51.2