Repository: calyxir/calyx (language: rust)

Issue:
Queues: benchmark specialized queues against binary heap variants
We have a small clutch of queue implementations now, and those have different capabilities. In increasing order of expressivity:

1. Round-Robin (resp. Strict) PIFOs can orchestrate `n` flows in a round-robin (resp. strict) policy. These are not strictly PIFOs: they cannot actually accept an item and a rank and enqueue that item with that rank.
2. Stable binary heaps can actually simulate PIFOs in a general sense. They take a value and a rank, and enqueue the value with that rank. Atop of stable binary heaps, we have built little layers of logic that grant ranks to values, such that {the layer of logic + the binary heap below it} together instruments a scheduling policy. We have such layers of logic for FIFO and PIFO policies. 

We have long had this vague notion that the simpler kinds of queues will be more performant. This issue seeks to test that for real. We will likely want to chat with @calebmkim, who has a little repository to measure LUTs, cycle counts, etc for a given design under a given set of inputs.

The first few concrete steps (just to get a comparison between 1 and 2 going) are:
- Implement two additional layers of logic on stable binary heaps:
    - `strict`
    - `round_robin`
- Check that these agree 100% with the outputs of the simplest kinds of queues (1, above).
- Measure the performance of each.

This work does not strictly have to happen during flag day, but I've put it in the flag day list because we will probably benefit from the tidying of flag day. Also, it kinda feels like this sort of benchmarking is "good housekeeping" when building multiple gadgets that have similar functionality.


Discussion / hints:
@polybeandip just wondering if you'd like to poke around some of this, especially as the main near-term development work is on stable binary heaps!
Happy to work on this; looks fun!

