Skip to content

MatthieuMichon/aoc-rtl

Folders and files

NameName
Last commit message
Last commit date

Latest commit

Β 

History

276 Commits
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 
Β 

Repository files navigation

License: MIT

Advent of Code on FPGA

Below are some designs solving select Advent of Code (AoC) puzzles.

Design Philosophy

Each puzzle follows a common base structure illustrated below:

flowchart

mk["Makefile"]
txt["Puzzle Input File"]
tcl["Vivado TCL Script"]
ans["(stdout) Puzzle Answer"]

subgraph shell["Shell Module (shell.sv)"]
  tap["BSCANE2"]
  subgraph usr["User Logic (user_logic.sv)"]
    tap-dec["TAP Decoder (tap_decoder.sv)"]
    log["Puzzle Solving Logic"]
    tap-enc["TAP Encoder (tap_encoder.sv)"]
  end
end

mk -- Invokes Vivado --> tcl
txt -- File Contents --> tcl
tcl <-- JTAG Interface --> tap
tcl -- Result --> ans
tap --JTAG-TAP--> tap-dec
tap-dec -- Puzzle Input Bytes --> log
log -- Computed Result --> tap-enc
tap-enc --JTAG-TAP--> tap
Loading

These implementations share the following common features:

  • All the puzzle FPGA firmwares only use the JTAG interface. The vivado.tcl TCL script uses the scan_dr_hw_jtag command for transfering the puzzle input contents via a BSCANE2 primitive instantiated in the firmware in the shell.sv top-level module
    • Puzzle contents are NOT embedded in the firmware bitstream
    • No UART interfaces are used
    • No IOs nor external clocks are used (see constraints.xdc), thus all the puzzle firmwares are agnostic of the board
  • Source code in plain/vanilla SystemVerilog
  • The puzzle firmwares can be built and run on any board featuring a Xilinx 7-series FPGA, assuming the device density is enough to fit the design
  • All the puzzles are solved on-board in a fraction of seconds, simulating most of them takes a few seconds with Verilator (compilation times usually largely being a bit longer)

Porting to Other Targets

The default target device is a Xilinx Zynq 7020. Different devices or families may have a different JTAG chains (with or without ARM DAP and PS TAP, single die or multi-SLR) resulting in different instruction register (IR) and data register (DR) lengths. Both are defined in the vivado.tcl script:

set zynq7_ir_length 10; # must match FPGA device family / SLR count
set zynq7_ir_user4 0x3e3; # same thing
set zynq7_dr_length_byte 9; # zynq7: extra bit for ARM DAP bypass reg

Due to the usage of a BSCANE2 primitive, small changes may be required to port the design to UltraScale devices (if I recall correctly, these devices use a different BSCANE2 clock constraint: INTERNAL_TCK). For Versal families, the porting may be more involved with BSCANE2 primitives being superseded by the CIPS component.

Porting to other vendors should also be straightforward, as the design is written in System Verilog and the only primitive requiring instantiating is a JTAG TAP controller and updating the three variables mentioned above.

Tools Used

  • Text editors: Zed, VSCodium, PyCharm
  • FPGA tools: Vivado 2025.2, Yosys online schematic viewer
  • Simulators: Icarus Verilog 12, Verilator 5, Vivado 2025.2 (xsim)
  • VCD viewers: Surfer, Vaporview, GTKWave

Getting Started

Each puzzle directory includes a Makefile supporting the following make targets.

Simulation with Icarus Verilog

make isim [INPUT_FILE=input.txt]
  • INPUT_FILE: puzzle contents input file, default is input.txt

Simulation with Verilator

make sim [INPUT_FILE=input.txt]
  • INPUT_FILE: puzzle contents input file, default is input.txt

Simulation with Vivado Xsim

Note

The Vivado xsim simuation target is only available on select puzzles (the ones in the 15/ directory).

make xsim [INPUT_FILE=input.txt]
  • INPUT_FILE: puzzle contents input file, default is input.txt

Synthesis / configuration / execution with Vivado

make synth [VVD_MODE=batch] [PART=xc7z020clg484-1] [VVD_TASK=all] [INPUT_FILE=input.txt]
  • VVD_MODE: Vivado invocation mode, default batch
  • PART: FPGA targeted part, default is xc7z020clg484-1
  • VVD_TASK: tasks executed in the vivado.tcl, default is all
    • all: all tasks below except lint
    • build: synthesis, pnr and bitstream generation
    • program: configures FPGA with current bitstream
    • run: program, load puzzle contents into the FPGA and readback results
    • lint: run the Vivado linting tool
  • INPUT_FILE: puzzle contents input file, default is input.txt

