This repo contains SystemVerilog implementation for Day 10 of Advent of Code 2025. It is implemented as an accelerator, with structured input fed from a controller. High-level architecture is as follows:
flowchart TB
subgraph Day 10
subgraph Controller
python-script[Python script 🐍]
end
subgraph accelerator[Accelerator 🏎️]
subgraph top-level[Top level]
input-reader[Input reader 👓]
subgraph configure-machine[Configure machine]
compute-rref[Compute RREF 🔢]
enumerate-solutions[Enumerate solutions 📋]
compute-rref -- RREF data --> enumerate-solutions
end
output-writer[Output writer ✍️]
input-reader -- structured input --> configure-machine
configure-machine -- structured output --> output-writer
end
end
python-script -- AXI stream --> top-level
top-level -- AXI stream --> python-script
end
The crux of the problem is representing the input buttons as an augmented matrix of binary elements
with columns being "buttons" (or, rather, their effect on the lights of the machine), RHS being the
target arrangement of the lights. The augmented matrix is a
GF(2) matrix, i.e. the elements are either zeros or ones and
the add operation is represented by XOR. The augmented matrix is converted to its RREF
representation in gf2_rref module, then
enumerate_solutions takes over to build up the null bases over free
variables and iterates over the possible solutions.
configure_machine module orchestrates
these two modules and tracks the solution involving minimum number of button presses.
On the top level, day10 module deals with parsing the AXI input from the
controller into structured data via day10_input_reader, which is then
passed to configure_machine, and then structured output data is
converted into output AXI stream in day10_output_writer to be sent
back to the controller.
The accelerator design pipelines input capture, RREF process, solution enumeration, and writeout as highlighted with respect to the main FSM waveforms below:
Major modules gf2_rref, enumerate_solutions and
configure_machine have a couple of reality-check tests in their
corresponding folder in tests/. There is also a single integration test that hooks up the
controller to the accelerator via Cocotb's AXI
extension to confirm the overall operation of the
system.
Running the tests:
$ make test # for unit tests
$ make test_integrationExample output from the integration test1:
[machine 1] expected:
[machine 1] [.##.]
[machine 1] configuring: (in 2 button presses)
[machine 1] [....]
[machine 1] [.#.#]
[machine 1] [.##.]
[machine 1] done configuring
[machine 1] lights match target arrangement: True
[machine 2] expected:
[machine 2] [...#.]
[machine 2] configuring: (in 3 button presses)
[machine 2] [.....]
[machine 2] [#...#]
...
The design's scale is configurable via MAX_NUM_LIGHTS and MAX_NUM_BUTTONS at instantiation of
the top level module day10. This allows to configure the design for various input
data sizes. Current limits are upper bound of an 8-bit number, since this is max that current
I/O setup can handle, but can be extended to support larger numbers.
To avoid ASCII-level parsing on the RTL front, a basic binary protocol was designed to send the parsed data to the accelerator. For each input controller sends:
- 8-bit unsigned number of lights for a given machine
Nbits corresponding to the target lights arrangement for the machine, whereNcorresponds to the number of lights;ceild to next 8-bit boundary- 8-bit unsigned number for buttons for a given machine
M*Nbits corresponding to a list of button's affects on the machine's lights, whereMcorresponds to the number of buttons; each button's data isceild to next 8-bit boundary
On the receiving side, the controller receives:
- 8-bit unsigned number representing minimum number of buttons to press for a given machine
Mbit corresponding to the mask of buttons that need to be pressed in order to achieve the target arrangement, whereMcorresponds to the number of buttons for that machine (since processing is currently sequential, the controller knows that the machine it last sent the data of is the one its reading the data for); this data is used to display an ASCII representation of the configuration process for each machine
RTL extracts structued day10_input_ifs form the corresponding fields
rather than interpreting textual syntax.
To confirm that the design is synthesizable, it has been hardened for ASIC tape-out via Tiny Tapeout's template repository and is available in this downstream repo: https://github.com/TheDeepestSpace/aoc-2025-ttsky.
For this project I decided to try following SVLint's approach
to writing SystemVerilog, which entails a lot restrictions when using combinational and sequential
logic. For instance, avoiding begin and end keywords within always_comb blocks to limit
potential for inferred latches, and being more explicit with if statements. Generally I feel like
it made me pay more attention to the design process as well as how my code is interpreted by both
other people and machines.
This repository contains a devcontainer setup that includes all the necessary tools needed for development and testing, with specific setup for VScode.
Debugging profiles for VaporView has also been checked into version control.
The current design prioritizes architectural clarity over aggressive optimization. Future work could explore:
- more aggressive pipelining for higher throughput
- parallelizing processing to provide another dimension for scaling
- adding SystemVerilog assertions to enforce internal invariants and external protocol compliance
- expansion of test coverage to include additional edge cases (e.g. no-solution, maximum-dimensions inputs)
- implementation of error handling on the accelerator side to detect and handle incorrectly structured inputs
Checkout my solution for Day 1 written in Hardcaml: https://github.com/TheDeepestSpace/aoc-2025-hardcaml
