/ build.mill
build.mill
  1  package build
  2  
  3  import mill._
  4  import mill.scalalib._
  5  import mill.javalib._
  6  
  7  import java.io.File
  8  import scala.sys.process._
  9  import mill.api.Task.Simple
 10  
 11  object hny2026 extends ScalaModule { module =>
 12    def scalaVersion = "2.13.18"
 13    val chiselVersion = "7.6.0"
 14    val scalaTestVersion = "3.2.19"
 15    val main = "hny2026.HNY2026"
 16  
 17    def scalacOptions = Seq(
 18      "-language:reflectiveCalls",
 19      "-language:implicitConversions",
 20      "-deprecation"
 21    )
 22  
 23    def mvnDeps = Seq(
 24      mvn"org.chipsalliance::chisel:$chiselVersion",
 25    )
 26  
 27    def scalacPluginMvnDeps = Seq(
 28      mvn"org.chipsalliance:::chisel-plugin:$chiselVersion",
 29    )
 30  
 31    object test extends ScalaTests with TestModule.ScalaTest {
 32      def mvnDeps = module.mvnDeps() ++ Seq(
 33        mvn"org.scalatest::scalatest::${module.scalaTestVersion}"
 34      )
 35    }
 36  }
 37  
 38  /**
 39   * Common build flow tasks.
 40   */
 41  trait Flow extends Module {
 42    /**
 43     * Optional top selection.
 44     */
 45    def top: Option[String] = None
 46  
 47    /**
 48     * Clock frequency in Hz.
 49     */
 50    def clockFrequency: Int
 51  
 52    /**
 53     * Override example:
 54     *
 55     * ```
 56     * override def staticSrc = Some(Task.Sources("src0.v", "src1.v", ...))
 57     * ```
 58     */
 59    def staticSrc: Option[Simple[Seq[PathRef]]] = None
 60  
 61    /**
 62     * Build Chisel project.
 63     *
 64     * @return    list of generated SV sources.
 65     */
 66    def generate = Task {
 67      hny2026.runner().run(Seq(s"clockFreq=$clockFrequency"), hny2026.main)
 68  
 69      File(Task.dest.toURI).listFiles{
 70        (_, fileName) => fileName.endsWith((".sv"))
 71      }.map(f => PathRef(os.Path(f)))
 72    }
 73  }
 74  
 75  /**
 76   * Gowin flow with Yosys and Nextpnr.
 77   */
 78  trait GowinFlow extends Flow {
 79    def family: String
 80    def device: String
 81  
 82    /**
 83     * Overriding example:
 84     *
 85     * ```
 86     * override def cstFile = Task.Source("resources/hny2026.cst")
 87     * ```
 88     */
 89    def cstFile: Simple[PathRef]
 90  
 91    /**
 92     * Synth with yosys.
 93     *
 94     * @return    path to json netlist.
 95     */
 96    def synth = Task {
 97      val out = Task.dest
 98      val synthJson = out / "synth.json"
 99      val genSrc = generate().map(_.path).mkString(" ")
100  
101      val stSrc = if (staticSrc.isDefined) {
102        (staticSrc.get)().map(_.path).mkString(" ")
103      } else ""
104  
105      val topCmd = top match {
106        case Some(t) => s"-top $t"
107        case _ => ""
108      }
109  
110      val yosysSlangPluginSo = sys.env.get("YOSYS_SLANG_SO")
111  
112      val (pluginArg, pluginCmd) = yosysSlangPluginSo match {
113        case Some(soName) => (Seq("-m", soName), "")
114        case _ => (Seq(), "plugin -i slang;")
115      }
116  
117      os.call((
118        "yosys", pluginArg, "-l", s"$out/yosys.log", "-p",
119        s"$pluginCmd read_slang $genSrc $stSrc; synth_gowin $topCmd -nowidelut -json $synthJson"
120      ))
121  
122      PathRef(synthJson)
123    }
124  
125    /**
126     * Place and route with nextpnr.
127     *
128     * @return    path to PnR json.
129     */
130    def pnr = Task {
131      val out = Task.dest
132      val pnrJson = out / "placenroute.json"
133      val synthJson = synth().path
134      val clockMhz = (clockFrequency.toDouble / 1000000).floor.toInt
135  
136      os.call((
137        "nextpnr-himbaechel",
138        "--json", synthJson,
139        "--write", pnrJson,
140        "--freq", s"$clockMhz",
141        "--device", device,
142        "--vopt", s"family=$family",
143        "--vopt", s"cst=${cstFile().path}",
144        "-l", s"$out/nextpnr.log"
145      ))
146  
147      PathRef(pnrJson)
148    }
149  
150    /**
151     * Build bitstream.
152     *
153     * @return    path to bitstream file.
154     */
155    def bitstream = Task {
156      val out = Task.dest
157      val bs = out / "bitstream.fs"
158      val pnrJson = pnr().path
159  
160      os.call(("gowin_pack", "-d", device, "-o", bs, pnrJson))
161  
162      PathRef(bs)
163    }
164  
165    /**
166     * Load bitstream into FPGA SRAM.
167     */
168    def load(args: String*) = Task.Command {
169      val bs = bitstream().path
170      os.call(
171        cmd = ("openFPGALoader", "-c", "ft2232", "-m", bs),
172        stdout = os.Inherit
173      )
174    }
175  
176    /**
177     * Birn FPGA flash with bitstream.
178     */
179    def burn(args: String*) = Task.Command {
180      val bs = bitstream().path
181      os.call(
182        cmd = ("openFPGALoader", "-c", "ft2232", "--unprotect-flash", "-f", bs),
183        stdout = os.Inherit
184      )
185    }
186  }
187  
188  /**
189   * TangNano 1K target.
190   *
191   * Build command.
192   *
193   * Generate SystemVerilog files from Chisel source:
194   * ```
195   * $ mill tangNano1k.generate
196   * ```
197   *
198   * Synthesize the source code using Yosys:
199   * ```
200   * $ mill tangNano1k.synth
201   * ```
202   *
203   * Place and route using Nextpnr:
204   * ```
205   * $ mill tangNano1k.pnr
206   * ```
207   *
208   * Build bitstream:
209   * ```
210   * $ mill tangNano1k.bitstream
211   * ```
212   *
213   * Load bitstream into FPGA's SRAM:
214   * ```
215   * $ mill tangNano1k.load
216   * ```
217   *
218   * Burn FPGA flash with a bistream:
219   * ```
220   * $ mill tangNano1k.burn
221   * ```
222   *
223   * Each subsequent command automatically calls the previous one, so when 'burn' is invoked, all
224   * required steps will be performed – SV generation, synthesis, PnR, and bitstream assembly.
225   */
226  object tangNano1k extends Module with GowinFlow {
227    def top = Some("hny2026_top")
228    def family = "GW1NZ-1"
229    def device = "GW1NZ-LV1QN48C6/I5"
230    def clockFrequency = 27000000
231  
232    def staticSrc = Some(Task.Sources("verilog/hny2026_top.v"))
233    def cstFile = Task.Source("resources/hny2026.cst")
234  }