Skip to content

Commit

Permalink
Queues: option for no errors (#1779)
Browse files Browse the repository at this point in the history
  • Loading branch information
anshumanmohan authored Nov 16, 2023
1 parent 879d13c commit bf07990
Show file tree
Hide file tree
Showing 13 changed files with 324 additions and 295 deletions.
19 changes: 19 additions & 0 deletions calyx-py/calyx/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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`.
Expand Down
4 changes: 2 additions & 2 deletions calyx-py/calyx/fifo_oracle.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions calyx-py/calyx/pifo_oracle.py
Original file line number Diff line number Diff line change
@@ -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)
4 changes: 2 additions & 2 deletions calyx-py/calyx/pifotree_oracle.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import calyx.queues as queues
import calyx.queue_util as queue_util
from calyx import queue_util


if __name__ == "__main__":
Expand All @@ -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)
Expand Down
40 changes: 11 additions & 29 deletions calyx-py/calyx/queue_call.py
Original file line number Diff line number Diff line change
@@ -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


Expand Down Expand Up @@ -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")
Expand All @@ -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]`
Expand All @@ -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,
Expand All @@ -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
],
),
]
8 changes: 2 additions & 6 deletions calyx-py/calyx/queue_data_gen.py
Original file line number Diff line number Diff line change
@@ -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]]

Expand All @@ -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),
}
}
Expand Down
102 changes: 67 additions & 35 deletions calyx-py/calyx/queues.py
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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.
Expand All @@ -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.
Expand All @@ -67,67 +86,76 @@ 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.

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:
Expand All @@ -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.
Expand Down
Loading

0 comments on commit bf07990

Please sign in to comment.