From 16e18351d5e402e272490841ea59707f8fba0ba7 Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Tue, 18 Jun 2024 09:28:49 -0400 Subject: [PATCH] Queues: tidy testing (#2155) * Comment re: usage * Basic script for queue gen * Maxlen and queue size no longer in queue_util * Slightly simplify datagen for ans mem * Rename flag * Simplify FIFO init * queue_len not a const * Stray file * queue len as cli * Queue size as CLI * Neaten * Numver of cmds as CLI * Max cmds as CLI * num cmds as CLI for eDSL programs too * Comment --- calyx-py/calyx/fifo_oracle.py | 8 ++- calyx-py/calyx/gen_queue_data_expect.sh | 17 ++++++ calyx-py/calyx/pifo_oracle.py | 8 ++- ...pifotree_oracle.py => pifo_tree_oracle.py} | 17 +++--- calyx-py/calyx/queue_call.py | 20 +++--- calyx-py/calyx/queue_data_gen.py | 61 +++++++++---------- calyx-py/calyx/queue_util.py | 10 +-- calyx-py/calyx/queues.py | 24 +++++--- calyx-py/test/correctness/queues/fifo.py | 4 +- calyx-py/test/correctness/queues/pifo.py | 5 +- calyx-py/test/correctness/queues/pifo_tree.py | 4 +- calyx-py/test/correctness/queues/sdn.py | 8 ++- runt.toml | 10 +-- 13 files changed, 116 insertions(+), 80 deletions(-) create mode 100755 calyx-py/calyx/gen_queue_data_expect.sh rename calyx-py/calyx/{pifotree_oracle.py => pifo_tree_oracle.py} (60%) diff --git a/calyx-py/calyx/fifo_oracle.py b/calyx-py/calyx/fifo_oracle.py index fd943b4a13..156442a0fc 100644 --- a/calyx-py/calyx/fifo_oracle.py +++ b/calyx-py/calyx/fifo_oracle.py @@ -1,8 +1,12 @@ +# For usage, see gen_queue_data_expect.sh + +import sys import calyx.queues as queues from calyx import queue_util if __name__ == "__main__": + max_cmds, len = int(sys.argv[1]), int(sys.argv[2]) commands, values = queue_util.parse_json() - fifo = queues.Fifo([], False) - ans = queues.operate_queue(commands, values, fifo) + fifo = queues.Fifo(len) + ans = queues.operate_queue(commands, values, fifo, max_cmds) queue_util.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/gen_queue_data_expect.sh b/calyx-py/calyx/gen_queue_data_expect.sh new file mode 100755 index 0000000000..d5d32b2487 --- /dev/null +++ b/calyx-py/calyx/gen_queue_data_expect.sh @@ -0,0 +1,17 @@ +#!/usr/bin/bash + +# For SDN, we use piezo mode when making the data file and +# use pifotree_oracle to generate the expected output +python3 queue_data_gen.py 20000 --no-err 16 > ../test/correctness/queues/sdn.data +cat ../test/correctness/queues/sdn.data | python3 pifo_tree_oracle.py 20000 16 > ../test/correctness/queues/sdn.expect + +# For the others, we drop piezo mode for data gen, and we use the appropriate +# oracle, which is one of the following: +# - fifo_oracle.py +# - pifo_oracle.py +# - pifo_tree_oracle.py + +for queue_kind in fifo pifo pifo_tree; do + python3 queue_data_gen.py 20000 --no-err 16 > ../test/correctness/queues/$queue_kind.data + cat ../test/correctness/queues/$queue_kind.data | python3 ${queue_kind}_oracle.py 20000 16 > ../test/correctness/queues/$queue_kind.expect +done diff --git a/calyx-py/calyx/pifo_oracle.py b/calyx-py/calyx/pifo_oracle.py index 9786ffc699..1d0156d5ad 100644 --- a/calyx-py/calyx/pifo_oracle.py +++ b/calyx-py/calyx/pifo_oracle.py @@ -1,11 +1,15 @@ +# For usage, see gen_queue_data_expect.sh + +import sys import calyx.queues as queues from calyx import queue_util if __name__ == "__main__": + max_cmds, len = int(sys.argv[1]), int(sys.argv[2]) commands, values = queue_util.parse_json() # Our PIFO is simple: it just orchestrates two FIFOs. The boundary is 200. - pifo = queues.Pifo(queues.Fifo([]), queues.Fifo([]), 200, False) + pifo = queues.Pifo(queues.Fifo(len), queues.Fifo(len), 200, len) - ans = queues.operate_queue(commands, values, pifo) + ans = queues.operate_queue(commands, values, pifo, max_cmds) queue_util.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/pifotree_oracle.py b/calyx-py/calyx/pifo_tree_oracle.py similarity index 60% rename from calyx-py/calyx/pifotree_oracle.py rename to calyx-py/calyx/pifo_tree_oracle.py index 6e97727dae..b05e8393fa 100644 --- a/calyx-py/calyx/pifotree_oracle.py +++ b/calyx-py/calyx/pifo_tree_oracle.py @@ -1,15 +1,13 @@ -# Usage: -# To make a .data file: -# python calyx-py/calyx/queue_data_gen.py --piezo > calyx-py/test/correctness/sdn.data -# To then make a .expect file: -# cat calyx-py/test/correctness/sdn.data | -# python calyx-py/calyx/pifotree_oracle.py > calyx-py/test/correctness/sdn.expect +# For usage, see gen_queue_data_expect.sh +import sys import calyx.queues as queues from calyx import queue_util if __name__ == "__main__": + + max_cmds, len = int(sys.argv[1]), int(sys.argv[2]) commands, values = queue_util.parse_json() # Our PIFO is a little complicated: it is a tree of queues. @@ -23,8 +21,11 @@ # - The boundary for this is 200. pifo = queues.Pifo( - queues.Pifo(queues.Fifo([]), queues.Fifo([]), 100), queues.Fifo([]), 200, False + queues.Pifo(queues.Fifo(len), queues.Fifo(len), 100, len), + queues.Fifo(len), + 200, + len, ) - ans = queues.operate_queue(commands, values, pifo) + ans = queues.operate_queue(commands, values, pifo, max_cmds) queue_util.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/queue_call.py b/calyx-py/calyx/queue_call.py index 97a0e3de4d..3b874458bb 100644 --- a/calyx-py/calyx/queue_call.py +++ b/calyx-py/calyx/queue_call.py @@ -4,7 +4,7 @@ import calyx.builder as cb -def insert_runner(prog, queue, name, stats_component=None): +def insert_runner(prog, queue, name, num_cmds, stats_component=None): """Inserts the component `name` into the program. This will be used to `invoke` the component `queue` and feed it one command. This component is designed to be invoked by some other component, and does not @@ -55,8 +55,8 @@ def insert_runner(prog, queue, name, stats_component=None): # - ref register `err`, which is raised if an error occurs. # Our memories and registers, all of which are passed to us by reference. - commands = runner.seq_mem_d1("commands", 2, queue_util.MAX_CMDS, 32, is_ref=True) - values = runner.seq_mem_d1("values", 32, queue_util.MAX_CMDS, 32, is_ref=True) + commands = runner.seq_mem_d1("commands", 2, num_cmds, 32, is_ref=True) + values = runner.seq_mem_d1("values", 32, num_cmds, 32, is_ref=True) has_ans = runner.reg(1, "has_ans", is_ref=True) ans = runner.reg(32, "component_ans", is_ref=True) err = runner.reg(1, "component_err", is_ref=True) @@ -77,8 +77,8 @@ def insert_runner(prog, queue, name, stats_component=None): lower_has_ans = runner.reg_store(has_ans, 0, "lower_has_ans") not_err = runner.not_use(err.out) - # Wiring that raises `err` iff `i = MAX_CMDS`. - check_if_out_of_cmds, _ = runner.eq_store_in_reg(i.out, queue_util.MAX_CMDS, err) + # Wiring that raises `err` iff `i = num_cmds`. + check_if_out_of_cmds, _ = runner.eq_store_in_reg(i.out, num_cmds, err) runner.control += [ write_cmd_to_reg, # `cmd := commands[i]` @@ -119,7 +119,7 @@ def insert_runner(prog, queue, name, stats_component=None): return runner -def insert_main(prog, queue, controller=None, stats_component=None): +def insert_main(prog, queue, num_cmds, controller=None, stats_component=None): """Inserts the component `main` into the program. It triggers the dataplane and controller components. """ @@ -128,16 +128,16 @@ def insert_main(prog, queue, controller=None, stats_component=None): stats = main.cell("stats_main", stats_component) if stats_component else None controller = main.cell("controller", controller) if controller else None - dataplane = insert_runner(prog, queue, "dataplane", stats_component) + dataplane = insert_runner(prog, queue, "dataplane", num_cmds, stats_component) dataplane = main.cell("dataplane", dataplane) has_ans = main.reg(1) dataplane_ans = main.reg(32) dataplane_err = main.reg(1) - commands = main.seq_mem_d1("commands", 2, queue_util.MAX_CMDS, 32, is_external=True) - values = main.seq_mem_d1("values", 32, queue_util.MAX_CMDS, 32, is_external=True) - ans_mem = main.seq_mem_d1("ans_mem", 32, queue_util.MAX_CMDS, 32, is_external=True) + commands = main.seq_mem_d1("commands", 2, num_cmds, 32, is_external=True) + values = main.seq_mem_d1("values", 32, num_cmds, 32, is_external=True) + ans_mem = main.seq_mem_d1("ans_mem", 32, num_cmds, 32, is_external=True) ans_neq_0 = main.neq_use(dataplane_ans.out, 0) # ans != 0 diff --git a/calyx-py/calyx/queue_data_gen.py b/calyx-py/calyx/queue_data_gen.py index ea7d9c37cd..f8b0063a3c 100644 --- a/calyx-py/calyx/queue_data_gen.py +++ b/calyx-py/calyx/queue_data_gen.py @@ -1,15 +1,10 @@ -# Usage: -# To make a .data file: -# python calyx-py/calyx/queue_data_gen.py --piezo > calyx-py/test/correctness/sdn.data -# To then make a .expect file: -# cat calyx-py/test/correctness/sdn.data | -# python calyx-py/calyx/pifotree_oracle.py > calyx-py/test/correctness/sdn.expect +# For usage, see gen_queue_data_expect.sh import random + import json import sys -from typing import Dict, Union -from calyx import queue_util +from typing import Dict, Union, Optional FormatType = Dict[str, Union[bool, str, int]] @@ -19,17 +14,16 @@ def format_gen(width: int) -> FormatType: return {"is_signed": False, "numeric_type": "bitnum", "width": width} -def piezo_special(): +def no_err_cmds_list(queue_size, num_cmds): """A special data-gen helper that creates a commands list while ensuring that there are: - No overflows. - No underflows. - - (queue_util.MAX_CMDS/2) pushes. - - As many pops as there are pushes. + - `num_cmds`/2 pushes and `num_cmds`/2 pops. A combination of the above means that no packet is left unpopped. """ running_count = 0 # The current size of the queue. - push_goal = int(queue_util.MAX_CMDS / 2) # How many pushes we want overall. + push_goal = int(num_cmds / 2) # How many pushes we want overall. total_push_count = 0 total_pop_count = 0 commands = [] @@ -39,7 +33,7 @@ def piezo_special(): # This would make us underflow, # so we'll change the command to `push` instead command = "push" - if command == "push" and running_count == queue_util.QUEUE_SIZE: + if command == "push" and running_count == queue_size: # This would make us overflow, # so we'll change the command to `pop` instead command = "pop" @@ -58,44 +52,47 @@ def piezo_special(): commands += (push_goal - total_pop_count) * [0] break - assert len(commands) == queue_util.MAX_CMDS + assert len(commands) == num_cmds return commands -def dump_json(piezo: bool): +def dump_json(num_cmds, no_err: bool, queue_size: Optional[int] = None): """Prints a JSON representation of the data to stdout. The data itself is populated randomly, following certain rules: - It has three "memories": `commands`, `values`, and `ans_mem`. - - The `commands` memory has queue_util.MAX_CMDS items, which are 0, 1, or 2. + - The `commands` memory has `num_cmds` items, which are 0, 1, or 2. 0: pop, 1: peek, 2: push - If the `piezo` flag is set, then items are chosen from 0 and 2 using a helper. - - The `values` memory has queue_util.MAX_CMDS items: + If the `no_err` flag is set, then items are chosen from 0 and 2 using a helper. + - The `values` memory has `num_cmds` items: random values between 0 and 400. - - The `ans_mem` memory has queue_util.MAX_CMDS items, all zeroes. + - The `ans_mem` memory has `num_cmds` items, all zeroes. - Each memory has a `format` field, which is a format object for a bitvector. """ commands = { "commands": { "data": ( - piezo_special() - if piezo - else [random.randint(0, 2) for _ in range(queue_util.MAX_CMDS)] + # The `commands` memory has `num_cmds` items, which are all 0, 1, or 2 + no_err_cmds_list(queue_size, num_cmds) + # If the `no_err` flag is set, then we use the special helper + # that ensures no overflow or overflow will occur. + if no_err + else [random.randint(0, 2) for _ in range(num_cmds)] ), "format": format_gen(2), } } values = { "values": { - "data": [random.randint(1, 400) for _ in range(queue_util.MAX_CMDS)], - # The `values` memory has queue_util.MAX_CMDS items: random values - # between 0 and 400. + "data": [random.randint(1, 400) for _ in range(num_cmds)], + # The `values` memory has `num_cmds` items, whihc are all + # random values between 0 and 400. "format": format_gen(32), } } ans_mem = { "ans_mem": { - "data": [0 for _ in range(queue_util.MAX_CMDS)], - # The `ans_mem` memory has queue_util.MAX_CMDS items, all zeroes. + "data": [0] * num_cmds, + # The `ans_mem` memory has `num_cmds` items, all zeroes. "format": format_gen(32), } } @@ -105,8 +102,10 @@ def dump_json(piezo: bool): if __name__ == "__main__": # Accept a flag that we pass to dump_json. - # This says whether we should have any 1s in the `commands` memory. - - piezo = len(sys.argv) > 1 and sys.argv[1] == "--piezo" + # This says whether we should use the special no_err helper. random.seed(5) - dump_json(piezo) + num_cmds = int(sys.argv[1]) + no_err = len(sys.argv) > 2 and sys.argv[2] == "--no-err" + if no_err: + queue_size = int(sys.argv[3]) + dump_json(num_cmds, no_err, queue_size if no_err else None) diff --git a/calyx-py/calyx/queue_util.py b/calyx-py/calyx/queue_util.py index d91980fa16..c62f94c480 100644 --- a/calyx-py/calyx/queue_util.py +++ b/calyx-py/calyx/queue_util.py @@ -1,18 +1,14 @@ import json import sys -MAX_CMDS = 20000 -QUEUE_SIZE = 16 - def parse_json(): """Effectively the opposite of `data_gen`: - Given a JSON file formatted for Calyx purposes, parse it into its two lists: - - The `commands` memory, which has MAX_CMDS items. - - The `values` memory, which has MAX_CMDS items. + Given a JSON file formatted for Calyx purposes, parses it into two lists: + - The `commands` list. + - The `values` list. Returns the two lists. """ - data = json.load(sys.stdin) commands = data["commands"]["data"] values = data["values"]["data"] diff --git a/calyx-py/calyx/queues.py b/calyx-py/calyx/queues.py index 8d4f156bd8..bef747d71b 100644 --- a/calyx-py/calyx/queues.py +++ b/calyx-py/calyx/queues.py @@ -1,7 +1,6 @@ from dataclasses import dataclass from typing import List from typing import Optional -from calyx import queue_util class QueueError(Exception): @@ -20,9 +19,9 @@ class Fifo: Otherwise, it allows those commands to fail silently but continues the simulation. """ - def __init__(self, data: List[int], error_mode=True, max_len: int = None): - self.data = data - self.max_len = max_len or queue_util.QUEUE_SIZE + def __init__(self, max_len: int, error_mode=True): + self.data = [] + self.max_len = max_len self.error_mode = error_mode def push(self, val: int) -> None: @@ -96,12 +95,19 @@ class Pifo: - We increment `pifo_len` by 1. """ - def __init__(self, queue_1, queue_2, boundary, error_mode=True, max_len=None): + def __init__( + self, + queue_1, + queue_2, + boundary, + max_len: int, + error_mode=True, + ): self.data = (queue_1, queue_2) self.hot = 0 self.pifo_len = len(queue_1) + len(queue_2) self.boundary = boundary - self.max_len = max_len or queue_util.QUEUE_SIZE + self.max_len = max_len self.error_mode = error_mode assert ( self.pifo_len <= self.max_len @@ -162,7 +168,7 @@ def __len__(self) -> int: return self.pifo_len -def operate_queue(commands, values, queue): +def operate_queue(commands, values, queue, max_cmds): """Given the two lists, one of commands and one of values. Feed these into our queue, and return the answer memory. """ @@ -191,6 +197,6 @@ def operate_queue(commands, values, queue): except QueueError: break - # Pad the answer memory with zeroes until it is of length MAX_CMDS. - ans += [0] * (queue_util.MAX_CMDS - len(ans)) + # Pad the answer memory with zeroes until it is of length `max_cmds`. + ans += [0] * (max_cmds - len(ans)) return ans diff --git a/calyx-py/test/correctness/queues/fifo.py b/calyx-py/test/correctness/queues/fifo.py index 6e30eaad55..2689f38416 100644 --- a/calyx-py/test/correctness/queues/fifo.py +++ b/calyx-py/test/correctness/queues/fifo.py @@ -1,4 +1,5 @@ # pylint: disable=import-error +import sys import calyx.builder as cb import calyx.queue_call as qc @@ -102,9 +103,10 @@ def insert_fifo(prog, name, queue_len_factor=QUEUE_LEN_FACTOR): def build(): """Top-level function to build the program.""" + num_cmds = int(sys.argv[1]) prog = cb.Builder() fifo = insert_fifo(prog, "fifo") - qc.insert_main(prog, fifo) + qc.insert_main(prog, fifo, num_cmds) return prog.program diff --git a/calyx-py/test/correctness/queues/pifo.py b/calyx-py/test/correctness/queues/pifo.py index 687610f220..d01b357ecb 100644 --- a/calyx-py/test/correctness/queues/pifo.py +++ b/calyx-py/test/correctness/queues/pifo.py @@ -1,4 +1,5 @@ # pylint: disable=import-error +import sys import fifo import calyx.builder as cb import calyx.queue_call as qc @@ -7,6 +8,7 @@ # The max length of the queue will be 2^QUEUE_LEN_FACTOR. QUEUE_LEN_FACTOR = 4 + def insert_flow_inference(comp: cb.ComponentBuilder, cmd, flow, boundary, group): """The flow is needed when the command is a push. If the value to be pushed is less than or equal to {boundary}, @@ -293,11 +295,12 @@ def insert_pifo( def build(): """Top-level function to build the program.""" + num_cmds = int(sys.argv[1]) prog = cb.Builder() fifo_l = fifo.insert_fifo(prog, "fifo_l", QUEUE_LEN_FACTOR) fifo_r = fifo.insert_fifo(prog, "fifo_r", QUEUE_LEN_FACTOR) pifo = insert_pifo(prog, "pifo", fifo_l, fifo_r, 200) - qc.insert_main(prog, pifo) + qc.insert_main(prog, pifo, num_cmds) return prog.program diff --git a/calyx-py/test/correctness/queues/pifo_tree.py b/calyx-py/test/correctness/queues/pifo_tree.py index 53267a068a..af5f60039a 100644 --- a/calyx-py/test/correctness/queues/pifo_tree.py +++ b/calyx-py/test/correctness/queues/pifo_tree.py @@ -1,4 +1,5 @@ # pylint: disable=import-error +import sys import fifo import pifo import calyx.builder as cb @@ -7,13 +8,14 @@ def build(): """Top-level function to build the program.""" + num_cmds = int(sys.argv[1]) prog = cb.Builder() fifo_purple = fifo.insert_fifo(prog, "fifo_purple") fifo_tangerine = fifo.insert_fifo(prog, "fifo_tangerine") pifo_red = pifo.insert_pifo(prog, "pifo_red", fifo_purple, fifo_tangerine, 100) fifo_blue = fifo.insert_fifo(prog, "fifo_blue") pifo_root = pifo.insert_pifo(prog, "pifo_root", pifo_red, fifo_blue, 200) - qc.insert_main(prog, pifo_root) + qc.insert_main(prog, pifo_root, num_cmds) return prog.program diff --git a/calyx-py/test/correctness/queues/sdn.py b/calyx-py/test/correctness/queues/sdn.py index 4d73ed8843..03d1e7f408 100644 --- a/calyx-py/test/correctness/queues/sdn.py +++ b/calyx-py/test/correctness/queues/sdn.py @@ -100,6 +100,9 @@ def build(static=False): """Top-level function to build the program. The `static` flag determines whether the program is static or dynamic. """ + static = "--static" in sys.argv + num_cmds = int(sys.argv[1]) + prog = cb.Builder() stats_component = insert_stats(prog, "stats", static) controller = insert_controller(prog, "controller", stats_component) @@ -119,11 +122,10 @@ def build(static=False): ) # The root PIFO will take a stats component by reference. - queue_call.insert_main(prog, pifo_root, controller, stats_component) + queue_call.insert_main(prog, pifo_root, num_cmds, controller, stats_component) return prog.program # We will have a command line argument to determine whether the program is static. if __name__ == "__main__": - static = sys.argv[1] == "--static" if len(sys.argv) > 1 else False - build(static=static).emit() + build().emit() diff --git a/runt.toml b/runt.toml index d4c3227ad1..043b705fab 100644 --- a/runt.toml +++ b/runt.toml @@ -134,7 +134,7 @@ paths = ["calyx-py/test/correctness/queues/*.py"] cmd = """ name=$(basename {} .py) && dir=$(dirname {}) && -python3 {} |\ +python3 {} 20000 |\ fud e --from calyx --to jq \ --through verilog \ --through dat \ @@ -149,7 +149,7 @@ paths = ["calyx-py/test/correctness/queues/sdn.py"] cmd = """ name=$(basename {} .py) && dir=$(dirname {}) && -python3 {} |\ +python3 {} 20000 |\ fud e --from calyx --to jq \ --through verilog \ --through dat \ @@ -169,7 +169,7 @@ paths = ["calyx-py/test/correctness/queues/sdn.py"] cmd = """ name=$(basename {} .py) && dir=$(dirname {}) && -python3 {} --static |\ +python3 {} 20000 --static |\ fud e --from calyx --to jq \ --through verilog \ --through dat \ @@ -185,7 +185,7 @@ paths = ["calyx-py/test/correctness/queues/sdn.py"] cmd = """ name=$(basename {} .py) && dir=$(dirname {}) && -python3 {} --static |\ +python3 {} 20000 --static |\ fud e --from calyx --to jq \ --through verilog \ --through dat \ @@ -588,7 +588,7 @@ fud2 {} --from calyx --to verilog-noverify 2> /dev/null name = "fud2 cocotb AXI-wrapped xecution" paths = ["yxi/tests/axi/*/*-mem-vec-add-verilog.v"] cmd = """ -fud2 {} --from verilog-noverify --to cocotb-axi --set sim.data=yxi/tests/axi/vectorized-add.data 2> /dev/null +fud2 {} --from verilog-noverify --to cocotb-axi --set sim.data=yxi/tests/axi/vectorized-add.data 2> /dev/null """ ##### Xilinx Tests ######