commit d7704ae61e4f14250ff19fc64ca3a681c41962b2 Author: Nikolay Puzanov Date: Tue Jan 6 22:43:26 2026 +0300 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..0ef645f --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +.metals +.bsp +/out +/generated +/build diff --git a/README.md b/README.md new file mode 100644 index 0000000..30094b0 --- /dev/null +++ b/README.md @@ -0,0 +1,59 @@ +# HNY2026 - New Year's Greeting on FPGA + +A Chisel-based FPGA design that transmits a New Year's greeting message via LED patterns on a TangNano 1K development board. + +![Video](./video/video.mp4) + +## How It Works + +The greeting is encoded as: + +- Green LED = bit '0' +- Red LED = bit '1' +- 8-bit ASCII (LSB) + parity bit + empty bit + +## FPGA Workflow (TangNano 1K) + +**Full build and load to FPGA SRAM:** +```bash +mill tangNano1k.load +``` + +**Full build and burn FPGA flash:** +```bash +mill tangNano1k.burn +``` + +**Step-by-step:** +```bash +mill tangNano1k.generate # Generate SystemVerilog +mill tangNano1k.synth # Synthesize with Yosys +mill tangNano1k.pnr # Place and route with Nextpnr +mill tangNano1k.bitstream # Generate bitstream +mill tangNano1k.load # Load to FPGA SRAM +mill tangNano1k.burn # Burn to FPGA flash +``` + +**Run all unit tests:** +```bash +mill hny2026.test +``` + +## Requirements + +- Scala and [Mill](https://mill-build.org/mill/cli/installation-ide.html) +- [Yosys](https://github.com/YosysHQ/yosys) with [Slang plugin](https://github.com/povik/yosys-slang) +- [Nextpnr](https://github.com/YosysHQ/nextpnr) +- Gowin pack tool (from [Apicula](https://github.com/YosysHQ/apicula) package) +- [openFPGALoader](https://github.com/trabucayre/openFPGALoader) +- [TangNano 1K](https://wiki.sipeed.com/hardware/en/tang/Tang-Nano-1K/Nano-1k.html) development board (GW1NZ-LV1QN48C6/I5) + +Yosys and other utilities can be taken from the [OSS CAD Suite](https://github.com/YosysHQ/oss-cad-suite-build) package or installed using the package manager [Nix](https://nixos.org/download/): + +```bash +nix-shell shell.nix +``` + +## Decoding the Message + +Capture the LED blinking on video (30 fps recommended), then extract the bit pattern: green=0, red=1. diff --git a/build.mill b/build.mill new file mode 100644 index 0000000..15a8154 --- /dev/null +++ b/build.mill @@ -0,0 +1,229 @@ +package build + +import mill._ +import mill.scalalib._ +import mill.javalib._ + +import java.io.File +import scala.sys.process._ +import mill.api.Task.Simple + +object hny2026 extends ScalaModule { module => + def scalaVersion = "2.13.18" + val chiselVersion = "7.6.0" + val scalaTestVersion = "3.2.19" + val main = "hny2026.HNY2026" + + def scalacOptions = Seq( + "-language:reflectiveCalls", + "-language:implicitConversions", + "-deprecation" + ) + + def mvnDeps = Seq( + mvn"org.chipsalliance::chisel:$chiselVersion", + ) + + def scalacPluginMvnDeps = Seq( + mvn"org.chipsalliance:::chisel-plugin:$chiselVersion", + ) + + object test extends ScalaTests with TestModule.ScalaTest { + def mvnDeps = module.mvnDeps() ++ Seq( + mvn"org.scalatest::scalatest::${module.scalaTestVersion}" + ) + } +} + +/** + * Common build flow tasks. + */ +trait Flow extends Module { + /** + * Optional top selection. + */ + def top: Option[String] = None + + /** + * Clock frequency in Hz. + */ + def clockFrequency: Int + + /** + * Override example: + * + * ``` + * override def staticSrc = Some(Task.Sources("src0.v", "src1.v", ...)) + * ``` + */ + def staticSrc: Option[Simple[Seq[PathRef]]] = None + + /** + * Build Chisel project. + * + * @return list of generated SV sources. + */ + def generate = Task { + hny2026.runner().run(Seq(s"clockFreq=$clockFrequency"), hny2026.main) + + File(Task.dest.toURI).listFiles{ + (_, fileName) => fileName.endsWith((".sv")) + }.map(f => PathRef(os.Path(f))) + } +} + +/** + * Gowin flow with Yosys and Nextpnr. + */ +trait GowinFlow extends Flow { + def family: String + def device: String + + /** + * Overriding example: + * + * ``` + * override def cstFile = Task.Source("resources/hny2026.cst") + * ``` + */ + def cstFile: Simple[PathRef] + + /** + * Synth with yosys. + * + * @return path to json netlist. + */ + def synth = Task { + val out = Task.dest + val synthJson = out / "synth.json" + val genSrc = generate().map(_.path).mkString(" ") + + val stSrc = if (staticSrc.isDefined) { + (staticSrc.get)().map(_.path).mkString(" ") + } else "" + + val topCmd = top match { + case Some(t) => s"-top $t" + case _ => "" + } + + val yosysSlangPlugin = sys.env("YOSYS_SLANG_SO") + + os.call(( + "yosys", "-m", yosysSlangPlugin, "-l", s"$out/yosys.log", "-p", + s"read_slang $genSrc $stSrc; synth_gowin $topCmd -nowidelut -json $synthJson" + )) + + PathRef(synthJson) + } + + /** + * Place and route with nextpnr. + * + * @return path to PnR json. + */ + def pnr = Task { + val out = Task.dest + val pnrJson = out / "placenroute.json" + val synthJson = synth().path + val clockMhz = (clockFrequency.toDouble / 1000000).floor.toInt + + os.call(( + "nextpnr-himbaechel", + "--json", synthJson, + "--write", pnrJson, + "--freq", s"$clockMhz", + "--device", device, + "--vopt", s"family=$family", + "--vopt", s"cst=${cstFile().path}", + "-l", s"$out/nextpnr.log" + )) + + PathRef(pnrJson) + } + + /** + * Build bitstream. + * + * @return path to bitstream file. + */ + def bitstream = Task { + val out = Task.dest + val bs = out / "bitstream.fs" + val pnrJson = pnr().path + + os.call(("gowin_pack", "-d", device, "-o", bs, pnrJson)) + + PathRef(bs) + } + + /** + * Load bitstream into FPGA SRAM. + */ + def load(args: String*) = Task.Command { + val bs = bitstream().path + os.call( + cmd = ("openFPGALoader", "-c", "ft2232", "-m", bs), + stdout = os.Inherit + ) + } + + /** + * Birn FPGA flash with bitstream. + */ + def burn(args: String*) = Task.Command { + val bs = bitstream().path + os.call( + cmd = ("openFPGALoader", "-c", "ft2232", "--unprotect-flash", "-f", bs), + stdout = os.Inherit + ) + } +} + +/** + * TangNano 1K target. + * + * Build command. + * + * Generate SystemVerilog files from Chisel source: + * ``` + * $ mill tangNano1k.generate + * ``` + * + * Synthesize the source code using Yosys: + * ``` + * $ mill tangNano1k.synth + * ``` + * + * Place and route using Nextpnr: + * ``` + * $ mill tangNano1k.pnr + * ``` + * + * Build bitstream: + * ``` + * $ mill tangNano1k.bitstream + * ``` + * + * Load bitstream into FPGA's SRAM: + * ``` + * $ mill tangNano1k.load + * ``` + * + * Burn FPGA flash with a bistream: + * ``` + * $ mill tangNano1k.burn + * ``` + * + * Each subsequent command automatically calls the previous one, so when 'burn' is invoked, all + * required steps will be performed – SV generation, synthesis, PnR, and bitstream assembly. + */ +object tangNano1k extends Module with GowinFlow { + def top = Some("hny2026_top") + def family = "GW1NZ-1" + def device = "GW1NZ-LV1QN48C6/I5" + def clockFrequency = 27000000 + + def staticSrc = Some(Task.Sources("verilog/hny2026_top.v")) + def cstFile = Task.Source("resources/hny2026.cst") +} diff --git a/hny2026/src/Hny2026.scala b/hny2026/src/Hny2026.scala new file mode 100644 index 0000000..5eec238 --- /dev/null +++ b/hny2026/src/Hny2026.scala @@ -0,0 +1,198 @@ +package hny2026 + +import circt.stage.ChiselStage +import chisel3._ +import chisel3.util._ + +/** + * Generates a single-cycle strobe pulse at a rate determined by the supplied configuration. The + * strobe is used to time the transmission of bits from a character to the LED driver in {@link + * CharSender}. + * + *

