/ 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 }