# Debugging Tips Debugging Calyx programs that run to completion and generate the wrong value can be challenging. We can first try to eliminate some common causes of problems. ## Disabling Optimizations The first step is disabling optimization passes. The Calyx compiler refers to bundles of optimizations using two *aliases*: `pre-opt` and `post-opt`. `pre-opt` passes run before the main compilation passes that remove the control program while `post-opt` passes run after. To disable the passes, add the flag `-d pre-opt -d post-opt` to compiler invocation: 1. For the compiler: `futil -d pre-opt -d post-opt`. 2. For `fud`: `fud ... -s futil.flags "-d pre-opt -d post-opt"`. If the execution generates the right result, then one of the optimizations passes is incorrect. To identify which optimization pass is wrong, add back individual passes and see when the execution fails. To do so, first run `futil --list-passes` to see the names of the passes that make up the `pre-opt` and `post-opt` aliases. Next, re-enable each pass by doing: `-d pre-opt -d post-opt -p -p ` and so on. ## Disabling Static Timing `static-timing` is one of the two control compilation passes. It uses the latency information on groups to generate faster hardware. Disable it using the flag `-d static-timing`. ## Reducing Test Files It is often possible to reduce the size of the example program that is generating incorrect results. In order to perform a reduction, we need to run the program twice, once with a "golden workflow" that we trust to generate the right result and once with the buggy workflow. For example, if we've identified the problem to be in one of the Calyx passes, the "golden workflow" is running the program without the pass while the buggy workflow is running the program with the pass enabled. This case is so common that we've written [a script][flag-cmp] that can run programs with different set of flags to the Calyx compiler and show the difference in the outputs after simulation. The script is invoked as: ``` tools/flag-compare.sh ``` ### Reducing Calyx Programs The best way to reduce Calyx program deleting group enables from the control program and seeing if the generated program still generates the wrong output. While doing this, make sure that you're not deleting an update to a loop variable which might cause infinite loops. ### Reducing Dahlia Programs If you're working with Dahlia programs, it is also possible to reduce the program with the script since it simply uses `fud` to run the program with the simulator. As with Calyx reduction, try deleting parts of the program and seeing if the flag configurations for the Calyx program still generate different outputs. ## Waveform Debugging Waveform debugging is the final way of debugging Calyx programs. A waveform captures the value of every port at every clock cycle and can be viewed using a wave viewer program like [GTKWave][gtkwave] or [WaveTrace][wavetrace] to look at the wave form. Because of this level of granularity, it generates a lot of information. To make the information a little more digestible, we can use information generated by Calyx during compilation. For waveform debugging, we recommend disabling the optimization passes and static timing compilation (unless you're debugging these passes). In this debugging strategy, we'll do the following: 1. Dump out the control FSM for the program we're debugging. 2. Find the FSM states that enable the particular groups that might be misbehaving. 3. Open the waveform viewer and find clock cycles where the FSM takes the corresponding values and identify other signals that we care about. Consider the control section from [examples/futil/dot-product.futil](https://github.com/cucapra/calyx/blob/master/examples/futil/dot-product.futil): ``` {{#include ../../examples/futil/dot-product.futil:control}} ``` Suppose that we want to make sure that `let0` is correctly performing its computation. We can generate the control FSM for the program using: futil -p top-down-cc This generates a Calyx program with several new groups. We want to look for groups with the prefix `tdcc` which look something like this: ``` group tdcc { let0[go] = !let0[done] & fsm.out == 4'd0 ? 1'd1; cs_wh.in = fsm.out == 4'd1 ? le0.out; cs_wh.write_en = fsm.out == 4'd1 ? 1'd1; cond0[go] = fsm.out == 4'd1 ? 1'd1; par[go] = !par[done] & cs_wh.out & fsm.out == 4'd2 ? 1'd1; let1[go] = !let1[done] & cs_wh.out & fsm.out == 4'd3 ? 1'd1; let2[go] = !let2[done] & cs_wh.out & fsm.out == 4'd4 ? 1'd1; upd2[go] = !upd2[done] & cs_wh.out & fsm.out == 4'd5 ? 1'd1; upd3[go] = !upd3[done] & cs_wh.out & fsm.out == 4'd6 ? 1'd1; ... } ``` The assignments to `let0[go]` indicate what conditions make the `let0` group execute. In this program, we have: let0[go] = !let0[done] & fsm.out == 4'd0 ? 1'd1; Which states that `let0` will be active when the state of the `fsm` register is `0` along with some other conditions. The remainder of the group defines how the state in the `fsm` variable changes: ``` ... fsm.in = fsm.out == 4'd0 & let0[done] ? 4'd1; fsm.write_en = fsm.out == 4'd0 & let0[done] ? 1'd1; fsm.in = fsm.out == 4'd1 & cond0[done] ? 4'd2; fsm.write_en = fsm.out == 4'd1 & cond0[done] ? 1'd1; ... ``` For example, we can see that when the value of the FSM is 0 and `let0[done]` becomes high, the FSM will take the value 1. Once we have this information, we can open the VCD file and look at points when the `fsm` register has the value 1 and check to see if the assignments in `let0` activated in the way we expected. [gtkwave]: http://gtkwave.sourceforge.net/ [wavetrace]: https://marketplace.visualstudio.com/items?itemName=wavetrace.wavetrace [flag-cmp]: https://github.com/cucapra/calyx/blob/master/tools/flag-compare.sh