From 2df97f19d3fdaeb215463acfe016f4948893f9e6 Mon Sep 17 00:00:00 2001 From: Anshuman Mohan <10830208+anshumanmohan@users.noreply.github.com> Date: Thu, 16 Nov 2023 16:10:48 -0500 Subject: [PATCH] `pifo`: better testing (#1763) --- calyx-py/calyx/fifo_oracle.py | 62 +------ calyx-py/calyx/pifo_oracle.py | 11 ++ .../{fifo_data_gen.py => queue_data_gen.py} | 7 +- calyx-py/calyx/queues.py | 171 ++++++++++++++++++ calyx-py/test/correctness/fifo.data | 28 +-- calyx-py/test/correctness/fifo.expect | 44 ++--- calyx-py/test/correctness/pifo.data | 38 ++-- calyx-py/test/correctness/pifo.expect | 54 +++--- 8 files changed, 272 insertions(+), 143 deletions(-) create mode 100644 calyx-py/calyx/pifo_oracle.py rename calyx-py/calyx/{fifo_data_gen.py => queue_data_gen.py} (93%) create mode 100644 calyx-py/calyx/queues.py diff --git a/calyx-py/calyx/fifo_oracle.py b/calyx-py/calyx/fifo_oracle.py index 6ca4fbd542..8cc4104f58 100644 --- a/calyx-py/calyx/fifo_oracle.py +++ b/calyx-py/calyx/fifo_oracle.py @@ -1,60 +1,8 @@ -import sys -import json - - -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. - Returns the two lists. - """ - - # The JSON file is piped to us in stdin. - data = json.load(sys.stdin) - commands = data["commands"]["data"] - values = data["values"]["data"] - return commands, values - - -def operate_fifo(commands, values): - """Given the three lists, operate a FIFO routine. - - Read the comammands list in order. - - When the value is 0, we "pop" the FIFO and write the value to the answer memory. - - When it is 1, we "peek" into the FIFO and write the value to the answer memory. - - When it is 2, we push the coressponding item in the `values` list to the FIFO. - - In the end, we return the answer memory. - """ - fifo = [] - ans = [] - for cmd, val in zip(commands, values): - if cmd == 0: - if len(fifo) == 0: - break - ans.append(fifo.pop(0)) - elif cmd == 1: - if len(fifo) == 0: - break - ans.append(fifo[0]) - elif cmd == 2: - fifo.append(val) - # Pad the answer memory with zeroes until it is of length ANS_MEM_LEN. - ans += [0] * (10 - len(ans)) - return ans - - -def dump_json(commands, values, ans_mem): - """Prints a JSON representation of the data to stdout.""" - payload = { - "ans_mem": ans_mem, - "commands": commands, - "values": values, - } - print(json.dumps(payload, indent=2)) +import queues if __name__ == "__main__": - commands, values = parse_json() - ans = operate_fifo(commands, values) - dump_json(commands, values, ans) + commands, values = queues.parse_json() + pifo = queues.Fifo([]) + ans = queues.operate_queue(commands, values, pifo) + queues.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/pifo_oracle.py b/calyx-py/calyx/pifo_oracle.py new file mode 100644 index 0000000000..55d7a81bc6 --- /dev/null +++ b/calyx-py/calyx/pifo_oracle.py @@ -0,0 +1,11 @@ +import queues + + +if __name__ == "__main__": + commands, values = queues.parse_json() + + # Our PIFO is simple: it just orchestrates two FIFOs. The boundary is 200. + pifo = queues.Pifo(queues.Fifo([]), queues.Fifo([]), 200) + + ans = queues.operate_queue(commands, values, pifo) + queues.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/fifo_data_gen.py b/calyx-py/calyx/queue_data_gen.py similarity index 93% rename from calyx-py/calyx/fifo_data_gen.py rename to calyx-py/calyx/queue_data_gen.py index da07f57f49..13f5cf211b 100644 --- a/calyx-py/calyx/fifo_data_gen.py +++ b/calyx-py/calyx/queue_data_gen.py @@ -1,5 +1,4 @@ import random -import time import json from typing import Dict, Union @@ -19,7 +18,7 @@ def dump_json(): The data itself is populated randomly, following certain rules: - It has three "memories": `commands`, `values`, and `ans_mem`. - The `commands` memory has MAX_CMDS items, which are 0, 1, or 2. - - The `values` memory has MAX_CMDS items: random values between 0 and 100. + - The `values` memory has MAX_CMDS items: random values between 0 and 400. - The `ans_mem` memory has ANS_MEM_LEN items, all zeroes. - Each memory has a `format` field, which is a format object for a bitvector. """ @@ -32,8 +31,8 @@ def dump_json(): } values = { "values": { - "data": [random.randint(0, 100) for _ in range(MAX_CMDS)], - # The `values` memory has MAX_CMDS items: random values between 0 and 100. + "data": [random.randint(0, 400) for _ in range(MAX_CMDS)], + # The `values` memory has MAX_CMDS items: random values between 0 and 00. "format": format_gen(32), } } diff --git a/calyx-py/calyx/queues.py b/calyx-py/calyx/queues.py new file mode 100644 index 0000000000..c9c493b9e6 --- /dev/null +++ b/calyx-py/calyx/queues.py @@ -0,0 +1,171 @@ +import sys +import json +from dataclasses import dataclass +from typing import List + +ANS_MEM_LEN = 10 + + +@dataclass +class Fifo: + """A FIFO data structure. + Supports the operations `push`, `pop`, and `peek`. + """ + + def __init__(self, data: List[int]): + self.data = data + + def push(self, val: int): + """Pushes `val` to the FIFO.""" + self.data.append(val) + + def pop(self) -> int: + """Pops the FIFO.""" + if len(self.data) == 0: + raise IndexError("Cannot pop from empty FIFO.") + return self.data.pop(0) + + def peek(self) -> int: + """Peeks into the FIFO.""" + if len(self.data) == 0: + raise IndexError("Cannot peek into empty FIFO.") + return self.data[0] + + def __len__(self) -> int: + return len(self.data) + + +@dataclass +class Pifo: + """A PIFO data structure. + Supports the operations `push`, `pop`, and `peek`. + + We do this by maintaining two queues that are given to us at initialization. + We toggle between these queues when popping/peeking. + We have a variable called `hot` that says which queue is to be popped/peeked next. + `hot` starts at 0. + We also take at initialization a `boundary` value. + + We maintain internally a variable called `pifo_len`: + the sum of the lengths of the two queues. + + When asked to pop: + - If `pifo_len` is 0, we raise an error. + - Else, if `hot` is 0, we try to pop from queue_0. + + If it succeeds, we flip `hot` to 1 and return the value we got. + + If it fails, we pop from queue_1 and return the value we got. + We leave `hot` as it was. + - If `hot` is 1, we proceed symmetrically. + - We decrement `pifo_len` by 1. + + When asked to peek: + We do the same thing as above, except: + - We peek instead of popping. + - We don't flip `hot`. + + When asked to push: + - If the value to be pushed is less than `boundary`, we push it into queue_1. + - Else, we push it into queue_2. + - We increment `pifo_len` by 1. + """ + + def __init__(self, queue_1, queue_2, boundary): + self.data = (queue_1, queue_2) + self.hot = 0 + self.pifo_len = len(queue_1) + len(queue_2) + self.boundary = boundary + + def push(self, val: int): + """Pushes `val` to the PIFO.""" + if val < self.boundary: + self.data[0].push(val) + else: + self.data[1].push(val) + self.pifo_len += 1 + + def pop(self) -> int: + """Pops the PIFO.""" + if self.pifo_len == 0: + raise IndexError("Cannot pop from empty PIFO.") + self.pifo_len -= 1 # We decrement `pifo_len` by 1. + if self.hot == 0: + try: + self.hot = 1 + return self.data[0].pop() + except IndexError: + return self.data[1].pop() + else: + try: + self.hot = 0 + return self.data[1].pop() + except IndexError: + return self.data[0].pop() + + def peek(self) -> int: + """Peeks into the PIFO.""" + if self.pifo_len == 0: + raise IndexError("Cannot peek into empty PIFO.") + if self.hot == 0: + try: + return self.data[0].peek() + except IndexError: + return self.data[1].peek() + else: + try: + return self.data[1].peek() + except IndexError: + return self.data[0].peek() + + def __len__(self) -> int: + return self.pifo_len + + +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. + Returns the two lists. + """ + + data = json.load(sys.stdin) + commands = data["commands"]["data"] + values = data["values"]["data"] + return commands, values + + +def dump_json(commands, values, ans_mem): + """Prints a JSON representation of the data to stdout.""" + payload = { + "ans_mem": ans_mem, + "commands": commands, + "values": values, + } + print(json.dumps(payload, indent=2)) + + +def operate_queue(commands, values, queue): + """Given the two lists, one of commands and one of values. + Feed these into our queue, and return the answer memory. + """ + + ans = [] + for cmd, val in zip(commands, values): + if cmd == 0: + try: + ans.append(queue.pop()) + except IndexError: + break + + elif cmd == 1: + try: + ans.append(queue.peek()) + except IndexError: + break + + elif cmd == 2: + queue.push(val) + + # Pad the answer memory with zeroes until it is of length ANS_MEM_LEN. + ans += [0] * (ANS_MEM_LEN - len(ans)) + return ans diff --git a/calyx-py/test/correctness/fifo.data b/calyx-py/test/correctness/fifo.data index 65ab3d659a..5f72a3db88 100644 --- a/calyx-py/test/correctness/fifo.data +++ b/calyx-py/test/correctness/fifo.data @@ -25,21 +25,21 @@ }, "values": { "data": [ - 47, - 60, - 31, - 48, - 69, - 13, - 73, - 31, - 1, - 93, - 27, + 190, + 240, + 126, + 194, + 278, 52, - 35, - 23, - 98 + 293, + 127, + 6, + 374, + 110, + 208, + 143, + 93, + 392 ], "format": { "is_signed": false, diff --git a/calyx-py/test/correctness/fifo.expect b/calyx-py/test/correctness/fifo.expect index 20595ecca5..d55133390a 100644 --- a/calyx-py/test/correctness/fifo.expect +++ b/calyx-py/test/correctness/fifo.expect @@ -1,13 +1,13 @@ { "ans_mem": [ - 47, - 47, - 47, - 31, - 31, - 69, - 13, - 73, + 190, + 190, + 190, + 126, + 126, + 278, + 52, + 293, 0, 0 ], @@ -29,20 +29,20 @@ 0 ], "values": [ - 47, - 60, - 31, - 48, - 69, - 13, - 73, - 31, - 1, - 93, - 27, + 190, + 240, + 126, + 194, + 278, 52, - 35, - 23, - 98 + 293, + 127, + 6, + 374, + 110, + 208, + 143, + 93, + 392 ] } diff --git a/calyx-py/test/correctness/pifo.data b/calyx-py/test/correctness/pifo.data index fc06886e71..5f72a3db88 100644 --- a/calyx-py/test/correctness/pifo.data +++ b/calyx-py/test/correctness/pifo.data @@ -2,19 +2,19 @@ "commands": { "data": [ 2, + 1, 2, - 0, - 0, + 1, 2, 2, 2, 2, - 1, 0, + 1, 0, + 2, 0, 0, - 2, 0 ], "format": { @@ -25,21 +25,21 @@ }, "values": { "data": [ - 101, - 102, - 0, - 0, - 103, - 104, - 201, - 202, - 0, - 0, - 0, - 0, - 0, - 105, - 0 + 190, + 240, + 126, + 194, + 278, + 52, + 293, + 127, + 6, + 374, + 110, + 208, + 143, + 93, + 392 ], "format": { "is_signed": false, diff --git a/calyx-py/test/correctness/pifo.expect b/calyx-py/test/correctness/pifo.expect index 0631155c8d..e29484e3fe 100644 --- a/calyx-py/test/correctness/pifo.expect +++ b/calyx-py/test/correctness/pifo.expect @@ -1,48 +1,48 @@ { "ans_mem": [ - 101, - 102, - 201, - 201, - 103, - 202, - 104, - 105, + 190, + 190, + 190, + 278, + 278, + 126, + 293, + 52, 0, 0 ], "commands": [ 2, + 1, 2, - 0, - 0, + 1, 2, 2, 2, 2, - 1, 0, + 1, 0, + 2, 0, 0, - 2, 0 ], "values": [ - 101, - 102, - 0, - 0, - 103, - 104, - 201, - 202, - 0, - 0, - 0, - 0, - 0, - 105, - 0 + 190, + 240, + 126, + 194, + 278, + 52, + 293, + 127, + 6, + 374, + 110, + 208, + 143, + 93, + 392 ] }