diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index 86a2d5985b..249d5e2c45 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -418,6 +418,20 @@ def fp_sop( ast.Stdlib.fixed_point_op(op_name, width, int_width, frac_width, True), ) + def unary_use(self, input, cell, groupname=None): + """Accepts a cell that performs some computation on value `input`. + Creates a combinational group that wires up the cell with this port. + Returns the cell and the combintational group. + comb group `groupname` { + `cell.name`.in = `input`; + } + Returns handles to the cell and the combinational group. + """ + groupname = groupname or f"{cell.name}_group" + with self.comb_group(groupname) as comb_group: + cell.in_ = input + return CellAndGroup(cell, comb_group) + def binary_use(self, left, right, cell, groupname=None): """Accepts a cell that performs some computation on values `left` and `right`. Creates a combinational group that wires up the cell with these ports. @@ -488,6 +502,11 @@ def sub_use(self, left, right, signed=False, cellname=None, width=None): width = self.try_infer_width(width, left, right) return self.binary_use(left, right, self.sub(width, cellname, signed)) + def not_use(self, input, cellname=None, width=None): + """Inserts wiring into `self` to compute `not input`.""" + width = self.try_infer_width(width, input, input) + return self.unary_use(input, self.not_(width, cellname)) + def bitwise_flip_reg(self, reg, cellname=None): """Inserts wiring into `self` to bitwise-flip the contents of `reg` and put the result back into `reg`. diff --git a/calyx-py/calyx/fifo_oracle.py b/calyx-py/calyx/fifo_oracle.py index 68f9905807..fd943b4a13 100644 --- a/calyx-py/calyx/fifo_oracle.py +++ b/calyx-py/calyx/fifo_oracle.py @@ -1,8 +1,8 @@ import calyx.queues as queues -import calyx.queue_util as queue_util +from calyx import queue_util if __name__ == "__main__": commands, values = queue_util.parse_json() - fifo = queues.Fifo([]) + fifo = queues.Fifo([], False) ans = queues.operate_queue(commands, values, fifo) queue_util.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/pifo_oracle.py b/calyx-py/calyx/pifo_oracle.py index f454a75407..9786ffc699 100644 --- a/calyx-py/calyx/pifo_oracle.py +++ b/calyx-py/calyx/pifo_oracle.py @@ -1,11 +1,11 @@ import calyx.queues as queues -import calyx.queue_util as queue_util +from calyx import queue_util if __name__ == "__main__": 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) + pifo = queues.Pifo(queues.Fifo([]), queues.Fifo([]), 200, False) ans = queues.operate_queue(commands, values, pifo) queue_util.dump_json(commands, values, ans) diff --git a/calyx-py/calyx/pifotree_oracle.py b/calyx-py/calyx/pifotree_oracle.py index 9ca1f2c350..6969f1be51 100644 --- a/calyx-py/calyx/pifotree_oracle.py +++ b/calyx-py/calyx/pifotree_oracle.py @@ -1,5 +1,5 @@ import calyx.queues as queues -import calyx.queue_util as queue_util +from calyx import queue_util if __name__ == "__main__": @@ -16,7 +16,7 @@ # - The boundary for this is 200. pifo = queues.Pifo( - queues.Pifo(queues.Fifo([]), queues.Fifo([]), 100), queues.Fifo([]), 200 + queues.Pifo(queues.Fifo([]), queues.Fifo([]), 100), queues.Fifo([]), 200, False ) ans = queues.operate_queue(commands, values, pifo) diff --git a/calyx-py/calyx/queue_call.py b/calyx-py/calyx/queue_call.py index e66b50fc73..8b85bf3952 100644 --- a/calyx-py/calyx/queue_call.py +++ b/calyx-py/calyx/queue_call.py @@ -1,5 +1,5 @@ # pylint: disable=import-error -import calyx.queue_util as queue_util +from calyx import queue_util import calyx.builder as cb @@ -55,6 +55,8 @@ def insert_main(prog, queue): incr_i = main.incr(i) # i++ incr_j = main.incr(j) # j++ + lower_err = main.reg_store(err, 0, "lower_err") # err := 1 + cmd_le_1 = main.le_use(cmd.out, 1) # cmd <= 1 read_cmd = main.mem_read_seq_d1(commands, i.out, "read_cmd_phase1") @@ -66,30 +68,12 @@ def insert_main(prog, queue): ) write_ans = main.mem_store_seq_d1(ans_mem, j.out, ans.out, "write_ans") - loop_goes_on = main.reg( - "loop_goes_on", 1 - ) # A flag to indicate whether the loop should continue - update_err_is_down, _ = main.eq_store_in_reg( - err.out, - 0, - "err_is_down", - 1, - loop_goes_on - # Does the `err` flag say that the loop should continue? - ) - update_i_neq_max_cmds, _ = main.neq_store_in_reg( - i.out, - cb.const(32, queue_util.MAX_CMDS), - "i_neq_max_cmds", - 32, - loop_goes_on - # Does the `i` index say that the loop should continue? - ) + i_lt_max_cmds = main.lt_use(i.out, queue_util.MAX_CMDS) + not_err = main.not_use(err.out) main.control += [ - update_err_is_down, - cb.while_( - loop_goes_on.out, # Run while the `err` flag is down + cb.while_with( + i_lt_max_cmds, # Run while i < MAX_CMDS [ read_cmd, write_cmd_to_reg, # `cmd := commands[i]` @@ -102,9 +86,8 @@ def insert_main(prog, queue): ref_ans=ans, ref_err=err, ), - update_err_is_down, # Does `err` say that the loop should be broken? - cb.if_( - loop_goes_on.out, # If the loop is not meant to be broken... + cb.if_with( + not_err, [ cb.if_with( cmd_le_1, # If the command was a pop or peek, @@ -113,11 +96,10 @@ def insert_main(prog, queue): incr_j, # And increment the answer index. ], ), - incr_i, # Increment the command index - update_i_neq_max_cmds, - # Did this increment make us need to break? ], ), + lower_err, # Lower the error flag + incr_i, # Increment the command index ], ), ] diff --git a/calyx-py/calyx/queue_data_gen.py b/calyx-py/calyx/queue_data_gen.py index 327f2d86db..d6fa9227cc 100644 --- a/calyx-py/calyx/queue_data_gen.py +++ b/calyx-py/calyx/queue_data_gen.py @@ -1,7 +1,7 @@ import random import json from typing import Dict, Union -import calyx.queue_util as queue_util +from calyx import queue_util FormatType = Dict[str, Union[bool, str, int]] @@ -23,11 +23,7 @@ def dump_json(): """ commands = { "commands": { - # We'll "rig" these random values a little. - # The first 5% of the commands will be 2 (push). - # The rest will be generated randomly from among 0, 1, and 2. - "data": [2] * (queue_util.MAX_CMDS // 20) - + [random.randint(0, 2) for _ in range(queue_util.MAX_CMDS * 19 // 20)], + "data": [random.randint(0, 2) for _ in range(queue_util.MAX_CMDS)], "format": format_gen(2), } } diff --git a/calyx-py/calyx/queues.py b/calyx-py/calyx/queues.py index 77f2982efc..8d4f156bd8 100644 --- a/calyx-py/calyx/queues.py +++ b/calyx-py/calyx/queues.py @@ -1,37 +1,52 @@ from dataclasses import dataclass from typing import List -import calyx.queue_util as queue_util +from typing import Optional +from calyx import queue_util + + +class QueueError(Exception): + """An error that occurs when we try to pop/peek from an empty queue,""" @dataclass class Fifo: """A FIFO data structure. Supports the operations `push`, `pop`, and `peek`. - Inherent to the queue is its `max_len`, which is given to us at initialization - and we cannot exceed. + Inherent to the queue is its `max_len`, + which is given at initialization and cannot be exceeded. + + If initialized with "error mode" turned on, the queue raises errors in case + of underflow or overflow and stops the simulation. + Otherwise, it allows those commands to fail silently but continues the simulation. """ - def __init__(self, data: List[int], max_len: int = None): + 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 + self.error_mode = error_mode - def push(self, val: int): + def push(self, val: int) -> None: """Pushes `val` to the FIFO.""" - if len(self.data) < self.max_len: - self.data.append(val) - else: - raise IndexError("Cannot push to full FIFO.") + if len(self.data) == self.max_len: + if self.error_mode: + raise QueueError("Cannot push to full FIFO.") + return + self.data.append(val) - def pop(self) -> int: + def pop(self) -> Optional[int]: """Pops the FIFO.""" if len(self.data) == 0: - raise IndexError("Cannot pop from empty FIFO.") + if self.error_mode: + raise QueueError("Cannot pop from empty FIFO.") + return None return self.data.pop(0) - def peek(self) -> int: + def peek(self) -> Optional[int]: """Peeks into the FIFO.""" if len(self.data) == 0: - raise IndexError("Cannot peek into empty FIFO.") + if self.error_mode: + raise QueueError("Cannot peek into empty FIFO.") + return None return self.data[0] def __len__(self) -> int: @@ -43,9 +58,10 @@ 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. + We do this by maintaining two sub-queues that are given to us at initialization. + We toggle between these sub-queues when popping/peeking. + We have a variable called `hot` that says which sub-queue is to be + popped/peeked next. `hot` starts at 0. We also take at initialization a `boundary` value. @@ -55,9 +71,12 @@ class Pifo: Inherent to the queue is its `max_len`, which is given to us at initialization and we cannot exceed. + If initialized with "error mode" turned on, the queue raises errors in case + of underflow or overflow and stops the simulation. + Otherwise, it allows those commands to fail silently but continues the simulation. When asked to pop: - - If `pifo_len` is 0, we raise an error. + - If `pifo_len` is 0, we fail silently or 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. @@ -67,22 +86,23 @@ class Pifo: When asked to peek: We do the same thing as above, except: - - We peek instead of popping. + - We peek into the sub-queue instead of popping it. - We don't flip `hot`. When asked to push: - - If the PIFO is at length `max_len`, we raise an error. + - If the PIFO is at length `max_len`, we fail silently or raise an error. - 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, max_len=None): + def __init__(self, queue_1, queue_2, boundary, error_mode=True, max_len=None): 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.error_mode = error_mode assert ( self.pifo_len <= self.max_len ) # We can't be initialized with a PIFO that is too long. @@ -90,44 +110,52 @@ def __init__(self, queue_1, queue_2, boundary, max_len=None): def push(self, val: int): """Pushes `val` to the PIFO.""" if self.pifo_len == self.max_len: - raise IndexError("Cannot push to full PIFO.") - if val < self.boundary: + if self.error_mode: + raise QueueError("Cannot push to full PIFO.") + return + if val <= self.boundary: self.data[0].push(val) else: self.data[1].push(val) self.pifo_len += 1 - def pop(self) -> int: + def pop(self) -> Optional[int]: """Pops the PIFO.""" if self.pifo_len == 0: - raise IndexError("Cannot pop from empty PIFO.") + if self.error_mode: + raise QueueError("Cannot pop from empty PIFO.") + return None 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: + except QueueError: + self.hot = 0 return self.data[1].pop() else: try: self.hot = 0 return self.data[1].pop() - except IndexError: + except QueueError: + self.hot = 1 return self.data[0].pop() - def peek(self) -> int: + def peek(self) -> Optional[int]: """Peeks into the PIFO.""" if self.pifo_len == 0: - raise IndexError("Cannot peek into empty PIFO.") + if self.error_mode: + raise QueueError("Cannot peek into empty PIFO.") + return None if self.hot == 0: try: return self.data[0].peek() - except IndexError: + except QueueError: return self.data[1].peek() else: try: return self.data[1].peek() - except IndexError: + except QueueError: return self.data[0].peek() def __len__(self) -> int: @@ -143,20 +171,24 @@ def operate_queue(commands, values, queue): for cmd, val in zip(commands, values): if cmd == 0: try: - ans.append(queue.pop()) - except IndexError: + result = queue.pop() + if result: + ans.append(result) + except QueueError: break elif cmd == 1: try: - ans.append(queue.peek()) - except IndexError: + result = queue.peek() + if result: + ans.append(queue.peek()) + except QueueError: break elif cmd == 2: try: queue.push(val) - except IndexError: + except QueueError: break # Pad the answer memory with zeroes until it is of length MAX_CMDS. diff --git a/calyx-py/test/correctness/fifo.data b/calyx-py/test/correctness/fifo.data index 7d0d68fb24..485b427245 100644 --- a/calyx-py/test/correctness/fifo.data +++ b/calyx-py/test/correctness/fifo.data @@ -1,11 +1,6 @@ { "commands": { "data": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -100,7 +95,12 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "format": { "is_signed": false, @@ -110,11 +110,6 @@ }, "values": { "data": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -209,7 +204,12 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ], "format": { "is_signed": false, diff --git a/calyx-py/test/correctness/fifo.expect b/calyx-py/test/correctness/fifo.expect index 63c85e68a1..b3523a34ef 100644 --- a/calyx-py/test/correctness/fifo.expect +++ b/calyx-py/test/correctness/fifo.expect @@ -1,54 +1,54 @@ { "ans_mem": [ - 296, - 296, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 92, + 92, + 92, + 100, + 100, + 386, + 125, + 236, + 176, + 176, + 176, + 128, + 128, + 151, + 221, + 174, + 47, + 47, + 159, + 368, + 24, + 41, + 207, + 207, + 207, + 16, + 252, + 252, + 106, + 106, + 106, + 106, + 106, + 106, + 66, + 66, + 66, + 374, + 222, + 222, + 150, + 150, + 150, + 267, + 267, + 267, + 267, + 373, + 373, 0, 0, 0, @@ -102,11 +102,6 @@ 0 ], "commands": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -201,14 +196,14 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "values": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -303,6 +298,11 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ] } diff --git a/calyx-py/test/correctness/pifo.data b/calyx-py/test/correctness/pifo.data index 7d0d68fb24..485b427245 100644 --- a/calyx-py/test/correctness/pifo.data +++ b/calyx-py/test/correctness/pifo.data @@ -1,11 +1,6 @@ { "commands": { "data": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -100,7 +95,12 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "format": { "is_signed": false, @@ -110,11 +110,6 @@ }, "values": { "data": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -209,7 +204,12 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ], "format": { "is_signed": false, diff --git a/calyx-py/test/correctness/pifo.expect b/calyx-py/test/correctness/pifo.expect index 342f598736..e13d98057b 100644 --- a/calyx-py/test/correctness/pifo.expect +++ b/calyx-py/test/correctness/pifo.expect @@ -1,54 +1,54 @@ { "ans_mem": [ - 4, - 4, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 92, + 92, + 92, + 386, + 386, + 100, + 236, + 125, + 176, + 176, + 176, + 128, + 128, + 221, + 151, + 174, + 47, + 47, + 159, + 368, + 24, + 207, + 41, + 41, + 41, + 252, + 16, + 16, + 374, + 374, + 374, + 374, + 374, + 374, + 106, + 106, + 106, + 222, + 66, + 66, + 267, + 267, + 267, + 150, + 150, + 150, + 150, + 373, + 373, 0, 0, 0, @@ -102,11 +102,6 @@ 0 ], "commands": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -201,14 +196,14 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "values": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -303,6 +298,11 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ] } diff --git a/calyx-py/test/correctness/pifo_tree.data b/calyx-py/test/correctness/pifo_tree.data index 7d0d68fb24..485b427245 100644 --- a/calyx-py/test/correctness/pifo_tree.data +++ b/calyx-py/test/correctness/pifo_tree.data @@ -1,11 +1,6 @@ { "commands": { "data": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -100,7 +95,12 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "format": { "is_signed": false, @@ -110,11 +110,6 @@ }, "values": { "data": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -209,7 +204,12 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ], "format": { "is_signed": false, diff --git a/calyx-py/test/correctness/pifo_tree.expect b/calyx-py/test/correctness/pifo_tree.expect index 342f598736..876500088f 100644 --- a/calyx-py/test/correctness/pifo_tree.expect +++ b/calyx-py/test/correctness/pifo_tree.expect @@ -1,54 +1,54 @@ { "ans_mem": [ - 4, - 4, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, - 0, + 92, + 92, + 92, + 386, + 386, + 125, + 236, + 100, + 176, + 176, + 176, + 128, + 128, + 221, + 151, + 174, + 47, + 47, + 159, + 368, + 24, + 207, + 41, + 41, + 41, + 252, + 106, + 106, + 374, + 374, + 374, + 374, + 374, + 374, + 16, + 16, + 16, + 222, + 150, + 150, + 267, + 267, + 267, + 66, + 66, + 66, + 66, + 373, + 373, 0, 0, 0, @@ -102,11 +102,6 @@ 0 ], "commands": [ - 2, - 2, - 2, - 2, - 2, 2, 1, 2, @@ -201,14 +196,14 @@ 2, 1, 1, - 1 + 1, + 2, + 0, + 1, + 0, + 2 ], "values": [ - 296, - 4, - 231, - 23, - 362, 92, 319, 100, @@ -303,6 +298,11 @@ 373, 162, 245, - 140 + 140, + 149, + 240, + 206, + 75, + 57 ] }