Fix patch:
diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py
index a928073a15..6b0cda8fc1 100644
--- a/calyx-py/calyx/builder.py
+++ b/calyx-py/calyx/builder.py
@@ -221,7 +221,7 @@ def case(
         width = self.infer_width(signal)
         ifs = []
         for branch, controllable in cases.items():
-            std_eq = self.eq(width, f"{signal.name}_eq_{branch}", signed)
+            std_eq = self.eq(width, self.generate_name(f"{signal.name}_eq_{branch}"), signed)
 
             with self.continuous:
                 std_eq.left = signal
diff --git a/frontends/queues/cycles.sh b/frontends/queues/cycles.sh
new file mode 100755
index 0000000000..63d3629473
--- /dev/null
+++ b/frontends/queues/cycles.sh
@@ -0,0 +1,35 @@
+#!/usr/bin/bash
+
+shopt -s globstar
+
+cd "$(dirname "$0")/../.." # move to root
+
+declare -a files=(frontends/queues/tests/**/*.py)
+num_files=${#files[@]}
+
+echo "{"
+
+for (( i=0; i<${num_files}; i++ )); do
+    file="${files[$i]}"
+    name="$(basename $file .py)"
+    dir="$(dirname $file)"
+    
+    cycles="$(python3 $file 20000 --keepgoing |\
+                fud e --from calyx --to jq \
+                    --through verilog \
+                    --through dat \
+                    -s verilog.data "$dir/$name.data" \
+                    -s jq.expr ".cycles" \
+                    -q)"
+
+    echo -n "\"${file#*tests/}\" : $cycles"
+
+    # because JSON doesn't allow trailing ','s
+    if [ $i -ne $(( num_files - 1 )) ]; then 
+        echo ","
+    else
+        echo ""
+    fi
+done
+
+echo "}"
diff --git a/frontends/queues/plot.py b/frontends/queues/plot.py
new file mode 100644
index 0000000000..b3ea212259
--- /dev/null
+++ b/frontends/queues/plot.py
@@ -0,0 +1,119 @@
+import os
+import sys
+import json
+from enum import Enum
+import matplotlib.pyplot as plt
+
+
+class Logic(Enum):
+    RR = 1
+    STRICT = 2
+
+def append_path_prefix(file):
+    path_to_script = os.path.dirname(__file__)
+    path_to_file = os.path.join(path_to_script, file)
+    return path_to_file
+
+def parse(stat, file):
+    out = {
+        "binheap" : { 
+            "round_robin" : {}, 
+            "strict" : {} 
+        },
+        "specialized" : { 
+            "round_robin" : {}, 
+            "strict" : {} 
+        }
+    }
+
+    with open(file) as file:
+        data = json.load(file)
+        for file, data in data.items():
+            if isinstance(data, dict):
+                data = data[stat]
+           
+            flow_no = file.split('flow')[0][-1]
+
+            if "round_robin" in file:
+                if "binheap" in file:
+                    out["binheap"]["round_robin"][flow_no]     = data
+                else:
+                    out["specialized"]["round_robin"][flow_no] = data
+            if "strict" in file:
+                if "binheap" in file:
+                    out["binheap"]["strict"][flow_no]          = data
+                else:
+                    out["specialized"]["strict"][flow_no]      = data
+
+    return out
+
+def draw(data, stat, logic, unit):
+    fig, ax = plt.subplots(1, 1)
+    fig.set_size_inches(20, 10, forward=True)
+    ax.set_xlabel("number of flows", fontsize=20)
+    if unit is None:
+        ax.set_ylabel(stat, fontsize=20)
+    else:
+        ax.set_ylabel(f"{stat} ({unit})", fontsize=20)
+    
+    file = ""
+
+    if logic == Logic.RR:
+        specialized = ax.scatter(
+                data["specialized"]["round_robin"].keys(),
+                data["specialized"]["round_robin"].values(),
+                c='b')
+        binheap = ax.scatter(
+                data["binheap"]["round_robin"].keys(),
+                data["binheap"]["round_robin"].values(),
+                c='g')
+
+        ax.set_title("Round Robin Queues", fontweight='bold', fontsize=20)
+        file = append_path_prefix(f"{stat}_round_robin")
+
+    elif logic == Logic.STRICT:
+        specialized = ax.scatter(
+                data["specialized"]["strict"].keys(),
+                data["specialized"]["strict"].values(),
+                c='b')
+        binheap = ax.scatter(
+                data["binheap"]["strict"].keys(),
+                data["binheap"]["strict"].values(),
+                c='g')
+
+        ax.set_title("Strict Queues", fontweight='bold', fontsize=20)
+        file = append_path_prefix(f"{stat}_strict")
+
+    plt.legend((specialized, binheap),
+               ("Specialized (i.e. Cassandra style)", "Binary Heap"),
+               fontsize=12)
+
+    plt.savefig(file)
+
+    print(f"Generated {file}.png")
+
+# Parse data for round_robin and strict queues
+stat = sys.argv[1]
+data = {}
+if stat == "total_time":
+    file1 = sys.argv[2]
+    file2 = sys.argv[3]
+
+    cycle_data = parse("cycles", file1)
+    slack_data = parse("worst_slack", file2)
+
+    data = cycle_data.copy()
+    for impl in data.keys():
+        for logic in data[impl].keys():
+            for flow_no in data[impl][logic].keys():
+                cycles = cycle_data[impl][logic][flow_no]
+                slack  = slack_data[impl][logic][flow_no]
+                data[impl][logic][flow_no] = (1000 * cycles) / (7 - slack)
+else:
+    file = sys.argv[2]
+    data = parse(stat, file)
+
+# Draw results
+unit = "μs" if stat == "total_time" else None
+draw(data, stat, Logic.RR,     unit)
+draw(data, stat, Logic.STRICT, unit)
diff --git a/frontends/queues/queues/__init__.py b/frontends/queues/queues/__init__.py
index 4ea212407c..1f57ae076b 100644
--- a/frontends/queues/queues/__init__.py
+++ b/frontends/queues/queues/__init__.py
@@ -5,4 +5,6 @@
 import queues.binheap.stable_binheap as stable_binheap
 import queues.binheap.fifo as binheap_fifo
 import queues.binheap.pifo as binheap_pifo
+import queues.binheap.round_robin as binheap_rr
+import queues.binheap.strict as binheap_strict
 import queues.binheap.binheap as binheap
diff --git a/frontends/queues/queues/binheap/flow_inference.py b/frontends/queues/queues/binheap/flow_inference.py
new file mode 100644
index 0000000000..27744e3381
--- /dev/null
+++ b/frontends/queues/queues/binheap/flow_inference.py
@@ -0,0 +1,33 @@
+# pylint: disable=import-error
+import calyx.builder as cb
+
+
+def insert_flow_inference(comp, value, flow, boundaries, name):
+    bound_checks = []
+
+    for b in range(len(boundaries)):
+        lt = comp.lt(32)
+        le = comp.le(32)
+        guard = comp.and_(1)
+
+        with comp.comb_group(f"{name}_bound_check_{b}") as bound_check_b:
+            le.left = value 
+            le.right = boundaries[b]
+            if b > 0:
+                lt.left = boundaries[b-1]
+                lt.right = value
+            else:
+                lt.left = 0 
+                lt.right = 1
+            guard.left = le.out
+            guard.right = lt.out
+
+        set_flow_b = comp.reg_store(flow, b, f"{name}_set_flow_{b}")
+        bound_check = cb.if_with(
+                            cb.CellAndGroup(guard, bound_check_b), 
+                            set_flow_b
+                        )
+
+        bound_checks.append(bound_check)
+
+    return cb.par(*bound_checks)
diff --git a/frontends/queues/queues/binheap/round_robin.py b/frontends/queues/queues/binheap/round_robin.py
new file mode 100644
index 0000000000..7e02d1ffd0
--- /dev/null
+++ b/frontends/queues/queues/binheap/round_robin.py
@@ -0,0 +1,120 @@
+# pylint: disable=import-error
+import calyx.builder as cb
+from calyx.utils import bits_needed
+from queues.binheap.stable_binheap import insert_stable_binheap
+from queues.binheap.flow_inference import insert_flow_inference
+
+FACTOR = 4
+
+
+def insert_binheap_rr(prog, name, boundaries, queue_size_factor=FACTOR):
+    n = len(boundaries)
+
+    comp = prog.component(name)
+
+    binheap = insert_stable_binheap(prog, "binheap", queue_size_factor)
+    binheap = comp.cell("binheap", binheap)
+
+    cmd = comp.input("cmd", 1)
+    value = comp.input("value", 32)
+
+    ans = comp.reg(32, "ans", is_ref=True)
+    err = comp.reg(1, "err", is_ref=True)
+
+    err_eq_0 = comp.eq_use(err.out, 0)
+
+    flow_in = comp.reg(bits_needed(n - 1), "flow_in")
+    infer_flow_in = insert_flow_inference(
+            comp, value, flow_in, boundaries, "infer_flow_in"
+    )
+
+    flow_out = comp.reg(bits_needed(n - 1), "flow_out")
+    infer_flow_out = insert_flow_inference(
+            comp, ans.out, flow_out, boundaries, "infer_flow_out"
+    )
+
+    rank_ptrs = [comp.reg(32, f"r_{i}") for i in range(n)]
+    rank_ptr_incrs = dict([(i, comp.incr(rank_ptrs[i], n)) for i in range(n)])
+
+    turn = comp.reg(bits_needed(n - 1), "turn")
+    turn_neq_flow_out = comp.neq_use(turn.out, flow_out.out)
+    turn_incr_mod_n = cb.if_with(
+                            comp.eq_use(turn.out, n - 1), 
+                            comp.reg_store(turn, 0),
+                            comp.incr(turn)
+                      )
+
+    init = comp.reg(1, "init")
+    init_eq_0 = comp.eq_use(init.out, 0)
+    init_state = cb.if_with(
+        init_eq_0,
+        [
+            cb.par(*[ comp.reg_store(rank_ptrs[i], i) for i in range(n) ]),
+            comp.incr(init)
+        ]
+    )
+
+    def binheap_invoke(value, rank):
+        return cb.invoke(
+            binheap,
+            in_value=value,
+            in_rank=rank,
+            in_cmd=cmd,
+            ref_ans=ans,
+            ref_err=err,
+        )
+    binheap_invokes = dict([ 
+                        (i, binheap_invoke(value, rank_ptrs[i].out)) 
+                        for i in range(n) 
+                      ])
+
+    update_state_pop = [
+                infer_flow_out,
+                cb.while_with(
+                    turn_neq_flow_out,
+                    [
+                        comp.case(turn.out, rank_ptr_incrs),
+                        turn_incr_mod_n
+                    ]
+                ),
+                turn_incr_mod_n
+            ]
+    update_state_push = comp.case(flow_in.out, rank_ptr_incrs)
+                            
+    comp.control += [
+        init_state,
+        infer_flow_in,
+        comp.case(flow_in.out, binheap_invokes),
+        cb.if_with(
+            err_eq_0,
+            comp.case(
+                cmd,
+                { 0: update_state_pop, 1: update_state_push }
+            )
+        )
+    ]
+
+    return comp
+
+
+def generate(prog, numflows):
+    """Generates queue with specific `boundaries`"""
+
+    if numflows == 2:
+        boundaries = [200, 400]
+    elif numflows == 3:
+        boundaries = [133, 266, 400]
+    elif numflows == 4:
+        boundaries = [100, 200, 300, 400]
+    elif numflows == 5:
+        boundaries = [80, 160, 240, 320, 400]
+    elif numflows == 6:
+        boundaries = [66, 100, 200, 220, 300, 400]
+    elif numflows == 7:
+        boundaries = [50, 100, 150, 200, 250, 300, 400]
+    else:
+        raise ValueError("Unsupported number of flows")
+
+    pifo = insert_binheap_rr(prog, "pifo", boundaries)
+
+    return pifo
diff --git a/frontends/queues/queues/binheap/strict.py b/frontends/queues/queues/binheap/strict.py
new file mode 100644
index 0000000000..0d545ce0ec
--- /dev/null
+++ b/frontends/queues/queues/binheap/strict.py
@@ -0,0 +1,80 @@
+# pylint: disable=import-error
+import calyx.builder as cb
+from calyx.utils import bits_needed
+from queues.binheap.stable_binheap import insert_stable_binheap
+from queues.binheap.flow_inference import insert_flow_inference
+
+FACTOR = 4
+
+
+def insert_binheap_strict(
+        prog, 
+        name, 
+        boundaries, 
+        order, 
+        queue_size_factor=FACTOR):
+    n = len(boundaries)
+
+    comp = prog.component(name)
+
+    binheap = insert_stable_binheap(prog, "binheap", queue_size_factor)
+    binheap = comp.cell("binheap", binheap)
+
+    cmd = comp.input("cmd", 1)
+    value = comp.input("value", 32)
+
+    ans = comp.reg(32, "ans", is_ref=True)
+    err = comp.reg(1, "err", is_ref=True)
+
+    flow = comp.reg(bits_needed(n - 1), "flow")
+    infer_flow = insert_flow_inference(
+            comp, value, flow, boundaries, "infer_flow"
+    )
+
+    def binheap_invoke(value, rank):
+        return cb.invoke(
+            binheap,
+            in_value=value,
+            in_rank=cb.const(32, rank),
+            in_cmd=cmd,
+            ref_ans=ans,
+            ref_err=err,
+        )
+    binheap_invokes = dict([ 
+                        (i, binheap_invoke(value, order.index(i))) 
+                        for i in range(n) 
+                      ])
+
+    comp.control += [ infer_flow, comp.case(flow.out, binheap_invokes) ]
+
+    return comp
+
+
+def generate(prog, numflows):
+    """Generates queue with specific `boundaries`"""
+
+    if numflows == 2:
+        boundaries = [200, 400]
+        order = [1, 0]
+    elif numflows == 3:
+        boundaries = [133, 266, 400]
+        order = [1, 2, 0]
+    elif numflows == 4:
+        boundaries = [100, 200, 300, 400]
+        order = [3, 0, 2, 1]
+    elif numflows == 5:
+        boundaries = [80, 160, 240, 320, 400]
+        order = [0, 1, 2, 3, 4]
+    elif numflows == 6:
+        boundaries = [66, 100, 200, 220, 300, 400]
+        order = [3, 1, 5, 2, 4, 0]
+    elif numflows == 7:
+        boundaries = [50, 100, 150, 200, 250, 300, 400]
+        order = [0, 1, 2, 3, 4, 5, 6]
+    else:
+        raise ValueError("Unsupported number of flows")
+
+    pifo = insert_binheap_strict(prog, "pifo", boundaries, order)
+
+    return pifo
+
diff --git a/frontends/queues/queues/strict_or_rr.py b/frontends/queues/queues/strict_or_rr.py
index 807cd2e26f..1b44909674 100644
--- a/frontends/queues/queues/strict_or_rr.py
+++ b/frontends/queues/queues/strict_or_rr.py
@@ -125,7 +125,7 @@ def insert_queue(
         for b in range(numflows)
     ]
     invoke_subqueues_value_guard = cb.par(
-        invoke_subqueues_value_guard_seq
+        *invoke_subqueues_value_guard_seq
     )  # Execute in parallel.
 
     incr_hot_wraparound = cb.if_with(
diff --git a/frontends/queues/resources.sh b/frontends/queues/resources.sh
new file mode 100755
index 0000000000..c79dc08fab
--- /dev/null
+++ b/frontends/queues/resources.sh
@@ -0,0 +1,41 @@
+#!/usr/bin/bash
+
+shopt -s globstar
+
+if [ "$#" -gt 1 ]; then
+    echo "usage: ./resources.sh [resource]"
+    exit 1
+fi
+
+cd "$(dirname "$0")/../.." # move to root
+
+declare -a files=(frontends/queues/tests/**/*.py)
+num_files=${#files[@]}
+
+echo "{"
+
+for (( i=0; i<${num_files}; i++ )); do
+    file="${files[$i]}"
+    name="$(basename $file .py)"
+    dir="$(dirname $file)"
+    
+    resources="$(python3 $file 20000 --keepgoing |\
+                fud e --from calyx --to resource-estimate)"
+
+    if [ "$#" -eq 1 ]; then
+        resource=$(jq ".$1" <<< "$resources")
+        echo -n "\"${file#*tests/}\" : $resource"
+    else
+        echo "\"${file#*tests/}\" :" 
+        echo -n "$resources"
+    fi
+
+    # because JSON doesn't allow trailing ','s
+    if [ $i -ne $(( num_files - 1 )) ]; then
+        echo ","
+    else
+        echo ""
+    fi
+done
+
+echo "}"
diff --git a/runt.toml b/runt.toml
index 5533ace483..95ebcc7a04 100644
--- a/runt.toml
+++ b/runt.toml
@@ -112,7 +112,9 @@ paths = [
     "frontends/queues/tests/*.py",
     "frontends/queues/tests/round_robin/*.py",
     "frontends/queues/tests/strict/*.py",
-    "frontends/queues/tests/binheap/*.py"
+    "frontends/queues/tests/binheap/*.py",
+    "frontends/queues/tests/binheap/round_robin/*.py",
+    "frontends/queues/tests/binheap/strict/*.py"
 ]
 cmd = """
 name=$(basename {} .py) &&


Test patch:
diff --git a/calyx-py/test/case.expect b/calyx-py/test/case.expect
index d201c6428f..89cbefc563 100644
--- a/calyx-py/test/case.expect
+++ b/calyx-py/test/case.expect
@@ -3,24 +3,24 @@ import "primitives/binary_operators.futil";
 component my_comp(in_1: 8) -> (out_1: 16) {
   cells {
     comp_reg = std_reg(1);
-    in_1_eq_1 = std_eq(8);
-    in_1_eq_2 = std_eq(8);
+    in_1_eq_1_1 = std_eq(8);
+    in_1_eq_2_2 = std_eq(8);
   }
   wires {
     group my_group {
 
     }
-    in_1_eq_1.left = in_1;
-    in_1_eq_1.right = 8'd1;
-    in_1_eq_2.left = in_1;
-    in_1_eq_2.right = 8'd2;
+    in_1_eq_1_1.left = in_1;
+    in_1_eq_1_1.right = 8'd1;
+    in_1_eq_2_2.left = in_1;
+    in_1_eq_2_2.right = 8'd2;
   }
   control {
     par {
-      if in_1_eq_1.out {
+      if in_1_eq_1_1.out {
         my_group;
       }
-      if in_1_eq_2.out {
+      if in_1_eq_2_2.out {
         invoke comp_reg(in=1'd1)();
       }
     }
diff --git a/frontends/queues/test_data_gen/clean_test_data.sh b/frontends/queues/test_data_gen/clean_test_data.sh
index 56a04ed2cf..14f3d43324 100755
--- a/frontends/queues/test_data_gen/clean_test_data.sh
+++ b/frontends/queues/test_data_gen/clean_test_data.sh
@@ -6,6 +6,8 @@ cd binheap/
 for test in fifo_test pifo_test stable_binheap_test; do
     rm -rf $test.data $test.expect
 done
+rm -rf round_robin/*.data round_robin/*.expect
+rm -rf strict/*.data strict/*.expect
 
 cd ../round_robin
 rm -rf *.data *.expect
diff --git a/frontends/queues/test_data_gen/gen_test_data.sh b/frontends/queues/test_data_gen/gen_test_data.sh
index 45d692886c..1448f826dd 100755
--- a/frontends/queues/test_data_gen/gen_test_data.sh
+++ b/frontends/queues/test_data_gen/gen_test_data.sh
@@ -29,18 +29,6 @@ for queue_kind in fifo pifo pifo_tree complex_tree; do
     [[ "$queue_kind" != "pifo" && $? -eq 0 ]] && echo "Generated ${queue_kind}_test.expect"
 done
 
-# Copying/Moving into binheap/
-
-cp $tests_dir/fifo_test.data $tests_dir/binheap/
-[[ $? -eq 0 ]] && echo "Generated binheap/fifo_test.data"
-cp $tests_dir/fifo_test.expect $tests_dir/binheap/
-[[ $? -eq 0 ]] && echo "Generated binheap/fifo_test.expect"
-
-mv $tests_dir/pifo_test.data $tests_dir/binheap/
-[[ $? -eq 0 ]] && echo "Generated binheap/pifo_test.data"
-mv $tests_dir/pifo_test.expect $tests_dir/binheap/
-[[ $? -eq 0 ]] && echo "Generated binheap/pifo_test.expect"
-
 
 # Here, we test the queues for non-work-conserving algorithms,
 # which are the following:
@@ -54,6 +42,7 @@ for queue_kind in pieo nwc_simple; do
     [[ $? -eq 0 ]] && echo "Generated ${queue_kind}_test.expect"
 done  
 
+
 # For the Binary Heap, we drop piezo mode and enable ranks for data gen and
 # use binheap_oracle to generate the expected output
 
@@ -62,6 +51,7 @@ python3 $data_gen_dir/gen_oracle_data.py $num_cmds --use-rank > $tests_dir/binhe
 cat $tests_dir/binheap/stable_binheap_test.data | python3 $data_gen_dir/binheap_oracle.py $num_cmds $queue_size --keepgoing > $tests_dir/binheap/stable_binheap_test.expect
 [[ $? -eq 0 ]] && echo "Generated binheap/stable_binheap_test.expect"
 
+
 # For the Round Robin queues, we drop piezo mode as well and use rrqueue_oracle to
 # generate the expected output for queues with 2..7 flows. This generates 6 data expect file pairs.
 
@@ -72,6 +62,7 @@ for n in {2..7}; do
     [[ $? -eq 0 ]] && echo "Generated round_robin/rr_${n}flow_test.expect"
 done
 
+
 # For Strict queues, we use strict_queue_oracle.py to generate the expected output
 # for queues with 2..6 flows, each with a different strict ordering. This generates 5
 # expect file pairs.
@@ -82,3 +73,40 @@ for n in {2..6}; do
     cat $tests_dir/strict/strict_${n}flow_test.data | python3 $data_gen_dir/strict_queue_oracle.py $num_cmds $queue_size $n --keepgoing > $tests_dir/strict/strict_${n}flow_test.expect
     [[ $? -eq 0 ]] && echo "Generated strict/strict_${n}flow_test.expect"
 done
+
+
+# Copying/Moving into binheap/
+
+cp $tests_dir/fifo_test.data $tests_dir/binheap/
+[[ $? -eq 0 ]] && echo "Generated binheap/fifo_test.data"
+cp $tests_dir/fifo_test.expect $tests_dir/binheap/
+[[ $? -eq 0 ]] && echo "Generated binheap/fifo_test.expect"
+
+mv $tests_dir/pifo_test.data $tests_dir/binheap/
+[[ $? -eq 0 ]] && echo "Generated binheap/pifo_test.data"
+mv $tests_dir/pifo_test.expect $tests_dir/binheap/
+[[ $? -eq 0 ]] && echo "Generated binheap/pifo_test.expect"
+
+for i in $tests_dir/round_robin/*.data; do
+    file="$(basename $i)"
+    cp $i $tests_dir/binheap/round_robin/$file
+    [[ $? -eq 0 ]] && echo "Generated binheap/round_robin/$file"
+done
+
+for i in $tests_dir/round_robin/*.expect; do
+    file="$(basename $i)"
+    cp $i $tests_dir/binheap/round_robin/$file
+    [[ $? -eq 0 ]] && echo "Generated binheap/round_robin/$file"
+done
+
+for i in $tests_dir/strict/*.data; do
+    file="$(basename $i)"
+    cp $i $tests_dir/binheap/strict/$file
+    [[ $? -eq 0 ]] && echo "Generated binheap/strict/$file"
+done
+
+for i in $tests_dir/strict/*.expect; do
+    file="$(basename $i)"
+    cp $i $tests_dir/binheap/strict/$file
+    [[ $? -eq 0 ]] && echo "Generated binheap/strict/$file"
+done
diff --git a/frontends/queues/tests/binheap/round_robin/rr_2flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_2flow_test.py
new file mode 100644
index 0000000000..572f1dfb15
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_2flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 2 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 2)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/round_robin/rr_3flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_3flow_test.py
new file mode 100644
index 0000000000..2162b757fa
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_3flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 3 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 3)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/round_robin/rr_4flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_4flow_test.py
new file mode 100644
index 0000000000..d326567ee9
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_4flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 4 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 4)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/round_robin/rr_5flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_5flow_test.py
new file mode 100644
index 0000000000..44fd71e2ca
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_5flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 5 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 5)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/round_robin/rr_6flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_6flow_test.py
new file mode 100644
index 0000000000..01596c534c
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_6flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 6 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 6)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/round_robin/rr_7flow_test.py b/frontends/queues/tests/binheap/round_robin/rr_7flow_test.py
new file mode 100644
index 0000000000..0c89de620a
--- /dev/null
+++ b/frontends/queues/tests/binheap/round_robin/rr_7flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_rr
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 7 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_rr.generate(prog, 7)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/strict/strict_2flow_test.py b/frontends/queues/tests/binheap/strict/strict_2flow_test.py
new file mode 100644
index 0000000000..dc66bb147f
--- /dev/null
+++ b/frontends/queues/tests/binheap/strict/strict_2flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_strict
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 2 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_strict.generate(prog, 2)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/strict/strict_3flow_test.py b/frontends/queues/tests/binheap/strict/strict_3flow_test.py
new file mode 100644
index 0000000000..8b2322c3fc
--- /dev/null
+++ b/frontends/queues/tests/binheap/strict/strict_3flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_strict
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 3 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_strict.generate(prog, 3)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/strict/strict_4flow_test.py b/frontends/queues/tests/binheap/strict/strict_4flow_test.py
new file mode 100644
index 0000000000..3e1227c8c4
--- /dev/null
+++ b/frontends/queues/tests/binheap/strict/strict_4flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_strict
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 4 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_strict.generate(prog, 4)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/strict/strict_5flow_test.py b/frontends/queues/tests/binheap/strict/strict_5flow_test.py
new file mode 100644
index 0000000000..d205171106
--- /dev/null
+++ b/frontends/queues/tests/binheap/strict/strict_5flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_strict
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 5 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_strict.generate(prog, 5)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()
diff --git a/frontends/queues/tests/binheap/strict/strict_6flow_test.py b/frontends/queues/tests/binheap/strict/strict_6flow_test.py
new file mode 100644
index 0000000000..2d7692d3f8
--- /dev/null
+++ b/frontends/queues/tests/binheap/strict/strict_6flow_test.py
@@ -0,0 +1,15 @@
+import sys
+import calyx.builder as cb
+import queues.queue_call as qc
+from queues import binheap_strict
+
+
+if __name__ == "__main__":
+    """Invoke the top-level function to build the program, with 6 flows."""
+    num_cmds = int(sys.argv[1])
+    keepgoing = "--keepgoing" in sys.argv
+
+    prog = cb.Builder()
+    pifo = binheap_strict.generate(prog, 6)
+    qc.insert_main(prog, pifo, num_cmds, keepgoing=keepgoing)
+    prog.program.emit()