From 5e8ff91b24bc941590c08b00a8d7e315f58a86a0 Mon Sep 17 00:00:00 2001 From: Nathaniel Navarro Date: Tue, 31 Dec 2024 03:28:51 -0800 Subject: [PATCH] [fud2] AXI wrapper script cleanup & unified python names (#2384) The WIP [PR that tries to get an `yxi` powered end to end xilinx tools workflow up and running](https://github.com/calyxir/calyx/pull/2267) ballooned into a beast and has gotten pretty stale. This is me trying to break things down into parts and slowly get everything merged. This changes the `*axi_generator.py` files to use underscores, which is needed for proper importing afaict. Also snuck in formatting changes to `dynamic_axi_generator.py`. These are pretty minor changes so going to merge when tests pass. --- fud2/scripts/axi.rhai | 6 +- .../tests__test@plan_axi-wrapped.snap | 2 +- runt.toml | 8 +- .../{axi-generator.py => axi_generator.py} | 4 + ...-generator.py => dynamic_axi_generator.py} | 188 ++++++++++-------- 5 files changed, 117 insertions(+), 91 deletions(-) rename yxi/axi-calyx/{axi-generator.py => axi_generator.py} (99%) rename yxi/axi-calyx/{dynamic-axi-generator.py => dynamic_axi_generator.py} (86%) diff --git a/fud2/scripts/axi.rhai b/fud2/scripts/axi.rhai index 2eab6522d8..b0705610d2 100644 --- a/fud2/scripts/axi.rhai +++ b/fud2/scripts/axi.rhai @@ -24,9 +24,9 @@ fn wrapper_setup(e) { let dynamic = e.config_constrained_or("dynamic", ["true", "false"], "false"); let generator_path = if dynamic == "true" { - "$calyx-base/yxi/axi-calyx/dynamic-axi-generator.py" + "$calyx-base/yxi/axi-calyx/dynamic_axi_generator.py" } else { - "$calyx-base/yxi/axi-calyx/axi-generator.py" + "$calyx-base/yxi/axi-calyx/axi_generator.py" }; e.config_var_or("axi-generator", "axi.generator", generator_path); e.config_var_or("python", "python", "python3"); @@ -36,6 +36,8 @@ fn wrapper_setup(e) { // Define a simple `combine` rule that just concatenates any numer of files. e.rule("combine", "cat $in > $out"); + // Removes imports and `external` primitive blocks added by passes by removing + // everything up until the first line containing `component main` e.rule( "remove-imports", "sed '1,/component main/{/component main/!d; }' $in > $out", diff --git a/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap b/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap index f97fff8a97..19cb16f35c 100644 --- a/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap +++ b/fud2/tests/snapshots/tests__test@plan_axi-wrapped.snap @@ -22,7 +22,7 @@ yxi = $calyx-base/target/debug/yxi rule yxi command = $yxi -l $calyx-base $in > $out -axi-generator = $calyx-base/yxi/axi-calyx/axi-generator.py +axi-generator = $calyx-base/yxi/axi-calyx/axi_generator.py python = python3 rule gen-axi command = $python $axi-generator $in > $out diff --git a/runt.toml b/runt.toml index 66d4a8cae9..4b439deb60 100644 --- a/runt.toml +++ b/runt.toml @@ -68,7 +68,7 @@ cmd = """ [[tests]] name = "fud2 yxi invocation" -paths = ["yxi/tests/ref-mems-vec-add.futil"] +paths = ["yxi/tests/yxi-tool/ref-mems-vec-add.futil"] cmd = """ fud2 {} --from calyx --to yxi """ @@ -604,14 +604,14 @@ fud e {} -s verilog.cycle_limit 500 \ name = "calyx-py read-write-compute AXI wrapper generation" paths = ["yxi/tests/axi/read-compute-write/*.yxi"] cmd = """ -python3 yxi/axi-calyx/axi-generator.py {} +python3 yxi/axi-calyx/axi_generator.py {} """ [[tests]] name = "calyx-py dynamic AXI wrapper generation" paths = ["yxi/tests/axi/dynamic/*.yxi"] cmd = """ -python3 yxi/axi-calyx/dynamic-axi-generator.py {} +python3 yxi/axi-calyx/dynamic_axi_generator.py {} """ #Ignore fud2 stderr for now due to ninja nondeterminism @@ -681,4 +681,4 @@ make --silent -B TEST_PATH={} COCOTB_LOG_LEVEL=CRITICAL |\ make clean && \ rm -f results.xml -""" \ No newline at end of file +""" diff --git a/yxi/axi-calyx/axi-generator.py b/yxi/axi-calyx/axi_generator.py similarity index 99% rename from yxi/axi-calyx/axi-generator.py rename to yxi/axi-calyx/axi_generator.py index f17845c18d..1c712542b8 100644 --- a/yxi/axi-calyx/axi-generator.py +++ b/yxi/axi-calyx/axi_generator.py @@ -1,3 +1,7 @@ +# The *original* axi generator which implement a read-compute-write sequence +# to get data in and out of the computational kernel. +# A `dynamic_axi_generator` also exists + from calyx.builder import ( Builder, add_comp_ports, diff --git a/yxi/axi-calyx/dynamic-axi-generator.py b/yxi/axi-calyx/dynamic_axi_generator.py similarity index 86% rename from yxi/axi-calyx/dynamic-axi-generator.py rename to yxi/axi-calyx/dynamic_axi_generator.py index bc7b7c37b0..b84547521e 100644 --- a/yxi/axi-calyx/dynamic-axi-generator.py +++ b/yxi/axi-calyx/dynamic_axi_generator.py @@ -1,11 +1,8 @@ -from calyx.builder import ( - Builder, - add_comp_ports, - invoke, - par, - while_, - if_ -) +# Implements an AXI controller which dynamically reads and writes data +# in and out of the computational kernel as needed. Compare with the +# read-compute-write implementation in the original `axi_generator`. + +from calyx.builder import Builder, add_comp_ports, invoke, par, while_, if_ from typing import Literal from math import log2, ceil import json @@ -17,11 +14,12 @@ width_key = "data_width" size_key = "total_size" name_key = "name" -#This returns an array based on dimensions of memory +# This returns an array based on dimensions of memory address_width_key = "idx_sizes" type_key = "memory_type" -#TODO (nathanielnrn): Should we make these comb groups? + +# TODO (nathanielnrn): Should we make these comb groups? def add_address_translator(prog, mem): address_width = mem[address_width_key][0] data_width = mem[width_key] @@ -32,13 +30,14 @@ def add_address_translator(prog, mem): translator_output = [("axi_address", 64)] add_comp_ports(address_translator, translator_inputs, translator_output) - #Cells - #XRT expects 64 bit address. - address_mult = address_translator.const_mult(64, width_in_bytes(data_width), f"mul_{name}") + # Cells + # XRT expects 64 bit address. + address_mult = address_translator.const_mult( + 64, width_in_bytes(data_width), f"mul_{name}" + ) pad_input_addr = address_translator.pad(address_width, 64, f"pad_input_addr") - - #Assignment + # Assignment with address_translator.continuous: pad_input_addr.in_ = address_translator.this()["calyx_mem_addr"] address_mult.in_ = pad_input_addr.out @@ -109,12 +108,14 @@ def _add_m_to_s_address_channel(prog, mem, prefix: Literal["AW", "AR"]): xhandshake_occurred.write_en = (~xhandshake_occurred.out) @ 1 # Drive output signals for transfer - m_to_s_address_channel.this()[f"{x}ADDR"] = m_to_s_address_channel.this()["axi_address"] + m_to_s_address_channel.this()[f"{x}ADDR"] = m_to_s_address_channel.this()[ + "axi_address" + ] # This is taken from mem size, we assume the databus width is the size # of our memory cell and that width is a power of 2 # TODO(nathanielnrn): convert to binary instead of decimal m_to_s_address_channel.this()[f"{x}SIZE"] = width_xsize(mem[width_key]) - #Dynamic accesses only need asingle transfer per transcation + # Dynamic accesses only need asingle transfer per transcation m_to_s_address_channel.this()[f"{x}LEN"] = 0 m_to_s_address_channel.this()[f"{x}BURST"] = 1 # Must be INCR for XRT # Required by spec, we hardcode to privileged, non-secure, data access @@ -126,12 +127,10 @@ def _add_m_to_s_address_channel(prog, mem, prefix: Literal["AW", "AR"]): bt_reg.write_en = 1 do_x_transfer.done = bt_reg.out - # ARLEN must be between 0-255, make sure to subtract 1 from yxi # size when assigning to ARLEN # assert mem[size_key] < 256, "Memory size must be less than 256" - m_to_s_address_channel.control += [ par( invoke(bt_reg, in_in=0), @@ -223,7 +222,7 @@ def add_read_channel(prog, mem): # Control invoke_n_RLAST = invoke(n_RLAST, in_in=1) # invoke_bt_reg = invoke(bt_reg, in_in=0) - + # Could arguably get rid of this while loop for the dynamic verison, but this # matches nicely with non dynamic version and conforms to spec, # and will be easier to exten to variable length dynamic transfers in the future @@ -241,11 +240,7 @@ def add_write_channel(prog, mem): name = mem[name_key] # Inputs/Outputs write_channel = prog.component(f"m_write_channel_{name}") - channel_inputs = [ - ("ARESETn", 1), - ("WREADY", 1), - ("write_data", data_width) - ] + channel_inputs = [("ARESETn", 1), ("WREADY", 1), ("write_data", data_width)] # TODO(nathanielnrn): We currently assume WDATA is the same width as the # memory. This limits throughput many AXI data busses are much wider # i.e., 512 bits. @@ -291,7 +286,7 @@ def add_write_channel(prog, mem): write_channel.this()["WLAST"] = 1 # done after handshake - #TODO(nathanielnrn): Perhaps we can combine between handshake_occurred and bt_reg + # TODO(nathanielnrn): Perhaps we can combine between handshake_occurred and bt_reg bt_reg.in_ = (wvalid.out & WREADY) @ 1 bt_reg.in_ = ~(wvalid.out & WREADY) @ 0 bt_reg.write_en = 1 @@ -347,6 +342,7 @@ def add_bresp_channel(prog, mem): # Control bresp_channel.control += [invoke(bt_reg, in_in=0), block_transfer] + def add_read_controller(prog, mem): data_width = mem[width_key] name = mem[name_key] @@ -371,18 +367,21 @@ def add_read_controller(prog, mem): (f"ARBURST", 2), (f"ARPROT", 3), (f"RREADY", 1), - #sent out to axi_dyn_mem + # sent out to axi_dyn_mem (f"read_data", data_width), ] add_comp_ports(read_controller, read_controller_inputs, read_controller_outputs) - #Cells - simple_ar_channel = read_controller.cell(f"ar_channel_{name}", prog.get_component(f"m_ar_channel_{name}")) - simple_read_channel = read_controller.cell(f"read_channel_{name}", prog.get_component(f"m_read_channel_{name}")) + # Cells + simple_ar_channel = read_controller.cell( + f"ar_channel_{name}", prog.get_component(f"m_ar_channel_{name}") + ) + simple_read_channel = read_controller.cell( + f"read_channel_{name}", prog.get_component(f"m_read_channel_{name}") + ) # No groups necesarry - # Control # Invokes @@ -416,6 +415,7 @@ def add_read_controller(prog, mem): simple_read_invoke, ] + def add_write_controller(prog, mem): data_width = mem[width_key] name = mem[name_key] @@ -446,13 +446,18 @@ def add_write_controller(prog, mem): add_comp_ports(write_controller, write_controller_inputs, write_controller_outputs) - #Cells - simple_aw_channel = write_controller.cell(f"aw_channel_{name}", prog.get_component(f"m_aw_channel_{name}")) - simple_write_channel = write_controller.cell(f"write_channel_{name}", prog.get_component(f"m_write_channel_{name}")) - simple_bresp_channel = write_controller.cell(f"bresp_channel_{name}", prog.get_component(f"m_bresp_channel_{name}")) + # Cells + simple_aw_channel = write_controller.cell( + f"aw_channel_{name}", prog.get_component(f"m_aw_channel_{name}") + ) + simple_write_channel = write_controller.cell( + f"write_channel_{name}", prog.get_component(f"m_write_channel_{name}") + ) + simple_bresp_channel = write_controller.cell( + f"bresp_channel_{name}", prog.get_component(f"m_bresp_channel_{name}") + ) # No groups necesarry - # Control # Invokes simple_aw_invoke = invoke( @@ -489,6 +494,7 @@ def add_write_controller(prog, mem): simple_bresp_invoke, ] + def add_axi_dyn_mem(prog, mem): address_width = mem[address_width_key][0] data_width = mem[width_key] @@ -497,7 +503,7 @@ def add_axi_dyn_mem(prog, mem): prog.import_("primitives/memories/dyn.futil") axi_dyn_mem = prog.component(f"axi_dyn_mem_{name}") # Inputs/Outputs - dyn_mem_inputs =[ + dyn_mem_inputs = [ ("addr0", address_width, [("write_together", 1), "data"]), ("content_en", 1, [("write_together", 1), ("go", 1)]), ("write_en", 1, [("write_together", 2)]), @@ -537,9 +543,15 @@ def add_axi_dyn_mem(prog, mem): add_comp_ports(axi_dyn_mem, dyn_mem_inputs, dyn_mem_outputs) # Cells - address_translator = axi_dyn_mem.cell(f"address_translator_{name}", prog.get_component(f"address_translator_{name}")) - read_controller = axi_dyn_mem.cell(f"read_controller_{name}", prog.get_component(f"read_controller_{name}")) - write_controller = axi_dyn_mem.cell(f"write_controller_{name}", prog.get_component(f"write_controller_{name}")) + address_translator = axi_dyn_mem.cell( + f"address_translator_{name}", prog.get_component(f"address_translator_{name}") + ) + read_controller = axi_dyn_mem.cell( + f"read_controller_{name}", prog.get_component(f"read_controller_{name}") + ) + write_controller = axi_dyn_mem.cell( + f"write_controller_{name}", prog.get_component(f"write_controller_{name}") + ) # Wires this_component = axi_dyn_mem.this() @@ -548,51 +560,54 @@ def add_axi_dyn_mem(prog, mem): address_translator.calyx_mem_addr = this_component["addr0"] axi_dyn_mem.this()["read_data"] = read_controller.read_data - #Control + # Control read_controller_invoke = invoke( - axi_dyn_mem.get_cell(f"read_controller_{name}"), - in_axi_address=address_translator.axi_address, - in_ARESETn=this_component[f"ARESETn"], - in_ARREADY=this_component[f"ARREADY"], - in_RVALID=this_component[f"RVALID"], - in_RLAST=this_component[f"RLAST"], - in_RDATA=this_component[f"RDATA"], - in_RRESP=this_component[f"RRESP"], - out_ARVALID=this_component[f"ARVALID"], - out_ARADDR=this_component[f"ARADDR"], - out_ARSIZE=this_component[f"ARSIZE"], - out_ARLEN=this_component[f"ARLEN"], - out_ARBURST=this_component[f"ARBURST"], - out_ARPROT=this_component[f"ARPROT"], - out_RREADY=this_component[f"RREADY"], - out_read_data=this_component[f"read_data"], - ) + axi_dyn_mem.get_cell(f"read_controller_{name}"), + in_axi_address=address_translator.axi_address, + in_ARESETn=this_component[f"ARESETn"], + in_ARREADY=this_component[f"ARREADY"], + in_RVALID=this_component[f"RVALID"], + in_RLAST=this_component[f"RLAST"], + in_RDATA=this_component[f"RDATA"], + in_RRESP=this_component[f"RRESP"], + out_ARVALID=this_component[f"ARVALID"], + out_ARADDR=this_component[f"ARADDR"], + out_ARSIZE=this_component[f"ARSIZE"], + out_ARLEN=this_component[f"ARLEN"], + out_ARBURST=this_component[f"ARBURST"], + out_ARPROT=this_component[f"ARPROT"], + out_RREADY=this_component[f"RREADY"], + out_read_data=this_component[f"read_data"], + ) write_controller_invoke = invoke( - axi_dyn_mem.get_cell(f"write_controller_{name}"), - in_axi_address=address_translator.axi_address, - in_write_data=this_component["write_data"], - in_ARESETn=this_component["ARESETn"], - in_AWREADY=this_component["AWREADY"], - in_WREADY=this_component["WREADY"], - in_BVALID=this_component["BVALID"], - out_AWVALID=this_component["AWVALID"], - out_AWADDR=this_component["AWADDR"], - out_AWSIZE=this_component["AWSIZE"], - out_AWLEN=this_component["AWLEN"], - out_AWBURST=this_component["AWBURST"], - out_AWPROT=this_component[f"AWPROT"], - out_WVALID=this_component["WVALID"], - out_WLAST=this_component["WLAST"], - out_WDATA=this_component["WDATA"], - out_BREADY=this_component["BREADY"], + axi_dyn_mem.get_cell(f"write_controller_{name}"), + in_axi_address=address_translator.axi_address, + in_write_data=this_component["write_data"], + in_ARESETn=this_component["ARESETn"], + in_AWREADY=this_component["AWREADY"], + in_WREADY=this_component["WREADY"], + in_BVALID=this_component["BVALID"], + out_AWVALID=this_component["AWVALID"], + out_AWADDR=this_component["AWADDR"], + out_AWSIZE=this_component["AWSIZE"], + out_AWLEN=this_component["AWLEN"], + out_AWBURST=this_component["AWBURST"], + out_AWPROT=this_component[f"AWPROT"], + out_WVALID=this_component["WVALID"], + out_WLAST=this_component["WLAST"], + out_WDATA=this_component["WDATA"], + out_BREADY=this_component["BREADY"], ) - + axi_dyn_mem.control += [ - if_(axi_dyn_mem.this()["write_en"], write_controller_invoke, read_controller_invoke) + if_( + axi_dyn_mem.this()["write_en"], + write_controller_invoke, + read_controller_invoke, + ) ] - # NOTE: Unlike the channel functions, this can expect multiple mems def add_main_comp(prog, mems): @@ -661,7 +676,9 @@ def add_main_comp(prog, mems): # TODO: Don't think these need to be marked external, but we # we need to raise them at some point form original calyx program - axi_mem = wrapper_comp.cell(f"axi_dyn_mem_{mem_name}", prog.get_component(f"axi_dyn_mem_{mem_name}")) + axi_mem = wrapper_comp.cell( + f"axi_dyn_mem_{mem_name}", prog.get_component(f"axi_dyn_mem_{mem_name}") + ) # Wires @@ -675,7 +692,9 @@ def add_main_comp(prog, mems): # Connect wrapper ports with axi_dyn_mem ports # Read controller portion inputs - axi_mem["ARESETn"] = wrapper_comp.this()[f"{mem_name}_ARESETn"] #note that both styles work + axi_mem["ARESETn"] = wrapper_comp.this()[ + f"{mem_name}_ARESETn" + ] # note that both styles work # wrapper_comp.this()[f"{mem_name}_ARESETn"] = axi_mem["ARESETn"] #note that both styles work axi_mem.ARREADY = wrapper_comp.this()[f"{mem_name}_ARREADY"] axi_mem.RVALID = wrapper_comp.this()[f"{mem_name}_RVALID"] @@ -705,7 +724,6 @@ def add_main_comp(prog, mems): wrapper_comp.this()[f"{mem_name}_WDATA"] = axi_mem.WDATA wrapper_comp.this()[f"{mem_name}_BREADY"] = axi_mem.BREADY - # Creates ` = internal_mem_` as refs in invocation of `main_compute` ref_mem_kwargs[f"ref_{mem_name}"] = axi_mem @@ -756,7 +774,7 @@ def build(): add_address_translator(prog, mem) add_read_controller(prog, mem) add_write_controller(prog, mem) - add_axi_dyn_mem(prog, mem) #TODO: need one for each mem + add_axi_dyn_mem(prog, mem) # TODO: need one for each mem add_main_comp(prog, mems) return prog.program @@ -771,7 +789,9 @@ def check_mems_wellformed(mems): mem[width_key] ).is_integer(), "Width must be a power of 2 to be correctly described by xSIZE" assert mem[size_key] > 0, "Memory size must be greater than 0" - assert mem[type_key] == "Dynamic", "Only dynamic memories are currently supported for dynamic axi" + assert ( + mem[type_key] == "Dynamic" + ), "Only dynamic memories are currently supported for dynamic axi" if __name__ == "__main__": @@ -789,4 +809,4 @@ def check_mems_wellformed(mems): yxifile = open(yxi_filename) yxi = json.load(yxifile) mems = yxi["memories"] - build().emit() \ No newline at end of file + build().emit()