Skip to content

Commit

Permalink
Queues: tidy testing (#2155)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
anshumanmohan authored Jun 18, 2024
1 parent 76cc300 commit 16e1835
Show file tree
Hide file tree
Showing 13 changed files with 116 additions and 80 deletions.
8 changes: 6 additions & 2 deletions calyx-py/calyx/fifo_oracle.py
Original file line number Diff line number Diff line change
@@ -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)
17 changes: 17 additions & 0 deletions calyx-py/calyx/gen_queue_data_expect.sh
Original file line number Diff line number Diff line change
@@ -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
8 changes: 6 additions & 2 deletions calyx-py/calyx/pifo_oracle.py
Original file line number Diff line number Diff line change
@@ -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)
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -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)
20 changes: 10 additions & 10 deletions calyx-py/calyx/queue_call.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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)
Expand All @@ -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]`
Expand Down Expand Up @@ -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.
"""
Expand All @@ -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

Expand Down
61 changes: 30 additions & 31 deletions calyx-py/calyx/queue_data_gen.py
Original file line number Diff line number Diff line change
@@ -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]]

Expand All @@ -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 = []
Expand All @@ -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"
Expand All @@ -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),
}
}
Expand All @@ -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)
10 changes: 3 additions & 7 deletions calyx-py/calyx/queue_util.py
Original file line number Diff line number Diff line change
@@ -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"]
Expand Down
24 changes: 15 additions & 9 deletions calyx-py/calyx/queues.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
from dataclasses import dataclass
from typing import List
from typing import Optional
from calyx import queue_util


class QueueError(Exception):
Expand All @@ -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:
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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.
"""
Expand Down Expand Up @@ -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
4 changes: 3 additions & 1 deletion calyx-py/test/correctness/queues/fifo.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
# pylint: disable=import-error
import sys
import calyx.builder as cb
import calyx.queue_call as qc

Expand Down Expand Up @@ -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


Expand Down
Loading

0 comments on commit 16e1835

Please sign in to comment.