Python Exploration Script

Available in select puzzles.

explore.py [filename]
  • filename: puzzle contents input file, default is input.txt

Puzzles

2025 Season

I opted to focus my efforts on the first part of the puzzles, thus most of them haven't the second part done.

Puzzle Simulation Synthesis On-board Remarks
1.1 🟑 Design creation 🟒 Integrate BSCANE2 primitive 🟑 Get familiar with JTAG TAP First attempts were rough
1.2 🟑 Modulo arithmetics πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Part 2
4.1 🟑 Used smarter algorithm πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Two-dimensional neighboors comparison
5.1 πŸ”΅ Brute force approach 🟑 Barely fits in a Zynq-7020 πŸ”΅ Right out of the box Comparison of value ranges
5.2 🟑 Wrong initial intuition 🟒 Barely fits in a Zynq-7020 πŸ”΅ Right out of the box Part 2
6.1 🟒 Simple array-based design 🟑 Some rework required πŸ”΅ Right out of the box Arithmetics
7.1 πŸ”΅ Combinatorial algorithm πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Binary graph
9.1 🟒 Storage and readback πŸ”΅ Synthesized right away 🟒 Initialy got a sim/syn mismatch Mismatch due to non initialized enum types
10.1 πŸ”΄ Forgot to check a blind side πŸ”΅ Synthesized right away πŸ”΅ Initialy got a sim mismatch Processing load fan-out accross multiple units making it running at line rate
11.1 ⚫ Hello dynamic programming ⚫ cursed DPRAM inference ⚫ Had to fix sim / synth mismatch DAG with bottom-up dynamic programming 🀯

2015 Season

Figured I shall start from the ground up.

Puzzle Simulation Synthesis On-board Remarks
1.1 πŸ”΅ Added Xilinx Xsim πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Didn't expect xsim to be so pedantic
1.2 πŸ”΅ Simple changes πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Easiest part 2
2.1 πŸ”΅ Math ops pipelining πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Read description a bit too fast
2.2 πŸ”΅ Math ops pipelining πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Even easier part 2
3.1 πŸ”΅ Quite simple πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Completely overhauled the JTAG TAP serialization logic
3.2 🟒 Got a reset corner-case πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Mismatch from reset handling
4.1 🟑 MD5 seriously?! 🧐 🟑 Initialy got a 78-level deep timing path πŸ™ƒ 🟑 Beware of Synth 8-87 Had a lot of fun with this one πŸ™ƒ
4.2 🟒 Superscalar version of the above 🟒 FPGA is fully packed πŸ”΅ Forgot changing to six leading zeroes Used a free-running clock cfgclk for faster processing
5.1 πŸ”΅ A breeze compared to the previous puzzles πŸ”΅ Synthesized right away πŸ”΅ Right out of the box Straightforward
5.2 🟒 Although non-trivial, was easy to implement πŸ”΅ Synthesized right away πŸ”΅ Right out of the box An off by one error initially slept in the RTL design
6.1 🟑 Had to be clever for this one πŸ”΅ Synthesized right away 🟒 CDC exposed way too early results strobe event Interesting on-board issue
6.2 🟑 Modeling in Python saved me from trouble πŸ”΄ Xilinx lists RAM resources in RAMB18 not RAMB36 😬 πŸ”΅ Right out of the box Large rework after noticing I'd run out of RAM blocks πŸ˜–

Symbology

Symbol Level Description Remarks
πŸ”΅ Trivial Straightforward Copy-paste; wiring or basic logic
🟒 Easy No surprises Worked as expected
🟑 Average Some thoughts Required multiple iterations
πŸ”΄ Challenging Serious thinking Required some serious thinking
⚫ Tedious Cursed puzzle Much harder than expected; learnt something new

Achievements

  • Found an issue with the run_state_hw_jtag Vivado TCL command and opened a support request
  • This repository was selected as a winning entry in the Jane Street's Advent of FPGA Challenge 2025: recognized as an "elegant framework" and as a submission which "demonstrates the real-world engineering challenges of getting hardware designs working correctly, not just in simulation.".