diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml index a2daaa681e..002a0c23c7 100644 --- a/.github/workflows/modules-zstd.yml +++ b/.github/workflows/modules-zstd.yml @@ -60,12 +60,20 @@ jobs: - name: Build and run ZSTD IR benchmark rules (opt) if: ${{ !cancelled() }} run: | - bazel run -c opt -- $(bazel query 'filter(".*_ir_benchmark", kind(rule, //xls/modules/zstd/...))') + for target in $(bazel query 'filter(".*_ir_benchmark$", kind(rule, //xls/modules/zstd/...))'); + do + echo "running $target"; + bazel run -c opt $target -- --logtostderr; + done - name: Build and run synthesis benchmarks of the ZSTD module (opt) if: ${{ !cancelled() }} run: | - bazel run -c opt -- $(bazel query 'filter(".*_benchmark_synth", kind(rule, //xls/modules/zstd/...))') + for target in $(bazel query 'filter(".*_benchmark_synth$", kind(rule, //xls/modules/zstd/...))'); + do + echo "running $target"; + bazel run -c opt $target -- --logtostderr; + done - name: Build ZSTD place and route targets (opt) if: ${{ !cancelled() }} diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 037f89288b..8717497922 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -847,7 +847,7 @@ place_and_route( name = "sequence_executor_place_and_route", clock_period = "750", core_padding_microns = 2, - min_pin_distance = "0.5", + min_pin_distance = "0.4", placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":sequence_executor_asap7", diff --git a/xls/modules/zstd/memory/BUILD b/xls/modules/zstd/memory/BUILD new file mode 100644 index 0000000000..2bbf77e836 --- /dev/null +++ b/xls/modules/zstd/memory/BUILD @@ -0,0 +1,714 @@ +# Copyright 2024 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") +load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") +load("@rules_hdl//verilog:providers.bzl", "verilog_library") +load("@xls_pip_deps//:requirements.bzl", "requirement") +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_benchmark_ir", + "xls_dslx_library", + "xls_dslx_test", + "xls_dslx_verilog", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +xls_dslx_library( + name = "axi_dslx", + srcs = ["axi.x"], +) + +xls_dslx_library( + name = "axi_st_dslx", + srcs = ["axi_st.x"], +) + +xls_dslx_library( + name = "common_dslx", + srcs = [ + "common.x", + ], + deps = [":axi_dslx"], +) + +xls_dslx_test( + name = "common_dslx_test", + library = ":common_dslx", + tags = ["manual"], +) + +CLOCK_PERIOD_PS = "750" +# Clock periods for modules that exceed the 750ps critical path in IR benchmark +AXI_READER_CLOCK_PERIOD_PS = "1800" +AXI_STREAM_REMOVE_EMPTY_CLOCK_PERIOD_PS = "1300" +MEM_READER_CLOCK_PERIOD_PS = "2600" + +common_codegen_args = { + "delay_model": "asap7", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + "clock_period_ps": CLOCK_PERIOD_PS, + "clock_margin_percent": "20", + "multi_proc": "true", +} + +xls_dslx_library( + name = "axi_reader_dslx", + srcs = ["axi_reader.x"], + deps = [ + ":axi_dslx", + ":axi_st_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "axi_reader_dslx_test", + library = ":axi_reader_dslx", + tags = ["manual"], +) + +axi_reader_codegen_args = common_codegen_args | { + "module_name": "axi_reader", + "pipeline_stages": "2", + "streaming_channel_data_suffix": "_data", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", + "clock_period_ps": AXI_READER_CLOCK_PERIOD_PS, +} + +xls_dslx_verilog( + name = "axi_reader_verilog", + codegen_args = axi_reader_codegen_args, + dslx_top = "AxiReaderInst", + library = ":axi_reader_dslx", + tags = ["manual"], + verilog_file = "axi_reader.v", +) + +xls_benchmark_ir( + name = "axi_reader_opt_ir_benchmark", + src = ":axi_reader_verilog.opt.ir", + benchmark_ir_args = axi_reader_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_reader__AxiReaderInst__AxiReader_0__16_32_4_4_4_3_2_14_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_reader_verilog_lib", + srcs = [ + ":axi_reader.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_reader_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_reader", + deps = [ + ":axi_reader_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_reader_benchmark_synth", + synth_target = ":axi_reader_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_reader_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_reader_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "axi_stream_remove_empty_dslx", + srcs = ["axi_stream_remove_empty.x"], + deps = [ + ":axi_st_dslx", + ], +) + +xls_dslx_test( + name = "axi_stream_remove_empty_dslx_test", + library = ":axi_stream_remove_empty_dslx", + tags = ["manual"], +) + +axi_stream_remove_empty_codegen_args = common_codegen_args | { + "module_name": "axi_stream_remove_empty", + "clock_period_ps": AXI_STREAM_REMOVE_EMPTY_CLOCK_PERIOD_PS, + "pipeline_stages": "2", +} + +xls_dslx_verilog( + name = "axi_stream_remove_empty_verilog", + codegen_args = axi_stream_remove_empty_codegen_args, + dslx_top = "AxiStreamRemoveEmptyInst", + library = ":axi_stream_remove_empty_dslx", + tags = ["manual"], + verilog_file = "axi_stream_remove_empty.v", +) + +xls_benchmark_ir( + name = "axi_stream_remove_empty_opt_ir_benchmark", + src = ":axi_stream_remove_empty_verilog.opt.ir", + benchmark_ir_args = axi_stream_remove_empty_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_stream_remove_empty__AxiStreamRemoveEmptyInst__AxiStreamRemoveEmpty_0__32_4_6_32_32_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_stream_remove_empty_verilog_lib", + srcs = [ + ":axi_stream_remove_empty.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_stream_remove_empty_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_stream_remove_empty", + deps = [ + ":axi_stream_remove_empty_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_stream_remove_empty_benchmark_synth", + synth_target = ":axi_stream_remove_empty_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_stream_remove_empty_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_stream_remove_empty_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "axi_stream_downscaler_dslx", + srcs = ["axi_stream_downscaler.x"], + deps = [ + ":axi_st_dslx", + ], +) + +xls_dslx_test( + name = "axi_stream_downscaler_dslx_test", + library = ":axi_stream_downscaler_dslx", + tags = ["manual"], +) + +axi_stream_downscaler_codegen_args = common_codegen_args | { + "module_name": "axi_stream_downscaler", + "pipeline_stages": "2", +} + +xls_dslx_verilog( + name = "axi_stream_downscaler_verilog", + codegen_args = axi_stream_downscaler_codegen_args, + dslx_top = "AxiStreamDownscalerInst", + library = ":axi_stream_downscaler_dslx", + tags = ["manual"], + verilog_file = "axi_stream_downscaler.v", +) + +xls_benchmark_ir( + name = "axi_stream_downscaler_opt_ir_benchmark", + src = ":axi_stream_downscaler_verilog.opt.ir", + benchmark_ir_args = axi_stream_downscaler_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_stream_downscaler__AxiStreamDownscalerInst__AxiStreamDownscaler_0__8_8_128_16_32_4_4_3_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_stream_downscaler_verilog_lib", + srcs = [ + ":axi_stream_downscaler.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_stream_downscaler_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_stream_downscaler", + deps = [ + ":axi_stream_downscaler_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_stream_downscaler_benchmark_synth", + synth_target = ":axi_stream_downscaler_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_stream_downscaler_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_stream_downscaler_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "mem_reader_dslx", + srcs = ["mem_reader.x"], + deps = [ + ":axi_dslx", + ":axi_reader_dslx", + ":axi_st_dslx", + ":axi_stream_downscaler_dslx", + ":axi_stream_remove_empty_dslx", + ], +) + +xls_dslx_test( + name = "mem_reader_dslx_test", + library = ":mem_reader_dslx", + tags = ["manual"], +) + +mem_reader_internal_codegen_args = common_codegen_args | { + "module_name": "mem_reader_internal", + "pipeline_stages": "10", +} + +xls_dslx_verilog( + name = "mem_reader_internal_verilog", + codegen_args = mem_reader_internal_codegen_args, + dslx_top = "MemReaderInternalInst", + library = ":mem_reader_dslx", + tags = ["manual"], + verilog_file = "mem_reader_internal.v", +) + +xls_benchmark_ir( + name = "mem_reader_internal_opt_ir_benchmark", + src = ":mem_reader_internal_verilog.opt.ir", + benchmark_ir_args = common_codegen_args | { + "pipeline_stages": "10", + "top": "__mem_reader__MemReaderInternalInst__MemReaderInternal_0__16_128_16_8_8_2_2_16_64_8_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "mem_reader_internal_verilog_lib", + srcs = [ + ":mem_reader_internal.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "mem_reader_internal_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "mem_reader_internal", + deps = [ + ":mem_reader_internal_verilog_lib", + ], +) + +benchmark_synth( + name = "mem_reader_internal_benchmark_synth", + synth_target = ":mem_reader_internal_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "mem_reader_internal_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":mem_reader_internal_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +mem_reader_codegen_args = common_codegen_args | { + "module_name": "mem_reader", + "pipeline_stages": "4", + "streaming_channel_data_suffix": "_data", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", + "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, +} + +xls_dslx_verilog( + name = "mem_reader_verilog", + codegen_args = mem_reader_codegen_args, + dslx_top = "MemReaderInst", + library = ":mem_reader_dslx", + tags = ["manual"], + verilog_file = "mem_reader.v", +) + +verilog_library( + name = "mem_reader_verilog_lib", + srcs = [ + ":mem_reader.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "mem_reader_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "mem_reader", + deps = [ + ":mem_reader_verilog_lib", + ], +) + +benchmark_synth( + name = "mem_reader_benchmark_synth", + synth_target = ":mem_reader_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "mem_reader_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":mem_reader_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +mem_reader_adv_codegen_args = common_codegen_args | { + "module_name": "mem_reader_adv", + "pipeline_stages": "4", + "streaming_channel_data_suffix": "_data", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", + "clock_period_ps": MEM_READER_CLOCK_PERIOD_PS, +} + +xls_dslx_verilog( + name = "mem_reader_adv_verilog", + codegen_args = mem_reader_adv_codegen_args, + dslx_top = "MemReaderAdvInst", + library = ":mem_reader_dslx", + tags = ["manual"], + verilog_file = "mem_reader_adv.v", +) + +verilog_library( + name = "mem_reader_adv_verilog_lib", + srcs = [ + ":mem_reader_adv.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "mem_reader_adv_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "mem_reader_adv", + deps = [ + ":mem_reader_adv_verilog_lib", + ], +) + +benchmark_synth( + name = "mem_reader_adv_benchmark_synth", + synth_target = ":mem_reader_adv_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "mem_reader_adv_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":mem_reader_adv_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "axi_writer_dslx", + srcs = ["axi_writer.x"], + deps = [ + ":axi_dslx", + ":axi_st_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "axi_writer_dslx_test", + library = ":axi_writer_dslx", + tags = ["manual"], +) + +axi_writer_codegen_args = common_codegen_args | { + "module_name": "axi_writer", + "pipeline_stages": "1", + "streaming_channel_data_suffix": "_data", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", +} + +xls_dslx_verilog( + name = "axi_writer_verilog", + codegen_args = axi_writer_codegen_args, + dslx_top = "AxiWriterInst", + library = ":axi_writer_dslx", + tags = ["manual"], + verilog_file = "axi_writer.v", +) + +xls_benchmark_ir( + name = "axi_writer_opt_ir_benchmark", + src = ":axi_writer_verilog.opt.ir", + benchmark_ir_args = axi_writer_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_writer__AxiWriterInst__AxiWriter_0__16_32_4_4_4_2_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_writer_verilog_lib", + srcs = [ + ":axi_writer.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_writer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_writer", + deps = [ + ":axi_writer_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_writer_benchmark_synth", + synth_target = ":axi_writer_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_writer_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_writer_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "axi_stream_add_empty_dslx", + srcs = ["axi_stream_add_empty.x"], + deps = [ + ":axi_dslx", + ":axi_st_dslx", + ":axi_writer_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "axi_stream_add_empty_dslx_test", + library = ":axi_stream_add_empty_dslx", +) + +axi_stream_add_empty_codegen_args = common_codegen_args | { + "module_name": "axi_stream_add_empty", + "pipeline_stages": "2", + "streaming_channel_data_suffix": "_data", +} + +xls_dslx_verilog( + name = "axi_stream_add_empty_verilog", + codegen_args = axi_stream_add_empty_codegen_args, + dslx_top = "AxiStreamAddEmptyInst", + library = ":axi_stream_add_empty_dslx", + tags = ["manual"], + verilog_file = "axi_stream_add_empty.v", +) + +xls_benchmark_ir( + name = "axi_stream_add_empty_opt_ir_benchmark", + src = ":axi_stream_add_empty_verilog.opt.ir", + benchmark_ir_args = axi_stream_add_empty_codegen_args | { + "pipeline_stages": "10", + "top": "__axi_stream_add_empty__AxiStreamAddEmptyInst__AxiStreamAddEmpty_0__16_32_4_2_32_32_next", + }, + tags = ["manual"], +) + +verilog_library( + name = "axi_stream_add_empty_verilog_lib", + srcs = [ + ":axi_stream_add_empty.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "axi_stream_add_empty_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "axi_stream_add_empty", + deps = [ + ":axi_stream_add_empty_verilog_lib", + ], +) + +benchmark_synth( + name = "axi_stream_add_empty_benchmark_synth", + synth_target = ":axi_stream_add_empty_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "axi_stream_add_empty_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_stream_add_empty_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) + +xls_dslx_library( + name = "mem_writer_dslx", + srcs = ["mem_writer.x"], + deps = [ + ":axi_dslx", + ":axi_st_dslx", + ":axi_stream_add_empty_dslx", + ":axi_writer_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "mem_writer_dslx_test", + library = ":mem_writer_dslx", +) + +mem_writer_codegen_args = common_codegen_args | { + "module_name": "mem_writer", + "pipeline_stages": "2", + "streaming_channel_data_suffix": "_data", + "multi_proc": "true", + "flop_inputs_kind": "skid", + "flop_outputs_kind": "skid", + "worst_case_throughput": "1", +} + +xls_dslx_verilog( + name = "mem_writer_verilog", + codegen_args = mem_writer_codegen_args, + dslx_top = "MemWriterInst", + library = ":mem_writer_dslx", + tags = ["manual"], + verilog_file = "mem_writer.v", +) + +verilog_library( + name = "mem_writer_verilog_lib", + srcs = [ + ":mem_writer.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "mem_writer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], + top_module = "mem_writer", + deps = [ + ":mem_writer_verilog_lib", + ], +) + +benchmark_synth( + name = "mem_writer_benchmark_synth", + synth_target = ":mem_writer_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "mem_writer_place_and_route", + clock_period = CLOCK_PERIOD_PS, + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":mem_writer_synth_asap7", + tags = ["manual"], + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/memory/README.md b/xls/modules/zstd/memory/README.md new file mode 100644 index 0000000000..6a0e4aedfb --- /dev/null +++ b/xls/modules/zstd/memory/README.md @@ -0,0 +1,89 @@ +# Memory + +This directory contains procs responsible for issuing read and write +transactions compatible with AXI subordinates. The AXI bus was selected +because it consists of five streams, which closely resemble the streams +to which XLS channels are translated. + +Although XLS channels do not fully conform to the AXI standard, with proper +I/O configuration, DSLX procs using XLS channels can successfully interface +and communicate with AXI4 peripherals. + +The signals used to form AXI channels are represented as individual fields +in dedicated structures. However, the XLS toolchain generates these fields +as a flattened bit array. As a result, the Verilog code produced from these +structures will not match the expected AXI bus signature. To interface with +other AXI peripherals, additional Verilog wrappers may be needed to split +the flattened bit vector into individual signals. + +# Data Structures + +As noted, the procs in this directory use channels with dedicated +structures to represent AXI bus signals in DSLX. For instance, the AXI read +interface comprises the AR channel, which is used to provide a read address, +and the length of data to read, as well as the R channel, which is used +to receive the read data. These channels are represented by the `AxiAr` and +`AxiR` structures in the `axi.x` file, which contains AXI4 bus definitions. +The structures used to represent AXI4 Stream interface can be found in +the `axi_st.x` file. + +# Main components + +The primary components of this directory are `MemReader` and `MemWriter`, +which facilitate issuing read and write transactions on the AXI bus. +They provide a convenient interface for the DSLX side of the design, +managing the complexities of AXI transactions. + +The `MemReader` includes several procs that can be used individually: + +- `AxiReader`: Handles the creation of AXI transactions, managing unaligned + addresses and issuing additional transactions when crossing the 4KB boundary + or when the read request is longer than maximum possible burst size on the AXI bus + +- `AxiStreamDownscaler`: An optional proc available in `MemReaderAdv`, + enabling DSLX designs to connect to a wider AXI bus. + +- `AxiStreamRemoveEmpty`: Removes empty data bits, identified by zeroed bits in + the `tkeep` and `tstr` signals of the AXI Stream. + +The `MemWriter` proc is organised in a similar manner, it consists of: + +- `AxiWriter`: Handles the creation of AXI write transactions, managing unaligned + addresses and issuing additional transactions when crossing the 4KB boundary, + or when the write request is longer than maximum possible burst size on the AXI bus + +- `AxiStreamAddEmpty`: Adds empty data bits in the stream of data to write. + It is used to shift the data in the stream to facilitate writes to unaligned + addresses. + +# Usage + +The list below shows the usage of the `MemReader` proc: + +1. Send a `MemReaderReq` to the `req_in_r` channel, providing the information + about the absolute address from which to read the data, and the length + of data to read. + +2. Wait for the response on the `resp_s` channel. The received packet + consists of: + + - the `status` of the operation indicating if the read was successful + - `data` read from the bus + - `length` of the valid `data` in bytes + - `last` flag used for marking the last `packet` with data + +The list below shows the usage of the `MemWriter` proc: + +1. Send a `MemWriterReq` to the `req_in_r` channel, providing the information + about the absolute address to which data should be sent, and length of the + transaction in bytes. + +2. Provide the data to write using the `MemWriterDataPacket` structure, which + should be send to `data_in_r` channel. The structure consists of + + - the actual `data` + - `length` of the `data` in bytes + - `last` flag used for marking the last `packet` with data. + +3. Wait for the response submitted on the `resp_s` channel, which indicates + if the write operation was successful or an error occurred. diff --git a/xls/modules/zstd/memory/axi.x b/xls/modules/zstd/memory/axi.x new file mode 100644 index 0000000000..09bfc194e2 --- /dev/null +++ b/xls/modules/zstd/memory/axi.x @@ -0,0 +1,123 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; + +pub enum AxiAxSize : u3 { + MAX_1B_TRANSFER = 0, + MAX_2B_TRANSFER = 1, + MAX_4B_TRANSFER = 2, + MAX_8B_TRANSFER = 3, + MAX_16B_TRANSFER = 4, + MAX_32B_TRANSFER = 5, + MAX_64B_TRANSFER = 6, + MAX_128B_TRANSFER = 7, +} + +pub enum AxiWriteResp : u3 { + OKAY = 0, + EXOKAY = 1, + SLVERR = 2, + DECERR = 3, + DEFER = 4, + TRANSFAULT = 5, + RESERVED = 6, + UNSUPPORTED = 7, +} + +pub enum AxiReadResp : u3 { + OKAY = 0, + EXOKAY = 1, + SLVERR = 2, + DECERR = 3, + PREFETCHED = 4, + TRANSFAULT = 5, + OKAYDIRTY = 6, + RESERVED = 7, +} + +pub enum AxiAxBurst : u2 { + FIXED = 0, + INCR = 1, + WRAP = 2, + RESERVED = 3, +} + +pub enum AxiAwCache : u4 { + DEV_NO_BUF = 0b0000, + DEV_BUF = 0b0001, + NON_C_NON_BUF = 0b0010, + NON_C_BUF = 0b0011, + WT_NO_ALLOC = 0b0110, + WT_RD_ALLOC = 0b0110, + WT_WR_ALLOC = 0b1110, + WT_ALLOC = 0b1110, + WB_NO_ALLOC = 0b0111, + WB_RD_ALLOC = 0b0111, + WB_WR_ALLOC = 0b1111, + WB_ALLOC = 0b1111, +} + +pub enum AxiArCache : u4 { + DEV_NO_BUF = 0b0000, + DEV_BUF = 0b0001, + NON_C_NON_BUF = 0b0010, + NON_C_BUF = 0b0011, + WT_NO_ALLOC = 0b1010, + WT_RD_ALLOC = 0b1110, + WT_WR_ALLOC = 0b1010, + WT_ALLOC = 0b1110, + WB_NO_ALLOC = 0b1011, + WB_RD_ALLOC = 0b1111, + WB_WR_ALLOC = 0b1011, + WB_ALLOC = 0b1111, +} + +pub struct AxiAw { + id: uN[ID_W], + addr: uN[ADDR_W], + size: AxiAxSize, + len: u8, + burst: AxiAxBurst, +} + +pub struct AxiW { + data: uN[DATA_W], + strb: uN[STRB_W], + last: u1 +} + +pub struct AxiB { + resp: AxiWriteResp, + id: uN[ID_W] +} + +pub struct AxiAr { + id: uN[ID_W], + addr: uN[ADDR_W], + region: u4, + len: u8, + size: AxiAxSize, + burst: AxiAxBurst, + cache: AxiArCache, + prot: u3, + qos: u4, +} + +pub struct AxiR { + id: uN[ID_W], + data: uN[DATA_W], + resp: AxiReadResp, + last: u1, +} diff --git a/xls/modules/zstd/memory/axi_reader.x b/xls/modules/zstd/memory/axi_reader.x new file mode 100644 index 0000000000..02ea24aff7 --- /dev/null +++ b/xls/modules/zstd/memory/axi_reader.x @@ -0,0 +1,921 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of AxiReader proc that can be used to +// to issue AXI read requests as an AXI Manager device + +import std; + +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_st; +import xls.modules.zstd.memory.common; + +enum AxiReaderFsm: u2 { + IDLE = 0, + REQ = 1, + RESP = 2, + ERROR = 3, +} + +// Request that can be submited to AxiReader to read data from an AXI bus +pub struct AxiReaderReq { + addr: uN[ADDR_W], // address from which to read the data, can be unaligned + len: uN[ADDR_W] // length of the read request, can be unaligned +} + +// errors that can be returned by AxiReader +pub enum AxiReaderError: u1 { + TRANSFER_ERROR = 0, +} + +// Returnes true if the provided data width is correct, +// according to the AXI specification. Otherwise it retuns false. +fn is_valid_axi_data_width(x: u32) -> bool { + match (x) { + u32:8 => true, + u32:16 => true, + u32:32 => true, + u32:64 => true, + u32:128 => true, + u32:512 => true, + u32:1024 => true, + _ => false, + } +} + +// Returns true if the provided address width is correct according to +// the AXI specification. Otherwise it retuns false. +fn is_valid_axi_addr_width(x: u32) -> bool { + x >= u32:1 && x <= u32:64 +} + +struct AxiReaderState< + ADDR_W: u32, + DATA_W_DIV8: u32, + LANE_W: u32, +> { + fsm: AxiReaderFsm, // state machine of the AxiReader proc + + tran_addr: uN[ADDR_W], // requested address + tran_len: uN[ADDR_W], // requested transfer length + + req_tran: u8, // requested transfer length + req_low_lane: uN[LANE_W], // low byte lane calculated for the first tra + req_high_lane: uN[LANE_W], // high byte lane calculated from the request +} + + +// Proc responsible for issuing AXI read request as an AXI Manager device. +// Supports unaligned transactions, and respects the 4kB boundaries. +pub proc AxiReader< + ADDR_W: u32, DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_DIV8:u32 = {DATA_W / u32:8}, + LANE_W: u32 = {std::clog2(DATA_W_DIV8)}, + LANE_DATA_W_DIV8_PLUS_ONE: u32 = {std::clog2(DATA_W_DIV8) + u32:1}, + TRAN_W: u32 = {std::clog2((u32:1 << ADDR_W) / DATA_W_DIV8)}, +> { + type Req = AxiReaderReq; + type Error = AxiReaderError; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + type Fsm = AxiReaderFsm; + type State = AxiReaderState; + type Resp = axi::AxiReadResp; + + type Data = uN[DATA_W]; + type Length = uN[ADDR_W]; + type Addr = uN[ADDR_W]; + type Lane = uN[LANE_W]; + + const_assert!(is_valid_axi_data_width(DATA_W)); + const_assert!(is_valid_axi_addr_width(ADDR_W)); + + req_r: chan in; + axi_ar_s: chan out; + axi_r_r: chan in; + axi_st_s: chan out; + err_s: chan out; + + config( + req_r: chan in, + axi_ar_s: chan out, + axi_r_r: chan in, + axi_st_s: chan out, + err_s: chan out + ) { + (req_r, axi_ar_s, axi_r_r, axi_st_s, err_s) + } + + init { zero!() } + + next(state: State) { + let tok0 = join(); + + const BYTES_IN_TRANSFER = DATA_W_DIV8 as Addr; + const MAX_AXI_BURST_BYTES = Addr:256 * BYTES_IN_TRANSFER; + const MAX_LANE = !Lane:0; + + // IDLE logic + + let do_recv_req = (state.fsm == Fsm::IDLE); + let (tok1_0, req) = recv_if(tok0, req_r, do_recv_req, zero!()); + + // REQ logic + + let aligned_addr = common::align(state.tran_addr); + let aligned_offset = common::offset(state.tran_addr); + + let bytes_to_max_burst = MAX_AXI_BURST_BYTES - aligned_offset as Length; + let bytes_to_4k = common::bytes_to_4k_boundary(state.tran_addr); + let tran_len = std::umin(state.tran_len, std::umin(bytes_to_4k, bytes_to_max_burst)); + let (req_low_lane, req_high_lane) = common::get_lanes(state.tran_addr, tran_len); + + let adjusted_tran_len = aligned_offset as Addr + tran_len; + let rest = std::mod_pow2(adjusted_tran_len, BYTES_IN_TRANSFER) != Length:0; + let ar_len = if rest { + std::div_pow2(adjusted_tran_len, BYTES_IN_TRANSFER) as u8 + } else { + (std::div_pow2(adjusted_tran_len, BYTES_IN_TRANSFER) - Length:1) as u8 + }; + + let next_tran_addr = state.tran_addr + tran_len; + let next_tran_len = state.tran_len - tran_len; + + let axi_ar = axi::AxiAr { + id: uN[ID_W]:0, + addr: aligned_addr, + region: u4:0, + len: ar_len, + size: common::axsize(), + burst: axi::AxiAxBurst::INCR, + cache: axi::AxiArCache::DEV_NO_BUF, + prot: u3:0, + qos: u4:0, + }; + + let do_send_req = (state.fsm == Fsm::REQ); + let tok2_1 = send_if(tok1_0, axi_ar_s, do_send_req, axi_ar); + + // RESP logic + + let do_recv_resp = state.fsm == Fsm::RESP; + let (tok1_1, axi_r) = recv_if(tok0, axi_r_r, do_recv_resp, zero!()); + + let is_err = axi_r.resp != Resp::OKAY; + let do_send_err = state.fsm == Fsm::RESP && is_err; + let tok2_1 = send_if(tok1_1, err_s, do_send_err, Error::TRANSFER_ERROR); + + let is_last_group = (state.req_tran == u8:0); + let is_last_tran = (state.tran_len == Addr:0); + let req_tran = state.req_tran - u8:1; + + let low_lane = state.req_low_lane; + let high_lane = if is_last_group { state.req_high_lane } else { MAX_LANE }; + let mask = common::lane_mask(low_lane, high_lane); + + let axi_st = AxiStream { + data: axi_r.data, + str: mask, + keep: mask, + last: is_last_group & is_last_tran, + id: uN[ID_W]:0, + dest: uN[DEST_W]:0, + }; + + let do_send_resp = state.fsm == Fsm::RESP && !is_err; + let tok2_2 = send_if(tok1_1, axi_st_s, do_send_resp, axi_st); + + match (state.fsm) { + Fsm::IDLE => State { + fsm: Fsm::REQ, + tran_addr: req.addr, + tran_len: req.len, + ..zero!() + }, + Fsm::REQ => State { + fsm: Fsm::RESP, + req_tran: ar_len, + tran_addr: next_tran_addr, + tran_len: next_tran_len, + req_low_lane: req_low_lane, + req_high_lane: req_high_lane, + }, + Fsm::RESP => { + match (is_last_group, is_last_tran, is_err) { + (true, true, false) => zero!(), + (true, true, true) => State { + fsm: Fsm::ERROR, + ..zero!() + }, + (true, _, false) => State { + fsm: Fsm::REQ, + ..state + }, + (_, _, _) => State { + fsm: Fsm::RESP, + req_tran: req_tran, + req_low_lane: Lane:0, + ..state + } + } + }, + _ => zero!(), + } + } +} + +const INST_ADDR_W = u32:16; +const INST_DATA_W = u32:32; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_DEST_W = INST_DATA_W / u32:8; +const INST_ID_W = u32:4; + +proc AxiReaderInst { + type Req = AxiReaderReq; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + type Error = AxiReaderError; + + config( + req_r: chan in, + axi_ar_s: chan out, + axi_r_r: chan in, + axi_st_s: chan out, + err_s: chan out, + ) { + + spawn AxiReader( + req_r, axi_ar_s, axi_r_r, axi_st_s, err_s + ); + } + + init { () } + next(state: ()) { } +} + + +const TEST_ADDR_W = u32:16; +const TEST_DATA_W = u32:32; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_DEST_W = TEST_DATA_W / u32:8; +const TEST_ID_W = TEST_DATA_W / u32:8; + +#[test_proc] +proc AxiReaderTest { + type Req = AxiReaderReq; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + type Error = AxiReaderError; + + type Addr = uN[TEST_ADDR_W]; + type Len = uN[TEST_ADDR_W]; + + type AxiId = uN[TEST_ID_W]; + type AxiAddr = uN[TEST_ADDR_W]; + type AxiLen = u8; + type AxiRegion = u4; + type AxiSize = axi::AxiAxSize; + type AxiBurst = axi::AxiAxBurst; + type AxiCache = axi::AxiArCache; + type AxiProt = u3; + type AxiQos = u4; + type AxiData = uN[TEST_DATA_W]; + type AxiReadResp = axi::AxiReadResp; + + type AxiStr = uN[TEST_DATA_W_DIV8]; + type AxiKeep = uN[TEST_DATA_W_DIV8]; + type AxiDest = uN[TEST_DEST_W]; + type AxiLast = u1; + + terminator: chan out; + req_s: chan out; + axi_ar_r: chan in; + axi_r_s: chan out; + axi_st_r: chan in; + err_r: chan in; + + init {} + + config(terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + let (axi_st_s, axi_st_r) = chan("axi_st"); + let (err_s, err_r) = chan("error"); + + spawn AxiReader( + req_r, axi_ar_s, axi_r_r, axi_st_s, err_s); + + (terminator, req_s, axi_ar_r, axi_r_s, axi_st_r, err_r) + } + + next (state: ()) { + let tok = join(); + + // Test 0: Error during the transaction + + let req = Req { addr: Addr:0x1000, len: Len:4 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0x0, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x0, + resp: AxiReadResp::SLVERR, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let (tok, _) = recv(tok, err_r); + + // Test 1: Single anligned transfer, all the bits used + + let req = Req { addr: Addr:0x1000, len: Len:4 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0x0, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 2: Single aligned transfer with unused bits + + let req = Req { addr: Addr:0x1000, len: Len:2 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0x0, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x3, + keep: AxiKeep:0x3, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 3: Single unligned transfer, all the remaining bits used + + let req = Req { addr: Addr:0x123, len: Len:1 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x120, + region: AxiRegion:0x0, + len: AxiLen:0x0, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x8, + keep: AxiKeep:0x8, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 4: Single unligned transfer with unused remaining bits + + let req = Req {addr: Addr:0x121, len: Len:1}; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x120, + region: AxiRegion:0x0, + len: AxiLen:0x0, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x2, + keep: AxiKeep:0x2, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 5: Multiple aligned transfers without crossing 4k boundary + + let req = Req { addr: Addr:0x2000, len: Len:16 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x2000, + region: AxiRegion:0x0, + len: AxiLen:0x3, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x5566_7788, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x9900_AABB, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0xCCDD_EEFF, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x5566_7788, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x9900_AABB, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0xCCDD_EEFF, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 6: Multiple aligned transfers crossing 4k boundary + + let req = Req { addr: Addr:0xFF8, len: Len:16 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0xFF8, + region: AxiRegion:0x0, + len: AxiLen:0x1, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x5566_7788, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x5566_7788, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0x1, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x9900_AABB, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0xCCDD_EEFF, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x9900_AABB, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0xCCDD_EEFF, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 7: Multiple transfers starting from an unaligned address, + // without crosing 4k boundary + + let req = Req { addr: Addr:0x2003, len: Len:16 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x2000, + region: AxiRegion:0x0, + len: AxiLen:0x4, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x5566_7788, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x9900_AABB, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0xCCDD_EEFF, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x8, + keep: AxiKeep:0x8, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x5566_7788, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x9900_AABB, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0xCCDD_EEFF, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x7, + keep: AxiKeep:0x7, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 8: Multiple transfers starting from an unaligned address, + // with crossing 4k boundary + + let req = Req { addr: Addr:0xFF6, len: Len:16 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0xFF4, + region: AxiRegion:0x0, + len: AxiLen:0x2, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x5566_7788, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x9900_AABB, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0xC, + keep: AxiKeep:0xC, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x5566_7788, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x9900_AABB, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0x1, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let r = AxiR { + id: AxiId:0, + data: AxiData:0xCCDD_EEFF, + resp: AxiReadResp::OKAY, + last: AxiLast:0, + }; + let tok = send(tok, axi_r_s, r); + let r = AxiR { + id: AxiId:0, + data: AxiData:0x1122_3344, + resp: AxiReadResp::OKAY, + last: AxiLast:1, + }; + let tok = send(tok, axi_r_s, r); + + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0xCCDD_EEFF, + str: AxiStr:0xF, + keep: AxiKeep:0xF, + last: AxiLast:0, + id: AxiId:0, + dest: AxiDest:0, + }); + let (tok, st) = recv(tok, axi_st_r); + assert_eq(st, AxiStream { + data: AxiData:0x1122_3344, + str: AxiStr:0x3, + keep: AxiKeep:0x3, + last: AxiLast:1, + id: AxiId:0, + dest: AxiDest:0, + }); + + // Test 9: Multiple transfers starting from aligned address, + // without crossing 4k boundary, but longer than max burst size + + let req = Req { addr: Addr:0x1000, len: Len:0x2000 }; + let tok = send(tok, req_s, req); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLen:0xFF, + size: AxiSize::MAX_4B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/axi_st.x b/xls/modules/zstd/memory/axi_st.x new file mode 100644 index 0000000000..30b5c700b0 --- /dev/null +++ b/xls/modules/zstd/memory/axi_st.x @@ -0,0 +1,27 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub struct AxiStream< + DATA_W: u32, + DEST_W: u32, + ID_W: u32, + DATA_W_DIV8: u32 //= {DATA_W / u32:8} +> { + data: uN[DATA_W], + str: uN[DATA_W_DIV8], + keep: uN[DATA_W_DIV8], + id: uN[ID_W], + dest: uN[DEST_W], + last: u1, +} diff --git a/xls/modules/zstd/memory/axi_stream_add_empty.x b/xls/modules/zstd/memory/axi_stream_add_empty.x new file mode 100644 index 0000000000..7a94d9c1e1 --- /dev/null +++ b/xls/modules/zstd/memory/axi_stream_add_empty.x @@ -0,0 +1,1021 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// AXI Stream Add Empty +// +// This proc adds support for performing write requests under unaligned addresses. +// It receives write request and calculates the write address offset from 4-bytes alignment. +// The offset is used to shift and padd with zero bytes the data to write received on the AxiStream +// interface. +// Shifted data is passed down to the AxiWriter proc that handles the write requests. + +import std; + +import xls.modules.zstd.memory.common; +import xls.modules.zstd.memory.axi_st; +import xls.modules.zstd.memory.axi_writer; + +enum AxiStreamAddEmptyFsm : u3 { + RECV_REQUEST = 0, + PASSTHROUGH = 1, + INJECT_PADDING = 2, + FORWARD_STREAM = 3, + ERROR = 7, +} + +struct AxiStreamAddEmptyState< + DATA_W: u32, + DEST_W: u32, + ID_W: u32, + DATA_W_LOG2: u32, + DATA_W_DIV8: u32, + ADDR_W: u32, +> { + fsm: AxiStreamAddEmptyFsm, + offset: uN[DATA_W_DIV8], + shift: uN[DATA_W], + adjusted_len: uN[ADDR_W], + do_recv_raw_stream: bool, + do_send_padded_stream: bool, + frame_to_send: axi_st::AxiStream, + buffer_frame: axi_st::AxiStream, + buffer_offset: uN[DATA_W_DIV8], + buffer_shift: uN[DATA_W], + id: uN[ID_W], + dest: uN[DEST_W], +} + +pub proc AxiStreamAddEmpty< + DATA_W: u32, + DEST_W: u32, + ID_W: u32, + ADDR_W: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + DATA_W_LOG2: u32 = {std::clog2(DATA_W / u32:8)}, +> { + type Req = axi_writer::AxiWriterRequest; + type AxiStream = axi_st::AxiStream; + type State = AxiStreamAddEmptyState; + type Fsm = AxiStreamAddEmptyFsm; + + type Addr = uN[ADDR_W]; + type Data = uN[DATA_W]; + type Strobe = uN[DATA_W_DIV8]; + + write_req_r: chan in; + raw_stream_r: chan in; + padded_stream_s: chan out; + + config ( + write_req_r: chan in, + raw_stream_r: chan in, + padded_stream_s: chan out, + ) { + ( + write_req_r, + raw_stream_r, + padded_stream_s + ) + } + + init { zero!() } + + next (state: State) { + let (tok, write_req) = recv_if(join(), write_req_r, state.fsm == Fsm::RECV_REQUEST, zero!()); + let (tok, raw_stream) = recv_if(join(), raw_stream_r, state.do_recv_raw_stream == true, AxiStream { last: true, ..zero!()}); + let tok = send_if(join(), padded_stream_s, state.do_send_padded_stream, state.frame_to_send); + + let next_state = match(state.fsm) { + Fsm::RECV_REQUEST => { + let offset = common::offset(write_req.address) as Strobe; + let goto_passthrough = offset == Strobe:0; + let adjusted_len = write_req.length + offset as Addr; + State { + fsm: if (goto_passthrough) { Fsm::PASSTHROUGH } else { Fsm::INJECT_PADDING }, + offset: offset, + adjusted_len: adjusted_len, + do_recv_raw_stream: true, + ..state + } + }, + Fsm::PASSTHROUGH => { + if (state.frame_to_send.last == true) { + State { + fsm: Fsm::RECV_REQUEST, + ..zero!() + } + } else { + State { + fsm: Fsm::PASSTHROUGH, + frame_to_send: raw_stream, + do_recv_raw_stream: !raw_stream.last, + do_send_padded_stream: true, + ..state + } + } + }, + Fsm::INJECT_PADDING => { + let shift = (state.offset as Data * Data:8); + let data_to_send = raw_stream.data << shift; + let strb_to_send = raw_stream.str << state.offset; + let keep_to_send = raw_stream.keep << state.offset; + let buffer_offset = DATA_W_DIV8 as Strobe - state.offset; + let buffer_shift = buffer_offset as Data * Data:8; + let buffer_data = raw_stream.data >> buffer_shift; + let buffer_strb = raw_stream.str >> buffer_offset; + let buffer_keep = raw_stream.keep >> buffer_offset; + let last = if (state.adjusted_len <= DATA_W_DIV8 as Addr) {raw_stream.last} else { false }; + State { + fsm: Fsm::FORWARD_STREAM, + shift: shift, + buffer_offset: buffer_offset, + buffer_shift: buffer_shift, + frame_to_send: AxiStream { + data: data_to_send, + str: strb_to_send, + keep: keep_to_send, + last: last, + ..raw_stream + }, + buffer_frame: AxiStream { + data: buffer_data, + str: buffer_strb, + keep: buffer_keep, + ..raw_stream + }, + do_recv_raw_stream: !raw_stream.last, + do_send_padded_stream: true, + id: raw_stream.id, + dest: raw_stream.dest, + ..state + } + }, + Fsm::FORWARD_STREAM => { + if (state.frame_to_send.last == true) { + State { + fsm: Fsm::RECV_REQUEST, + ..zero!() + } + } else { + let data_to_send = (raw_stream.data << state.shift) | state.buffer_frame.data; + let strb_to_send = (raw_stream.str << state.offset) | state.buffer_frame.str; + let keep_to_send = (raw_stream.keep << state.offset) | state.buffer_frame.keep; + let buffer_data = raw_stream.data >> state.buffer_shift; + let buffer_strb = raw_stream.str >> state.buffer_offset; + let buffer_keep = raw_stream.keep >> state.buffer_offset; + // Current frame is last and there is no more data to send in the next frame + let finish_early = (buffer_strb == Strobe:0) && raw_stream.last; + + State { + fsm: Fsm::FORWARD_STREAM, + frame_to_send: AxiStream { + data: data_to_send, + str: strb_to_send, + keep: keep_to_send, + last: if (finish_early) { true } else { state.buffer_frame.last }, + id: state.id, + dest: state.dest, + }, + buffer_frame: AxiStream { + data: buffer_data, + str: buffer_strb, + keep: buffer_keep, + id: state.id, + dest: state.dest, + ..raw_stream + }, + do_recv_raw_stream: !raw_stream.last, + ..state + } + } + }, + Fsm::ERROR => { + state + }, + _ => { + assert!(false, "Invalid state"); + State { + fsm: Fsm::ERROR, + ..state + } + } + }; + + next_state + } +} + +const INST_ADDR_W = u32:16; +const INST_DATA_W = u32:32; +const INST_DEST_W = u32:32; +const INST_ID_W = u32:32; +const INST_DATA_W_DIV8 = u32:4; +const INST_DATA_W_LOG2 = u32:6; + +type InstAxiStream = axi_st::AxiStream; +type InstReq = axi_writer::AxiWriterRequest; + +proc AxiStreamAddEmptyInst { + config ( + write_req_r: chan in, + raw_stream_r: chan in, + padded_stream_s: chan out, + ) { + spawn AxiStreamAddEmpty ( + write_req_r, + raw_stream_r, + padded_stream_s + ); + } + + init { } + + next (state:()) { } +} + +const TEST_ADDR_W = u32:16; +const TEST_DATA_W = u32:32; +const TEST_DEST_W = u32:32; +const TEST_ID_W = u32:32; +const TEST_DATA_W_DIV8 = u32:4; + +type TestAxiStream = axi_st::AxiStream; +type TestReq = axi_writer::AxiWriterRequest; + +type TestAddr = uN[TEST_ADDR_W]; +type TestLength = uN[TEST_ADDR_W]; +type TestData = uN[TEST_DATA_W]; +type TestStrobe = uN[TEST_DATA_W_DIV8]; +type TestId = uN[TEST_ID_W]; +type TestDest = uN[TEST_DEST_W]; + +const TEST_WRITE_REQUEST = TestReq[13]:[ + TestReq { + address: TestAddr:0x0, + length: TestLength:8 + }, + TestReq { + address: TestAddr:0x1, + length: TestLength:8 + }, + TestReq { + address: TestAddr:0x2, + length: TestLength:8 + }, + TestReq { + address: TestAddr:0x3, + length: TestLength:8 + }, + TestReq { + address: TestAddr:0x4, + length: TestLength:8 + }, + TestReq { + address: TestAddr:0x4, + length: TestLength:16 + }, + TestReq { + address: TestAddr:0x5, + length: TestLength:16 + }, + TestReq { + address: TestAddr:0x6, + length: TestLength:16 + }, + TestReq { + address: TestAddr:0x7, + length: TestLength:16 + }, + TestReq { + address: TestAddr:0x8, + length: TestLength:16 + }, + TestReq { + address: TestAddr:0x3, + length: TestLength:10 + }, + TestReq { + address: TestAddr:0x1, + length: TestLength:1 + }, + TestReq { + address: TestAddr:0x1, + length: TestLength:4 + }, +]; + +const TEST_STREAM_IN = TestAxiStream[35]:[ + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:0, + dest: TestDest:0, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:0, + dest: TestDest:0, + }, + + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:1, + dest: TestDest:1, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:1, + dest: TestDest:1, + }, + + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:2, + dest: TestDest:2, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:2, + dest: TestDest:2, + }, + + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:3, + dest: TestDest:3, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:3, + dest: TestDest:3, + }, + + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:4, + dest: TestDest:4, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:4, + dest: TestDest:4, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:5, + dest: TestDest:5, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:6, + dest: TestDest:6, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:7, + dest: TestDest:7, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:8, + dest: TestDest:8, + }, + + TestAxiStream { + data: TestData:0x1734_6B45, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0x0476_2A22, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:9, + dest: TestDest:0, + }, + TestAxiStream { + data: TestData:0xE304, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + last: true, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0xF1, + str: TestStrobe:0b0001, + keep: TestStrobe:0b0001, + last: true, + id: TestId:10, + dest: TestDest:10, + }, + TestAxiStream { + data: TestData:0x01EAF614, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:11, + dest: TestDest:11, + }, +]; + +const TEST_STREAM_OUT = TestAxiStream[43]:[ + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:0, + dest: TestDest:0, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:0, + dest: TestDest:0, + }, + + TestAxiStream { + data: TestData:0xADBE_EF00, + str: TestStrobe:0b1110, + keep: TestStrobe:0b1110, + last: false, + id: TestId:1, + dest: TestDest:1, + }, + TestAxiStream { + data: TestData:0xFEBA_ADDE, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:1, + dest: TestDest:1, + }, + TestAxiStream { + data: TestData:0x0000_00CA, + str: TestStrobe:0b0001, + keep: TestStrobe:0b0001, + last: true, + id: TestId:1, + dest: TestDest:1, + }, + + TestAxiStream { + data: TestData:0xBEEF_0000, + str: TestStrobe:0b1100, + keep: TestStrobe:0b1100, + last: false, + id: TestId:2, + dest: TestDest:2, + }, + TestAxiStream { + data: TestData:0xBAAD_DEAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:2, + dest: TestDest:2, + }, + TestAxiStream { + data: TestData:0x0000_CAFE, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + last: true, + id: TestId:2, + dest: TestDest:2, + }, + + TestAxiStream { + data: TestData:0xEF00_0000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + last: false, + id: TestId:3, + dest: TestDest:3, + }, + TestAxiStream { + data: TestData:0xADDE_ADBE, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:3, + dest: TestDest:3, + }, + TestAxiStream { + data: TestData:0x00CA_FEBA, + str: TestStrobe:0b0111, + keep: TestStrobe:0b0111, + last: true, + id: TestId:3, + dest: TestDest:3, + }, + + TestAxiStream { + data: TestData:0xDEAD_BEEF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0xCAFEBAAD, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:4, + dest: TestDest:4, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:4, + dest: TestDest:4, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:4, + dest: TestDest:4, + }, + + TestAxiStream { + data: TestData:0x6543_2100, + str: TestStrobe:0b1110, + keep: TestStrobe:0b1110, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0xEDCBA987, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0xBCDEFFFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0x3456789A, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:5, + dest: TestDest:5, + }, + TestAxiStream { + data: TestData:0x12, + str: TestStrobe:0b0001, + keep: TestStrobe:0b0001, + last: true, + id: TestId:5, + dest: TestDest:5, + }, + + TestAxiStream { + data: TestData:0x4321_0000, + str: TestStrobe:0b1100, + keep: TestStrobe:0b1100, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0xCBA98765, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0xDEFFFFED, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0x56789ABC, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:6, + dest: TestDest:6, + }, + TestAxiStream { + data: TestData:0x1234, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + last: true, + id: TestId:6, + dest: TestDest:6, + }, + + TestAxiStream { + data: TestData:0x2100_0000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0xA9876543, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0xFFFFEDCB, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0x789ABCDE, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:7, + dest: TestDest:7, + }, + TestAxiStream { + data: TestData:0x1234_56, + str: TestStrobe:0b0111, + keep: TestStrobe:0b0111, + last: true, + id: TestId:7, + dest: TestDest:7, + }, + + TestAxiStream { + data: TestData:0x8765_4321, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0xFFEDCBA9, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0x9ABCDEFF, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:8, + dest: TestDest:8, + }, + TestAxiStream { + data: TestData:0x12345678, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: true, + id: TestId:8, + dest: TestDest:8, + }, + + TestAxiStream { + data: TestData:0x45000000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + last: false, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0x2217346B, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0x0404762A, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + last: false, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0x000000E3, + str: TestStrobe:0b0001, + keep: TestStrobe:0b0001, + last: true, + id: TestId:9, + dest: TestDest:9, + }, + TestAxiStream { + data: TestData:0x0000F100, + str: TestStrobe:0b0010, + keep: TestStrobe:0b0010, + last: true, + id: TestId:10, + dest: TestDest:10, + }, + TestAxiStream { + data: TestData:0xEAF61400, + str: TestStrobe:0b1110, + keep: TestStrobe:0b1110, + last: false, + id: TestId:11, + dest: TestDest:11, + }, + TestAxiStream { + data: TestData:0x01, + str: TestStrobe:0b0001, + keep: TestStrobe:0b0001, + last: true, + id: TestId:11, + dest: TestDest:11, + }, +]; + +#[test_proc] +proc AxiStreamAddEmptyTest { + terminator: chan out; + + write_req_s: chan out; + raw_stream_s: chan out; + padded_stream_r: chan in; + + config ( + terminator: chan out, + ) { + let (write_req_s, write_req_r) = chan("write_req"); + let (raw_stream_s, raw_stream_r) = chan("raw_stream"); + let (padded_stream_s, padded_stream_r) = chan("stream_out"); + + spawn AxiStreamAddEmpty ( + write_req_r, + raw_stream_r, + padded_stream_s + ); + + ( + terminator, + write_req_s, + raw_stream_s, + padded_stream_r, + ) + } + + init { } + + next (state: ()) { + let tok = for ((i, test_write_req), tok): ((u32, TestReq), token) in enumerate(TEST_WRITE_REQUEST) { + let tok = send(tok, write_req_s, test_write_req); + trace_fmt!("Sent #{} write request {:#x}", i + u32:1, test_write_req); + tok + }(join()); + + let tok = for ((i, test_raw_stream), tok): ((u32, TestAxiStream), token) in enumerate(TEST_STREAM_IN) { + let tok = send(tok, raw_stream_s, test_raw_stream); + trace_fmt!("Sent #{} stream input {:#x}", i + u32:1, test_raw_stream); + tok + }(tok); + + let tok = for ((i, test_stream_out), tok): ((u32, TestAxiStream), token) in enumerate(TEST_STREAM_OUT) { + let (tok, stream_out) = recv(tok, padded_stream_r); + trace_fmt!("Received #{} stream output {:#x}", i + u32:1, stream_out); + assert_eq(test_stream_out, stream_out); + tok + }(tok); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/axi_stream_downscaler.x b/xls/modules/zstd/memory/axi_stream_downscaler.x new file mode 100644 index 0000000000..4dc983936f --- /dev/null +++ b/xls/modules/zstd/memory/axi_stream_downscaler.x @@ -0,0 +1,264 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + + +// This file contains implementation of AxiStreamDownscaler that can be used +// to convert AxiStream transactions from a wider bus, to multiple transactions +// on more narrow bus. + +import std; +import xls.modules.zstd.memory.axi_st; + +struct AxiStreamDownscalerState< + IN_W: u32, OUT_W: u32, DEST_W: u32, ID_W: u32, + IN_W_DIV8: u32, // = {IN_W / u32:8}, + RATIO_W: u32, // = {std::clog2((IN_W / OUT_W) + u32:1)}, +> { + in_data: axi_st::AxiStream, + i: uN[RATIO_W], +} + +// A proc responsible for converting Axi Stream transactions from a wider bus, +// to multiple transactions on more narrow bus +pub proc AxiStreamDownscaler< + IN_W: u32, OUT_W: u32, DEST_W: u32, ID_W: u32, + IN_W_DIV8: u32 = {IN_W / u32:8}, + OUT_W_DIV8: u32 = {OUT_W / u32:8}, + RATIO: u32 = {IN_W / OUT_W}, + RATIO_W: u32 = {std::clog2((IN_W / OUT_W) + u32:1)} +> { + type State = AxiStreamDownscalerState; + type InStream = axi_st::AxiStream; + type OutStream = axi_st::AxiStream; + + // Assumptions related to parameters + const_assert!(IN_W >= OUT_W); // input should be wider than output + const_assert!(IN_W % OUT_W == u32:0); // output width should be a multiple of input width + + // checks for parameters + const_assert!(RATIO == IN_W / OUT_W); + const_assert!(RATIO_W == std::clog2((IN_W / OUT_W) + u32:1)); + + in_r: chan in; + out_s: chan out; + + config( + in_r: chan in, + out_s: chan out + ) { (in_r, out_s) } + + init { zero!() } + + next(state: State) { + const MAX_ITER = RATIO as uN[RATIO_W] - uN[RATIO_W]:1; + + let tok0 = join(); + + let do_recv = (state.i == uN[RATIO_W]:0); + let (tok1, in_data) = recv_if(tok0, in_r, do_recv, state.in_data); + + let is_last_iter = (state.i == MAX_ITER); + + let data = in_data.data[OUT_W * state.i as u32 +: uN[OUT_W]]; + let keep = in_data.keep[OUT_W_DIV8 * state.i as u32 +: uN[OUT_W_DIV8]]; + let str = in_data.str[OUT_W_DIV8 * state.i as u32 +: uN[OUT_W_DIV8]]; + let id = in_data.id; + let dest = in_data.dest; + let last = if is_last_iter { in_data.last } else { u1:0 }; + + let out_data = OutStream { data, keep, str, last, id, dest }; + + let tok = send(tok1, out_s, out_data); + + if is_last_iter { + zero!() + } else { + let i = state.i + uN[RATIO_W]:1; + State { in_data, i } + } + } +} + + +const INST_IN_W = u32:128; +const INST_IN_W_DIV8 = INST_IN_W / u32:8; +const INST_OUT_W = u32:32; +const INST_OUT_W_DIV8 = INST_OUT_W / u32:8; +const INST_DEST_W = u32:8; +const INST_ID_W = u32:8; + +proc AxiStreamDownscalerInst { + type InStream = axi_st::AxiStream; + type OutStream = axi_st::AxiStream; + + config( + in_r: chan in, + out_s: chan out + ) { + spawn AxiStreamDownscaler< + INST_IN_W, INST_OUT_W, INST_DEST_W, INST_ID_W + >(in_r, out_s); + } + + init { } + + next(state: ()) { } +} + + +const TEST_IN_W = u32:128; +const TEST_IN_W_DIV8 = TEST_IN_W / u32:8; +const TEST_OUT_W = u32:32; +const TEST_OUT_W_DIV8 = TEST_OUT_W / u32:8; +const TEST_DEST_W = u32:8; +const TEST_ID_W = u32:8; +const TEST_RATIO = TEST_IN_W / TEST_OUT_W; +const TEST_RATIO_W = std::clog2((TEST_IN_W / TEST_OUT_W) + u32:1); + +#[test_proc] +proc AxiStreamWitdhDownscalerTest { + type InStream = axi_st::AxiStream; + type OutStream = axi_st::AxiStream; + type InData = uN[TEST_IN_W]; + type InStr = uN[TEST_IN_W_DIV8]; + type InKeep = uN[TEST_IN_W_DIV8]; + type OutData = uN[TEST_OUT_W]; + type OutStr = uN[TEST_OUT_W_DIV8]; + type OutKeep = uN[TEST_OUT_W_DIV8]; + type Id = uN[TEST_ID_W]; + type Dest = uN[TEST_DEST_W]; + + terminator: chan out; + in_s: chan out; + out_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("out"); + + spawn AxiStreamDownscaler< + TEST_IN_W, TEST_OUT_W, TEST_DEST_W, TEST_ID_W + > (in_r, out_s); + + (terminator, in_s, out_r) + } + + init { } + + next(state: ()) { + let tok = join(); + + // Test 1 + let tok = send(tok, in_s, InStream { + data: InData:0xAAAA_BBBB_CCCC_DDDD_1111_2222_3333_4444, + str: InStr:0x0FF0, + keep: InKeep:0x0FF0, + last: u1:1, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0x3333_4444, + str: OutStr:0x0, + keep: OutKeep:0x0, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0x1111_2222, + str: OutStr:0xF, + keep: OutKeep:0xF, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0xCCCC_DDDD, + str: OutStr:0xF, + keep: OutKeep:0xF, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0xAAAA_BBBB, + str: OutStr:0x0, + keep: OutKeep:0x0, + last: u1:1, + id: Id:0xAB, + dest: Dest:0xCD + }); + + // Test 2 + let tok = send(tok, in_s, InStream { + data: InData:0xAAAA_BBBB_CCCC_DDDD_1111_2222_3333_4444, + str: InStr:0x1234, + keep: InKeep:0x1234, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0x3333_4444, + str: OutStr:0x4, + keep: OutKeep:0x4, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0x1111_2222, + str: OutStr:0x3, + keep: OutKeep:0x3, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0xCCCC_DDDD, + str: OutStr:0x2, + keep: OutKeep:0x2, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + let (tok, data) = recv(tok, out_r); + assert_eq(data, OutStream { + data: OutData:0xAAAA_BBBB, + str: OutStr:0x1, + keep: OutKeep:0x1, + last: u1:0, + id: Id:0xAB, + dest: Dest:0xCD + }); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/axi_stream_remove_empty.x b/xls/modules/zstd/memory/axi_stream_remove_empty.x new file mode 100644 index 0000000000..a61ec479fc --- /dev/null +++ b/xls/modules/zstd/memory/axi_stream_remove_empty.x @@ -0,0 +1,410 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of AxiStreamRemoveEmpty proc, +// which is used to remove bytes marked as containing no data in the Axi Stream + +import std; +import xls.modules.zstd.memory.axi_st; + +struct AxiStreamRemoveEmptyState< + DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, +> { + data: uN[DATA_W], + len: uN[DATA_W_LOG2], + last: bool, + id: uN[ID_W], + dest: uN[DEST_W], +} + + +// Returns a tuple containing data and length, afer removing non-data +// bytes from the in_data varaiable, using information from keep and str fields +fn remove_empty_bytes ( + in_data: uN[DATA_W], keep: uN[DATA_W_DIV8], str: uN[DATA_W_DIV8] +) -> (uN[DATA_W], uN[DATA_W_LOG2]) { + + const EXT_OFFSET_W = DATA_W_LOG2 + u32:3; + + type Data = uN[DATA_W]; + type Str = uN[DATA_W_DIV8]; + type Keep = uN[DATA_W_DIV8]; + type Offset = uN[DATA_W_LOG2]; + type OffsetExt = uN[EXT_OFFSET_W]; + type Length = uN[DATA_W_LOG2]; + + let (data, len, _) = for (i, (data, len, offset)): (u32, (Data, Length, Offset)) in range(u32:0, DATA_W_DIV8) { + if str[i +: u1] & keep[i +: u1] { + ( + data | (in_data & (Data:0xFF << (u32:8 * i))) >> (OffsetExt:8 * offset as OffsetExt), + len + Length:8, + offset, + ) + } else { + (data, len, offset + Offset:1) + } + }((Data:0, Length:0, Offset:0)); + (data, len) +} + +// Returns the number of bytes that should be soted in the state in case we +// ar not able to send all of them in a single transaction. +fn get_overflow_len(len1: uN[LENGTH_W], len2: uN[LENGTH_W]) -> uN[LENGTH_W] { + const LENGTH_W_PLUS_ONE = LENGTH_W + u32:1; + type LengthPlusOne = uN[LENGTH_W_PLUS_ONE]; + + const MAX_DATA_LEN = DATA_W as LengthPlusOne; + (len1 as LengthPlusOne + len2 as LengthPlusOne - MAX_DATA_LEN) as uN[LENGTH_W] +} + +// Return the new mask for keep and str fields, calculated using new data length +fn get_mask(len: uN[DATA_W_LOG2]) -> uN[DATA_W_DIV8] { + const MAX_LEN = DATA_W as uN[DATA_W_LOG2]; + const MASK = !uN[DATA_W_DIV8]:0; + + let shift = std::div_pow2((MAX_LEN - len), uN[DATA_W_LOG2]:8); + MASK >> shift +} + +// A proc that removes empty bytes from the Axi Stream and provides aligned data +// to other procs, allowing for a simpler implementation of the receiving side +// of the design. +pub proc AxiStreamRemoveEmpty< + DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + DATA_W_LOG2: u32 = {std::clog2(DATA_W + u32:1)}, +> { + type AxiStream = axi_st::AxiStream; + type State = AxiStreamRemoveEmptyState; + + type Offset = uN[DATA_W_LOG2]; + type Length = uN[DATA_W_LOG2]; + type Keep = uN[DATA_W_DIV8]; + type Str = uN[DATA_W_DIV8]; + type Data = uN[DATA_W]; + + stream_in_r: chan in; + stream_out_s: chan out; + + config ( + stream_in_r: chan in, + stream_out_s: chan out, + ) { + (stream_in_r, stream_out_s) + } + + init { zero!() } + + next (state: State) { + const MAX_LEN = DATA_W as Length; + const MAX_MASK = !uN[DATA_W_DIV8]:0; + + let do_recv = !state.last; + let (tok, stream_in) = recv_if(join(), stream_in_r, !state.last, zero!()); + let (id, dest) = if !state.last { + (stream_in.id, stream_in.dest) + } else { + (state.id, state.dest) + }; + + let (data, len) = remove_empty_bytes( + stream_in.data, stream_in.keep, stream_in.str + ); + + let empty_input_bytes = MAX_LEN - len; + let empty_state_bytes = MAX_LEN - state.len; + + let exceeds_transfer = (empty_input_bytes < state.len); + let exact_transfer = (empty_input_bytes == state.len); + + let combined_state_data = state.data | data << state.len; + let combined_input_data = data | state.data << len; + + let overflow_len = get_overflow_len(state.len, len); + let sum_len = state.len + len; + let sum_mask = get_mask(sum_len); + + let (next_state, do_send, data) = if !state.last & exceeds_transfer { + // flush and store + ( + State { + data: data >> empty_state_bytes, + len: overflow_len, + last: stream_in.last, + id: stream_in.id, + dest: stream_in.dest, + }, + true, + AxiStream { + data: combined_state_data, + str: MAX_MASK, + keep: MAX_MASK, + last: false, + id, dest + } + ) + } else if state.last | stream_in.last | exact_transfer { + // flush only + ( + zero!(), + true, + AxiStream { + data: combined_state_data, + str: sum_mask, + keep: sum_mask, + last: state.last | stream_in.last, + id, dest + } + ) + } else { + // store + ( + State { + data: combined_input_data, + len: sum_len, + ..state + }, + false, + zero!(), + ) + }; + + send_if(tok, stream_out_s, do_send, data); + next_state + } +} + + +const INST_DATA_W = u32:32; +const INST_DEST_W = u32:32; +const INST_ID_W = u32:32; + +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_DATA_W_LOG2 = std::clog2(INST_DATA_W + u32:1); + +type InstAxiStream = axi_st::AxiStream; + +proc AxiStreamRemoveEmptyInst { + config ( + stream_in_r: chan in, + stream_out_s: chan out, + ) { + spawn AxiStreamRemoveEmpty ( + stream_in_r, + stream_out_s + ); + } + + init { } + + next (state:()) { } +} + + +const TEST_DATA_W = u32:32; +const TEST_DEST_W = u32:32; +const TEST_ID_W = u32:32; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; + +type TestAxiStream = axi_st::AxiStream; + +#[test_proc] +proc AxiStreamRemoveEmptyTest { + terminator: chan out; + stream_in_s: chan out; + stream_out_r: chan in; + + config ( + terminator: chan out, + ) { + let (stream_in_s, stream_in_r) = chan("stream_in"); + let (stream_out_s, stream_out_r) = chan("stream_out"); + + spawn AxiStreamRemoveEmpty( + stream_in_r, stream_out_s + ); + + (terminator, stream_in_s, stream_out_r) + } + + init { } + + next (state: ()) { + + type Data = uN[TEST_DATA_W]; + type Keep = uN[TEST_DATA_W_DIV8]; + type Str = uN[TEST_DATA_W_DIV8]; + type Id = uN[TEST_ID_W]; + type Dest = uN[TEST_DEST_W]; + + let tok = join(); + + // Test 1: All bits set, last set + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xAABB_CCDD, + str: Str:0xF, + keep: Keep:0xF, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xAABB_CCDD, + str: Str:0xF, + keep: Keep:0xF, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + // Test 2: Non of bits set, last set + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xAABB_CCDD, + str: Str:0x0, + keep: Keep:0x0, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x0, + str: Str:0x0, + keep: Keep:0x0, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + // Test 3: Some bits set, last set + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xAABB_CCDD, + str: Str:0x5, + keep: Keep:0x5, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xBBDD, + str: Str:0x3, + keep: Keep:0x3, + last: u1:1, + id: Id:3, + dest: Dest:0, + }); + + // Test 4: Some bits set, last set in the last transfer. + // The last transfer is aligned + + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0000_BBAA, + str: Str:0b0011, + keep: Keep:0b0011, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xFFEE_DDCC, + str: Str:0b1111, + keep: Keep:0b1111, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x0022_0011, + str: Str:0b0101, + keep: Keep:0b0101, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xDDCC_BBAA, + str: Str:0xF, + keep: Keep:0xF, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0x2211_FFEE, + str: Str:0xF, + keep: Keep:0xF, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + + // Test 5: Some bits set, last set in the last transfer. + // The last transfer is not aligned + + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00BB_00AA, + str: Str:0b0101, + keep: Keep:0b0101, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0x00DD_CC00, + str: Str:0b0110, + keep: Keep:0b0110, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let tok = send(tok, stream_in_s, TestAxiStream { + data: Data:0xFF00_00EE, + str: Str:0b1001, + keep: Keep:0b1001, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xDDCC_BBAA, + str: Str:0xF, + keep: Keep:0xF, + last: u1:0, + id: Id:0, + dest: Dest:0, + }); + let (tok, stream_out) = recv(tok, stream_out_r); + assert_eq(stream_out, TestAxiStream { + data: Data:0xFFEE, + str: Str:0x3, + keep: Keep:0x3, + last: u1:1, + id: Id:0, + dest: Dest:0, + }); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/axi_writer.x b/xls/modules/zstd/memory/axi_writer.x new file mode 100644 index 0000000000..982c444bcf --- /dev/null +++ b/xls/modules/zstd/memory/axi_writer.x @@ -0,0 +1,720 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// AXI Writer +// +// Part of the main controller, which translates write requests +// (address and length tuples) into AXI Write Transactions. +// Data to write is read from the AXI Stream interface which comes from the +// AxiStreamAddEmpty proc which is responsible for preparing the data for writes +// under unaligned addresses + +import std; + +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_st; +import xls.modules.zstd.memory.common; + +pub struct AxiWriterRequest { + address: uN[ADDR_W], + length: uN[ADDR_W] +} + +enum AxiWriterFsm : u4 { + IDLE = 0, + TRANSFER_LENGTH = 1, + CALC_NEXT_TRANSFER = 2, + TRANSFER_DISPATCH = 3, + AXI_WRITE_AW = 4, + AXI_WRITE_W = 5, + AXI_WRITE_B = 6, + RESP_OK = 7, + ERROR = 15, +} + +pub enum AxiWriterRespStatus : u1 { + OKAY = 0, + ERROR = 1, +} + +pub struct AxiWriterResp { + status: AxiWriterRespStatus +} + +struct AxiWriterState< + ADDR_W: u32, + DATA_W: u32, + ID_W: u32, + DATA_W_DIV8: u32, + LANE_W: u32 +> { + fsm: AxiWriterFsm, + transfer_data: AxiWriterRequest, + aw_bundle: axi::AxiAw, + w_bundle: axi::AxiW, + b_bundle: axi::AxiB, + burst_counter: u8, + burst_end: u8, + recv_new_write_req: bool, + transaction_len: uN[ADDR_W], + bytes_to_4k: uN[ADDR_W], + bytes_to_max_axi_burst: uN[ADDR_W], + address_align_offset: uN[ADDR_W], + req_low_lane: uN[LANE_W], + req_high_lane: uN[LANE_W], +} + +pub proc AxiWriter< + ADDR_W: u32, DATA_W: u32, DEST_W: u32, ID_W: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + LANE_W: u32 = {std::clog2(DATA_W / u32:8)} +> { + type Req = AxiWriterRequest; + type AxiStream = axi_st::AxiStream; + type AxiAw = axi::AxiAw; + type AxiW = axi::AxiW; + type AxiB = axi::AxiB; + type Resp = axi::AxiWriteResp; + type AxiAxSize = axi::AxiAxSize; + type AxiAxBurst = axi::AxiAxBurst; + type State = AxiWriterState; + type Fsm = AxiWriterFsm; + + type Addr = uN[ADDR_W]; + type Lane = uN[LANE_W]; + type Id = uN[ID_W]; + type Length = u8; + + ch_write_req: chan in; + ch_write_resp: chan out; + ch_axi_aw: chan out; + ch_axi_w: chan out; + ch_axi_b: chan in; + ch_axi_st_read: chan in; + + config( + ch_write_req: chan in, + ch_write_resp: chan out, + ch_axi_aw: chan out, + ch_axi_w: chan out, + ch_axi_b: chan in, + ch_axi_st_read: chan in + ) { + (ch_write_req, ch_write_resp, ch_axi_aw, ch_axi_w, ch_axi_b, ch_axi_st_read) + } + + init { + State { + recv_new_write_req: true, + ..zero!() + } + } + + next(state: State) { + const BYTES_IN_TRANSFER = DATA_W_DIV8 as Addr; + const MAX_AXI_BURST_BYTES = Addr:256 * BYTES_IN_TRANSFER; + + let tok_0 = join(); + + // Address Generator + let (tok_1, recv_transfer_data) = recv_if( + tok_0, ch_write_req, state.fsm == Fsm::IDLE && state.recv_new_write_req, + state.transfer_data); + + let tok_2 = send_if(tok_0, ch_axi_aw, state.fsm == Fsm::AXI_WRITE_AW, state.aw_bundle); + + let (tok_3, r_data) = recv_if( + tok_0, ch_axi_st_read, state.fsm == Fsm::AXI_WRITE_W, + zero!()); + + // Wait for B + let (tok_5, b_data) = recv_if( + tok_0, ch_axi_b, state.fsm == Fsm::AXI_WRITE_B, + AxiB { resp: Resp::OKAY, id: Id:0 }); + + let req_end = state.fsm == Fsm::RESP_OK && state.recv_new_write_req; + let error_state = state.fsm == Fsm::ERROR; + let resp = if (error_state) {AxiWriterResp{status: AxiWriterRespStatus::ERROR}} else {AxiWriterResp{status: AxiWriterRespStatus::OKAY}}; + let do_handle_resp = error_state | req_end; + let tok_6 = send_if(tok_0, ch_write_resp, do_handle_resp, resp); + + let next_state = match(state.fsm) { + Fsm::IDLE => { + let bytes_to_4k = common::bytes_to_4k_boundary(recv_transfer_data.address); + let address_align_offset = common::offset(recv_transfer_data.address) as Addr; + let bytes_to_max_axi_burst = MAX_AXI_BURST_BYTES - address_align_offset as Addr; + State { + fsm: Fsm::TRANSFER_LENGTH, + transfer_data: recv_transfer_data, + address_align_offset: address_align_offset, + bytes_to_4k: bytes_to_4k, + bytes_to_max_axi_burst: bytes_to_max_axi_burst, + ..state + } + }, + Fsm::TRANSFER_LENGTH => { + let tran_len = std::umin(state.transfer_data.length, std::umin(state.bytes_to_4k, state.bytes_to_max_axi_burst)); + State { + fsm: Fsm::CALC_NEXT_TRANSFER, + transaction_len: tran_len, + ..state + } + }, + Fsm::CALC_NEXT_TRANSFER => { + let next_address = state.transfer_data.address + state.transaction_len; + let next_length = state.transfer_data.length - state.transaction_len; + let next_transfer_data = Req { + address: next_address, + length: next_length, + }; + let (req_low_lane, req_high_lane) = common::get_lanes(state.transfer_data.address, state.transaction_len); + let aw_addr = common::align(recv_transfer_data.address); + + State { + fsm: Fsm::TRANSFER_DISPATCH, + aw_bundle: AxiAw { + addr: aw_addr, + ..state.aw_bundle + }, + transfer_data: next_transfer_data, + req_low_lane: req_low_lane, + req_high_lane: req_high_lane, + ..state + } + }, + Fsm::TRANSFER_DISPATCH => { + let recv_new_write_req = state.transfer_data.length == Addr:0; + let id = state.aw_bundle.id + Id:1; + let full_transaction_len = state.transaction_len + state.address_align_offset; + let div = std::div_pow2(full_transaction_len, DATA_W_DIV8 as Addr); + let rem = std::mod_pow2(full_transaction_len, DATA_W_DIV8 as Addr); + let len = if (rem == Addr:0) { (div - Addr:1) as Length } else { div as Length }; + + State { + fsm: Fsm::AXI_WRITE_AW, + aw_bundle: AxiAw { + id: id, + size: common::axsize(), + len: len, + burst: AxiAxBurst::INCR, + ..state.aw_bundle + }, + burst_end: len, + recv_new_write_req: recv_new_write_req, + ..state + } + }, + Fsm::AXI_WRITE_AW => { + + State { + fsm: Fsm::AXI_WRITE_W, + ..state + } + }, + Fsm::AXI_WRITE_W => { + let next_burst_counter = state.burst_counter + Length:1; + + let (next_fsm, req_low_lane, req_high_lane) = if (state.burst_counter == state.burst_end) { + (Fsm::AXI_WRITE_B, state.req_low_lane, state.req_high_lane) + } else { + (Fsm::AXI_WRITE_W, Lane:0, state.req_high_lane) + }; + + State { + fsm: next_fsm, + burst_counter: next_burst_counter, + req_low_lane: req_low_lane, + req_high_lane: req_high_lane, + ..state + } + }, + Fsm::AXI_WRITE_B => { + if (b_data.resp == Resp::OKAY) { + State { + fsm: Fsm::RESP_OK, + b_bundle: b_data, + burst_counter: Length:0, + ..state + } + } else { + State { + fsm: Fsm::ERROR, + b_bundle: b_data, + ..state + } + } + }, + Fsm::RESP_OK => { + State { + fsm: Fsm::IDLE, + ..state + } + }, + Fsm::ERROR => { + State { + fsm: Fsm::IDLE, + ..state + } + }, + _ => { + assert!(false, "Invalid state"); + State { + fsm: Fsm::ERROR, + ..state + } + } + }; + + let w_bundle = match(state.fsm) { + Fsm::AXI_WRITE_W => { + let last = state.burst_counter == state.burst_end; + let low_lane = state.req_low_lane; + let high_lane = if (last) { state.req_high_lane } else {Lane:3}; + let mask = common::lane_mask(low_lane, high_lane); + + AxiW { + data: r_data.data, + strb: mask, + last: last, + } + }, + _ => { + zero!() + } + }; + + // Send W + let tok_4 = send_if( + tok_3, ch_axi_w, state.fsm == Fsm::AXI_WRITE_W, w_bundle); + + next_state + } +} + +const INST_ADDR_W = u32:16; +const INST_DATA_W = u32:32; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_DEST_W = INST_DATA_W / u32:8; +const INST_ID_W = INST_DATA_W / u32:8; + +proc AxiWriterInst { + type InstReq = AxiWriterRequest; + type InstAxiWriterResp = AxiWriterResp; + type InstAxiStream = axi_st::AxiStream; + type InstAxiAw = axi::AxiAw; + type InstAxiW = axi::AxiW; + type InstAxiB = axi::AxiB; + + config(ch_write_req: chan in, + ch_write_resp: chan out, + ch_axi_aw: chan out, + ch_axi_w: chan out, + ch_axi_b: chan in, + ch_axi_st_read: chan in) { + + spawn AxiWriter< + INST_ADDR_W, INST_DATA_W, INST_DEST_W, INST_ID_W>( + ch_write_req, ch_write_resp, ch_axi_aw, ch_axi_w, ch_axi_b, ch_axi_st_read); + () + } + + init { () } + + next(state: ()) { } +} + +const TEST_ADDR_W = u32:16; +const TEST_DATA_W = u32:32; +const TEST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const TEST_DEST_W = INST_DATA_W / u32:8; +const TEST_ID_W = INST_DATA_W / u32:8; + +#[test_proc] +proc AxiWriterTest { + type TestReq = AxiWriterRequest; + type TestAxiWriterResp = AxiWriterResp; + type TestAxiStream = axi_st::AxiStream; + type TestAxiAw = axi::AxiAw; + type TestAxiW = axi::AxiW; + type TestAxiB = axi::AxiB; + + type TestAxiWriteResp = axi::AxiWriteResp; + type TestAxiWriterRespStatus = AxiWriterRespStatus; + type TestAxiAxBurst = axi::AxiAxBurst; + type TestAxiAxSize = axi::AxiAxSize; + type TestAddr = uN[TEST_ADDR_W]; + type TestLength = uN[TEST_ADDR_W]; + type TestDataBits = uN[TEST_DATA_W]; + type TestStrobe = uN[TEST_DATA_W_DIV8]; + type TestId = uN[TEST_ID_W]; + type TestDest = uN[TEST_DEST_W]; + + terminator: chan out; + write_req_s: chan out; + write_resp_r: chan in; + axi_aw_r: chan in; + axi_w_r: chan in; + axi_b_s: chan out; + axi_st_read_s: chan out; + + config( + terminator: chan out, + ) { + let (write_req_s, write_req_r) = chan("write_req"); + let (write_resp_s, write_resp_r) = chan("write_resp"); + let (axi_aw_s, axi_aw_r) = chan("axi_aw"); + let (axi_w_s, axi_w_r) = chan("axi_w"); + let (axi_b_s, axi_b_r) = chan("axi_b"); + let (axi_st_read_s, axi_st_read_r) = chan("axi_st"); + + spawn AxiWriter< + TEST_ADDR_W, TEST_DATA_W, TEST_DEST_W, TEST_ID_W + >(write_req_r, write_resp_s, axi_aw_s, axi_w_s, axi_b_r, axi_st_read_r); + (terminator, write_req_s, write_resp_r, axi_aw_r, axi_w_r, axi_b_s, axi_st_read_s) + } + + init { () } + + next(state: ()) { + let tok = join(); + + // Aligned single transfer + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0x0, + length: TestLength:4 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x11223344, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:1, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:1, + addr: TestAddr:0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x11223344, + strb: TestStrobe:0xF, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:1, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned 2 transfers + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0xf3, + length: TestLength:3 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x11000000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + id: TestId:2, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x00003322, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + id: TestId:2, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:2, + addr: TestAddr:0xf0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:1, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x11000000, + strb: TestStrobe:0b1000, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00003322, + strb: TestStrobe:0b0011, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:2, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Aligned 2 transfers + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0x100, + length: TestLength:8 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x44332211, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:3, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x88776655, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:3, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:3, + addr: TestAddr:0x100, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:1, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44332211, + strb: TestStrobe:0b1111, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x88776655, + strb: TestStrobe:0b1111, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:3, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unligned 3 transfers + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0x1F3, + length: TestLength:7 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x11000000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + id: TestId:4, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x55443322, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:4, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x00007766, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + id: TestId:4, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:4, + addr: TestAddr:0x1F0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:2, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x11000000, + strb: TestStrobe:0b1000, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x55443322, + strb: TestStrobe:0b1111, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00007766, + strb: TestStrobe:0b0011, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:4, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Crossing AXI 4kB boundary, aligned 2 burst transfers + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0x0FFC, + length: TestLength:8 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x44332211, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:5, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x88776655, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:5, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:5, + addr: TestAddr:0xFFC, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44332211, + strb: TestStrobe:0b1111, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:5, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:6, + addr: TestAddr:0x1000, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x88776655, + strb: TestStrobe:0b1111, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:6, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Crossing AXI 4kB boundary, unaligned 2 burst transfers + let tok = send(tok, write_req_s, TestReq { + address: TestAddr:0x1FFF, + length: TestLength:7 + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x11000000, + str: TestStrobe:0b1000, + keep: TestStrobe:0b1000, + id: TestId:7, + dest: TestDest:0, + last: true, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x55443322, + str: TestStrobe:0b1111, + keep: TestStrobe:0b1111, + id: TestId:7, + dest: TestDest:0, + last: false, + }); + let tok = send(tok, axi_st_read_s, TestAxiStream { + data: TestDataBits:0x00007766, + str: TestStrobe:0b0011, + keep: TestStrobe:0b0011, + id: TestId:7, + dest: TestDest:0, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:7, + addr: TestAddr:0x1FFC, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x11000000, + strb: TestStrobe:0b1000, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:7, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAw { + id: TestId:8, + addr: TestAddr:0x2000, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:1, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x55443322, + strb: TestStrobe:0b1111, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00007766, + strb: TestStrobe:0b0011, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:8, + }); + let (tok, resp) = recv(tok, write_resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/common.x b/xls/modules/zstd/memory/common.x new file mode 100644 index 0000000000..7ae1a5649c --- /dev/null +++ b/xls/modules/zstd/memory/common.x @@ -0,0 +1,243 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file includes various helpers used in the implementation +// of AxiReader and AxiWriter. + +import std; + +import xls.modules.zstd.memory.axi; + +// Returns a value rounded down to a multiple of ALIGN +pub fn align(x: uN[N]) -> uN[N] { + x & !(all_ones!() as uN[N]) +} + +#[test] +fn test_align() { + assert_eq(align(u32:0x1000), u32:0x1000); + assert_eq(align(u32:0x1001), u32:0x1001); + + assert_eq(align(u32:0x1000), u32:0x1000); + assert_eq(align(u32:0x1001), u32:0x1000); + assert_eq(align(u32:0x1002), u32:0x1002); + + assert_eq(align(u32:0x1000), u32:0x1000); + assert_eq(align(u32:0x1001), u32:0x1000); + assert_eq(align(u32:0x1002), u32:0x1000); + assert_eq(align(u32:0x1003), u32:0x1000); + assert_eq(align(u32:0x1004), u32:0x1004); + + assert_eq(align(u32:0x1000), u32:0x1000); + assert_eq(align(u32:0x1001), u32:0x1000); + assert_eq(align(u32:0x1002), u32:0x1000); + assert_eq(align(u32:0x1003), u32:0x1000); + assert_eq(align(u32:0x1004), u32:0x1000); + assert_eq(align(u32:0x1005), u32:0x1000); + assert_eq(align(u32:0x1006), u32:0x1000); + assert_eq(align(u32:0x1007), u32:0x1000); + assert_eq(align(u32:0x1008), u32:0x1008); +} + +// "Returns the remainder left after aligning the value to ALIGN +pub fn offset(x: uN[N]) -> uN[LOG_ALIGN] { + type Offset = uN[LOG_ALIGN]; + checked_cast(x & (all_ones!() as uN[N])) +} + +#[test] +fn test_offset() { + const OFFSET_W = std::clog2(u32:1); + type Offset = uN[OFFSET_W]; + + assert_eq(offset(u32:0x1000), Offset:0x0); + assert_eq(offset(u32:0x1001), Offset:0x0); + + const OFFSET_W = std::clog2(u32:2); + type Offset = uN[OFFSET_W]; + + assert_eq(offset(u32:0x1000), Offset:0x0); + assert_eq(offset(u32:0x1001), Offset:0x1); + assert_eq(offset(u32:0x1002), Offset:0x0); + + const OFFSET_W = std::clog2(u32:4); + type Offset = uN[OFFSET_W]; + + assert_eq(offset(u32:0x1000), Offset:0x0); + assert_eq(offset(u32:0x1001), Offset:0x1); + assert_eq(offset(u32:0x1002), Offset:0x2); + assert_eq(offset(u32:0x1003), Offset:0x3); + assert_eq(offset(u32:0x1004), Offset:0x0); + + const OFFSET_W = std::clog2(u32:8); + type Offset = uN[OFFSET_W]; + + assert_eq(offset(u32:0x1000), Offset:0x0); + assert_eq(offset(u32:0x1001), Offset:0x1); + assert_eq(offset(u32:0x1002), Offset:0x2); + assert_eq(offset(u32:0x1003), Offset:0x3); + assert_eq(offset(u32:0x1004), Offset:0x4); + assert_eq(offset(u32:0x1005), Offset:0x5); + assert_eq(offset(u32:0x1006), Offset:0x6); + assert_eq(offset(u32:0x1007), Offset:0x7); + assert_eq(offset(u32:0x1008), Offset:0x0); +} + +// Returns a tuple representing the byte lanes used in a transaction. +// The first value indicates the starting byte lane of the data bus that holds +// valid data in the initial transaction, while the second value identifies +// the last byte lane containing valid data in the entire transfer. +pub fn get_lanes< + DATA_W_DIV8: u32, + ADDR_W: u32, + LANE_W: u32 = {std::clog2(DATA_W_DIV8)} +>(addr: uN[ADDR_W], len: uN[ADDR_W]) -> (uN[LANE_W], uN[LANE_W]) { + type Lane = uN[LANE_W]; + + let low_lane = checked_cast(offset(addr)); + let len_mod = checked_cast(std::mod_pow2(len, DATA_W_DIV8 as uN[ADDR_W])); + const MAX_LANE = std::unsigned_max_value(); + + let high_lane = low_lane + len_mod + MAX_LANE; + (low_lane, high_lane) +} + +#[test] +fn test_get_lanes() { + const DATA_W_DIV8 = u32:32 / u32:8; + const ADDR_W = u32:16; + const LANE_W = std::clog2(DATA_W_DIV8); + + type Addr = uN[ADDR_W]; + type Length = uN[ADDR_W]; + type Lane = uN[LANE_W]; + + assert_eq(get_lanes(Addr:0x0, Length:0x1), (Lane:0, Lane:0)); + assert_eq(get_lanes(Addr:0x0, Length:0x2), (Lane:0, Lane:1)); + assert_eq(get_lanes(Addr:0x0, Length:0x3), (Lane:0, Lane:2)); + assert_eq(get_lanes(Addr:0x0, Length:0x4), (Lane:0, Lane:3)); + assert_eq(get_lanes(Addr:0x0, Length:0x5), (Lane:0, Lane:0)); + assert_eq(get_lanes(Addr:0x0, Length:0x6), (Lane:0, Lane:1)); + assert_eq(get_lanes(Addr:0x0, Length:0x7), (Lane:0, Lane:2)); + assert_eq(get_lanes(Addr:0x0, Length:0x8), (Lane:0, Lane:3)); + + assert_eq(get_lanes(Addr:0x1, Length:0x1), (Lane:1, Lane:1)); + assert_eq(get_lanes(Addr:0x1, Length:0x2), (Lane:1, Lane:2)); + assert_eq(get_lanes(Addr:0x1, Length:0x3), (Lane:1, Lane:3)); + assert_eq(get_lanes(Addr:0x1, Length:0x4), (Lane:1, Lane:0)); + assert_eq(get_lanes(Addr:0x1, Length:0x5), (Lane:1, Lane:1)); + assert_eq(get_lanes(Addr:0x1, Length:0x6), (Lane:1, Lane:2)); + assert_eq(get_lanes(Addr:0x1, Length:0x7), (Lane:1, Lane:3)); + assert_eq(get_lanes(Addr:0x1, Length:0x8), (Lane:1, Lane:0)); + + assert_eq(get_lanes(Addr:0xFFE, Length:0x1), (Lane:2, Lane:2)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x2), (Lane:2, Lane:3)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x3), (Lane:2, Lane:0)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x4), (Lane:2, Lane:1)); + + assert_eq(get_lanes(Addr:0xFFE, Length:0xFFE), (Lane:2, Lane:3)); + assert_eq(get_lanes(Addr:0xFFE, Length:0xFFF), (Lane:2, Lane:0)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x1000), (Lane:2, Lane:1)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x1001), (Lane:2, Lane:2)); + assert_eq(get_lanes(Addr:0xFFE, Length:0x1002), (Lane:2, Lane:3)); +} + +// Returns a mask for the data transfer, used in the tkeep and tstrb signals +// in the AXI Stream. The mask sets ones for byte lanes greater than or equal +// to the low byte lane and less than or equal to the high byte lane. +pub fn lane_mask< + DATA_W_DIV8: u32, + LANE_W: u32 = {std::clog2(DATA_W_DIV8)}, + ITER_W: u32 = {std::clog2(DATA_W_DIV8) + u32:1} +>(low_lane: uN[LANE_W], high_lane: uN[LANE_W]) -> uN[DATA_W_DIV8] { + + type Iter = uN[ITER_W]; + const ITER_MAX = Iter:1 << LANE_W; + + type Mask = uN[DATA_W_DIV8]; + + let low_mask = for (i, mask) in Iter:0..ITER_MAX { + if i >= low_lane as Iter { + mask | Mask:0x1 << i + } else { mask } + }(uN[DATA_W_DIV8]:0); + + let high_mask = for (i, mask) in Iter:0..ITER_MAX { + if i <= high_lane as Iter { + mask | Mask:0x1 << i + } else { mask } + }(uN[DATA_W_DIV8]:0); + + low_mask & high_mask +} + +#[test] +fn test_lane_mask() { + const DATA_W_DIV8 = u32:32/u32:8; + const LANE_W = std::clog2(DATA_W_DIV8); + + type Mask = uN[DATA_W_DIV8]; + type Lane = uN[LANE_W]; + + assert_eq(lane_mask(Lane:0, Lane:0), Mask:0b0001); + assert_eq(lane_mask(Lane:0, Lane:1), Mask:0b0011); + assert_eq(lane_mask(Lane:0, Lane:2), Mask:0b0111); + assert_eq(lane_mask(Lane:0, Lane:3), Mask:0b1111); + + assert_eq(lane_mask(Lane:1, Lane:0), Mask:0b0000); + assert_eq(lane_mask(Lane:1, Lane:1), Mask:0b0010); + assert_eq(lane_mask(Lane:1, Lane:2), Mask:0b0110); + assert_eq(lane_mask(Lane:1, Lane:3), Mask:0b1110); + + assert_eq(lane_mask(Lane:2, Lane:0), Mask:0b0000); + assert_eq(lane_mask(Lane:2, Lane:1), Mask:0b0000); + assert_eq(lane_mask(Lane:2, Lane:2), Mask:0b0100); + assert_eq(lane_mask(Lane:2, Lane:3), Mask:0b1100); + + assert_eq(lane_mask(Lane:3, Lane:0), Mask:0b0000); + assert_eq(lane_mask(Lane:3, Lane:1), Mask:0b0000); + assert_eq(lane_mask(Lane:3, Lane:2), Mask:0b0000); + assert_eq(lane_mask(Lane:3, Lane:3), Mask:0b1000); +} + +// Returns the number of bits remaining to 4kB +pub fn bytes_to_4k_boundary(addr: uN[ADDR_W]) -> uN[ADDR_W] { + const AXI_4K_BOUNDARY = uN[ADDR_W]:0x1000; + AXI_4K_BOUNDARY - std::mod_pow2(addr, AXI_4K_BOUNDARY) +} + +#[test] +fn test_bytes_to_4k_boundary() { + assert_eq(bytes_to_4k_boundary(u32:0x0), u32:0x1000); + assert_eq(bytes_to_4k_boundary(u32:0x1), u32:0xFFF); + assert_eq(bytes_to_4k_boundary(u32:0xFFF), u32:0x1); + assert_eq(bytes_to_4k_boundary(u32:0x1000), u32:0x1000); + assert_eq(bytes_to_4k_boundary(u32:0x1001), u32:0xFFF); + assert_eq(bytes_to_4k_boundary(u32:0x1FFF), u32:0x1); +} + +// Returns the AxSIZE value for the given bus width. +// Assumes that whole bus width will be used to siplify AxiReader proc +pub fn axsize() -> axi::AxiAxSize { + match (DATA_W_DIV8) { + u32:1 => axi::AxiAxSize::MAX_1B_TRANSFER, + u32:2 => axi::AxiAxSize::MAX_2B_TRANSFER, + u32:4 => axi::AxiAxSize::MAX_4B_TRANSFER, + u32:8 => axi::AxiAxSize::MAX_8B_TRANSFER, + u32:16 => axi::AxiAxSize::MAX_16B_TRANSFER, + u32:32 => axi::AxiAxSize::MAX_32B_TRANSFER, + u32:64 => axi::AxiAxSize::MAX_64B_TRANSFER, + _ => axi::AxiAxSize::MAX_128B_TRANSFER, + } +} diff --git a/xls/modules/zstd/memory/mem_reader.x b/xls/modules/zstd/memory/mem_reader.x new file mode 100644 index 0000000000..ea96264728 --- /dev/null +++ b/xls/modules/zstd/memory/mem_reader.x @@ -0,0 +1,732 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; + +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_st; + +import xls.modules.zstd.memory.axi_reader; +import xls.modules.zstd.memory.axi_stream_downscaler; +import xls.modules.zstd.memory.axi_stream_remove_empty; + + +// This module provides the MemReader and MemReaderAdv procs for handling +// read transactions on the AXI bus. Both readers spawn helper components +// that simplifies interactions with them for other procs. +// Use MemReader when the data width on both the DSLX and AXI sides +// is the same. For cases where the AXI width is a multiple of the DSLX width, +// use MemReaderAdv. Other configurations are not supported. + +// Enum containing information about the status of the response +pub enum MemReaderStatus : u1 { + OKAY = 0, + ERROR = 1, +} + +// Request that can be submited to MemReader to read data from an AXI bus +pub struct MemReaderReq { + addr: uN[DSLX_ADDR_W], // + length: uN[DSLX_ADDR_W] // +} + +// Response received rom the MemReader proc +pub struct MemReaderResp { + status: MemReaderStatus, // status of the request + data: uN[DSLX_DATA_W], // data read from the AXI bus + length: uN[DSLX_ADDR_W], // length of the data in bytes + last: bool, // if this is the last packet to expect as a response +} + +enum MemReaderFsm : u3 { + REQUEST = 0, + RESPONSE = 1, + RESPONSE_ZERO = 2, + RESPONSE_ERROR = 3, +} + +struct MemReaderState { + fsm: MemReaderFsm, + error: bool, + base: uN[AXI_ADDR_W], +} + +// A proc implementing the logic for issuing requests to AxiReader, +// receiving the data, and convering the data to the specified output format. +proc MemReaderInternal< + // DSLX side parameters + DSLX_DATA_W: u32, DSLX_ADDR_W: u32, + // AXI side parameters + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, + // parameters calculated from other values + DSLX_DATA_W_DIV8: u32 = {DSLX_DATA_W / u32:8}, + AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, + AXI_TO_DSLX_RATIO: u32 = {AXI_DATA_W / DSLX_DATA_W}, + AXI_TO_DSLX_RATIO_W: u32 = {std::clog2((AXI_DATA_W / DSLX_DATA_W) + u32:1)} +> { + type Req = MemReaderReq; + type Resp = MemReaderResp; + type Length = uN[DSLX_ADDR_W]; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiReaderError = axi_reader::AxiReaderError; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStreamInput = axi_st::AxiStream; + type AxiStreamOutput = axi_st::AxiStream; + + type State = MemReaderState; + type Fsm = MemReaderFsm; + + // Assumptions related to parameters + const_assert!(DSLX_DATA_W % u32:8 == u32:0); // DSLX-side data width should be divisible by 8 + const_assert!(AXI_DATA_W % u32:8 == u32:0); // AXI-side data width should be divisible by 8 + const_assert!(AXI_DATA_W >= DSLX_DATA_W); // AXI-side width should be wider or has the same width as DSLX-side + const_assert!(AXI_DATA_W % DSLX_DATA_W == u32:0); // DSLX-side width should be a multiple of AXI-side width + + // checks for parameters + const_assert!(DSLX_DATA_W_DIV8 == DSLX_DATA_W / u32:8); + const_assert!(AXI_DATA_W_DIV8 == AXI_DATA_W / u32:8); + const_assert!(AXI_TO_DSLX_RATIO == AXI_DATA_W / DSLX_DATA_W); + const_assert!(AXI_TO_DSLX_RATIO_W == std::clog2((AXI_DATA_W / DSLX_DATA_W) + u32:1)); + + req_r: chan in; + resp_s: chan out; + + reader_req_s: chan out; + reader_err_r: chan in; + + axi_st_out_r: chan in; + + config( + req_r: chan in, + resp_s: chan out, + reader_req_s: chan out, + reader_err_r: chan in, + axi_st_out_r: chan in, + ) { + (req_r, resp_s, reader_req_s, reader_err_r, axi_st_out_r) + } + + init { zero!() } + + next(state: State) { + type Resp = MemReaderResp; + type DslxData = uN[DSLX_DATA_W]; + type DslxLength = uN[DSLX_ADDR_W]; + type AxiLength = uN[AXI_ADDR_W]; + type AxiStr = uN[AXI_DATA_W_DIV8]; + type AxiKeep = uN[AXI_DATA_W_DIV8]; + type Status = MemReaderStatus; + + const READER_RESP_ERROR = Resp { status: Status::ERROR, ..zero!() }; + const READER_RESP_ZERO = Resp { status: Status::OKAY, last: true, ..zero!() }; + + let tok0 = join(); + let (tok, error_info, error) = + recv_non_blocking(tok0, reader_err_r, zero!()); + + // Request + let do_handle_req = !error && (state.fsm == Fsm::REQUEST); + let (tok, req) = recv_if(tok0, req_r, do_handle_req, zero!()); + let is_zero_len = (req.length == uN[DSLX_ADDR_W]:0); + + let reader_req = axi_reader::AxiReaderReq { + addr: req.addr, + len: req.length + }; + let do_send_reader_req = !error && !is_zero_len && (state.fsm == Fsm::REQUEST); + let tok = send_if(tok0, reader_req_s, do_send_reader_req, reader_req); + + let do_handle_resp = !error && (state.fsm == Fsm::RESPONSE); + let (tok, st) = recv_if(tok0, axi_st_out_r, do_handle_resp, zero!()); + + let length = std::popcount(st.str | st.keep) as Length; + let reader_resp_ok = Resp { status: Status::OKAY, data: st.data, length, last: st.last }; + + let reader_resp = if state.fsm == Fsm::RESPONSE_ERROR { + READER_RESP_ERROR + } else if state.fsm == Fsm::RESPONSE_ZERO { + READER_RESP_ZERO + } else { + reader_resp_ok + }; + + let do_send_resp = do_handle_resp || + (state.fsm == Fsm::RESPONSE_ERROR) || + (state.fsm == Fsm::RESPONSE_ZERO); + let tok = send_if(tok0, resp_s, do_send_resp, reader_resp); + + let next_state = match (state.fsm) { + Fsm::REQUEST => { + if error { + State { fsm: Fsm::RESPONSE_ERROR, ..zero!() } + } else if is_zero_len { + State { fsm: Fsm::RESPONSE_ZERO, ..state } + } else { + State { fsm: Fsm::RESPONSE, ..state } + } + }, + Fsm::RESPONSE => { + if error { + State { fsm: Fsm::RESPONSE_ERROR, ..zero!() } + } else if st.last { + State { fsm: Fsm::REQUEST, ..state } + } else { + State { fsm: Fsm::RESPONSE, ..state } + } + }, + Fsm::RESPONSE_ZERO => { + if error { + State { fsm: Fsm::RESPONSE_ERROR, ..zero!() } + } else { + State { fsm: Fsm::REQUEST, ..state } + } + }, + Fsm::RESPONSE_ERROR => { + if error { + State { fsm: Fsm::RESPONSE_ERROR, ..zero!() } + } else { + State { fsm: Fsm::REQUEST, ..state } + } + }, + _ => { + fail!("invalid_state", false); + state + }, + }; + + next_state + } +} + +// A proc that integrates other procs to create a functional design for +// performing AXI read transactions. It allows for connecting narrow DSLX-side +// with wider AXI-side, if the wider side has to be a multiple of the narrower side. +pub proc MemReaderAdv< + // DSLX side parameters + DSLX_DATA_W: u32, DSLX_ADDR_W: u32, + // AXI side parameters + AXI_DATA_W: u32, AXI_ADDR_W: u32, AXI_DEST_W: u32, AXI_ID_W: u32, + // parameters calculated from other values + DSLX_DATA_W_DIV8: u32 = {DSLX_DATA_W / u32:8}, + AXI_DATA_W_DIV8: u32 = {AXI_DATA_W / u32:8}, + AXI_TO_DSLX_RATIO: u32 = {AXI_DATA_W / DSLX_DATA_W}, + AXI_TO_DSLX_RATIO_W: u32 = {std::clog2((AXI_DATA_W / DSLX_DATA_W) + u32:1)} +> { + type Req = MemReaderReq; + type Resp = MemReaderResp; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + type AxiStreamInput = axi_st::AxiStream; + type AxiStreamOutput = axi_st::AxiStream; + type AxiReaderError = axi_reader::AxiReaderError; + + config( + req_r: chan in, + resp_s: chan out, + axi_ar_s: chan out, + axi_r_r: chan in + ) { + let (reader_req_s, reader_req_r) = chan("reader_req"); + let (reader_err_s, reader_err_r) = chan("reader_err"); + + let (axi_st_in_s, axi_st_in_r) = chan("axi_st_in"); + let (axi_st_remove_s, axi_st_remove_r) = chan("axi_st_remove"); + let (axi_st_out_s, axi_st_out_r) = chan("axi_st_out"); + + spawn MemReaderInternal< + DSLX_DATA_W, DSLX_ADDR_W, + AXI_DATA_W, AXI_ADDR_W, AXI_DEST_W, AXI_ID_W, + >(req_r, resp_s, reader_req_s, reader_err_r, axi_st_out_r); + + spawn axi_reader::AxiReader< + AXI_ADDR_W, AXI_DATA_W, AXI_DEST_W, AXI_ID_W + >(reader_req_r, axi_ar_s, axi_r_r, axi_st_in_s, reader_err_s); + + spawn axi_stream_downscaler::AxiStreamDownscaler< + AXI_DATA_W, DSLX_DATA_W, AXI_DEST_W, AXI_ID_W + >(axi_st_in_r, axi_st_remove_s); + + spawn axi_stream_remove_empty::AxiStreamRemoveEmpty< + DSLX_DATA_W, AXI_DEST_W, AXI_ID_W + >(axi_st_remove_r, axi_st_out_s); + + () + } + + init { } + next(state: ()) { } +} + +// A proc that integrates other procs to create a functional design for +// performing AXI read transactions. The proc allows for interfacing with +// AXI bus that has the same data width as DSLX-side of the design. +pub proc MemReader< + DATA_W: u32, ADDR_W: u32, DEST_W: u32, ID_W: u32, + CHANNEL_DEPTH: u32 = {u32:0}, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, +> { + type Req = MemReaderReq; + type Resp = MemReaderResp; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiR = axi::AxiR; + type AxiAr = axi::AxiAr; + type AxiStreamInput = axi_st::AxiStream; + type AxiStreamOutput = axi_st::AxiStream; + type AxiReaderError = axi_reader::AxiReaderError; + + config( + req_r: chan in, + resp_s: chan out, + axi_ar_s: chan out, + axi_r_r: chan in + ) { + let (reader_req_s, reader_req_r) = chan("reader_req"); + let (reader_err_s, reader_err_r) = chan("reader_err"); + + let (axi_st_in_s, axi_st_in_r) = chan("axi_st_in"); + let (axi_st_out_s, axi_st_out_r) = chan("axi_st_out"); + + spawn MemReaderInternal< + DATA_W, ADDR_W, DATA_W, ADDR_W, DEST_W, ID_W + >(req_r, resp_s, reader_req_s, reader_err_r, axi_st_out_r); + + spawn axi_reader::AxiReader< + ADDR_W, DATA_W, DEST_W, ID_W + >(reader_req_r, axi_ar_s, axi_r_r, axi_st_in_s, reader_err_s); + + spawn axi_stream_remove_empty::AxiStreamRemoveEmpty< + DATA_W, DEST_W, ID_W + >(axi_st_in_r, axi_st_out_s); + () + } + + init { } + next(state: ()) { } +} + + +const INST_ADV_AXI_DATA_W = u32:128; +const INST_ADV_AXI_ADDR_W = u32:16; +const INST_ADV_AXI_DEST_W = u32:8; +const INST_ADV_AXI_ID_W = u32:8; +const INST_ADV_AXI_DATA_W_DIV8 = INST_ADV_AXI_DATA_W / u32:8; + +const INST_ADV_DSLX_ADDR_W = u32:16; +const INST_ADV_DSLX_DATA_W = u32:64; +const INST_ADV_DSLX_DATA_W_DIV8 = INST_ADV_DSLX_DATA_W / u32:8; + +proc MemReaderAdvInst { + type Req = MemReaderReq; + type Resp = MemReaderResp; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + + type State = MemReaderState; + type Fsm = MemReaderFsm; + + config( + req_r: chan in, + resp_s: chan out, + axi_ar_s: chan out, + axi_r_r: chan in + ) { + spawn MemReaderAdv< + INST_ADV_DSLX_DATA_W, INST_ADV_DSLX_ADDR_W, + INST_ADV_AXI_DATA_W, INST_ADV_AXI_ADDR_W, INST_ADV_AXI_DEST_W, INST_ADV_AXI_ID_W, + >(req_r, resp_s, axi_ar_s, axi_r_r); + + () + } + + init { } + next(state: ()) { } +} + +proc MemReaderInternalInst { + type Req = MemReaderReq; + type Resp = MemReaderResp; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiReaderError = axi_reader::AxiReaderError; + + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStreamOutput = axi_st::AxiStream; + + config( + req_r: chan in, + resp_s: chan out, + reader_req_s: chan out, + reader_err_r: chan in, + axi_st_out_r: chan in + ) { + spawn MemReaderInternal< + INST_ADV_DSLX_DATA_W, INST_ADV_DSLX_ADDR_W, + INST_ADV_AXI_DATA_W, INST_ADV_AXI_ADDR_W, INST_ADV_AXI_DEST_W, INST_ADV_AXI_ID_W, + >(req_r, resp_s, reader_req_s, reader_err_r, axi_st_out_r); + () + } + + init { } + next(state: ()) { } +} + +const INST_DATA_W = u32:64; +const INST_ADDR_W = u32:16; +const INST_DEST_W = u32:8; +const INST_ID_W = u32:8; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; + + +proc MemReaderInst { + type Req = MemReaderReq; + type Resp = MemReaderResp; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + + type State = MemReaderState; + type Fsm = MemReaderFsm; + + config( + req_r: chan in, + resp_s: chan out, + axi_ar_s: chan out, + axi_r_r: chan in + ) { + spawn MemReader< + INST_DATA_W, INST_ADDR_W, INST_DEST_W, INST_ID_W, + >(req_r, resp_s, axi_ar_s, axi_r_r); + + () + } + + init { } + next(state: ()) { } +} + +const TEST_AXI_DATA_W = u32:128; +const TEST_AXI_ADDR_W = u32:16; +const TEST_AXI_DEST_W = u32:8; +const TEST_AXI_ID_W = u32:8; +const TEST_AXI_DATA_W_DIV8 = TEST_AXI_DATA_W / u32:8; + +const TEST_DSLX_ADDR_W = u32:16; +const TEST_DSLX_DATA_W = u32:64; +const TEST_DSLX_DATA_W_DIV8 = TEST_DSLX_DATA_W / u32:8; + +#[test_proc] +proc MemReaderTest { + type Req = MemReaderReq; + type Resp = MemReaderResp; + type Fsm = MemReaderFsm; + + type AxiReaderReq = axi_reader::AxiReaderReq; + type AxiAr = axi::AxiAr; + type AxiR = axi::AxiR; + type AxiStream = axi_st::AxiStream; + + type Addr = uN[TEST_AXI_ADDR_W]; + type Length = uN[TEST_DSLX_ADDR_W]; + type Data = uN[TEST_DSLX_DATA_W]; + + type AxiAddr = uN[TEST_AXI_ADDR_W]; + type AxiBurst = axi::AxiAxBurst; + type AxiCache = axi::AxiArCache; + type AxiData = uN[TEST_AXI_DATA_W]; + type AxiId = uN[TEST_AXI_ID_W]; + type AxiLast = bool; + type AxiLength = uN[8]; + type AxiProt = uN[3]; + type AxiQos = uN[4]; + type AxiRegion = uN[4]; + type AxiResp = axi::AxiReadResp; + type AxiSize = axi::AxiAxSize; + + type Status = MemReaderStatus; + + terminator: chan out; + req_s: chan out; + resp_r: chan in; + axi_ar_r: chan in; + axi_r_s: chan out; + + config(terminator: chan out) { + let (req_s, req_r) = chan("req"); + let (resp_s, resp_r) = chan("resp"); + let (axi_ar_s, axi_ar_r) = chan("axi_ar"); + let (axi_r_s, axi_r_r) = chan("axi_r"); + + spawn MemReaderAdv< + TEST_DSLX_DATA_W, TEST_DSLX_ADDR_W, + TEST_AXI_DATA_W, TEST_AXI_ADDR_W, TEST_AXI_DEST_W, TEST_AXI_ID_W, + >(req_r, resp_s, axi_ar_s, axi_r_r); + (terminator, req_s, resp_r, axi_ar_r, axi_r_s) + } + + init { } + + next(state: ()) { + let tok = join(); + + // empty transfers, should be just confirmed internaly + let tok = send(tok, req_s, Req { addr: Addr:0x1100, length: Length:0x0 }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0, + length: Length:0, + last: true + }); + + // aligned transfer shorter than full AXI-side, + // that fits one DSLX-side width + + let tok = send(tok, req_s, Req { addr: Addr:0x1100, length: Length:0x1 }); + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0x1100, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0xFF, + length: Length:1, + last: true + }); + + // unaligned transfer shorter than full AXI-side, + // that fits one DSLX-side width + + let tok = send(tok, req_s, Req { addr: Addr:0x1001, length: Length:0x1 }); + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0xEE, + length: Length:1, + last: true + }); + + // unaligned transfer shorter than full AXI-side, + // that fits one DSLX-side width and crosess 4k boundary + + let tok = send(tok, req_s, Req { addr: Addr:0xFFF, length: Length:0x2 }); + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0xFF0, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0x11FF, + length: Length:2, + last: true + }); + + + let tok = send(tok, req_s, Req { addr: Addr:0xFFF, length: Length:0x10 }); + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0xFF0, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLength:0x0, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0x00AA_BBCC_DDEE_FF11, + length: Length:8, + last: false + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0x2233_4455_6677_8899, + length: Length:8, + last: true + }); + + let tok = send(tok, req_s, Req { addr: Addr:0x1001, length: Length:17 }); + let (tok, ar) = recv(tok, axi_ar_r); + assert_eq(ar, AxiAr { + id: AxiId:0x0, + addr: AxiAddr:0x1000, + region: AxiRegion:0x0, + len: AxiLength:0x1, + size: AxiSize::MAX_16B_TRANSFER, + burst: AxiBurst::INCR, + cache: AxiCache::DEV_NO_BUF, + prot: AxiProt:0x0, + qos: AxiQos:0x0 + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:false + }); + + let tok = send(tok, axi_r_s, AxiR { + id: AxiId:0x0, + data: AxiData:0x1122_3344_5566_7788_9900_AABB_CCDD_EEFF, + resp: AxiResp::OKAY, + last: AxiLast:true + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0x8899_00AA_BBCC_DDEE, + length: Length:8, + last: false + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0xFF11_2233_4455_6677, + length: Length:8, + last: false + }); + + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, Resp { + status: Status::OKAY, + data: Data:0xEE, + length: Length:1, + last: true + }); + + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/memory/mem_writer.x b/xls/modules/zstd/memory/mem_writer.x new file mode 100644 index 0000000000..277c9910ef --- /dev/null +++ b/xls/modules/zstd/memory/mem_writer.x @@ -0,0 +1,636 @@ +// Copyright 2024 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// Memory Writer +// +// This module implements the communication with memory through the AXI interface +// that facilitates the write operations to the memory. +// +// Memory Writer proc is configured with a base address used for calculating +// addresses for pending write requests. +// Write Requests consist of the offset from the base address and the length +// of the data to write in bytes. +// Data to write is received through generic DSLX data packets which are then +// formed into AxiStream frames and passed along with the write request to +// the underlying procs. +// The first proc in the data path is the AxiStreamAddEmpty that prepares +// data to write for the next proc which is the AxiWriter. +// Axi writer takes the write request from MemWriter and data stream from AxiStreamAddEmpty +// and forms valid AXI write transactions. + +import std; + +import xls.modules.zstd.memory.axi; +import xls.modules.zstd.memory.axi_st; +import xls.modules.zstd.memory.common; +import xls.modules.zstd.memory.axi_writer; +import xls.modules.zstd.memory.axi_stream_add_empty; + +pub struct MemWriterReq { + addr: uN[ADDR_W], + length: uN[ADDR_W], +} + +pub struct MemWriterDataPacket { + data: uN[DATA_W], + length: uN[ADDR_W], // Expressed in bytes + last: bool, +} + +enum MemWriterFsm : u2 { + RECV_REQ = 0, + SEND_WRITE_REQ = 1, + RECV_DATA = 2, + SEND_DATA = 3, +} + +struct MemWriterState< + ADDR_W: u32, + DATA_W: u32, + DEST_W: u32, + ID_W: u32, + DATA_W_DIV8: u32 +> { + fsm: MemWriterFsm, + req_len: sN[ADDR_W], + axi_writer_req: axi_writer::AxiWriterRequest, +} + +proc MemWriter< + ADDR_W: u32, DATA_W: u32, DEST_W: u32, ID_W: u32, WRITER_ID: u32, + DATA_W_DIV8: u32 = {DATA_W / u32:8}, + DATA_W_LOG2: u32 = {std::clog2(DATA_W / u32:8)} +> { + type Req = MemWriterReq; + type Data = MemWriterDataPacket; + type AxiWriterReq = axi_writer::AxiWriterRequest; + type AxiWriterResp = axi_writer::AxiWriterResp; + type PaddingReq = axi_writer::AxiWriterRequest; + type AxiStream = axi_st::AxiStream; + type AxiAW = axi::AxiAw; + type AxiW = axi::AxiW; + type AxiB = axi::AxiB; + type State = MemWriterState; + type Fsm = MemWriterFsm; + + type Length = uN[ADDR_W]; + type sLength = sN[ADDR_W]; + type Strobe = uN[DATA_W_DIV8]; + type Id = uN[ID_W]; + type Dest = uN[DEST_W]; + + req_in_r: chan in; + data_in_r: chan in; + axi_writer_req_s: chan out; + padding_req_s: chan out; + axi_st_raw_s: chan out; + resp_s: chan out; + + config( + req_in_r: chan in, + data_in_r: chan in, + axi_aw_s: chan out, + axi_w_s: chan out, + axi_b_r: chan in, + resp_s: chan out, + ) { + let (axi_writer_req_s, axi_writer_req_r) = chan("axi_writer_req"); + let (padding_req_s, padding_req_r) = chan("padding_req"); + let (axi_st_raw_s, axi_st_raw_r) = chan("axi_st_raw"); + let (axi_st_padded_s, axi_st_padded_r) = chan("axi_st_padded"); + + spawn axi_stream_add_empty::AxiStreamAddEmpty< + DATA_W, DEST_W, ID_W, ADDR_W + >(padding_req_r, axi_st_raw_r, axi_st_padded_s); + spawn axi_writer::AxiWriter< + ADDR_W, DATA_W, DEST_W, ID_W + >(axi_writer_req_r, resp_s, axi_aw_s, axi_w_s, axi_b_r, axi_st_padded_r); + + (req_in_r, data_in_r, axi_writer_req_s, padding_req_s, axi_st_raw_s, resp_s) + } + + init { zero!() } + + next(state: State) { + let tok_0 = join(); + let (tok_2, req_in) = recv_if(tok_0, req_in_r, state.fsm == Fsm::RECV_REQ, zero!()); + let tok_3 = send_if(tok_0, axi_writer_req_s, state.fsm == Fsm::SEND_WRITE_REQ, state.axi_writer_req); + let tok_4 = send_if(tok_3, padding_req_s, state.fsm == Fsm::SEND_WRITE_REQ, state.axi_writer_req); + let (tok_5, data_in) = recv_if(tok_0, data_in_r, state.fsm == Fsm::SEND_DATA, zero!()); + + let next_state = match(state.fsm) { + Fsm::RECV_REQ => { + State { + fsm: Fsm::SEND_WRITE_REQ, + req_len: req_in.length as sLength, + axi_writer_req: AxiWriterReq { + address: req_in.addr, + length: req_in.length + }, + } + }, + Fsm::SEND_WRITE_REQ => { + State { + fsm: Fsm::SEND_DATA, + ..state + } + }, + Fsm::SEND_DATA => { + let next_req_len = state.req_len - sLength:4; + State { + fsm: if (next_req_len <= sLength:0) {Fsm::RECV_REQ} else {Fsm::SEND_DATA}, + req_len: next_req_len, + ..state + } + }, + _ => { + assert!(false, "Invalid state"); + state + } + }; + + let raw_axi_st_frame = match(state.fsm) { + Fsm::SEND_DATA => { + let next_req_len = state.req_len - sLength:4; + let str_keep = ((Length:1 << data_in.length) - Length:1) as Strobe; + AxiStream { + data: data_in.data, + str: str_keep, + keep: str_keep, + last: (next_req_len <= sLength:0), + id: WRITER_ID as Id, + dest: WRITER_ID as Dest + } + }, + _ => { + zero!() + } + }; + + let tok_6 = send_if(tok_5, axi_st_raw_s, state.fsm == Fsm::SEND_DATA, raw_axi_st_frame); + + next_state + } +} + +const INST_ADDR_W = u32:16; +const INST_DATA_W = u32:32; +const INST_DATA_W_DIV8 = INST_DATA_W / u32:8; +const INST_DEST_W = INST_DATA_W / u32:8; +const INST_ID_W = INST_DATA_W / u32:8; +const INST_DATA_W_LOG2 = u32:6; +const INST_WRITER_ID = u32:2; + +proc MemWriterInst { + type InstReq = MemWriterReq; + type InstData = MemWriterDataPacket; + type InstAxiStream = axi_st::AxiStream; + type InstAxiAW = axi::AxiAw; + type InstAxiW = axi::AxiW; + type InstAxiB = axi::AxiB; + type InstAxiWriterResp = axi_writer::AxiWriterResp; + + config( + req_in_r: chan in, + data_in_r: chan in, + axi_aw_s: chan out, + axi_w_s: chan out, + axi_b_r: chan in, + resp_s: chan out + ) { + spawn MemWriter< + INST_ADDR_W, INST_DATA_W, INST_DEST_W, INST_ID_W, INST_WRITER_ID + >(req_in_r, data_in_r, axi_aw_s, axi_w_s, axi_b_r, resp_s); + () + } + + init { () } + + next(state: ()) { } +} + +const TEST_ADDR_W = u32:16; +const TEST_DATA_W = u32:32; +const TEST_DATA_W_DIV8 = TEST_DATA_W / u32:8; +const TEST_DEST_W = TEST_DATA_W / u32:8; +const TEST_ID_W = TEST_DATA_W / u32:8; +const TEST_DATA_W_LOG2 = u32:6; +const TEST_WRITER_ID = u32:2; + +type TestReq = MemWriterReq; +type TestData = MemWriterDataPacket; +type TestAxiWriterResp = axi_writer::AxiWriterResp; +type TestAxiWriterRespStatus = axi_writer::AxiWriterRespStatus; +type TestAxiStream = axi_st::AxiStream; +type TestAxiAW = axi::AxiAw; +type TestAxiW = axi::AxiW; +type TestAxiB = axi::AxiB; +type TestAxiWriteResp = axi::AxiWriteResp; +type TestAxiAxBurst = axi::AxiAxBurst; +type TestAxiAxSize = axi::AxiAxSize; + +type TestAddr = uN[TEST_ADDR_W]; +type TestLength = uN[TEST_ADDR_W]; +type TestDataBits = uN[TEST_DATA_W]; +type TestStrobe = uN[TEST_DATA_W_DIV8]; +type TestId = uN[TEST_ID_W]; +type TestDest = uN[TEST_DEST_W]; + +#[test_proc] +proc MemWriterTest { + terminator: chan out; + req_in_s: chan out; + data_in_s: chan out; + axi_aw_r: chan in; + axi_w_r: chan in; + axi_b_s: chan out; + resp_r: chan in; + + config( + terminator: chan out, + ) { + let (req_in_s, req_in_r) = chan("req_in"); + let (data_in_s, data_in_r) = chan("data_in"); + let (axi_aw_s, axi_aw_r) = chan("axi_aw"); + let (axi_w_s, axi_w_r) = chan("axi_w"); + let (axi_b_s, axi_b_r) = chan("axi_b"); + let (resp_s, resp_r) = chan("resp"); + spawn MemWriter< + TEST_ADDR_W, TEST_DATA_W, TEST_DEST_W, TEST_ID_W, TEST_WRITER_ID + >(req_in_r, data_in_r, axi_aw_s, axi_w_s, axi_b_r, resp_s); + (terminator, req_in_s, data_in_s, axi_aw_r, axi_w_r, axi_b_s, resp_r) + } + + init { () } + + next(state: ()) { + let tok = join(); + + // Aligned single transfer + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x0, + length: TestLength:4 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x44332211, + length: TestLength:4, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:1, + addr: TestAddr:0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44332211, + strb: TestStrobe:0xF, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:1, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned single transfer + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x10, + length: TestLength:1 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00000055, + length: TestLength:1, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:2, + addr: TestAddr:0x10, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00000055, + strb: TestStrobe:0x1, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:2, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned single transfer + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x24, + length: TestLength:2 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00007766, + length: TestLength:2, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:3, + addr: TestAddr:0x24, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00007766, + strb: TestStrobe:0x3, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:3, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned single transfer + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x38, + length: TestLength:3 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00AA9988, + length: TestLength:3, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:4, + addr: TestAddr:0x38, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00AA9988, + strb: TestStrobe:0x7, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:4, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned single transfer + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x71, + length: TestLength:1 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00000088, + length: TestLength:1, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:5, + addr: TestAddr:0x70, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00008800, + strb: TestStrobe:0x2, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:5, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unaligned 2 transfers + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0xf3, + length: TestLength:3 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00112233, + length: TestLength:3, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:6, + addr: TestAddr:0xf0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:1, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x33000000, + strb: TestStrobe:0x8, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00001122, + strb: TestStrobe:0x3, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:6, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Unligned 3 transfers + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x1f3, + length: TestLength:7 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x11223344, + length: TestLength:4, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00556677, + length: TestLength:3, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:7, + addr: TestAddr:0x1f0, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:2, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44000000, + strb: TestStrobe:0x8, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x77112233, + strb: TestStrobe:0xF, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00005566, + strb: TestStrobe:0x3, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:7, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Crossing AXI 4kB boundary, aligned 2 burst transfers + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x0FFC, + length: TestLength:8 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x44332211, + length: TestLength:4, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x88776655, + length: TestLength:4, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:8, + addr: TestAddr:0xFFC, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x44332211, + strb: TestStrobe:0b1111, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:8, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:9, + addr: TestAddr:0x1000, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x88776655, + strb: TestStrobe:0b1111, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:9, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + // Crossing AXI 4kB boundary, unaligned 2 burst transfers + let tok = send(tok, req_in_s, TestReq { + addr: TestAddr:0x1FFF, + length: TestLength:7 + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x44332211, + length: TestLength:4, + last: false, + }); + let tok = send(tok, data_in_s, TestData { + data: TestDataBits:0x00776655, + length: TestLength:3, + last: true, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:10, + addr: TestAddr:0x1FFC, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:0, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x11000000, + strb: TestStrobe:0b1000, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:7, + }); + let (tok, aw) = recv(tok, axi_aw_r); + assert_eq(aw, TestAxiAW { + id: TestId:11, + addr: TestAddr:0x2000, + size: TestAxiAxSize::MAX_4B_TRANSFER, + len: u8:1, + burst: TestAxiAxBurst::INCR, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x55443322, + strb: TestStrobe:0b1111, + last: false, + }); + let (tok, w) = recv(tok, axi_w_r); + assert_eq(w, TestAxiW { + data: TestDataBits:0x00007766, + strb: TestStrobe:0b0011, + last: true, + }); + let tok = send(tok, axi_b_s, TestAxiB { + resp: TestAxiWriteResp::OKAY, + id: TestId:11, + }); + let (tok, resp) = recv(tok, resp_r); + assert_eq(resp, TestAxiWriterResp{status: TestAxiWriterRespStatus::OKAY}); + + send(tok, terminator, true); + } +}