The strobe generator works by counting clock cycles so that a pulse occurs once every + * cntToInt cycles plus a fractional adjustment. The fractional part is implemented by + * a second counter that counts fracCntTo pulses before a full cntToInt + * cycle is considered complete. This yields a frame rate accurate to within {@code + * frameRateAccuracy}.

+ * + * @param cfg configuration that contains the clock frequency, frame rate and accuracy target. + */ +class StrobeGenerator(cfg: HnyConfig) extends Module { + val strobe = IO(Output(Bool())) + + val cntTo = (((cfg.clockFreq / cfg.frameRate) / cfg.frameRateAccuracy).round * cfg.frameRateAccuracy) + val cntToInt = cntTo.round + val cntInt = RegInit(0.U(log2Up(cntToInt+1).W)) + val cntIntDone = cntInt === 0.U + val fracPart = cntTo - cntToInt + val cntFracDone = Wire(Bool()) + + strobe := cntIntDone + + if (fracPart != 0) { + val fracCntTo = (1 / fracPart.abs).toInt + val fracCnt = RegInit(0.U(log2Up(fracCntTo).W)) + + when(cntIntDone) { + when(cntFracDone) { + fracCnt := 0.U + } otherwise { + fracCnt := fracCnt + 1.U + } + } + + cntFracDone := fracCnt === (fracCntTo-1).U + } else { + cntFracDone := false.B + } + + when(cntIntDone) { + when(cntFracDone) { + cntInt := { + if (fracPart > 0) + cntToInt.U + else + (cntToInt - 2).U + } + } otherwise { + cntInt := (cntToInt - 1).U + } + } otherwise { + cntInt := cntInt - 1.U + } +} + +/** + * Convenience constructor for a strobe generator. + * + * @param cfg configuration for the strobe generator. + * @return a {@code Bool} that goes high for one cycle at the desired frame rate. + */ +object StrobeGenerator { + def apply(cfg: HnyConfig): Bool = { + val sg = Module(new StrobeGenerator(cfg)) + sg.strobe + } +} + +/** + * Sends a single byte of data to the LED matrix. The module implements a 1-bit wide serial output + * using the two signals {@code one} and {@code zero}. When the {@code strobe} from {@link + * StrobeGenerator} goes high, the next bit of the current character is shifted out. The MSB is a + * parity bit that is the XOR of all data bits. The last bit is a flag that indicates + * that the character has been fully transmitted. + * + * @param cfg configuration that defines the data width and other timing parameters. + */ +class CharSender(cfg: HnyConfig) extends Module { + val io = IO(new Bundle { + val data = Flipped(Decoupled(UInt(cfg.dataWidth.W))) + val one = Bool() + val zero = Bool() + }) + + val strobe = StrobeGenerator(cfg) + val send = RegInit(false.B) + val data = Reg(Bits((cfg.dataWidth+2).W)) // Extra bits for parity and done flag + val one = RegInit(false.B) + val zero = RegInit(false.B) + val parity = io.data.bits.xorR + + io.data.ready := !send + io.one := one + io.zero := zero + + when(send) { + when(strobe) { + data := data >> 1 + + when(data.head(data.getWidth-1) === 0.U) { + send := false.B + } otherwise { + one := data(0) + zero := ~data(0) + } + } + } otherwise { + one := false.B + zero := false.B + + when(io.data.valid) { + data := true.B ## parity ## io.data.bits + send := true.B + } + } +} + +/** + * Top-level module that drives the RGB LEDs to display a string. Each character of the provided + * string is sent to the LED driver one after the other. The module uses a {@link CharSender} + * instance for the actual bit-streaming and routes the {@code one} and {@code zero} signals to the + * red and green LEDs respectively. + * + * @param cfg configuration that controls the serial timing and data width. + * @param str string to display on the LED matrix. + */ +class HNY2026(cfg: HnyConfig, str: String) extends Module { + val io = IO(new Bundle { + val ledR = Output(Bool()) + val ledG = Output(Bool()) + val ledB = Output(Bool()) + }) + + val sender = Module(new CharSender(cfg)) + val chars = VecInit(str.map(c => c.toByte.U(cfg.dataWidth.W))) + val charCnt = RegInit(UInt(log2Up(str.length()).W), 0.U) + + sender.io.data.valid := true.B + sender.io.data.bits := chars(charCnt) + + when(sender.io.data.ready) { + when(charCnt === (chars.length - 1).U) { + charCnt := 0.U + } otherwise { + charCnt := charCnt + 1.U + } + } + + io.ledR := sender.io.one + io.ledG := sender.io.zero + io.ledB := false.B +} + +/** + * Entry point that parses command-line arguments, creates a {@link HnyConfig} instance and emits + * the corresponding SystemVerilog file for the {@link HNY2026} module. + * + *
+ *   mill hny2026.runMain hny2026.HNY2026
+ * 
+ * + * Available arguments: + * + */ +object HNY2026 extends App { + val argsMap = args.map { s => + val ss = s.split("=") + if (ss.length == 1) + (ss(0), "") + else + (ss(0), ss(1)) + }.toMap + + val clockFreq = argsMap.getOrElse("clockFreq", "27000000").toInt + println(s"Clock frequency = $clockFreq Hz") + + val cfg = HnyConfig( + clockFreq = clockFreq, + frameRate = 30, + frameRateAccuracy = 0.0001, + dataWidth = 8 + ) + + ChiselStage.emitSystemVerilogFile( + new HNY2026(cfg, "HNY2026! Vsem dobra :)") + ) +} diff --git a/hny2026/src/HnyConfig.scala b/hny2026/src/HnyConfig.scala new file mode 100644 index 0000000..080b8d2 --- /dev/null +++ b/hny2026/src/HnyConfig.scala @@ -0,0 +1,35 @@ +package hny2026 + +import chisel3._ +import chisel3.util._ + +/** + * Configuration container for the HNY2026 design. + * + *

The parameters control the timing of the strobe generator and the serial transmission of + * characters to the LED matrix.

+ * + * @param clockFreq The frequency of the input clock in hertz. + * Default is 27 MHz. + * @param frameRate Desired frame rate in frames-per-second. + * The design uses this to calculate the + * strobe period; e.g. 30 fps is the + * default for my phone camera. + * @param frameRateAccuracy The acceptable relative error on the frame + * rate. A smaller value yields a more + * accurate strobe but may increase the + * hardware resource usage. The default + * (0.0001) gives <0.01 % error. + * @param dataWidth Width in bits of the data payload for each + * character. The default is 8 bits, matching + * an ASCII byte. The actual transmitted + * stream is `dataWidth + 2` bits, the + * additional bits being a parity bit and a + * empty ending bit. + */ +case class HnyConfig( + clockFreq: Int = 27000000, + frameRate: Double = 30.0, + frameRateAccuracy: Double = 0.0001, + dataWidth: Int = 8 +) diff --git a/hny2026/src/TestGen.scala b/hny2026/src/TestGen.scala new file mode 100644 index 0000000..c305b56 --- /dev/null +++ b/hny2026/src/TestGen.scala @@ -0,0 +1,44 @@ +package hny2026 + +import circt.stage.ChiselStage +import chisel3._ +import chisel3.util._ + +/** + * Run: mill hny2026.runMain hny2026.TestGen_StrobeGenerator + */ +object TestGen_StrobeGenerator extends App { + println(ChiselStage.emitSystemVerilog( + new StrobeGenerator(HnyConfig(27000000, 30.3)), + firtoolOpts = Array( + "--disable-all-randomization", + "--strip-debug-info" + ) + )) +} + +/** + * Run: mill hny2026.runMain hny2026.TestGen_CharSender + */ +object TestGen_CharSender extends App { + println(ChiselStage.emitSystemVerilog( + new CharSender(HnyConfig(27000000, 30.3)), + firtoolOpts = Array( + "--disable-all-randomization", + "--strip-debug-info" + ) + )) +} + +/** + * Run: mill hny2026.runMain hny2026.TestGen_HNY2026 + */ +object TestGen_HNY2026 extends App { + println(ChiselStage.emitSystemVerilog( + new HNY2026(HnyConfig(27000000, 30.3), "Hello!"), + firtoolOpts = Array( + "--disable-all-randomization", + "--strip-debug-info" + ) + )) +} diff --git a/hny2026/test/src/Hny2026.scala b/hny2026/test/src/Hny2026.scala new file mode 100644 index 0000000..e77ba66 --- /dev/null +++ b/hny2026/test/src/Hny2026.scala @@ -0,0 +1,185 @@ +package hny2026.tests + +import chisel3._ +import chisel3.simulator.scalatest.ChiselSim +import chisel3.simulator.HasSimulator +import svsim.verilator.Backend.CompilationSettings +import org.scalatest.flatspec.AnyFlatSpec +import scala.util.Random + +import hny2026._ + +/** + * Run all tests: + * $ mill hny2026.test + * + * Run only this test: + * $ mill hny2026.test.testOnly hny2026.tests.StrobeGeneratorTest + */ +class StrobeGeneratorTest extends AnyFlatSpec with ChiselSim { + val clockFreq = 10000 + + // Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957) + implicit val verilator: HasSimulator = HasSimulator.simulators + .verilator(verilatorSettings = + CompilationSettings.default.withTraceStyle( + Some( + CompilationSettings.TraceStyle( + kind = CompilationSettings.TraceKind.Vcd)))) + + /** + * Use Chisel testbench because ChiselSim don't support fork-join constructions. + * + * From documentation (https://www.chisel-lang.org/docs/appendix/migrating-from-chiseltest): + * > ChiselSim also does not currently have any support for fork-join, so any tests + * > using those constructs will need to be rewritten in a single-threaded manner. + * + * @param frameRate + */ + class StrobeGeneratorTB(frameRate: Double) extends Module { + val io = IO(new Bundle { + val duration = Input(UInt(32.W)) + val strobeCount = Output(UInt(32.W)) + val start = Input(Bool()) + val done = Output(Bool()) + }) + + val strobe = StrobeGenerator(HnyConfig(clockFreq, frameRate)) + val cycles = RegInit(chiselTypeOf(io.duration), 0.U) + val strobes = RegInit(chiselTypeOf(io.strobeCount), 0.U) + val count = RegInit(false.B) + val done = RegInit(false.B) + + io.strobeCount := strobes + io.done := done + + when(!done) { + when(count) { + when(cycles === io.duration) { + count := false.B + done := true.B + } + } otherwise { + when(io.start) { + count := true.B + } + } + } + + when(count) { + cycles := cycles + 1.U + when(strobe) { + strobes := strobes + 1.U + } + } + } + + /** + * TODO: Add description + * + * @param dut + * @param frameRate + * @return + */ + def check(dut: StrobeGeneratorTB)(implicit frameRate: Double): Unit = { + val scale = if ((clockFreq / frameRate).isWhole) 10 else 100 + val expect = (frameRate * scale).round.toInt + val duration = (clockFreq * scale).toInt + + enableWaves() + dut.io.duration.poke(duration) + dut.io.start.poke(true) + dut.clock.stepUntil(dut.io.done, 1, duration + 10) + + val strobes = dut.io.strobeCount.peek().litValue + + println(s"$strobes strobes counted. Expected [${expect-1}..${expect+1}]") + assert((strobes >= expect - 1) && (strobes <= expect + 1)) + } + + behavior of "StrobeGenerator" + + it should "produce 25 strobes per second" in { + implicit val frameRate = 25.0 + simulate(new StrobeGeneratorTB(frameRate))(check) + } + + it should "produce 30 strobes per second" in { + implicit val frameRate = 30.0 + simulate(new StrobeGeneratorTB(frameRate))(check) + } + + it should "produce 30.1 strobes per second" in { + implicit val frameRate = 30.1 + simulate(new StrobeGeneratorTB(frameRate))(check) + } + + it should "produce 30.4 strobes per second" in { + implicit val frameRate = 30.4 + simulate(new StrobeGeneratorTB(frameRate))(check) + } +} + +/** + * Run only this test: + * $ mill hny2026.test.testOnly hny2026.tests.CharSenderTest + */ +class CharSenderTest extends AnyFlatSpec with ChiselSim { + val cfg = HnyConfig(1000, 25) + val dataLength = 10 + + // Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957) + implicit val verilator: HasSimulator = HasSimulator.simulators + .verilator(verilatorSettings = + CompilationSettings.default.withTraceStyle( + Some( + CompilationSettings.TraceStyle( + kind = CompilationSettings.TraceKind.Vcd)))) + + behavior of "CharSender" + + it should s"send $dataLength random bytes" in { + simulate(new CharSender(cfg)) { dut => + enableWaves() + val data = List.fill(dataLength)(Random.nextInt(Math.pow(2, cfg.dataWidth).toInt)) + + data.foreach { x => + dut.io.data.bits.poke(x) + dut.io.data.valid.poke(true.B) + dut.clock.step() + dut.clock.stepUntil(dut.io.data.ready, 1, 1000) + dut.io.data.valid.poke(false.B) + } + + dut.clock.step((cfg.clockFreq / cfg.frameRate * cfg.dataWidth * 2).toInt) + println("See results in the wave diagram.") + } + } +} + +/** + * Run only this test: + * $ mill hny2026.test.testOnly hny2026.tests.HNY2026Test + */ +class HNY2026Test extends AnyFlatSpec with ChiselSim { + val cfg = HnyConfig(1000, 25) + val str = "Hello!" + + // Enable tracing (see: https://github.com/chipsalliance/chisel/discussions/3957) + implicit val verilator: HasSimulator = HasSimulator.simulators + .verilator(verilatorSettings = + CompilationSettings.default.withTraceStyle( + Some( + CompilationSettings.TraceStyle( + kind = CompilationSettings.TraceKind.Vcd)))) + + behavior of "HNY2026" + + it should s"send string '$str'" in { + simulate(new HNY2026(cfg, str)) { dut => + enableWaves() + dut.clock.step((cfg.clockFreq / cfg.frameRate * cfg.dataWidth * str.length() * 2).toInt) + println("See results in the wave diagram.") + } + } +} diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000..5fbd1e0 --- /dev/null +++ b/shell.nix @@ -0,0 +1,46 @@ +{ pkgs ? import {} }: + +with pkgs; +let yosys-slang = + stdenv.mkDerivation (finalAttrs: { + pname = "yosys-slang"; + version = "64b44616a3798f07453b14ea03e4ac8a16b77313"; + + src = fetchFromGitHub { + owner = "povik"; + repo = "yosys-slang"; + rev = "${finalAttrs.version}"; + sha256 = "sha256-kfu59/M3+IM+5ZMd+Oy4IZf4JWuVtPDlkHprk0FB8t4="; + fetchSubmodules = true; + }; + + buildInputs = [ + yosys + cmake + python3 + ]; + + installPhase = '' + mkdir -p $out/lib + cp slang.so $out/lib/slang.so + ''; + }); +in +mkShell { + packages = [ + gnumake boost zlib patchelf mill verilator haskellPackages.sv2v + yosys yosys-slang nextpnr python313Packages.apycula openfpgaloader + ]; + + shellHook = '' + ## Shell name + export NIX_SHELL_NAME="[hny2026]" + export YOSYS_SLANG_SO="${yosys-slang}/lib/slang.so" + + find $HOME/.cache/llvm-firtool/ -type f -executable -exec patchelf --set-interpreter ${pkgs.glibc}/lib64/ld-linux-x86-64.so.2 {} \; + find $HOME/.cache/llvm-firtool/ -type f -executable -exec patchelf --add-needed ${zlib}/lib/libz.so.1 {} \; + + find $HOME/.cache/mill/ -type f -executable -exec patchelf --set-interpreter ${pkgs.glibc}/lib64/ld-linux-x86-64.so.2 {} \; + find $HOME/.cache/mill/ -type f -executable -exec patchelf --add-needed ${zlib}/lib/libz.so.1 {} \; + ''; +} diff --git a/tangNano1k/resources/hny2026.cst b/tangNano1k/resources/hny2026.cst new file mode 100644 index 0000000..be65d02 --- /dev/null +++ b/tangNano1k/resources/hny2026.cst @@ -0,0 +1,18 @@ +//Copyright (C)2014-2021 Gowin Semiconductor Corporation. +//All rights reserved. +//File Title: Physical Constraints file +//GOWIN Version: 1.9.8 +//Part Number: GW1NZ-LV1QN48C6/I5 +//Device: GW1NZ-1 +//Created Time: Thu 09 16 14:45:08 2021 + +IO_LOC "led[2]" 11; +IO_PORT "led[2]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8; +IO_LOC "led[1]" 10; +IO_PORT "led[1]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8; +IO_LOC "led[0]" 9; +IO_PORT "led[0]" IO_TYPE=LVCMOS33 PULL_MODE=UP DRIVE=8; +IO_LOC "sys_rst_n" 13; +IO_PORT "sys_rst_n" IO_TYPE=LVCMOS33 PULL_MODE=UP; +IO_LOC "sys_clk" 47; +IO_PORT "sys_clk" IO_TYPE=LVCMOS33 PULL_MODE=UP; diff --git a/tangNano1k/verilog/hny2026_top.v b/tangNano1k/verilog/hny2026_top.v new file mode 100644 index 0000000..ad0e41c --- /dev/null +++ b/tangNano1k/verilog/hny2026_top.v @@ -0,0 +1,26 @@ +// %SOURCE_FILE_HEADER% +// + +module hny2026_top ( + input wire sys_clk, + input wire sys_rst_n, + // 0 - R, 1 - B, 2 - G + output wire [2:0] led +); + + reg [2:0] rst_sync; + wire reset = ~rst_sync[0]; + always @(posedge sys_clk) rst_sync <= {sys_rst_n, rst_sync[2:1]}; + + wire [2:0] led_inv; + assign led = ~led_inv; + + HNY2026 hny2026 ( + .clock(sys_clk), + .reset(reset), + .io_ledR(led_inv[0]), + .io_ledG(led_inv[2]), + .io_ledB(led_inv[1]) + ); + +endmodule // hny2026_top diff --git a/video/video.mp4 b/video/video.mp4 new file mode 100644 index 0000000..54edab7 Binary files /dev/null and b/video/video.mp4 differ