diff --git a/calyx-py/calyx/builder.py b/calyx-py/calyx/builder.py index c206c3b595..7a6ce0c0fd 100644 --- a/calyx-py/calyx/builder.py +++ b/calyx-py/calyx/builder.py @@ -523,17 +523,23 @@ def bitwise_flip_reg(self, reg, cellname=None): not_group.done = reg.done return not_group - def incr(self, reg, val=1, signed=False, cellname=None): + def incr(self, reg, val=1, signed=False, cellname=None, static=False): """Inserts wiring into `self` to perform `reg := reg + val`.""" cellname = cellname or f"{reg.name}_incr" width = reg.infer_width_reg() add_cell = self.add(width, cellname, signed) - with self.group(f"{cellname}_group") as incr_group: + group = ( + self.static_group(f"{cellname}_group", 1) + if static + else self.group(f"{cellname}_group") + ) + with group as incr_group: add_cell.left = reg.out add_cell.right = const(width, val) reg.write_en = 1 reg.in_ = add_cell.out - incr_group.done = reg.done + if not static: + incr_group.done = reg.done return incr_group def decr(self, reg, val=1, signed=False, cellname=None): @@ -847,6 +853,32 @@ def invoke(cell: CellBuilder, **kwargs) -> ast.Invoke: ) +def static_invoke(cell: CellBuilder, **kwargs) -> ast.Invoke: + """Build a `static invoke` control statement. + + The keyword arguments should have the form `in_*`, `out_*`, or `ref_*`, where + `*` is the name of an input port, output port, or ref cell on the invoked cell. + """ + return ast.StaticInvoke( + cell._cell.id, + [ + (k[3:], ExprBuilder.unwrap(v)) + for (k, v) in kwargs.items() + if k.startswith("in_") + ], + [ + (k[4:], ExprBuilder.unwrap(v)) + for (k, v) in kwargs.items() + if k.startswith("out_") + ], + [ + (k[4:], CellBuilder.unwrap_id(v)) + for (k, v) in kwargs.items() + if k.startswith("ref_") + ], + ) + + class ControlBuilder: """Wraps control statements for convenient construction.""" @@ -1256,6 +1288,15 @@ def par(*args) -> ast.ParComp: return ast.ParComp([as_control(x) for x in args]) +def static_par(*args) -> ast.StaticParComp: + """Build a static parallel composition of control expressions. + + Each argument will become its own parallel arm in the resulting composition. + So `par([a,b])` becomes `par {seq {a; b;}}` while `par(a, b)` becomes `par {a; b;}`. + """ + return ast.StaticParComp([as_control(x) for x in args]) + + def seq(*args) -> ast.SeqComp: """Build a sequential composition of control expressions. @@ -1268,6 +1309,18 @@ def seq(*args) -> ast.SeqComp: return ast.SeqComp([as_control(x) for x in args]) +def static_seq(*args) -> ast.StaticSeqComp: + """Build a static sequential composition of control expressions. + + Prefer use of python list syntax over this function. Use only when not directly + modifying the control program with the `+=` operator. + Each argument will become its own sequential arm in the resulting composition. + So `seq([a,b], c)` becomes `seq { seq {a; b;} c }` while `seq(a, b, c)` becomes `seq + {a; b; c;}`. + """ + return ast.StaticSeqComp([as_control(x) for x in args]) + + def add_comp_params(comp: ComponentBuilder, input_ports: List, output_ports: List): """ Adds `input_ports`/`output_ports` as inputs/outputs to comp. diff --git a/calyx-py/test/correctness/pifo.py b/calyx-py/test/correctness/pifo.py index 42c1699df2..ae868d1ac7 100644 --- a/calyx-py/test/correctness/pifo.py +++ b/calyx-py/test/correctness/pifo.py @@ -44,7 +44,7 @@ def invoke_subqueue(queue_cell, cmd, value, ans, err) -> cb.invoke: ) -def insert_pifo(prog, name, queue_l, queue_r, boundary, stats=None): +def insert_pifo(prog, name, queue_l, queue_r, boundary, stats=None, static=False): """Inserts the component `pifo` into the program. The PIFO achieves a 50/50 split between two "flows" or "kinds". @@ -301,7 +301,11 @@ def insert_pifo(prog, name, queue_l, queue_r, boundary, stats=None): # Increment the length and also # tell the stats component what flow we pushed. len_incr, - cb.invoke(stats, in_flow=flow.out), + ( + cb.static_invoke(stats, in_flow=flow.out) + if static + else cb.invoke(stats, in_flow=flow.out) + ), ], ), ], diff --git a/calyx-py/test/correctness/piezo_pifotree.data b/calyx-py/test/correctness/sdn.data similarity index 100% rename from calyx-py/test/correctness/piezo_pifotree.data rename to calyx-py/test/correctness/sdn.data diff --git a/calyx-py/test/correctness/piezo_pifotree.expect b/calyx-py/test/correctness/sdn.expect similarity index 100% rename from calyx-py/test/correctness/piezo_pifotree.expect rename to calyx-py/test/correctness/sdn.expect diff --git a/calyx-py/test/correctness/piezo_pifotree.py b/calyx-py/test/correctness/sdn.py similarity index 77% rename from calyx-py/test/correctness/piezo_pifotree.py rename to calyx-py/test/correctness/sdn.py index 45204cdde2..43650f0cf9 100644 --- a/calyx-py/test/correctness/piezo_pifotree.py +++ b/calyx-py/test/correctness/sdn.py @@ -6,7 +6,7 @@ from calyx import queue_util -def insert_stats(prog, name): +def insert_stats(prog, name, static=False): """Inserts a stats component called `name` into the program `prog`. It maintains: @@ -20,9 +20,13 @@ def insert_stats(prog, name): When invoked, the component reads the flow index and increments `count_0_sto` or `count_1_sto` as appropriate. + + If `static` is False, this is a dynamic component. + Otherwise, it is a static component with delay 1. """ - stats: cb.ComponentBuilder = prog.component(name) + stats: cb.ComponentBuilder = prog.component(name, latency=1 if static else None) + flow = stats.input("flow", 1) stats.output("count_0", 32) stats.output("count_1", 32) @@ -32,24 +36,36 @@ def insert_stats(prog, name): count_1_sto = stats.reg("count_1_sto", 32) # Wiring to increment the appropriate register. - count_0_incr = stats.incr(count_0_sto) - count_1_incr = stats.incr(count_1_sto) + count_0_incr = stats.incr(count_0_sto, static=static) + count_1_incr = stats.incr(count_1_sto, static=static) - # Equality checks. - flow_eq_0 = stats.eq_use(flow, 0) - flow_eq_1 = stats.eq_use(flow, 1) + # The rest of the logic varies depending on whether the component is static. - with stats.continuous: - stats.this().count_0 = count_0_sto.out - stats.this().count_1 = count_1_sto.out + # If not static, we can use comb groups. + if not static: + flow_eq_0 = stats.eq_use(flow, 0) - # The main logic. - stats.control += [ - cb.par( - cb.if_with(flow_eq_0, [count_0_incr]), - cb.if_with(flow_eq_1, [count_1_incr]), - ), - ] + with stats.continuous: + stats.this().count_0 = count_0_sto.out + stats.this().count_1 = count_1_sto.out + + stats.control += cb.par( + cb.if_with(flow_eq_0, count_0_incr, count_1_incr), + ) + + # If static, we need to use continuous assignments and not comb groups. + else: + eq_cell = stats.eq(1, "eq_cell") + + with stats.continuous: + stats.this().count_0 = count_0_sto.out + stats.this().count_1 = count_1_sto.out + eq_cell.left = flow + eq_cell.right = 0 + + stats.control += cb.static_par( + cb.static_if(eq_cell.out, count_0_incr, count_1_incr), + ) return stats @@ -139,16 +155,18 @@ def insert_main(prog, dataplane, controller, stats_component): ] -def build(): - """Top-level function to build the program.""" +def build(static=False): + """Top-level function to build the program. + The `static` flag determines whether the program is static or dynamic. + """ prog = cb.Builder() - stats_component = insert_stats(prog, "stats") + stats_component = insert_stats(prog, "stats", static) 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, stats_component + prog, "pifo_root", pifo_red, fifo_blue, 200, stats_component, static ) # The root PIFO will take a stats component by reference. dataplane = queue_call.insert_runner(prog, pifo_root, "dataplane", stats_component) diff --git a/calyx-py/test/correctness/static/sdn_static.data b/calyx-py/test/correctness/static/sdn_static.data new file mode 100644 index 0000000000..a2f8b6cab5 --- /dev/null +++ b/calyx-py/test/correctness/static/sdn_static.data @@ -0,0 +1,329 @@ +{ + "commands": { + "data": [ + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 2, + 0, + 0, + 2, + 0, + 2, + 2, + 0, + 2, + 0, + 2, + 2, + 2, + 2, + 2, + 0, + 2, + 2, + 0, + 0, + 2, + 0, + 2, + 2, + 0, + 2, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 2, + 2, + 2, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 2, + 2, + 0, + 2, + 2, + 0, + 2, + 2, + 2, + 0, + 0 + ], + "format": { + "is_signed": false, + "numeric_type": "bitnum", + "width": 2 + } + }, + "values": { + "data": [ + 320, + 76, + 369, + 352, + 158, + 247, + 82, + 368, + 24, + 41, + 307, + 273, + 207, + 16, + 121, + 379, + 304, + 176, + 128, + 233, + 333, + 215, + 74, + 28, + 326, + 16, + 252, + 171, + 106, + 66, + 374, + 288, + 67, + 322, + 211, + 54, + 86, + 222, + 190, + 76, + 30, + 215, + 150, + 72, + 232, + 317, + 86, + 267, + 232, + 249, + 352, + 373, + 162, + 245, + 140, + 149, + 240, + 206, + 75, + 57, + 193, + 272, + 91, + 321, + 255, + 173, + 92, + 45, + 251, + 139, + 263, + 400, + 280, + 257, + 184, + 32, + 396, + 182, + 355, + 300, + 339, + 17, + 388, + 156, + 186, + 286, + 360, + 342, + 143, + 248, + 135, + 394, + 353, + 366, + 150, + 174, + 332, + 91, + 297, + 5 + ], + "format": { + "is_signed": false, + "numeric_type": "bitnum", + "width": 32 + } + }, + "ans_mem": { + "data": [ + 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, + 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, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "format": { + "is_signed": false, + "numeric_type": "bitnum", + "width": 32 + } + } +} diff --git a/calyx-py/test/correctness/static/sdn_static.expect b/calyx-py/test/correctness/static/sdn_static.expect new file mode 100644 index 0000000000..3cbecc4ce8 --- /dev/null +++ b/calyx-py/test/correctness/static/sdn_static.expect @@ -0,0 +1,308 @@ +{ + "ans_mem": [ + 76, + 320, + 352, + 24, + 273, + 41, + 304, + 176, + 233, + 28, + 322, + 67, + 190, + 215, + 76, + 317, + 150, + 267, + 162, + 232, + 75, + 352, + 140, + 245, + 57, + 240, + 149, + 255, + 91, + 251, + 173, + 400, + 45, + 355, + 139, + 300, + 17, + 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, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0 + ], + "commands": [ + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 0, + 0, + 0, + 2, + 2, + 0, + 2, + 2, + 0, + 0, + 2, + 0, + 2, + 2, + 0, + 2, + 0, + 2, + 2, + 2, + 2, + 2, + 0, + 2, + 2, + 0, + 0, + 2, + 0, + 2, + 2, + 0, + 2, + 2, + 2, + 0, + 2, + 0, + 0, + 0, + 0, + 0, + 2, + 2, + 2, + 2, + 2, + 0, + 2, + 2, + 0, + 2, + 0, + 0, + 2, + 2, + 0, + 2, + 2, + 0, + 2, + 2, + 2, + 0, + 0 + ], + "values": [ + 320, + 76, + 369, + 352, + 158, + 247, + 82, + 368, + 24, + 41, + 307, + 273, + 207, + 16, + 121, + 379, + 304, + 176, + 128, + 233, + 333, + 215, + 74, + 28, + 326, + 16, + 252, + 171, + 106, + 66, + 374, + 288, + 67, + 322, + 211, + 54, + 86, + 222, + 190, + 76, + 30, + 215, + 150, + 72, + 232, + 317, + 86, + 267, + 232, + 249, + 352, + 373, + 162, + 245, + 140, + 149, + 240, + 206, + 75, + 57, + 193, + 272, + 91, + 321, + 255, + 173, + 92, + 45, + 251, + 139, + 263, + 400, + 280, + 257, + 184, + 32, + 396, + 182, + 355, + 300, + 339, + 17, + 388, + 156, + 186, + 286, + 360, + 342, + 143, + 248, + 135, + 394, + 353, + 366, + 150, + 174, + 332, + 91, + 297, + 5 + ] +} diff --git a/calyx-py/test/correctness/static/sdn_static.py b/calyx-py/test/correctness/static/sdn_static.py new file mode 100644 index 0000000000..c011ef3e4c --- /dev/null +++ b/calyx-py/test/correctness/static/sdn_static.py @@ -0,0 +1,9 @@ +# Import the sdn module, which is one level up. +import sys +import os + +sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) +import sdn + +if __name__ == "__main__": + sdn.build(static=True).emit() diff --git a/runt.toml b/runt.toml index fc4b140d38..8d9d77acba 100644 --- a/runt.toml +++ b/runt.toml @@ -114,6 +114,21 @@ python3 {} |\ -q """ +[[tests]] +name = "[calyx-py] static correctness" +paths = ["calyx-py/test/correctness/static/*.py"] +cmd = """ +name=$(basename {} .py) && +dir=$(dirname {}) && +python3 {} |\ + fud e --from calyx --to jq \ + --through verilog \ + --through dat \ + -s verilog.data "$dir/$name.data" \ + -s calyx.flags "-d well-formed" \ + -s jq.expr ".memories" \ + -q +""" ##### Correctness Tests ##### ## Tests that ensure that individual control