From a32ee3909f076cb0b914aa3ce1b3bd2f27278d7e Mon Sep 17 00:00:00 2001 From: Michal Czyz Date: Thu, 14 Mar 2024 13:27:08 +0100 Subject: [PATCH] DSLX DMA: Implementation Signed-off-by: Michal Czyz --- .github/workflows/xls-modules-dma.json | 37 + .github/workflows/xls-modules-dma.yml | 230 +++++ xls/modules/dma/BUILD | 623 ++++++++++++ xls/modules/dma/README.md | 411 ++++++++ xls/modules/dma/address_generator.x | 279 ++++++ xls/modules/dma/axi_csr.x | 266 +++++ xls/modules/dma/bus/axi_pkg.x | 198 ++++ xls/modules/dma/bus/axi_st_pkg.x | 48 + xls/modules/dma/common.x | 37 + xls/modules/dma/config.x | 48 + xls/modules/dma/csr.x | 1233 ++++++++++++++++++++++++ xls/modules/dma/fifo.x | 478 +++++++++ xls/modules/dma/frontend_reader.x | 255 +++++ xls/modules/dma/frontend_writer.x | 300 ++++++ xls/modules/dma/gpf.x | 169 ++++ xls/modules/dma/main_controller.x | 711 ++++++++++++++ 16 files changed, 5323 insertions(+) create mode 100644 .github/workflows/xls-modules-dma.json create mode 100644 .github/workflows/xls-modules-dma.yml create mode 100644 xls/modules/dma/BUILD create mode 100644 xls/modules/dma/README.md create mode 100644 xls/modules/dma/address_generator.x create mode 100644 xls/modules/dma/axi_csr.x create mode 100644 xls/modules/dma/bus/axi_pkg.x create mode 100644 xls/modules/dma/bus/axi_st_pkg.x create mode 100644 xls/modules/dma/common.x create mode 100644 xls/modules/dma/config.x create mode 100644 xls/modules/dma/csr.x create mode 100644 xls/modules/dma/fifo.x create mode 100644 xls/modules/dma/frontend_reader.x create mode 100644 xls/modules/dma/frontend_writer.x create mode 100644 xls/modules/dma/gpf.x create mode 100644 xls/modules/dma/main_controller.x diff --git a/.github/workflows/xls-modules-dma.json b/.github/workflows/xls-modules-dma.json new file mode 100644 index 0000000000..5aeadd9712 --- /dev/null +++ b/.github/workflows/xls-modules-dma.json @@ -0,0 +1,37 @@ +{ + "dma": { + "name": "//xls/modules/dma", + "rules": [ + { + "ir": "csr_opt_ir_benchmark", + "verilog": "verilog_csr", + "synthesis": "csr_benchmark_synth", + "pnr": "csr_place_and_route" + }, + { + "ir": "axi_csr_opt_ir_benchmark", + "verilog": "verilog_axi_csr", + "synthesis": "axi_csr_benchmark_synth", + "pnr": "axi_csr_place_and_route" + }, + { + "ir": "address_generator_opt_ir_benchmark", + "verilog": "verilog_address_generator", + "synthesis": "address_generator_benchmark_synth", + "pnr": "address_generator_place_and_route" + }, + { + "ir": "frontend_reader_opt_ir_benchmark", + "verilog": "verilog_frontend_reader", + "synthesis": "frontend_reader_benchmark_synth", + "pnr": "frontend_reader_place_and_route" + }, + { + "ir": "frontend_writer_opt_ir_benchmark", + "verilog": "verilog_frontend_writer", + "synthesis": "frontend_writer_benchmark_synth", + "pnr": "frontend_writer_place_and_route" + } + ] + } +} diff --git a/.github/workflows/xls-modules-dma.yml b/.github/workflows/xls-modules-dma.yml new file mode 100644 index 0000000000..fd6d372f71 --- /dev/null +++ b/.github/workflows/xls-modules-dma.yml @@ -0,0 +1,230 @@ +# https://docs.github.com/en/actions/reference/workflow-syntax-for-github-actions +# See also: https://github.com/marketplace/actions/bazel-action + +name: XLS Modules DMA +on: + # Avoid triggering on pushes to /all/ open PR branches. + push: + branches: + - main + paths-ignore: + # Do not trigger action when docs are updated. + - 'docs/**' + pull_request: + branches: + - main + # This lets us trigger manually from the UI. + workflow_dispatch: + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} + +env: + XLS_MODULE: //xls/modules/dma + XLS_MODULE_NAME: dma + # Intensive runs can cause the runner to starve and crash + BAZEL_RESOURCES_OPT: "--local_cpu_resources=HOST_CPUS-1 --local_ram_resources=HOST_RAM*.9" + CACHE_KEY: bazel-cache-dma-${{ github.sha }} + CACHE_RESTORE_KEY: bazel-cache-dma + # OpenROAD cache is large, so let's split usage + CACHE_KEY_IMPL: bazel-cache-dma-impl-${{ github.sha }} + CACHE_RESTORE_KEY_IMPL: bazel-cache-dma-impl + +jobs: + build: + name: BUILD + runs-on: ubuntu-22.04 + timeout-minutes: 600 + steps: + - uses: actions/checkout@v4 + + - name: Bazel Cache + uses: actions/cache@v4 + with: + path: "~/.cache/bazel" + key: ${{ env.CACHE_KEY }} + restore-keys: ${{ env.CACHE_RESTORE_KEY }} + + - name: Increase build space + run: | + echo "Before cleanup" + df -H + sudo rm -rf /usr/share/dotnet/* + sudo rm -rf /usr/local/lib/android/* + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + echo "After cleanup" + df -H + + - name: Install dependencies via apt + run: | + sudo apt-get update + sudo apt-get -qy --no-install-recommends install \ + build-essential \ + gfortran \ + libblas-dev \ + liblapack-dev \ + libtinfo5 \ + python-is-python3 \ + python3-dev \ + python3-distutils + + - name: Bazel Build Tools (opt) + run: | + bazel build -c opt --test_output=errors -- \ + //xls/dslx:interpreter_main \ + //xls/dslx/ir_convert:ir_converter_main \ + //xls/tools:opt_main \ + //xls/tools:codegen_main \ + //xls/dslx:dslx_fmt + + test: + needs: build + name: Test + runs-on: ubuntu-22.04 + timeout-minutes: 600 + strategy: + fail-fast: false + matrix: + dslx_test: ["test_common", + "test_csr", + "test_axi_csr", + "test_address_generator", + "test_frontend_writer", + "test_frontend_reader", + "test_main_controller" + ] + steps: + - uses: actions/checkout@v4 + + - name: Bazel Cache + uses: actions/cache@v4 + with: + path: "~/.cache/bazel" + key: ${{env.CACHE_KEY}} + restore-keys: ${{env.CACHE_RESTORE_KEY}} + + - name: Test + run: | + bazel run -c opt --test_output=errors -- ${{env.XLS_MODULE}}:${{ matrix.dslx_test }} + + format: + name: Format + runs-on: ubuntu-22.04 + timeout-minutes: 600 + steps: + - uses: actions/checkout@v4 + + - name: Bazel Cache + uses: actions/cache@v4 + with: + path: "~/.cache/bazel" + key: ${{env.CACHE_KEY}} + restore-keys: ${{env.CACHE_RESTORE_KEY}} + + # Once https://github.com/google/xls/issues/1285 is implemented, + # we could replace these with a single rule + - name: Test formatting + run: | + bazel run -c opt --test_output=errors -- \ + //xls/modules/dma:fmt_address_generator \ + //xls/modules/dma:fmt_axi_csr \ + //xls/modules/dma:fmt_common \ + //xls/modules/dma:fmt_config \ + //xls/modules/dma:fmt_csr \ + //xls/modules/dma:fmt_fifo \ + //xls/modules/dma:fmt_frontend_reader \ + //xls/modules/dma:fmt_frontend_writer \ + //xls/modules/dma:fmt_gpf \ + //xls/modules/dma:fmt_main_controller \ + //xls/modules/dma:fmt_bus_axi_pkg \ + //xls/modules/dma:fmt_bus_axi_st_pkg + + + config-matrix: + name: Matrix configuration + runs-on: ubuntu-22.04 + timeout-minutes: 60 + outputs: + json_rules: ${{ env.json_rules }} + steps: + - uses: actions/checkout@v4 + + - name: Read json file + id: read-json + run: | + sudo apt-get update + sudo apt-get -qqy --no-install-recommends install jq + echo "json_rules=$(jq -rc 'del(.dma.name)|.dma' .github/workflows/xls-modules-${{ env.XLS_MODULE_NAME }}.json)" | tee -a "$GITHUB_ENV" + + implement: + needs: config-matrix + name: Implementation + runs-on: ubuntu-22.04 + timeout-minutes: 600 + strategy: + fail-fast: false + matrix: ${{ fromJson( needs.config-matrix.outputs.json_rules ) }} + steps: + - uses: actions/checkout@v4 + + - name: Increase build space + run: | + echo "Before cleanup" + df -H + sudo rm -rf /usr/share/dotnet/* + sudo rm -rf /usr/local/lib/android/* + sudo rm -rf /usr/share/dotnet + sudo rm -rf /opt/ghc + sudo rm -rf "/usr/local/share/boost" + sudo rm -rf "$AGENT_TOOLSDIRECTORY" + echo "After cleanup" + df -H + + - name: Bazel Cache + uses: actions/cache@v4 + with: + path: "~/.cache/bazel" + key: ${{env.CACHE_KEY_IMPL}} + restore-keys: ${{env.CACHE_RESTORE_KEY_IMPL}} + + - name: IR + run: | + bazel run -c opt ${{ env.BAZEL_RESOURCES_OPT }} -- ${{ env.XLS_MODULE }}:${{ matrix.rules.ir }} + + - name: Verilog + run: | + bazel build -c opt ${{ env.BAZEL_RESOURCES_OPT }} -- ${{ env.XLS_MODULE }}:${{ matrix.rules.verilog }} + + - name: Synthesis + run: | + bazel run -c opt ${{ env.BAZEL_RESOURCES_OPT }} -- ${{ env.XLS_MODULE }}:${{ matrix.rules.synthesis }} + + - name: P&R + run: | + bazel build -c opt ${{ env.BAZEL_RESOURCES_OPT }} -- ${{ env.XLS_MODULE }}:${{ matrix.rules.pnr }} + + # ${variable/character_to_replace/new_character} + # ${variable/ slash / underscore } + - name: Prepare artifact name + if: always() + shell: bash + run: | + name_input=${{env.XLS_MODULE}}/${{ matrix.rules.ir }} + name_output="${name_input//\//_}" + echo "artifact_name=${name_output}" >> "$GITHUB_ENV" + + - name: Artifacts + if: always() + uses: actions/upload-artifact@v4 + with: + name: artifacts-impl-${{ env.artifact_name }} + path: | + ./bazel-bin/${{env.XLS_MODULE}}/*.log + ./bazel-bin/${{env.XLS_MODULE}}/*.textproto + ./bazel-bin/${{env.XLS_MODULE}}/*.ir + ./bazel-bin/${{env.XLS_MODULE}}/*.v + ./bazel-bin/${{env.XLS_MODULE}}/*.sv diff --git a/xls/modules/dma/BUILD b/xls/modules/dma/BUILD new file mode 100644 index 0000000000..3c7c3bf8be --- /dev/null +++ b/xls/modules/dma/BUILD @@ -0,0 +1,623 @@ +# Copyright 2023 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/build_rules:xls_build_defs.bzl", + "xls_benchmark_ir", + "xls_dslx_fmt_test", + "xls_dslx_ir", + "xls_dslx_library", + "xls_dslx_test", + "xls_dslx_verilog", + "xls_ir_opt_ir", + "xls_ir_verilog", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +# Common +xls_dslx_library( + name = "dma_common", + srcs = [ + "bus/axi_pkg.x", + "bus/axi_st_pkg.x", + "common.x", + "config.x", + "gpf.x", + ], +) + +xls_dslx_test( + name = "test_common", + library = "dma_common", +) + +# CSR +xls_dslx_library( + name = "csr_lib", + srcs = [ + "csr.x", + ], + deps = [ + ":dma_common", + ], +) + +xls_dslx_test( + name = "test_csr", + library = "csr_lib", +) + +xls_dslx_verilog( + name = "verilog_csr", + codegen_args = { + "module_name": "csr", + "delay_model": "asap7", + "pipeline_stages": "4", + "worst_case_throughput": "4", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "csr", + library = "csr_lib", + opt_ir_args = { + "top": "__csr__csr__Csr_0__8_32_14_next", + }, + verilog_file = "csr.v", +) + +xls_benchmark_ir( + name = "csr_opt_ir_benchmark", + src = ":verilog_csr.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "4", + "worst_case_throughput": "4", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "verilog_csr_lib", + srcs = [ + ":csr.v", + ], +) + +synthesize_rtl( + name = "csr_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "csr", + deps = [ + ":verilog_csr_lib", + ], +) + +benchmark_synth( + name = "csr_benchmark_synth", + synth_target = ":csr_synth_asap7", +) + +place_and_route( + name = "csr_place_and_route", + clock_period = "2000", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":csr_synth_asap7", + target_die_utilization_percentage = "10", +) + +# AXI CSR +xls_dslx_library( + name = "axi_csr_lib", + srcs = [ + "axi_csr.x", + ], + deps = [ + ":csr_lib", + ":dma_common", + ], +) + +xls_dslx_test( + name = "test_axi_csr", + library = "axi_csr_lib", +) + +xls_dslx_verilog( + name = "verilog_axi_csr", + codegen_args = { + "module_name": "axi_csr", + "delay_model": "asap7", + "pipeline_stages": "4", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "axi_csr", + library = "axi_csr_lib", + opt_ir_args = { + "top": "__axi_csr__axi_csr__AxiCsr_0__32_32_4_14_4_next", + }, + verilog_file = "axi_csr.v", +) + +xls_benchmark_ir( + name = "axi_csr_opt_ir_benchmark", + src = ":verilog_axi_csr.opt.ir", + benchmark_ir_args = { + "delay_model": "asap7", + "pipeline_stages": "4", + }, +) + +verilog_library( + name = "verilog_axi_csr_lib", + srcs = [ + ":axi_csr.v", + ], +) + +synthesize_rtl( + name = "axi_csr_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "axi_csr", + deps = [ + ":verilog_axi_csr_lib", + ], +) + +benchmark_synth( + name = "axi_csr_benchmark_synth", + synth_target = ":axi_csr_synth_asap7", +) + +place_and_route( + name = "axi_csr_place_and_route", + clock_period = "2000", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":axi_csr_synth_asap7", + target_die_utilization_percentage = "10", +) + +# FIFO +xls_dslx_library( + name = "fifo", + srcs = [ + "fifo.x", + ], + deps = [ + ":dma_common", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "test_fifo", + library = "fifo", +) + +# xls_dslx_ir( +# name = "fifo_ir", +# dslx_top = "fifo_synth", +# ir_file = "fifo_ir.ir", +# library = "fifo", +# ) + +# xls_ir_opt_ir( +# name = "fifo_ir_opt", +# src = "fifo_ir.ir", +# # FIXME: Top level is not correctly generated in verilog +# top = "__fifo__fifo_synth__FIFO__Writer_0__4_8_1_1_16_1_next" +# ) + +# xls_ir_verilog( +# name = "verilog_fifo", +# src = ":fifo_ir_opt.opt.ir", +# codegen_args = { +# "module_name": "fifo", +# "delay_model": "unit", +# "pipeline_stages": "3", +# "worst_case_throughput": "2", +# "reset": "rst", +# "use_system_verilog": "false", +# # TODO: setup configuration for RAM macro generation +# # https://google.github.io/xls/codegen_options/#rams-experimental +# # https://github.com/google/xls/blob/609a7ab89d96d2a7396d2418d00d30e4cb57b119/xls/codegen/ram_configuration.h#L99 +# # https://github.com/google/xls/blob/609a7ab89d96d2a7396d2418d00d30e4cb57b119/docs_src/tutorials/xlscc_memory.md?plain=1#L140 +# }, +# verilog_file = "fifo.v", +# ) + +# Address Generator +xls_dslx_library( + name = "address_generator_lib", + srcs = [ + "address_generator.x", + ], + deps = [ + ":dma_common", + ], +) + +xls_dslx_test( + name = "test_address_generator", + library = "address_generator_lib", +) + +xls_dslx_verilog( + name = "verilog_address_generator", + codegen_args = { + "module_name": "address_generator", + "delay_model": "asap7", + "pipeline_stages": "3", + "worst_case_throughput": "3", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "address_generator", + library = "address_generator_lib", + opt_ir_args = { + "top": "__address_generator__address_generator__AddressGenerator_0__32_4_next", + }, + verilog_file = "address_generator.v", +) + +xls_benchmark_ir( + name = "address_generator_opt_ir_benchmark", + src = ":verilog_address_generator.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "3", + "worst_case_throughput": "3", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "verilog_address_generator_lib", + srcs = [ + ":address_generator.v", + ], +) + +synthesize_rtl( + name = "address_generator_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "address_generator", + deps = [ + ":verilog_address_generator_lib", + ], +) + +benchmark_synth( + name = "address_generator_benchmark_synth", + synth_target = ":address_generator_synth_asap7", +) + +place_and_route( + name = "address_generator_place_and_route", + clock_period = "2000", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":address_generator_synth_asap7", + target_die_utilization_percentage = "10", +) + +# Frontend Reader +xls_dslx_library( + name = "frontend_reader_lib", + srcs = [ + "frontend_reader.x", + ], + deps = [ + ":dma_common", + ], +) + +xls_dslx_test( + name = "test_frontend_reader", + library = "frontend_reader_lib", +) + +xls_dslx_verilog( + name = "verilog_frontend_reader", + codegen_args = { + "module_name": "frontend_reader", + "delay_model": "asap7", + "pipeline_stages": "4", + "worst_case_throughput": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "frontend_reader", + library = "frontend_reader_lib", + opt_ir_args = { + "top": "__frontend_reader__frontend_reader__FrontendReader_0__32_32_4_4_4_next", + }, + verilog_file = "frontend_reader.v", +) + +xls_benchmark_ir( + name = "frontend_reader_opt_ir_benchmark", + src = ":verilog_frontend_reader.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "4", + "worst_case_throughput": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "verilog_frontend_reader_lib", + srcs = [ + ":frontend_reader.v", + ], +) + +synthesize_rtl( + name = "frontend_reader_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "frontend_reader", + deps = [ + ":verilog_frontend_reader_lib", + ], +) + +benchmark_synth( + name = "frontend_reader_benchmark_synth", + synth_target = ":frontend_reader_synth_asap7", +) + +place_and_route( + name = "frontend_reader_place_and_route", + clock_period = "2000", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":frontend_reader_synth_asap7", + target_die_utilization_percentage = "10", +) + +# Frontend writer +xls_dslx_library( + name = "frontend_writer_lib", + srcs = [ + "frontend_writer.x", + ], + deps = [ + ":dma_common", + ], +) + +xls_dslx_test( + name = "test_frontend_writer", + library = "frontend_writer_lib", +) + +xls_dslx_verilog( + name = "verilog_frontend_writer", + codegen_args = { + "module_name": "frontend_writer", + "delay_model": "asap7", + "pipeline_stages": "4", + "worst_case_throughput": "3", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "frontend_writer", + library = "frontend_writer_lib", + opt_ir_args = { + "top": "__frontend_writer__frontend_writer__FrontendWriter_0__32_32_4_4_4_4_next", + }, + verilog_file = "frontend_writer.v", +) + +xls_benchmark_ir( + name = "frontend_writer_opt_ir_benchmark", + src = ":verilog_frontend_writer.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "4", + "worst_case_throughput": "3", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "verilog_frontend_writer_lib", + srcs = [ + ":frontend_writer.v", + ], +) + +synthesize_rtl( + name = "frontend_writer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "frontend_writer", + deps = [ + ":verilog_frontend_writer_lib", + ], +) + +benchmark_synth( + name = "frontend_writer_benchmark_synth", + synth_target = ":frontend_writer_synth_asap7", +) + +place_and_route( + name = "frontend_writer_place_and_route", + clock_period = "2000", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":frontend_writer_synth_asap7", + target_die_utilization_percentage = "10", +) + +# Main controller +xls_dslx_library( + name = "main_controller_lib", + srcs = [ + "main_controller.x", + ], + deps = [ + ":address_generator_lib", + ":axi_csr_lib", + ":dma_common", + ":frontend_reader_lib", + ":frontend_writer_lib", + ], +) + +xls_dslx_test( + name = "test_main_controller", + library = "main_controller_lib", +) + +# FIXME: Two instances of address generator with the same parameters conflict with each other +# related issue: https://github.com/google/xls/issues/1202 + +# xls_dslx_verilog( +# name = "verilog_main_controller", +# opt_ir_args = { +# "inline_procs": "true", +# "top" : "__main_controller__main_controller__MainController_0__32_32_4_4_4_14_4_next", +# }, +# codegen_args = { +# "module_name": "main_controller", +# "delay_model": "asap7", +# "pipeline_stages": "4", +# "reset": "rst", +# "use_system_verilog": "false", +# }, +# dslx_top = "main_controller", +# library = "main_controller_lib", +# verilog_file = "main_controller.v", +# ) + +# xls_benchmark_ir( +# name = "main_controller_opt_ir_benchmark", +# src = ":verilog_main_controller.opt.ir", +# benchmark_ir_args = { +# "pipeline_stages": "3", +# "worst_case_throughput": "3", +# "delay_model": "asap7", +# }, +# ) + +# verilog_library( +# name = "verilog_main_controller_lib", +# srcs = [ +# ":main_controller.v", +# ], +# ) + +# synthesize_rtl( +# name = "main_controller_synth_asap7", +# standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", +# top_module = "main_controller", +# deps = [ +# ":verilog_main_controller_lib", +# ], +# ) + +# benchmark_synth( +# name = "main_controller_benchmark_synth", +# synth_target = ":main_controller_synth_asap7", +# ) + +# place_and_route( +# name = "main_controller_place_and_route", +# clock_period = "750", +# core_padding_microns = 2, +# min_pin_distance = "0.5", +# placement_density = "0.30", +# stop_after_step = "global_routing", +# synthesized_rtl = ":main_controller_synth_asap7", +# target_die_utilization_percentage = "10", +# ) + +# Formatting +xls_dslx_fmt_test( + name = "fmt_address_generator", + src = "address_generator.x", +) + +xls_dslx_fmt_test( + name = "fmt_axi_csr", + src = "axi_csr.x", +) + +xls_dslx_fmt_test( + name = "fmt_common", + src = "common.x", +) + +xls_dslx_fmt_test( + name = "fmt_config", + src = "config.x", +) + +xls_dslx_fmt_test( + name = "fmt_csr", + src = "csr.x", +) + +xls_dslx_fmt_test( + name = "fmt_fifo", + src = "fifo.x", +) + +xls_dslx_fmt_test( + name = "fmt_frontend_reader", + src = "frontend_reader.x", +) + +xls_dslx_fmt_test( + name = "fmt_frontend_writer", + src = "frontend_writer.x", +) + +xls_dslx_fmt_test( + name = "fmt_gpf", + src = "gpf.x", +) + +xls_dslx_fmt_test( + name = "fmt_main_controller", + src = "main_controller.x", +) + +xls_dslx_fmt_test( + name = "fmt_bus_axi_pkg", + src = "bus/axi_pkg.x", +) + +xls_dslx_fmt_test( + name = "fmt_bus_axi_st_pkg", + src = "bus/axi_st_pkg.x", +) diff --git a/xls/modules/dma/README.md b/xls/modules/dma/README.md new file mode 100644 index 0000000000..d66a2769cb --- /dev/null +++ b/xls/modules/dma/README.md @@ -0,0 +1,411 @@ +# Direct Memory Access + +## Overview + +Direct Memory Access enables AXI communication between any AXI capable memory and AXI-Stream capable Generic Physical Function (GPF), e.g. an encoding accelerator. +The system is designed to use the AXI interface as the main System Bus. +It is expected that the DMA, an accessible memory module (Main Memory) and a Host Application Module (Host) are also connected to the System Bus. +The host application is responsible for accessing DMA Control and Status Registers (CSR) and issuing a processing request. +The processing request defines the number and addresses of memory transfers. +After receiving a valid request, the DMA accesses the Main Memory to read a block of data and sends it to the GPF. +A FIFO queue is put in place so that data traffic can flow to the GPF over AXI-Stream, regardless of the pending System Bus transactions. +Once the GPF finishes its job, a return data path is used to send the processed data back to the Main Memory. + +## Top-level + +The main controller defines the following independent parameters: + +| Parameter | Parameter Identifier | Default value | +| :---------------: | :------------------: | :-----------: | +| Address Bus Width | `ADDR_W` | 32 | +| Data Bus Width | `DATA_W` | 32 | +| Number of CSRs | `REGS_N` | 14 | + +Full list is under development, e.g. it is expected that FIFO depth will also be a top-level configurable parameter. + +## Submodules + +### Control and Status Registers + +The Control and Status Registers implementation is centered around a parameterizable memory array, which targets the 32-bit width and 14 registers. +It is expected that CSRs will be synthesized as flip flops and since most of the 32 bits are unused, they will be removed from the design by optimization tools in the conversion flow. + +The implementation is divided into 2 parts: the array, which can be accessed with generic request/response interfaces, and the wrapper, which provides the AXI functionality. +Only a subset of AXI features and properties are supported and usage should be limited to: +* transactions of length 1 +* 32-bit data payloads +* aligned data transfers +* all bits in the transfer are valid + +It is currently assumed that the CSRs are located at the 0x0000 offset of the system memory map. + +#### CSR Features + +Most of the control and configuration communication occurs between CSRs and the Address Generators (AG). +Transfers begin upon write to the `Control Register` and this information is passed to the corresponding AG. +In response, the AG sets the busy bit in the `Status Register` and manages the frontend transactions. +Only once all transactions were performed, does the AG signal `done` via a write to the `Interrupt status register` and the busy bit is cleared. +Further changes to the behavior of the DMA will be enabled after implementation of `loop mode` and `external frame synchronization`. +Details of all registers and their respective bits' meaning are described below. + +#### Future development + +All features that do not yet perform exactly as described in the table below are listed here: +* `Control register` + * `writer/reader sync disable`, synchronization is not implemented. DSLX DMA works as if the bit was always set +* the `Interrupt mask register` has no effect on the core +* the interrupt bits in the `Interrupt status register` are set when the transfer is done, but there is no interrupt signal to the outside world. Also, it is cleared by writing '0', not '1'. +* The following registers are implemented, but they operate as generic R/W registers with no dedicated function: + * `Version register` + * `Configuration register` + +#### Register table + +**Register table follows [the FastVDMA project](https://antmicro.github.io/fastvdma/RegisterMap.html).** + +**This DMA targets only AXI interfaces, so the `configuration register` should be unused, writes to it are ignored.** + +Register layout is shown in the table below: + +| Address | Role | +| ------- | --------------------------- | +| `0x00` | Control register | +| `0x04` | Status register | +| `0x08` | Interrupt mask register | +| `0x0c` | Interrupt status register | +| `0x10` | Reader start address | +| `0x14` | Reader line length | +| `0x18` | Reader line count | +| `0x1c` | Reader stride between lines | +| `0x20` | Writer start address | +| `0x24` | Writer line length | +| `0x28` | Writer line count | +| `0x2c` | Writer stride between lines | +| `0x30` | Version register | +| `0x34` | Configuration register | + + +#### Detailed register description + +Provided descriptions are implementation targets, compare with previous section to make sure that the corresponding feature is already working. + +##### Control register (0x00) + +| Bit | Name | Description | +| ---- | ------------------- | --------------------------------------------------------------------------------------------------- | +| 0 | Writer start | Write `1` to start write frontend (This bit automatically resets itself to `0` if not in loop mode) | +| 1 | Reader start | Write `1` to start read frontend (This bit automatically resets itself to `0` if not in loop mode) | +| 2 | Writer sync disable | Write `1` to disable waiting for external writer synchronization (rising edge on `writerSync`) | +| 3 | Reader sync disable | Write `1` to disable waiting for external reader synchronization (rising edge on `readerSync`) | +| 4 | Writer loop mode | Write `1` to automatically start next write frontend transfer after finishing the current one | +| 5 | Reader loop mode | Write `1` to automatically start next read frontend transfer after finishing the current one | +| 6-31 | - | Unused | + +--- + +##### Status register (0x04) + +| Bit | Name | Description | +| ---- | ----------- | ---------------------------------------------------------- | +| 0 | Writer busy | Reads as `1` when write frontend is busy transferring data | +| 1 | Reader busy | Reads as `1` when read frontend is busy transferring data | +| 2-31 | - | Unused | + +--- + +##### Interrupt mask register (0x08) + +| Bit | Name | Description | +| ---- | ----------- | ------------------------------------ | +| 0 | Writer mask | Write `1` to enable writer interrupt | +| 1 | Reader mask | Write `1` to enable reader interrupt | +| 2-31 | - | Unused | + +--- + +##### Interrupt status register (0x0c) + +| Bit | Name | Description | +| ---- | ---------------- | -------------------------------------------------------------------------------- | +| 0 | Writer interrupt | Reads as `1` if writer interrupt has occurred, write `1` to clear that interrupt | +| 1 | Reader interrupt | Reads as `1` if reader interrupt has occurred, write `1` to clear that interrupt | +| 2-31 | - | Unused | + +--- + +##### Reader start address (0x10) + +| Bit | Name | Description | +| ---- | ------------- | -------------------------------------------------------------------------- | +| 0-31 | Start address | Reader start address (set to `0` if reader frontend is a stream interface) | + +--- + +##### Reader line length (0x14) + +| Bit | Name | Description | +| ---- | ----------- | -------------------------------------------------------- | +| 0-31 | Line length | Reader line length (as number of reader data bus widths) | + +--- + +##### Reader line count (0x18) + +| Bit | Name | Description | +| ---- | ---------- | ----------------- | +| 0-31 | Line count | Reader line count | + +--- + +##### Reader stride between lines (0x1c) + +| Bit | Name | Description | +| ---- | ------ | ------------------------------------------------------------------- | +| 0-31 | Stride | Gap between consecutive lines (as number of reader data bus widths) | + +--- + +##### Writer start address (0x20) + +| Bit | Name | Description | +| ---- | ------------- | -------------------------------------------------------------------------- | +| 0-31 | Start address | Writer start address (set to `0` if writer frontend is a stream interface) | + +--- + +##### Writer line length (0x24) + +| Bit | Name | Description | +| ---- | ----------- | -------------------------------------------------------- | +| 0-31 | Line length | Writer line length (as number of writer data bus widths) | + +--- + +##### Writer line count (0x28) + +| Bit | Name | Description | +| ---- | ---------- | ----------------- | +| 0-31 | Line count | Writer line count | + +##### Writer stride between lines (0x2c) + +| Bit | Name | Description | +| ---- | ------ | ------------------------------------------------------------------- | +| 0-31 | Stride | Gap between consecutive lines (as number of writer data bus widths) | + +--- + +##### Version register (0x30) + +| Bit | Name | Description | +| ---- | ---------------- | --------------------- | +| 0-31 | Version register | Holds the DMA version | + +--- + +##### Configuration register (0x34) + +| Bit | Name | Description | +| ---- | ---------------------- | ------------------------------------ | +| 0-31 | Configuration register | Reader, writer and control bus types | + +### FIFO + +FIFOs serve a crucial role in buffering input and output data of the GPF. +The DSLX implementation is divided into 3 processes: a memory wrapper, a reader and a writer. +The memory wrapper spawns the dual port RAM model (`examples/ram.x`), which has 3 channels per port. +These channels are used to signal: a request, a read response and a write response. In FIFO, however, ports are used either for writes or reads, so one of the responses is left unused, e.g. a port used for reads does not need the write response. +This motivates the choice to handle the 2 unused channels inside of the wrapper and use the 4 remaining channels as IO. + +The reader process is connected to port 0 of the RAM and is responsible for fetching data from the memory and streaming it to the GPF. +Only a subset of AXI Stream features and properties are supported, and usage should be limited to: +* transactions of length 1 +* 32-bit data payloads +* aligned data transfers +* all bits in the transfer are valid + +The reader process manages the state of the read pointer, which holds the address to the "First Out" data in the memory. +Anytime a read is performed, the pointer is incremented, however, it is also compared with the write pointer to ensure that the queue is not empty (reading before a write occurred). + +Similarly, the writer process manages the state of the write pointer, which holds the address to the "Last In" data in the memory. Anytime a write is performed, the pointer is incremented, however, it is also compared with the read pointer to ensure that old data is not overwritten (writing before data could be read). + +Pointer synchronization for these 2 processes is performed via dedicated channels. + +### Main Controller + +The Main Controller is responsible for coordinating the data flow of the DMA Core and comprises: +* Control and Status Registers +* Address Generators (one for writes, one for reads) +* Frontends (one for writes, one for reads) + +Changes in the CSR configuration issue new processing requests: the type and number of AXI transactions needed to perform the task are calculated. + +The flow can be summarized as follows: +* After power-up, the controller defaults to an `IDLE` state, which means that no transfers are processed +* The only way to change the state is to set the `ControlRegister.start` bit + * `StatusRegister.busy` bit is set accordingly until the Controller performs all transfers +* The number and type of transactions is determined by the `Transfer configuration registers` settings + +``` + // Main controller pseudo-code + + for device in {reader, writer}: + + while( !ControlRegister.start ){} + + update_configuration() + Set StatusRegister.busy + + do { + + Until all transfers are completed: + Access System Bus, Convert to AXI Stream, Access the FIFO + + } while ( ControlRegister.loop_mode ) + + Clear StatusRegister.busy + Trigger an interrupt + +``` + +#### Address Generator + +The address generator is responsible for calculating all addresses required for the DMA transfer. +The calculation is performed based on the configuration registers: +* `Reader/Writer start address`, +* `Reader/Writer line length`, +* `Reader/Writer line count`, +* `Reader/Writer stride between lines` + +Let's denote: +* Starting address of the first transfer is A +* A memory element is an interval of length D bytes + * D is also equal to the data bus width expressed in bytes (DATA_W) +* Line is composed of a number L of memory elements (line length) +* Stride S is the increment between lines (empty gaps) +* A complete DMA transfer reads/writes C lines + +Example #1: + +``` + A := 0x1000 + C := 4 + D := 4 + L := 1 + S := 0 +``` + +Resulting transfers T (in a byte aligned memory) + + T([0x1000, 0x1003]) + T([0x1004, 0x1007]) + T([0x1008, 0x100b]) + T([0x100c, 0x100f]) + +Example #2: + +``` + A := 0x1000 + C := 4 + D := 4 + L := 2 + S := 1 +``` + +Resulting transfers T (in a byte aligned memory) + +``` + T([0x1000, 0x1003]) // This is Line 1 + T([0x1004, 0x1007]) // This is Line 1 + // stride is 1, so skip [0x1008, 0x100b] + T([0x100c, 0x100f]) + T([0x1010, 0x1013]) + T([0x1018, 0x101b]) + T([0x101c, 0x101f]) + T([0x1024, 0x1027]) + T([0x1028, 0x102b]) +``` + +In pseudo-code, the function of the address generator can be represented as: + +``` +function address_generator(A,C,D,L,S): + tab_address = [] + for c in range(C): + for k in range(L): + a = A + D*(k + c*(L+S)) + tab_address.append(a) + return tab_address +``` + + +#### Frontend + +Frontend is responsible for converting addresses from the address generator into valid AXI/AXI-Stream transactions. +The frontend communicates with the `Address Generator` via `start` and `done` channels to signal start and end of transactions. +The `Address Generator` sends a message on the `start` channel and `(Address, Length)` tuple, then awaits for a message on the `done` channel. +`done` will be asserted only once the `Frontend` ensures that the current transaction is completed. + +#### Interrupt Controller + +Work on the interrupt controller has not yet begun. + +## Implementation of AMBA interfaces in DSLX + +Specifications of AMBA interfaces (AXI, AXI Stream, AHB, etc.) were designed and ratified with an HDL implementation in mind, but there are distinct differences between modeling in DSLX and HDLs like Verilog. +First of all, DSLX automatically infers valid/ready handshakes from the description of channels and calls to `send()` and `recv()` functions (and their conditional and/or non-blocking versions). +The side effect of this automatic translation is that valid/ready signals, which are mandatory in the AXI specification, are not explicitly visible in the DSLX implementation and are later given obfuscated names. +Thus, conformance to the specification is hard to establish at the DSLX level and since there is no automatic way to find inferred signals, it requires manual work from designers to create testbenches at Verilog level. +The inability to assess specification conformance is a problem that needs to be solved or it may result in compromised interoperability or compatibility of DSLX implementations with existing HDL designs. + +### AXI + +AXI relies in total on 5 channels for enabling read and write transactions: 3 are used to complete writes and 2 to complete reads. +In case of writes, the flow consists of the following steps: +* Setup the transaction, which is confirmed with a handshake on AW channel +* Send the payload, which is confirmed with a handshake on W channel +* Receive the error status, which is confirmed with a handshake on B channel. +If we only consider the simplest transactions, i.e. a burst of length 1 and data aligned to the bus width, the DSLX implementation may look like this on the AXI subordinate side: + +``` +next(...) { + let (tok, aw_payload) = recv(tok, aw_ch); + let (tok, w_payload) = recv(tok, w_ch); + let tok = send(tok, b_ch, OKAY); + ... + let result = process(aw_payload, w_payload); +} +``` + +and on the AXI Manager side: + +``` +next(...) { + let (aw,w) = consume(data); + ... + let tok = send(tok, aw_ch, aw); + let tok = send(tok, w_ch, w); + let (tok, b_resp) = recv(tok, b_ch); +} +``` + +It is expected that this style of description yields an AXI compliant handshake, however, designer has no control over it and may not adjust code to handle corner cases. + +## Software based control + +The Host manages the DMA via CSRs. + +The recommended flow is to: +* select mode of operation by setting bits in the `Configuration Register` + * currently only AXI/AXI-Stream is supported +* set memory block configuration in the `Writer_*` and `Reader_*` registers +* enable interrupts +* issue processing begin request via write to the `Control register` +* await for an interrupt and handle it with an Interrupt Service Routine + +The ISR is expected to: + * Read `Interrupt status register` to identify the interrupt trigger + +The `dma_irq` pin of the DMA Module is set to `1` to signal that there is a pending interrupt. +The Host is responsible for reading the Interrupt Status Register and clearing the interrupt bit. +The interrupts occur either when DMA is done with the workload or an error occurred. diff --git a/xls/modules/dma/address_generator.x b/xls/modules/dma/address_generator.x new file mode 100644 index 0000000000..b9d739260e --- /dev/null +++ b/xls/modules/dma/address_generator.x @@ -0,0 +1,279 @@ +// Copyright 2023-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. + +// Address Generator + +import std; +import xls.modules.dma.common; +import xls.modules.dma.config; + +type TransferDescBundle = common::TransferDescBundle; +type MainCtrlBundle = common::MainCtrlBundle; + +enum AddressGeneratorStatusEnum : u2 { + IDLE = 0, + WAIT = 1, + BUSY = 2, + DONE = 3, +} + +struct AddressGeneratorState { + configuration: MainCtrlBundle, + status: AddressGeneratorStatusEnum, + transfer: TransferDescBundle, + it: u32, + rsp_counter: u32, +} + +proc AddressGenerator { + configuration: chan> in; + start_ch: chan in; + busy_ch: chan out; + done_ch: chan out; + addr_gen_req: chan> out; + addr_gen_rsp: chan<()> in; + + config(configuration: chan> in, start_ch: chan in, + busy_ch: chan out, done_ch: chan out, + addr_gen_req: chan> out, addr_gen_rsp: chan<()> in) { + (configuration, start_ch, busy_ch, done_ch, addr_gen_req, addr_gen_rsp) + } + + init { + (AddressGeneratorState { + configuration: common::zeroMainCtrlBundle(), + status: AddressGeneratorStatusEnum::IDLE, + transfer: common::zeroTransferDescBundle(), + it: u32:0, + rsp_counter: u32:0 + }) + } + + next(tok: token, state: AddressGeneratorState) { + + let (tok, start) = recv(tok, start_ch); + let goto_wait = start && (state.status == AddressGeneratorStatusEnum::IDLE); + + let (tok, configuration) = recv(tok, configuration); + let goto_busy = state.status == AddressGeneratorStatusEnum::WAIT; + let configuration = if goto_busy { + trace_fmt!("[AG] New configuration = {}", configuration); + configuration + } else { + state.configuration + }; + + let send_transfer = state.status == AddressGeneratorStatusEnum::BUSY && + (state.it != (state.configuration.line_count as u32)); + let tok = send_if(tok, addr_gen_req, send_transfer, state.transfer); + + let it = if send_transfer { state.it + u32:1 } else { state.it }; + + let (tok, _, valid_addr_gen_rsp) = recv_if_non_blocking( + tok, addr_gen_rsp, state.status == AddressGeneratorStatusEnum::BUSY, ()); + + let rsp_counter = + if valid_addr_gen_rsp { state.rsp_counter + u32:1 } else { state.rsp_counter }; + + let goto_done = (state.status == AddressGeneratorStatusEnum::BUSY) && + (rsp_counter == (state.configuration.line_count as u32)); + + let tok = send_if(tok, done_ch, goto_done, u1:1); + + let goto_idle = state.status == AddressGeneratorStatusEnum::DONE; + + // Next state logic + let nextState = if state.status == AddressGeneratorStatusEnum::IDLE { + if goto_wait { + AddressGeneratorStatusEnum::WAIT + } else { + AddressGeneratorStatusEnum::IDLE + } + } else if state.status == AddressGeneratorStatusEnum::WAIT { + if goto_busy { + AddressGeneratorStatusEnum::BUSY + } else { + AddressGeneratorStatusEnum::WAIT + } + } else if state.status == AddressGeneratorStatusEnum::BUSY { + if goto_done { + AddressGeneratorStatusEnum::DONE + } else { + AddressGeneratorStatusEnum::BUSY + } + } else if state.status == AddressGeneratorStatusEnum::DONE { + if goto_idle { + AddressGeneratorStatusEnum::IDLE + } else { + AddressGeneratorStatusEnum::DONE + } + } else { + AddressGeneratorStatusEnum::IDLE + }; + // trace_fmt!("State : {} Next state : {}", state.status, nextState); + + let nextBundle = if state.status == AddressGeneratorStatusEnum::BUSY { + TransferDescBundle { + address: + state.transfer.address + + (DATA_W_DIV8 as uN[ADDR_W]) * + (state.configuration.line_length + state.configuration.line_stride), + length: state.configuration.line_length + } + } else if state.status == AddressGeneratorStatusEnum::WAIT { + TransferDescBundle { + address: configuration.start_address, length: configuration.line_length + } + } else { + state.transfer + }; + + let it = if state.status == AddressGeneratorStatusEnum::DONE { u32:0 } else { it }; + let rsp_counter = + if state.status == AddressGeneratorStatusEnum::DONE { u32:0 } else { rsp_counter }; + + let is_busy = (state.status != AddressGeneratorStatusEnum::IDLE) as u1; + let tok = send(tok, busy_ch, is_busy); + + trace!(state); + AddressGeneratorState { + configuration, transfer: nextBundle, status: nextState, it, rsp_counter + } + } +} + +pub fn AddressGeneratorReferenceFunction + (config: MainCtrlBundle) -> TransferDescBundle[C] { + + let a = for (i, a): (u32, TransferDescBundle[C]) in range(u32:0, C) { + if i == u32:0 { + update( + a, i, + TransferDescBundle { + address: config.start_address, length: config.line_length + }) + } else { + update( + a, i, + TransferDescBundle { + address: + (a[i - u32:1]).address + + DATA_W_DIV8 * (config.line_length + config.line_stride), + length: config.line_length + }) + } + }(TransferDescBundle[C]:[common::zeroTransferDescBundle(), ...]); + a +} + +#[test] +fn TestAddressGeneratorReferenceFunction() { + let ADDR_W = u32:32; + let dataWidthDiv8 = u32:4; + let testConfig = MainCtrlBundle { + start_address: uN[ADDR_W]:1000, + line_count: uN[ADDR_W]:4, + line_length: uN[ADDR_W]:3, + line_stride: uN[ADDR_W]:2 + }; + // TODO: Is using parametric from a struct field a good practice? + let C = testConfig.line_count; + let a = AddressGeneratorReferenceFunction(testConfig); + assert_eq((a[0]).address, u32:1000); + assert_eq((a[1]).address, u32:1020); + assert_eq((a[2]).address, u32:1040); + assert_eq((a[3]).address, u32:1060); + + assert_eq((a[0]).length, u32:3); + assert_eq((a[1]).length, u32:3); + assert_eq((a[2]).length, u32:3); + assert_eq((a[3]).length, u32:3); +} + +const TEST_DATA_W_DIV8 = u32:4; +const TEST_ADDR_W = u32:32; + +#[test_proc] +proc TestAddressGenerator { + configuration: chan> out; + start_ch: chan out; + busy_ch: chan in; + done_ch: chan in; + addr_gen_req: chan> in; + addr_gen_rsp: chan<()> out; + terminator: chan out; + + config(terminator: chan out) { + let (configuration_s, configuration_r) = chan>; + let (start_ch_s, start_ch_r) = chan; + let (busy_ch_s, busy_ch_r) = chan; + let (done_ch_s, done_ch_r) = chan; + let (addr_gen_req_s, addr_gen_req_r) = chan>; + let (addr_gen_rsp_s, addr_gen_rsp_r) = chan<()>; + spawn AddressGenerator( + configuration_r, start_ch_r, busy_ch_s, done_ch_s, addr_gen_req_s, addr_gen_rsp_r); + ( + configuration_s, start_ch_s, busy_ch_r, done_ch_r, addr_gen_req_r, addr_gen_rsp_s, + terminator, + ) + } + + init { (u32:0) } + + next(tok: token, state: u32) { + let testConfig = MainCtrlBundle { + start_address: uN[TEST_ADDR_W]:1000, + line_count: uN[TEST_ADDR_W]:5, + line_length: uN[TEST_ADDR_W]:3, + line_stride: uN[TEST_ADDR_W]:0 + }; + + let tok = send(tok, start_ch, u1:1); + let tok = send(tok, configuration, testConfig); + + let (tok, r_data, r_data_valid) = + recv_non_blocking(tok, addr_gen_req, common::zeroTransferDescBundle()); + + let state = if r_data_valid { + trace_fmt!("r_data = {}", r_data); + let tok = send(tok, addr_gen_rsp, ()); + state + u32:1 + } else { + state + }; + + let (tok, done, done_valid) = recv_non_blocking(tok, done_ch, u1:1); + + let do_terminate = done && done_valid; + if do_terminate { assert_eq(state, testConfig.line_count); } else { }; + let tok = send_if(tok, terminator, do_terminate, do_terminate); + + state + } +} + +// Verilog example +proc address_generator { + config(configuration: chan> in, start_ch: chan in, + busy_ch: chan out, done_ch: chan out, + addr_gen_req: chan> out, addr_gen_rsp: chan<()> in) { + spawn AddressGenerator( + configuration, start_ch, busy_ch, done_ch, addr_gen_req, addr_gen_rsp); + () + } + + init { () } + + next(tok: token, state: ()) { } +} diff --git a/xls/modules/dma/axi_csr.x b/xls/modules/dma/axi_csr.x new file mode 100644 index 0000000000..882fab0ee2 --- /dev/null +++ b/xls/modules/dma/axi_csr.x @@ -0,0 +1,266 @@ +// Copyright 2023-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.dma.bus.axi_pkg; +import xls.modules.dma.common; +import xls.modules.dma.config; +import xls.modules.dma.csr; + +// FIXME: casting imported types is a workaround +// https://github.com/google/xls/issues/1030 +type MainCtrlBundle = common::MainCtrlBundle; + +type AxiAwBundle = axi_pkg::AxiAwBundle; +type AxiWBundle = axi_pkg::AxiWBundle; +type AxiBBundle = axi_pkg::AxiBBundle; +type AxiArBundle = axi_pkg::AxiArBundle; +type AxiRBundle = axi_pkg::AxiRBundle; + +struct axi_csr_state { + waddr: uN[ADDR_W], + wdata: uN[DATA_W], + raddr: uN[ADDR_W], + rdata: uN[DATA_W], +} + +// AXI4 subordinate receives AXI4 transactions and translates them into simple +// read/writes for CSR +proc AxiCsr { + aw_ch: chan> in; + w_ch: chan> in; + b_ch: chan> out; + ar_ch: chan> in; + r_ch: chan> out; + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan out; + ch_writer_configuration: chan> out; + ch_writer_busy: chan in; + ch_writer_done: chan in; + ch_reader_start: chan out; + ch_reader_configuration: chan> out; + ch_reader_busy: chan in; + ch_reader_done: chan in; + + config(aw_ch: chan> in, w_ch: chan> in, + b_ch: chan> out, ar_ch: chan> in, + r_ch: chan> out, ch_writer_start: chan out, + ch_writer_configuration: chan> out, ch_writer_busy: chan in, + ch_writer_done: chan in, ch_reader_start: chan out, + ch_reader_configuration: chan> out, ch_reader_busy: chan in, + ch_reader_done: chan in, reader_sync_req: chan<()> in, reader_sync_rsp: chan<()> out, + writer_sync_req: chan<()> in, writer_sync_rsp: chan<()> out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + spawn csr::Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start, + ch_writer_configuration, ch_writer_busy, ch_writer_done, ch_reader_start, + ch_reader_configuration, ch_reader_busy, ch_reader_done, reader_sync_req, + reader_sync_rsp, writer_sync_req, writer_sync_rsp); + ( + aw_ch, w_ch, b_ch, ar_ch, r_ch, read_req_s, read_resp_r, write_req_s, write_resp_r, + ch_writer_start, ch_writer_configuration, ch_writer_busy, ch_writer_done, + ch_reader_start, ch_reader_configuration, ch_reader_busy, ch_reader_done, + ) + } + + init { + axi_csr_state { + waddr: uN[ADDR_W]:0, wdata: uN[DATA_W]:0, raddr: uN[ADDR_W]:0, rdata: uN[DATA_W]:0 + } + } + + next(tok: token, state: axi_csr_state) { + // AW Channel Handler + let (tok, aw_payload, aw_valid) = recv_non_blocking(tok, aw_ch, zero!()); + + // CSR Addresses in software are expressed in bytes. + // CSR Addresses in hardware are expressed in words. + // The AXI address must be translated: divided by 4 with default config. + // FIXME: Address translation is only correct with 32-bit configuration. + // In general, this should be: addr /= len(word)/len(byte) + let w_addr = if aw_valid { aw_payload.awaddr >> uN[ADDR_W]:2 } else { state.waddr }; + + // W channel Handler + let (tok, w_payload, w_valid) = recv_non_blocking(tok, w_ch, zero!()); + let w_data = if w_valid { w_payload.wdata } else { state.wdata }; + + // Handle write to CSR + let tok = send_if(tok, write_req, w_valid, csr::WriteWordReq(w_addr, w_data)); + let (tok, _, csr_write_valid) = recv_non_blocking(tok, write_resp, csr::WriteResp {}); + + // B Channel Handlers + let b_msg = AxiBBundle { bresp: axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY, bid: uN[ID_W]:0 }; + let tok = send_if(tok, b_ch, csr_write_valid, b_msg); + + // AR Channel Handler + let zero_AxiArBundle = axi_pkg::zeroAxiArBundle(); + let (tok, ar_payload, ar_valid) = recv_non_blocking(tok, ar_ch, zero_AxiArBundle); + + // CSR Addresses in software are expressed in bytes. + // CSR Addresses in hardware are expressed in words. + // The AXI address must be translated: divided by 4 with default config. + // FIXME: Address translation is only correct with 32-bit configuration. + // In general, this should be: addr /= len(word)/len(byte) + let r_addr = if ar_valid { ar_payload.araddr >> uN[ADDR_W]:2 } else { state.raddr }; + + // Handle Read from CSR + let tok = send_if(tok, read_req, ar_valid, csr::ReadWordReq(r_addr)); + let (tok, r_data, csr_read_valid) = + recv_non_blocking(tok, read_resp, zero!()); + + // R Channel Handler + let tok = send_if( + tok, r_ch, csr_read_valid, + AxiRBundle { + rid: uN[ID_W]:0, + rdata: r_data.data, + rresp: axi_pkg::AXI_READ_RESPONSE_CODES::OKAY, + rlast: uN[1]:1 + }); + + axi_csr_state { waddr: w_addr, wdata: w_data, raddr: r_addr, rdata: r_data.data } + } +} + +// Tests +const TEST_ID_W = u32:4; +const TEST_ADDR_W = config::CSR_ADDR_W; +const TEST_DATA_W = config::CSR_DATA_W; +const TEST_STRB_W = config::CSR_DATA_W / u32:8; +const TEST_REGS_N = config::CSR_REGS_N; + +#[test_proc] +proc test_axi_csr { + aw_ch: chan> out; + w_ch: chan> out; + b_ch: chan> in; + ar_ch: chan> out; + r_ch: chan> in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (aw_req_s, aw_req_r) = chan>; + let (w_req_s, w_req_r) = chan>; + let (b_req_s, b_req_r) = chan>; + let (ar_ch_s, ar_ch_r) = chan>; + let (r_ch_s, r_ch_r) = chan>; + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn AxiCsr( + aw_req_r, w_req_r, b_req_s, ar_ch_r, r_ch_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + aw_req_s, w_req_s, b_req_r, ar_ch_s, r_ch_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { () } + + next(tok: token, state: ()) { + let id = uN[TEST_ID_W]:0; + + // Write to all CSRs + for (i, tok): (u32, token) in u32:0..TEST_REGS_N { + let addr = (i << u32:2) as uN[TEST_ADDR_W]; + let data = (u32:0x00000033 + i) as uN[TEST_DATA_W]; + + let aw = axi_pkg::simpleAxiAwBundle(addr, id); + let w = axi_pkg::simpleAxiWBundle(data); + + let tok = send(tok, aw_ch, aw); + let tok = send(tok, w_ch, w); + let (tok, b_resp) = recv(tok, b_ch); + assert_eq(b_resp.bresp, axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY); + (tok) + }(tok); + + // Read all values and compare with writes + for (i, tok): (u32, token) in u32:0..TEST_REGS_N { + let addr = (i << u32:2) as uN[TEST_ADDR_W]; + let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:0); + let tok = send(tok, ar_ch, ar); + let (tok, rcv) = recv(tok, r_ch); + assert_eq(rcv.rdata, (u32:0x00000033 + i) as uN[TEST_DATA_W]); + (tok) + }(tok); + + let tok = send(tok, terminator, true); + } +} + +// Verilog example +proc axi_csr { + config(aw_ch: chan> in, + w_ch: chan> in, + b_ch: chan> out, + ar_ch: chan> in, + r_ch: chan> out, + ch_writer_start: chan out, + ch_writer_configuration: chan> out, + ch_writer_busy: chan in, ch_writer_done: chan in, ch_reader_start: chan out, + ch_reader_configuration: chan> out, + ch_reader_busy: chan in, ch_reader_done: chan in, reader_sync_req: chan<()> in, + reader_sync_rsp: chan<()> out, writer_sync_req: chan<()> in, + writer_sync_rsp: chan<()> out) { + spawn AxiCsr< + config::TOP_ID_W, config::TOP_ADDR_W, config::TOP_DATA_W, config::TOP_STRB_W, config::TOP_REGS_N>( + aw_ch, w_ch, b_ch, ar_ch, r_ch, ch_writer_start, ch_writer_configuration, + ch_writer_busy, ch_writer_done, ch_reader_start, ch_reader_configuration, + ch_reader_busy, ch_reader_done, reader_sync_req, reader_sync_rsp, writer_sync_req, + writer_sync_rsp); + () + } + + init { () } + + next(tok: token, state: ()) { } +} diff --git a/xls/modules/dma/bus/axi_pkg.x b/xls/modules/dma/bus/axi_pkg.x new file mode 100644 index 0000000000..56073123b9 --- /dev/null +++ b/xls/modules/dma/bus/axi_pkg.x @@ -0,0 +1,198 @@ +// Copyright 2023-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 Package + +import std; + +pub enum AXI_AXSIZE_ENCODING : 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 AXI_WRITE_RESPONSE_CODES : u3 { + OKAY = 0, + EXOKAY = 1, + SLVERR = 2, + DECERR = 3, + DEFER = 4, + TRANSFAULT = 5, + RESERVED = 6, + UNSUPPORTED = 7, +} + +pub enum AXI_READ_RESPONSE_CODES : u3 { + OKAY = 0, + EXOKAY = 1, + SLVERR = 2, + DECERR = 3, + PREFETCHED = 4, + TRANSFAULT = 5, + OKAYDIRTY = 6, + RESERVED = 7, +} + +pub enum AXI_AXBURST_ENCODING : u2 { + FIXED = 0, + INCR = 1, + WRAP = 2, + RESERVED = 3, +} + +pub enum AXI_AWCACHE_ENCODING : 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 AXI_ARCACHE_ENCODING : 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 AxiAwBundle { + awid: uN[ID_W], + awaddr: uN[ADDR_W], + awsize: AXI_AXSIZE_ENCODING, + awlen: uN[8], + awburst: AXI_AXBURST_ENCODING, +} + +pub struct AxiWBundle { wdata: uN[DATA_W], wstrb: uN[STRB_W], wlast: u1 } + +pub struct AxiBBundle { bresp: AXI_WRITE_RESPONSE_CODES, bid: uN[ID_W] } + +pub fn simpleAxiAwBundle(addr: uN[ADDR_W], id: uN[ID_W]) -> AxiAwBundle { + AxiAwBundle { + awid: id, + awaddr: addr, + awsize: AXI_AXSIZE_ENCODING::MAX_8B_TRANSFER, + awlen: uN[8]:0, + awburst: AXI_AXBURST_ENCODING::FIXED + } +} + +// TODO: #984, zero! does not work with parametric types +pub fn zeroAxiAwBundle() -> AxiAwBundle { + AxiAwBundle { + awid: uN[ID_W]:0, + awaddr: uN[ADDR_W]:0, + awsize: AXI_AXSIZE_ENCODING::MAX_8B_TRANSFER, + awlen: u8:0, + awburst: AXI_AXBURST_ENCODING::FIXED + } +} + +pub fn simpleAxiWBundle(data: uN[DATA_W]) -> AxiWBundle { + let strb = std::unsigned_max_value(); + AxiWBundle { wdata: data, wstrb: strb, wlast: u1:0 } +} + +// TODO: #984, zero! does not work with parametric types +pub fn zeroAxiWBundle() -> AxiWBundle { + AxiWBundle { wdata: uN[DATA_W]:0, wstrb: uN[STRB_W]:0, wlast: u1:0 } +} + +pub fn simpleAxiBBundle() -> AxiBBundle { + AxiBBundle { bresp: AXI_WRITE_RESPONSE_CODES::OKAY, bid: uN[ID_W]:0 } +} + +// TODO: #984, zero! does not work with parametric types +pub fn zeroAxiBBundle() -> AxiBBundle { + AxiBBundle { bresp: AXI_WRITE_RESPONSE_CODES::OKAY, bid: uN[ID_W]:0 } +} + +pub struct AxiArBundle { + arid: uN[ID_W], + araddr: uN[ADDR_W], + arregion: uN[4], + arlen: uN[8], + arsize: AXI_AXSIZE_ENCODING, + arburst: AXI_AXBURST_ENCODING, + arcache: AXI_ARCACHE_ENCODING, + arprot: uN[3], + arqos: uN[4], +} + +pub struct AxiRBundle { + rid: uN[ID_W], + rdata: uN[DATA_W], + rresp: AXI_READ_RESPONSE_CODES, + rlast: uN[1], +} + +pub fn simpleAxiArBundle + (addr: uN[ADDR_W], id: uN[ID_W], arlen: u8) -> AxiArBundle { + AxiArBundle { + arid: id, + araddr: addr, + arregion: uN[4]:0, + arlen, + arsize: AXI_AXSIZE_ENCODING::MAX_1B_TRANSFER, + arburst: AXI_AXBURST_ENCODING::FIXED, + arcache: AXI_ARCACHE_ENCODING::DEV_NO_BUF, + arprot: uN[3]:0, + arqos: uN[4]:0 + } +} + +// TODO: #984, zero! does not work with parametric types +pub fn zeroAxiArBundle() -> AxiArBundle { + AxiArBundle { + arid: uN[ID_W]:0, + araddr: uN[ADDR_W]:0, + arregion: uN[4]:0, + arlen: uN[8]:0, + arsize: AXI_AXSIZE_ENCODING::MAX_1B_TRANSFER, + arburst: AXI_AXBURST_ENCODING::FIXED, + arcache: AXI_ARCACHE_ENCODING::DEV_NO_BUF, + arprot: uN[3]:0, + arqos: uN[4]:0 + } +} + +pub fn simpleAxiRBundle(data: uN[DATA_W], id: uN[ID_W]) -> AxiRBundle { + AxiRBundle { rid: id, rdata: data, rresp: AXI_READ_RESPONSE_CODES::OKAY, rlast: uN[1]:0 } +} + +pub fn zeroAxiRBundle() -> AxiRBundle { + AxiRBundle { + rid: uN[ID_W]:0, rdata: uN[DATA_W]:0, rresp: AXI_READ_RESPONSE_CODES::OKAY, rlast: uN[1]:0 + } +} diff --git a/xls/modules/dma/bus/axi_st_pkg.x b/xls/modules/dma/bus/axi_st_pkg.x new file mode 100644 index 0000000000..3e9fca4b53 --- /dev/null +++ b/xls/modules/dma/bus/axi_st_pkg.x @@ -0,0 +1,48 @@ +// Copyright 2023-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 Package + +pub struct AxiStreamBundle { + tdata: uN[DATA_W], + tstr: uN[DATA_W_DIV8], + tkeep: uN[DATA_W_DIV8], + tlast: u1, + tid: uN[ID_W], + tdest: uN[DEST_W], +} + +pub fn simpleAxiStreamBundle + (data: uN[DATA_W]) -> AxiStreamBundle { + AxiStreamBundle { + tdata: data, + tstr: uN[DATA_W_DIV8]:0, + tkeep: uN[DATA_W_DIV8]:0, + tlast: u1:0, + tid: uN[ID_W]:0, + tdest: uN[DEST_W]:0 + } +} + +pub fn zeroAxiStreamBundle + () -> AxiStreamBundle { + AxiStreamBundle { + tdata: uN[DATA_W]:0, + tstr: uN[DATA_W_DIV8]:0, + tkeep: uN[DATA_W_DIV8]:0, + tlast: u1:0, + tid: uN[ID_W]:0, + tdest: uN[DEST_W]:0 + } +} diff --git a/xls/modules/dma/common.x b/xls/modules/dma/common.x new file mode 100644 index 0000000000..9151aa5d41 --- /dev/null +++ b/xls/modules/dma/common.x @@ -0,0 +1,37 @@ +// Copyright 2023-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. + +// Common + +pub struct TransferDescBundle { address: uN[ADDR_W], length: uN[ADDR_W] } + +pub struct MainCtrlBundle { + start_address: uN[ADDR_W], + line_count: uN[ADDR_W], + line_length: uN[ADDR_W], + line_stride: uN[ADDR_W], +} + +pub fn zeroTransferDescBundle() -> TransferDescBundle { + TransferDescBundle { address: uN[ADDR_W]:0, length: uN[ADDR_W]:0 } +} + +pub fn zeroMainCtrlBundle() -> MainCtrlBundle { + MainCtrlBundle { + start_address: uN[ADDR_W]:0, + line_count: uN[ADDR_W]:0, + line_length: uN[ADDR_W]:0, + line_stride: uN[ADDR_W]:0 + } +} diff --git a/xls/modules/dma/config.x b/xls/modules/dma/config.x new file mode 100644 index 0000000000..5b1119605e --- /dev/null +++ b/xls/modules/dma/config.x @@ -0,0 +1,48 @@ +// Copyright 2023-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. + +// Configuration + +import std; +import xls.modules.dma.bus.axi_pkg; + +// Top config (used in synthesis) +pub const TOP_ADDR_W = u32:32; +pub const TOP_DATA_W = u32:32; +pub const TOP_DATA_W_DIV8 = TOP_DATA_W / u32:8; +pub const TOP_DEST_W = TOP_DATA_W / u32:8; +pub const TOP_ID_W = TOP_DATA_W / u32:8; +pub const TOP_REGS_N = u32:14; +pub const TOP_STRB_W = TOP_DATA_W / u32:8; + +// CSR Config +pub const CSR_DATA_W = u32:32; +pub const CSR_ADDR_W = u32:32; +pub const CSR_REGS_N = u32:14; + +// Register Map +pub const CONTROL_REGISTER = uN[CSR_ADDR_W]:0x00; +pub const STATUS_REGISTER = uN[CSR_ADDR_W]:0x01; +pub const INTERRUPT_MASK_REGISTER = uN[CSR_ADDR_W]:0x02; +pub const INTERRUPT_STATUS_REGISTER = uN[CSR_ADDR_W]:0x03; +pub const READER_START_ADDRESS = uN[CSR_ADDR_W]:0x04; +pub const READER_LINE_LENGTH = uN[CSR_ADDR_W]:0x05; +pub const READER_LINE_COUNT = uN[CSR_ADDR_W]:0x06; +pub const READER_STRIDE_BETWEEN_LINES = uN[CSR_ADDR_W]:0x07; +pub const WRITER_START_ADDRESS = uN[CSR_ADDR_W]:0x08; +pub const WRITER_LINE_LENGTH = uN[CSR_ADDR_W]:0x09; +pub const WRITER_LINE_COUNT = uN[CSR_ADDR_W]:0x0a; +pub const WRITER_STRIDE_BETWEEN_LINES = uN[CSR_ADDR_W]:0x0b; +pub const VERSION_REGISTER = uN[CSR_ADDR_W]:0x0c; +pub const CONFIGURATION_REGISTER = uN[CSR_ADDR_W]:0x0d; diff --git a/xls/modules/dma/csr.x b/xls/modules/dma/csr.x new file mode 100644 index 0000000000..8f2049dea0 --- /dev/null +++ b/xls/modules/dma/csr.x @@ -0,0 +1,1233 @@ +// Copyright 2023-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. + +// Generic Register File + +import std; +import xls.modules.dma.config; +import xls.modules.dma.common; + +// FIXME: casting imported types is a workaround +// https://github.com/google/xls/issues/1030 +type MainCtrlBundle = common::MainCtrlBundle; + +pub struct ReadReq { addr: uN[ADDR_W] } + +pub struct ReadResp { data: uN[DATA_W] } + +pub struct WriteReq { addr: uN[ADDR_W], data: uN[DATA_W] } + +pub struct WriteResp {} + +pub fn WriteWordReq(addr: uN[ADDR_W], data: uN[DATA_W]) -> WriteReq { + WriteReq { addr, data } +} + +pub fn ReadWordReq(addr: uN[ADDR_W]) -> ReadReq { ReadReq { addr } } + +#[test] +fn TestWrite() { + assert_eq(WriteWordReq(u8:0x34, u16:0x1234), WriteReq { addr: u8:0x34, data: u16:0x1234 }) +} + +#[test] +fn TestRead() { assert_eq(ReadWordReq(u8:0x34), ReadReq { addr: u8:0x34 }) } + +pub fn ptrace(registers: uN[DATA_W][REGS_N]) -> () { + trace_fmt!("------------------------------------"); + trace_fmt!("CONTROL_REGISTER 0x00 {:b}", registers[0]); + trace_fmt!("STATUS_REGISTER 0x01 {:b}", registers[1]); + trace_fmt!("INTERRUPT_MASK_REGISTER 0x02 {:b}", registers[2]); + trace_fmt!("INTERRUPT_STATUS_REGISTER 0x03 {:b}", registers[3]); + trace_fmt!("READER_START_ADDRESS 0x04 {:x}", registers[4]); + trace_fmt!("READER_LINE_LENGTH 0x05 {:x}", registers[5]); + trace_fmt!("READER_LINE_COUNT 0x06 {:x}", registers[6]); + trace_fmt!("READER_STRIDE_BETWEEN_LINES 0x07 {:x}", registers[7]); + trace_fmt!("WRITER_START_ADDRESS 0x08 {:x}", registers[8]); + trace_fmt!("WRITER_LINE_LENGTH 0x09 {:x}", registers[9]); + trace_fmt!("WRITER_LINE_COUNT 0x0A {:x}", registers[10]); + trace_fmt!("WRITER_STRIDE_BETWEEN_LINES 0x0B {:x}", registers[11]); + trace_fmt!("VERSION_REGISTER 0x0C {:x}", registers[12]); + trace_fmt!("CONFIGURATION_REGISTER 0x0D {:x}", registers[13]); + trace_fmt!("------------------------------------"); +} + +struct CsrState { + register_file: uN[DATA_W][REGS_N], + writer_sync: u1, + writer_wait: u1, + reader_sync: u1, + reader_wait: u1, +} + +// TODO: If software writes '1' to interrupt register bit, the bit should be cleared +// TODO: Feature to synchronize to external clock signal is missing +proc Csr { + read_req: chan> in; + read_resp: chan> out; + write_req: chan> in; + write_resp: chan out; + ch_writer_start: chan out; + ch_writer_configuration: chan> out; + ch_writer_busy: chan in; + ch_writer_done: chan in; + ch_reader_start: chan out; + ch_reader_configuration: chan> out; + ch_reader_busy: chan in; + ch_reader_done: chan in; + reader_sync_req: chan<()> in; + reader_sync_rsp: chan<()> out; + writer_sync_req: chan<()> in; + writer_sync_rsp: chan<()> out; + + config(read_req: chan> in, read_resp: chan> out, + write_req: chan> in, write_resp: chan out, + ch_writer_start: chan out, ch_writer_configuration: chan> out, + ch_writer_busy: chan in, ch_writer_done: chan in, ch_reader_start: chan out, + ch_reader_configuration: chan> out, ch_reader_busy: chan in, + ch_reader_done: chan in, reader_sync_req: chan<()> in, reader_sync_rsp: chan<()> out, + writer_sync_req: chan<()> in, writer_sync_rsp: chan<()> out) { + ( + read_req, read_resp, write_req, write_resp, ch_writer_start, ch_writer_configuration, + ch_writer_busy, ch_writer_done, ch_reader_start, ch_reader_configuration, + ch_reader_busy, ch_reader_done, reader_sync_req, reader_sync_rsp, writer_sync_req, + writer_sync_rsp, + ) + } + + init { + (CsrState { + register_file: uN[DATA_W][REGS_N]:[uN[DATA_W]:0, ...], + writer_sync: u1:0, + reader_sync: u1:0, + writer_wait: u1:0, + reader_wait: u1:0 + }) + } + + next(tok: token, state: CsrState) { + trace!(state.register_file); + trace!(state.writer_sync); + trace!(state.writer_wait); + trace!(state.reader_sync); + trace!(state.reader_wait); + // Handle read request + let zero_read_req = ReadReq { addr: uN[ADDR_W]:0 }; + let (tok, read_req, read_req_valid) = recv_non_blocking(tok, read_req, zero_read_req); + + // Handle write request + let zero_write_req = WriteReq { addr: uN[ADDR_W]:0, data: uN[DATA_W]:0 }; + let (tok, write_req, write_req_valid) = recv_non_blocking(tok, write_req, zero_write_req); + + // Handle read response + let read_value = state.register_file[read_req.addr]; + let read_resp_value = ReadResp { data: read_value }; + let tok = send_if(tok, read_resp, read_req_valid, read_resp_value); + + // Handle write response + let next_register_file = if write_req_valid { + update(state.register_file, write_req.addr, write_req.data) + } else { + state.register_file + }; + let tok = send_if(tok, write_resp, write_req_valid, WriteResp {}); + + // Interface to Address Generator + + // Writer: External synchronization + let (tok, _, writer_sync_valid) = recv_non_blocking(tok, writer_sync_req, ()); + let tok = send_if(tok, writer_sync_rsp, writer_sync_valid, ()); + + let next_writer_sync = if writer_sync_valid { u1:1 } else { state.writer_sync }; + + let next_writer_wait = if state.register_file[config::CONTROL_REGISTER][0:1] { + u1:1 + } else { + state.writer_wait + }; + + // Writer: Transfer + let start_write_transfer = if state.register_file[config::CONTROL_REGISTER][2:3] { + state.register_file[config::CONTROL_REGISTER][0:1] + } else { + state.writer_wait & state.writer_sync + }; + + let (next_writer_sync, next_writer_wait) = if state.writer_wait && state.writer_sync { + (u1:0, u1:0) + } else { + (next_writer_sync, next_writer_wait) + }; + + // Writer: Transfer configuration + let tok = send(tok, ch_writer_start, start_write_transfer); + let writer_ctrl_bundle = MainCtrlBundle { + start_address: state.register_file[config::WRITER_START_ADDRESS] as uN[ADDR_W], + line_count: state.register_file[config::WRITER_LINE_LENGTH] as uN[ADDR_W], + line_length: state.register_file[config::WRITER_LINE_COUNT] as uN[ADDR_W], + line_stride: state.register_file[config::WRITER_STRIDE_BETWEEN_LINES] as uN[ADDR_W] + }; + let tok = send(tok, ch_writer_configuration, writer_ctrl_bundle); + + // Writer: Status busy + let (tok, writer_busy, writer_busy_valid) = recv_non_blocking(tok, ch_writer_busy, u1:0); + let next_register_file = if writer_busy_valid { + let status_register = next_register_file[config::STATUS_REGISTER]; + // Bit 0 is "Writer busy" + let status_register = bit_slice_update(status_register, uN[ADDR_W]:0, writer_busy); + update(next_register_file, config::STATUS_REGISTER, status_register) + } else { + next_register_file + }; + + // Writer: Interrupt + let (tok, writer_done, writer_done_valid) = recv_non_blocking(tok, ch_writer_done, u1:0); + let next_register_file = if writer_done_valid { + let irq_status_register = next_register_file[config::INTERRUPT_STATUS_REGISTER]; + // Bit 0 is "writer done" + let irq_status_register = + bit_slice_update(irq_status_register, uN[ADDR_W]:0, writer_done); + update(next_register_file, config::INTERRUPT_STATUS_REGISTER, irq_status_register) + } else { + next_register_file + }; + + // reader: External synchronization + let (tok, _, reader_sync_valid) = recv_non_blocking(tok, reader_sync_req, ()); + let tok = send_if(tok, reader_sync_rsp, reader_sync_valid, ()); + + let next_reader_sync = if reader_sync_valid { u1:1 } else { state.reader_sync }; + + let next_reader_wait = if state.register_file[config::CONTROL_REGISTER][1:2] { + u1:1 + } else { + state.reader_wait + }; + + // reader: Transfer + let start_read_transfer = if state.register_file[config::CONTROL_REGISTER][3:4] { + state.register_file[config::CONTROL_REGISTER][1:2] + } else { + state.reader_wait & state.reader_sync + }; + + let (next_reader_sync, next_reader_wait) = if state.reader_wait && state.reader_sync { + (u1:0, u1:0) + } else { + (next_reader_sync, next_reader_wait) + }; + + // Reader: Transfer configuration + let tok = send(tok, ch_reader_start, start_read_transfer); + let reader_ctrl_bundle = MainCtrlBundle { + start_address: state.register_file[config::READER_START_ADDRESS] as uN[ADDR_W], + line_count: state.register_file[config::READER_LINE_LENGTH] as uN[ADDR_W], + line_length: state.register_file[config::READER_LINE_COUNT] as uN[ADDR_W], + line_stride: state.register_file[config::READER_STRIDE_BETWEEN_LINES] as uN[ADDR_W] + }; + let tok = send(tok, ch_reader_configuration, reader_ctrl_bundle); + + // Reader: Status busy + let (tok, reader_busy, reader_busy_valid) = recv_non_blocking(tok, ch_reader_busy, u1:0); + let next_register_file = if reader_busy_valid { + let status_register = next_register_file[config::STATUS_REGISTER]; + // Bit 1 is "reader busy" + let status_register = bit_slice_update(status_register, uN[ADDR_W]:1, reader_busy); + update(next_register_file, config::STATUS_REGISTER, status_register) + } else { + next_register_file + }; + + // Reader: Interrupt + let (tok, reader_done, reader_done_valid) = recv_non_blocking(tok, ch_reader_done, u1:0); + let next_register_file = if reader_done_valid { + let irq_status_register = next_register_file[config::INTERRUPT_STATUS_REGISTER]; + // Bit 1 is "reader done" + let irq_status_register = + bit_slice_update(irq_status_register, uN[ADDR_W]:1, reader_done); + update(next_register_file, config::INTERRUPT_STATUS_REGISTER, irq_status_register) + } else { + next_register_file + }; + + // Common: Clear start bit unless in loop-mode + let control_register = state.register_file[config::CONTROL_REGISTER]; + let is_writer_loop_mode = control_register[4:5]; + let is_reader_loop_mode = control_register[5:6]; + // + let next_register_file = if !is_writer_loop_mode & control_register[0:1] { + let control_register = next_register_file[config::CONTROL_REGISTER]; + let control_register = bit_slice_update(control_register, uN[ADDR_W]:0, u1:0); + update(next_register_file, config::CONTROL_REGISTER, control_register) + } else { + next_register_file + }; + // + let next_register_file = if !is_reader_loop_mode & control_register[1:2] { + let control_register = next_register_file[config::CONTROL_REGISTER]; + let control_register = bit_slice_update(control_register, uN[ADDR_W]:1, u1:0); + update(next_register_file, config::CONTROL_REGISTER, control_register) + } else { + next_register_file + }; + + // State + CsrState { + register_file: next_register_file, + writer_sync: next_writer_sync, + reader_sync: next_reader_sync, + writer_wait: next_writer_wait, + reader_wait: next_reader_wait + } + } +} + +// Test writing and reading from CSRs + +// Important notes for test! +// 1. +// Processing is selected with a CTRL_WORD: +// 000011 = 3 - in single mode +// 001111 = 15 - in single mode, disable sync +// 110011 = 51 - in loop mode +// 111111 = 63 - in loop mode, disable sync +// 2. +// TODO: Improve signalling in SingleMode tests +// The state variable in this test: +// - increments after initial config +// - increments with each transfer +// - increases by 10 with each `next` cycle without a transfer +// In Single Mode, there is only 1 valid transfer, so we expect the state to be 2, +// however, there will be some cycles without transfers. +// The value of 1200 is set to let the test execute 120 `next` cycles, +// which is sufficient for current implementation. + +const TEST_ADDR_W = config::CSR_ADDR_W; +const TEST_DATA_W = config::CSR_DATA_W; +const TEST_REGS_W = config::CSR_DATA_W; +const TEST_REGS_N = config::CSR_REGS_N; + +type AdrDatPair = (uN[TEST_ADDR_W], uN[TEST_DATA_W]); + +#[test_proc] +proc TestReadWrite { + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + read_req_s, read_resp_r, write_req_s, write_resp_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { () } + + next(tok: token, state: ()) { + // Write to memory + // Add 4 to data so that 2 LSB are not used (they reset themselves) + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_REGS_N) { + let address = i as uN[TEST_ADDR_W]; + let data = (address + uN[TEST_ADDR_W]:4) as uN[TEST_DATA_W]; + let tok = send(tok, write_req, WriteWordReq(address, data)); + let (tok, _) = recv(tok, write_resp); + tok + }(tok); + + // Read from memory + let tok = for (i, tok): (u32, token) in range(u32:0, TEST_REGS_N) { + let address = i as uN[TEST_ADDR_W]; + let data = (address + uN[TEST_ADDR_W]:4) as uN[TEST_DATA_W]; + let tok = send(tok, read_req, ReadWordReq(address)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(data, read_data.data as uN[TEST_DATA_W]); + tok + }(tok); + + let tok = send(tok, terminator, true); + } +} + +#[test_proc] +proc TestLoopMode { + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + read_req_s, read_resp_r, write_req_s, write_resp_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { (u32:0) } + + next(tok: token, state: u32) { + let CTRL_WORD = uN[TEST_DATA_W]:51; + // let CTRL_WORD = uN[TEST_DATA_W]:63; + + let state = if state == u32:0 { + // Prepare configs + let rw_config = MainCtrlBundle { + start_address: u32:0x1000, + line_count: u32:0x64, + line_length: u32:0x01, + line_stride: u32:0x00 + }; + + let init_csr_values = AdrDatPair[u32:8]:[ + (config::READER_START_ADDRESS, rw_config.start_address), + (config::READER_LINE_LENGTH, rw_config.line_length), + (config::READER_LINE_COUNT, rw_config.line_count), + (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::WRITER_START_ADDRESS, rw_config.start_address), + (config::WRITER_LINE_LENGTH, rw_config.line_length), + (config::WRITER_LINE_COUNT, rw_config.line_count), + (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + ]; + + for (i, tok): (u32, token) in u32:0..u32:8 { + let tok = send( + tok, write_req, WriteWordReq((init_csr_values[i]).0, (init_csr_values[i]).1)); + let (tok, _) = recv(tok, write_resp); + (tok) + }(tok); + + // Status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + // Enable interrupts + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3)); + let (tok, _) = recv(tok, write_resp); + + // Interrupt status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + let tok = send(tok, write_req, WriteWordReq(config::CONTROL_REGISTER, CTRL_WORD)); + let (tok, _) = recv(tok, write_resp); + u32:1 + } else { + state + }; + + if CTRL_WORD[2:4] == u2:0 { + // Synchronize to external + let tok = send(tok, reader_sync_req, ()); + let (tok, _) = recv(tok, reader_sync_rsp); + + let tok = send(tok, writer_sync_req, ()); + let (tok, _) = recv(tok, writer_sync_rsp); + } else { + + + }; + + // AG receives the config + let (tok, do_writer_start, do_writer_start_valid) = + recv_non_blocking(tok, ch_writer_start, u1:0); + let (tok, do_reader_start, do_reader_start_valid) = + recv_non_blocking(tok, ch_reader_start, u1:0); + + let state = if do_writer_start_valid && do_writer_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_writer_configuration); + let tok = send(tok, ch_writer_busy, u1:1); + let tok = send(tok, ch_writer_done, u1:1); + + // Status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, status.data as uN[TEST_ADDR_W]); + + let tok = send(tok, ch_writer_busy, u1:0); + + // Interrupt status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + + state + u32:1 + } else { + state + u32:10 + }; + + let state = if do_reader_start_valid && do_reader_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_reader_configuration); + + // AG sets busy + let tok = send(tok, ch_reader_busy, u1:1); + + // AG sets done + let tok = send(tok, ch_reader_done, u1:1); + + // Status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, status.data as uN[TEST_ADDR_W]); + + // AG clears busy status + let tok = send(tok, ch_reader_busy, u1:0); + + // Interrupt status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + state + u32:1 + } else { + state + u32:10 + }; + + let do_terminate = state > u32:3; + let tok = send_if(tok, terminator, do_terminate, do_terminate); + state + } +} + +#[test_proc] +proc TestLoopModeDisableSync { + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + read_req_s, read_resp_r, write_req_s, write_resp_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { (u32:0) } + + next(tok: token, state: u32) { + // let CTRL_WORD = uN[TEST_DATA_W]:51; + let CTRL_WORD = uN[TEST_DATA_W]:63; + + let state = if state == u32:0 { + // Prepare configs + let rw_config = MainCtrlBundle { + start_address: u32:0x1000, + line_count: u32:0x64, + line_length: u32:0x01, + line_stride: u32:0x00 + }; + + let init_csr_values = AdrDatPair[u32:8]:[ + (config::READER_START_ADDRESS, rw_config.start_address), + (config::READER_LINE_LENGTH, rw_config.line_length), + (config::READER_LINE_COUNT, rw_config.line_count), + (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::WRITER_START_ADDRESS, rw_config.start_address), + (config::WRITER_LINE_LENGTH, rw_config.line_length), + (config::WRITER_LINE_COUNT, rw_config.line_count), + (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + ]; + + for (i, tok): (u32, token) in u32:0..u32:8 { + let tok = send( + tok, write_req, WriteWordReq((init_csr_values[i]).0, (init_csr_values[i]).1)); + let (tok, _) = recv(tok, write_resp); + (tok) + }(tok); + + // Status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + // Enable interrupts + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3)); + let (tok, _) = recv(tok, write_resp); + + // Interrupt status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + let tok = send(tok, write_req, WriteWordReq(config::CONTROL_REGISTER, CTRL_WORD)); + let (tok, _) = recv(tok, write_resp); + u32:1 + } else { + state + }; + + if CTRL_WORD[2:4] == u2:0 { + // Synchronize to external + let tok = send(tok, reader_sync_req, ()); + let (tok, _) = recv(tok, reader_sync_rsp); + + let tok = send(tok, writer_sync_req, ()); + let (tok, _) = recv(tok, writer_sync_rsp); + } else { + + + }; + + // AG receives the config + let (tok, do_writer_start, do_writer_start_valid) = + recv_non_blocking(tok, ch_writer_start, u1:0); + let (tok, do_reader_start, do_reader_start_valid) = + recv_non_blocking(tok, ch_reader_start, u1:0); + + let state = if do_writer_start_valid && do_writer_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_writer_configuration); + let tok = send(tok, ch_writer_busy, u1:1); + let tok = send(tok, ch_writer_done, u1:1); + + // Status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, status.data as uN[TEST_ADDR_W]); + + let tok = send(tok, ch_writer_busy, u1:0); + + // Interrupt status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + + state + u32:1 + } else { + state + u32:10 + }; + + let state = if do_reader_start_valid && do_reader_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_reader_configuration); + + // AG sets busy + let tok = send(tok, ch_reader_busy, u1:1); + + // AG sets done + let tok = send(tok, ch_reader_done, u1:1); + + // Status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, status.data as uN[TEST_ADDR_W]); + + // AG clears busy status + let tok = send(tok, ch_reader_busy, u1:0); + + // Interrupt status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + state + u32:1 + } else { + state + u32:10 + }; + + let do_terminate = state > u32:3; + let tok = send_if(tok, terminator, do_terminate, do_terminate); + state + } +} + +#[test_proc] +proc TestSingleMode { + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + read_req_s, read_resp_r, write_req_s, write_resp_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { (u32:0) } + + next(tok: token, state: u32) { + let CTRL_WORD = uN[TEST_DATA_W]:3; + // let CTRL_WORD = uN[TEST_DATA_W]:15; + + let state = if state == u32:0 { + // Prepare configs + let rw_config = MainCtrlBundle { + start_address: u32:0x1000, + line_count: u32:0x64, + line_length: u32:0x01, + line_stride: u32:0x00 + }; + + let init_csr_values = AdrDatPair[u32:8]:[ + (config::READER_START_ADDRESS, rw_config.start_address), + (config::READER_LINE_LENGTH, rw_config.line_length), + (config::READER_LINE_COUNT, rw_config.line_count), + (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::WRITER_START_ADDRESS, rw_config.start_address), + (config::WRITER_LINE_LENGTH, rw_config.line_length), + (config::WRITER_LINE_COUNT, rw_config.line_count), + (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + ]; + + for (i, tok): (u32, token) in u32:0..u32:8 { + let tok = send( + tok, write_req, WriteWordReq((init_csr_values[i]).0, (init_csr_values[i]).1)); + let (tok, _) = recv(tok, write_resp); + (tok) + }(tok); + + // Status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + // Enable interrupts + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3)); + let (tok, _) = recv(tok, write_resp); + + // Interrupt status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + let tok = send(tok, write_req, WriteWordReq(config::CONTROL_REGISTER, CTRL_WORD)); + let (tok, _) = recv(tok, write_resp); + + if CTRL_WORD[2:4] == u2:0 { + trace_fmt!("SYNC TO EXTERNAL"); + // Synchronize to external + let tok = send(tok, reader_sync_req, ()); + let (tok, _) = recv(tok, reader_sync_rsp); + + let tok = send(tok, writer_sync_req, ()); + let (tok, _) = recv(tok, writer_sync_rsp); + } else { + + + }; + + u32:1 + } else { + state + }; + + // AG receives the config + let (tok, do_writer_start, do_writer_start_valid) = + recv_non_blocking(tok, ch_writer_start, u1:0); + let (tok, do_reader_start, do_reader_start_valid) = + recv_non_blocking(tok, ch_reader_start, u1:0); + + let state = if do_writer_start_valid && do_writer_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_writer_configuration); + let tok = send(tok, ch_writer_busy, u1:1); + let tok = send(tok, ch_writer_done, u1:1); + + // Status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, status.data as uN[TEST_ADDR_W]); + + let tok = send(tok, ch_writer_busy, u1:0); + + // Interrupt status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + + state + u32:1 + } else { + state + u32:10 + }; + + let state = if do_reader_start_valid && do_reader_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_reader_configuration); + + // AG sets busy + let tok = send(tok, ch_reader_busy, u1:1); + + // AG sets done + let tok = send(tok, ch_reader_done, u1:1); + + // Status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, status.data as uN[TEST_ADDR_W]); + + // AG clears busy status + let tok = send(tok, ch_reader_busy, u1:0); + + // Interrupt status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + state + u32:1 + } else { + state + u32:10 + }; + + let do_terminate = state == u32:1203; + trace_fmt!("state = {}", state); + let tok = send_if(tok, terminator, do_terminate, do_terminate); + state + } +} + +#[test_proc] +proc TestSingleModeDisableSync { + read_req: chan> out; + read_resp: chan> in; + write_req: chan> out; + write_resp: chan in; + ch_writer_start: chan in; + ch_writer_configuration: chan> in; + ch_writer_busy: chan out; + ch_writer_done: chan out; + ch_reader_start: chan in; + ch_reader_configuration: chan> in; + ch_reader_busy: chan out; + ch_reader_done: chan out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (read_req_s, read_req_r) = chan>; + let (read_resp_s, read_resp_r) = chan>; + let (write_req_s, write_req_r) = chan>; + let (write_resp_s, write_resp_r) = chan; + + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = + chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = + chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + spawn Csr( + read_req_r, read_resp_s, write_req_r, write_resp_s, ch_writer_start_s, + ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, ch_reader_start_s, + ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, reader_sync_req_r, + reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + ( + read_req_s, read_resp_r, write_req_s, write_resp_r, ch_writer_start_r, + ch_writer_configuration_r, ch_writer_busy_s, ch_writer_done_s, ch_reader_start_r, + ch_reader_configuration_r, ch_reader_busy_s, ch_reader_done_s, reader_sync_req_s, + reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { (u32:0) } + + next(tok: token, state: u32) { + // let CTRL_WORD = uN[TEST_DATA_W]:3; + let CTRL_WORD = uN[TEST_DATA_W]:15; + + let state = if state == u32:0 { + // Prepare configs + let rw_config = MainCtrlBundle { + start_address: u32:0x1000, + line_count: u32:0x64, + line_length: u32:0x01, + line_stride: u32:0x00 + }; + + let init_csr_values = AdrDatPair[u32:8]:[ + (config::READER_START_ADDRESS, rw_config.start_address), + (config::READER_LINE_LENGTH, rw_config.line_length), + (config::READER_LINE_COUNT, rw_config.line_count), + (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::WRITER_START_ADDRESS, rw_config.start_address), + (config::WRITER_LINE_LENGTH, rw_config.line_length), + (config::WRITER_LINE_COUNT, rw_config.line_count), + (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + ]; + + for (i, tok): (u32, token) in u32:0..u32:8 { + let tok = send( + tok, write_req, WriteWordReq((init_csr_values[i]).0, (init_csr_values[i]).1)); + let (tok, _) = recv(tok, write_resp); + (tok) + }(tok); + + // Status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + // Enable interrupts + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3)); + let (tok, _) = recv(tok, write_resp); + + // Interrupt status register should be clear + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, read_data) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:0, read_data.data as uN[TEST_ADDR_W]); + + let tok = send(tok, write_req, WriteWordReq(config::CONTROL_REGISTER, CTRL_WORD)); + let (tok, _) = recv(tok, write_resp); + + if CTRL_WORD[2:4] == u2:0 { + trace_fmt!("SYNC TO EXTERNAL"); + // Synchronize to external + let tok = send(tok, reader_sync_req, ()); + let (tok, _) = recv(tok, reader_sync_rsp); + + let tok = send(tok, writer_sync_req, ()); + let (tok, _) = recv(tok, writer_sync_rsp); + } else { + + + }; + + u32:1 + } else { + state + }; + + // AG receives the config + let (tok, do_writer_start, do_writer_start_valid) = + recv_non_blocking(tok, ch_writer_start, u1:0); + let (tok, do_reader_start, do_reader_start_valid) = + recv_non_blocking(tok, ch_reader_start, u1:0); + + let state = if do_writer_start_valid && do_writer_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_writer_configuration); + let tok = send(tok, ch_writer_busy, u1:1); + let tok = send(tok, ch_writer_done, u1:1); + + // Status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, status.data as uN[TEST_ADDR_W]); + + let tok = send(tok, ch_writer_busy, u1:0); + + // Interrupt status register should be 1 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:1, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + + state + u32:1 + } else { + state + u32:10 + }; + + let state = if do_reader_start_valid && do_reader_start { + trace_fmt!("-----"); + let (tok, _) = recv(tok, ch_reader_configuration); + + // AG sets busy + let tok = send(tok, ch_reader_busy, u1:1); + + // AG sets done + let tok = send(tok, ch_reader_done, u1:1); + + // Status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, _) = recv(tok, read_resp); + + let tok = send(tok, read_req, ReadWordReq(config::STATUS_REGISTER)); + let (tok, status) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, status.data as uN[TEST_ADDR_W]); + + // AG clears busy status + let tok = send(tok, ch_reader_busy, u1:0); + + // Interrupt status register should be 2 + let tok = send(tok, read_req, ReadWordReq(config::INTERRUPT_STATUS_REGISTER)); + let (tok, irq) = recv(tok, read_resp); + assert_eq(uN[TEST_ADDR_W]:2, irq.data as uN[TEST_ADDR_W]); + + // Host clears interrupt + let tok = send( + tok, write_req, WriteWordReq(config::INTERRUPT_STATUS_REGISTER, uN[TEST_DATA_W]:0)); + let (tok, _) = recv(tok, write_resp); + state + u32:1 + } else { + state + u32:10 + }; + + let do_terminate = state == u32:1203; + trace_fmt!("state = {}", state); + let tok = send_if(tok, terminator, do_terminate, do_terminate); + state + } +} + +// FIXME: Verilog generation +// Verilog example +proc csr { + config(read_req: chan> in, read_resp: chan> out, + write_req: chan> in, write_resp: chan out, + ch_writer_start: chan out, ch_writer_configuration: chan> out, + ch_writer_busy: chan in, ch_writer_done: chan in, ch_reader_start: chan out, + ch_reader_configuration: chan> out, ch_reader_busy: chan in, + ch_reader_done: chan in, reader_sync_req: chan<()> in, reader_sync_rsp: chan<()> out, + writer_sync_req: chan<()> in, writer_sync_rsp: chan<()> out) { + + spawn Csr( + read_req, read_resp, write_req, write_resp, ch_writer_start, ch_writer_configuration, + ch_writer_busy, ch_writer_done, ch_reader_start, ch_reader_configuration, + ch_reader_busy, ch_reader_done, reader_sync_req, reader_sync_rsp, writer_sync_req, + writer_sync_rsp); + () + } + + init { () } + + next(tok: token, state: ()) { } +} diff --git a/xls/modules/dma/fifo.x b/xls/modules/dma/fifo.x new file mode 100644 index 0000000000..4b4c843a5e --- /dev/null +++ b/xls/modules/dma/fifo.x @@ -0,0 +1,478 @@ +// Copyright 2023-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. + +// FIFO + +import std; +import xls.examples.ram; +import xls.modules.dma.config; +import xls.modules.dma.gpf; +import xls.modules.dma.bus.axi_st_pkg; + +type AxiStreamBundle = axi_st_pkg::AxiStreamBundle; + +struct ReaderState { ptr_r: uN[ADDR_W], is_empty: u1, ptr_w: uN[ADDR_W] } + +struct WriterState { ptr_w: uN[ADDR_W], is_full: u1, ptr_r: uN[ADDR_W] } + +proc FifoRAM { + req_chan0: chan> in; + resp_chan0: chan> out; + req_chan1: chan> in; + wr_comp_chan1: chan<()> out; + wr_comp_chan0: chan<()> in; + resp_chan1: chan> in; + + config(req_chan0: chan> in, + resp_chan0: chan> out, + req_chan1: chan> in, wr_comp_chan1: chan<()> out) { + + let (wr_comp_chan0_s, wr_comp_chan0_r) = chan<()>; + let (resp_chan1_s, resp_chan1_r) = chan>; + + spawn ram::RamModel2RW( + req_chan0, resp_chan0, wr_comp_chan0_s, req_chan1, resp_chan1_s, wr_comp_chan1); + + (req_chan0, resp_chan0, req_chan1, wr_comp_chan1, wr_comp_chan0_r, resp_chan1_r) + } + + init { () } + + next(tok: token, state: ()) { + let (tok, _, _) = recv_non_blocking(tok, wr_comp_chan0, ()); + let zero_rw_ram_resp = ram::RWRamResp { data: uN[DATA_W]:0 }; + let (tok, _, _) = recv_non_blocking(tok, resp_chan1, zero_rw_ram_resp); + } +} + +proc Reader { + ch_read: chan> out; + ch_mem_read_req: chan> out; + ch_mem_read_rsp: chan> in; + ch_ptr_r: chan> out; + ch_ptr_w: chan> in; + + config(ch_read: chan> out, + ch_mem_read_req: chan> out, + ch_mem_read_rsp: chan> in, + ch_ptr_r: chan> out, ch_ptr_w: chan> in) { + (ch_read, ch_mem_read_req, ch_mem_read_rsp, ch_ptr_r, ch_ptr_w) + } + + init { (ReaderState { ptr_r: uN[ADDR_W]:0, is_empty: u1:1, ptr_w: uN[ADDR_W]:0 }) } + + next(tok: token, state: ReaderState) { + trace_fmt!("Reader State = {}", state); + + // Obtain writer state + let tok = send(tok, ch_ptr_r, state); + let (tok, writer_state, update_ptr_w) = + recv_non_blocking(tok, ch_ptr_w, zero!()); + + let ptr_w = if update_ptr_w { writer_state.ptr_w } else { state.ptr_w }; + + // Check state + // let is_almost_empty = (state.ptr_r + uN[ADDR_W]:1) == ptr_w; + let is_empty = (state.ptr_r == ptr_w); //|| is_almost_empty; + + // Fetch data from RAM + let tok = send_if( + tok, ch_mem_read_req, !state.is_empty, + ram::RWRamReq { + addr: state.ptr_r, + data: uN[DATA_W]:0, + write_mask: (), + read_mask: (), + we: false, + re: true + }); + + let zero_rw_ram_resp = ram::RWRamResp { data: uN[DATA_W]:0 }; + let (tok, read_data) = recv_if(tok, ch_mem_read_rsp, !state.is_empty, zero_rw_ram_resp); + + // Push data into stream interface + let tok = send_if( + tok, ch_read, !state.is_empty, + AxiStreamBundle { + tdata: read_data.data, + tstr: uN[DATA_W_DIV8]:0, + tkeep: uN[DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[ID_W]:0, + tdest: uN[DEST_W]:0 + }); + + let ptr_r = if !state.is_empty { state.ptr_r + uN[ADDR_W]:1 } else { state.ptr_r }; + + ReaderState { ptr_r, is_empty, ptr_w } + } +} + +proc Writer { + ch_write: chan> in; + ch_mem_write_req: chan> out; + ch_mem_write_rsp: chan<()> in; + ch_ptr_r: chan> in; + ch_ptr_w: chan> out; + + config(ch_write: chan> in, + ch_mem_write_req: chan> out, + ch_mem_write_rsp: chan<()> in, ch_ptr_r: chan> in, + ch_ptr_w: chan> out) { + (ch_write, ch_mem_write_req, ch_mem_write_rsp, ch_ptr_r, ch_ptr_w) + } + + init { (WriterState { ptr_w: uN[ADDR_W]:0, is_full: u1:0, ptr_r: uN[ADDR_W]:0 }) } + + next(tok: token, state: WriterState) { + trace_fmt!("Writer state = {}", state); + + // Obtain reader state + let tok = send(tok, ch_ptr_w, state); + let (tok, reader_state, update_ptr_r) = + recv_non_blocking(tok, ch_ptr_r, zero!()); + let ptr_r = if update_ptr_r { reader_state.ptr_r } else { state.ptr_r }; + + // Check state + let is_full = (state.ptr_w + uN[ADDR_W]:1) == ptr_r; + + // Check for write requests + let zero_write = AxiStreamBundle { + tdata: uN[DATA_W]:0, + tstr: uN[DATA_W_DIV8]:0, + tkeep: uN[DATA_W_DIV8]:0, + tlast: u1:0, + tid: uN[ID_W]:0, + tdest: uN[DEST_W]:0 + }; + let (tok, write_data) = recv_if(tok, ch_write, !is_full, zero_write); + + // Send write req to RAM + let ram_req = ram::RWRamReq { + addr: state.ptr_w, + data: write_data.tdata, + write_mask: (), + read_mask: (), + we: true, + re: false + }; + let tok = send(tok, ch_mem_write_req, ram_req); + + // Complete write requests + trace_fmt!("Waiting for mem, req = {}", ram_req); + let (tok, _) = recv(tok, ch_mem_write_rsp); + trace_fmt!("Never got response!"); + let ptr_w = state.ptr_w + uN[ADDR_W]:1; + + WriterState { ptr_w, is_full, ptr_r } + } +} + +proc FIFO { + ch_read: chan> out; + ch_write: chan> in; + + config(ch_read: chan> out, + ch_write: chan> in) { + let (ch_mem_read_req_s, ch_mem_read_req_r) = chan>; + let (ch_mem_read_rsp_s, ch_mem_read_rsp_r) = chan>; + let (ch_mem_write_req_s, ch_mem_write_req_r) = chan>; + let (ch_mem_write_rsp_s, ch_mem_write_rsp_r) = chan<()>; + let (ch_ptr_r_s, ch_ptr_r_r) = chan>; + let (ch_ptr_w_s, ch_ptr_w_r) = chan>; + + spawn FifoRAM( + ch_mem_read_req_r, ch_mem_read_rsp_s, ch_mem_write_req_r, ch_mem_write_rsp_s); + + spawn Reader( + ch_read, ch_mem_read_req_s, ch_mem_read_rsp_r, ch_ptr_r_s, ch_ptr_w_r); + + spawn Writer( + ch_write, ch_mem_write_req_s, ch_mem_write_rsp_r, ch_ptr_r_r, ch_ptr_w_s); + + (ch_read, ch_write) + } + + init { () } + + next(tok: token, state: ()) { } +} + +const TEST_0_DATA_W = u32:8; +const TEST_0_DATA_W_DIV8 = u32:1; +const TEST_0_ID_W = u32:1; +const TEST_0_DEST_W = u32:1; +const TEST_0_ADDR_W = u32:4; +const TEST_0_FIFO_L = u32:16; + +const TEST_0_MAX_ITER = u32:73; +const TEST_0_BEGIN = uN[TEST_0_DATA_W]:10; + +pub struct TestState { data: uN[DATA_W], iter: u32, read_counter: u32 } + +#[test_proc] +proc test_fifo { + ch_fifo_read: + chan> in; + ch_fifo_write: + chan> out; + terminator: chan out; + + config(terminator: chan out) { + let (ch_fifo_read_s, ch_fifo_read_r) = + chan>; + let (ch_fifo_write_s, ch_fifo_write_r) = + chan>; + spawn FIFO< + TEST_0_DATA_W, TEST_0_DATA_W_DIV8, TEST_0_ID_W, TEST_0_DEST_W, TEST_0_ADDR_W, TEST_0_FIFO_L>( + ch_fifo_read_s, ch_fifo_write_r); + (ch_fifo_read_r, ch_fifo_write_s, terminator) + } + + init { (TestState { data: TEST_0_BEGIN, iter: u32:0, read_counter: u32:0 }) } + + next(tok: token, state: TestState) { + // Write to FIFO + let write_data = AxiStreamBundle + { + tdata: state.data, + tstr: uN[TEST_0_DATA_W_DIV8]:0, + tkeep: uN[TEST_0_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_0_ID_W]:0, + tdest: uN[TEST_0_DEST_W]:0 + }; + let do_send = state.iter < TEST_0_MAX_ITER; + if do_send { trace_fmt!("Write DATA={}", write_data.tdata); } else { }; + let tok = send_if(tok, ch_fifo_write, do_send, write_data); + + // Read from FIFO + let zero_read_data = AxiStreamBundle + { + tdata: uN[TEST_0_DATA_W]:0, + tstr: uN[TEST_0_DATA_W_DIV8]:0, + tkeep: uN[TEST_0_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_0_ID_W]:0, + tdest: uN[TEST_0_DEST_W]:0 + }; + let (tok, read_data, is_r_valid) = recv_non_blocking(tok, ch_fifo_read, zero_read_data); + + let read_counter = if is_r_valid { + trace_fmt!("Read DATA={}", read_data.tdata); + state.read_counter + u32:1 + } else { + state.read_counter + }; + + if is_r_valid { + assert_eq(read_data.tdata as u32, (TEST_0_BEGIN as u32) + (read_counter - u32:1)); + } else { + + + }; + + // Terminate test? + let terminate = read_counter == TEST_0_MAX_ITER; + if terminate { trace_fmt!("Terminate at: {}", read_counter); } else { }; + let tok = send_if(tok, terminator, terminate, true); + + TestState { + data: state.data + uN[TEST_0_DATA_W]:1, iter: state.iter + u32:1, read_counter + } + } +} + +const TEST_1_DATA_W = u32:8; +const TEST_1_DATA_W_DIV8 = u32:1; +const TEST_1_ID_W = u32:1; +const TEST_1_DEST_W = u32:1; +const TEST_1_ADDR_W = u32:4; +const TEST_1_FIFO_L = u32:16; + +const TEST_1_MAX_ITER = u32:10; +const TEST_1_BEGIN = uN[TEST_1_DATA_W]:10; + +// This test proc affects exeuction of the previous test proc! +// If test_double_fifo_gpf is commented, then test_fifo proc ends succesfully. +// Otherwise: +// [ RUN UNITTEST ] test_fifo +// E1219 13:17:55.614045 108138 run_routines.cc:282] Internal error: DEADLINE_EXCEEDED: Exceeded +// limit of 100000 proc ticks before terminating +// [ FAILED ] test_fifo: internal error: DEADLINE_EXCEEDED: Exceeded limit of 100000 proc +// ticks before terminating +// [ RUN UNITTEST ] test_double_fifo_gpf +// [ OK ] +// [===============] 2 test(s) ran; 1 failed; 0 skipped. + +// FIFO_0 --> GPF --> FIFO_1 + +#[test_proc] +proc test_double_fifo_gpf { + ch_fifo1_read: + chan> in; + ch_fifo0_write: + chan> out; + terminator: chan out; + + config(terminator: chan out) { + let (ch_fifo0_read_s, ch_fifo0_read_r) = + chan>; + let (ch_fifo0_write_s, ch_fifo0_write_r) = + chan>; + + let (ch_fifo1_read_s, ch_fifo1_read_r) = + chan>; + let (ch_fifo1_write_s, ch_fifo1_write_r) = + chan>; + + // Order of `spawn` expressions here matters! + + // Using option 1 + // I trace the state of 2 RAMs in 2 FIFOs + // 1st FIFO: RAM State = [10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 0, 0, 0, 0, 0, 0] + // 2nd FIFO: RAM State = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + // This is almost what I expected whilst developing. There is a bunch of writes to first FIFO + // and I expect that later data will go through the GPF and into the 2nd FIFO. + + // Using option 2 (reverse order of `spawns`) + // I trace the state of 2 RAMs in 2 FIFOs + // 1st FIFO: RAM State = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + // 2nd FIFO: RAM State = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0] + // Both are always empty, not even the first write occurs. + + // I find this behaviour confusing and am not sure how to continue with testing/debugging the + // FIFO + + // Option 1. + // spawn FIFO + // (ch_fifo1_read_s, ch_fifo1_write_r); + + // spawn FIFO + // (ch_fifo0_read_s, ch_fifo0_write_r); + + // Option 2. + spawn FIFO< + TEST_1_DATA_W, TEST_1_DATA_W_DIV8, TEST_1_ID_W, TEST_1_DEST_W, TEST_1_ADDR_W, TEST_1_FIFO_L>( + ch_fifo0_read_s, ch_fifo0_write_r); + + spawn FIFO< + TEST_1_DATA_W, TEST_1_DATA_W_DIV8, TEST_1_ID_W, TEST_1_DEST_W, TEST_1_ADDR_W, TEST_1_FIFO_L>( + ch_fifo1_read_s, ch_fifo1_write_r); + + spawn gpf::gpf( + ch_fifo0_read_r, ch_fifo1_write_s); + + (ch_fifo1_read_r, ch_fifo0_write_s, terminator) + } + + init { (TestState { data: TEST_1_BEGIN, iter: u32:0, read_counter: u32:0 }) } + + next(tok: token, state: TestState) { + // Write to FIFO + let write_data = AxiStreamBundle + { + tdata: state.data, + tstr: uN[TEST_1_DATA_W_DIV8]:0, + tkeep: uN[TEST_1_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_1_ID_W]:0, + tdest: uN[TEST_1_DEST_W]:0 + }; + let do_send = state.iter < TEST_1_MAX_ITER; + if do_send { trace_fmt!("Write DATA={}", write_data.tdata); } else { }; + let tok = send_if(tok, ch_fifo0_write, do_send, write_data); + + // Read from FIFO + let zero_read_data = AxiStreamBundle + { + tdata: uN[TEST_1_DATA_W]:0, + tstr: uN[TEST_1_DATA_W_DIV8]:0, + tkeep: uN[TEST_1_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_1_ID_W]:0, + tdest: uN[TEST_1_DEST_W]:0 + }; + let (tok, read_data, is_r_valid) = recv_non_blocking(tok, ch_fifo1_read, zero_read_data); + + let read_counter = if is_r_valid { + trace_fmt!("Read DATA={}", read_data.tdata); + state.read_counter + u32:1 + } else { + state.read_counter + }; + + if is_r_valid { + assert_eq(read_data.tdata as u32, (TEST_1_BEGIN as u32) + (read_counter - u32:1)); + } else { + + + }; + + // Terminate test? + // let terminate = read_counter == TEST_1_MAX_ITER; + let terminate = state.iter >= u32:50; // Even with 1000 I don't see data in RAM (option 2) + if terminate { trace_fmt!("Terminate at: {}", read_counter); } else { }; + let tok = send_if(tok, terminator, terminate, true); + + TestState { + data: state.data + uN[TEST_1_DATA_W]:1, iter: state.iter + u32:1, read_counter + } + } +} + +// This proc affects exeuction of the previous test proc! +// If commented, `test_fifo` proc ends succesfully. +// If uncommented, this proc causes the 'test_fifo' to end with: +// E1219 13:15:11.058317 106946 run_routines.cc:282] Internal error: DEADLINE_EXCEEDED: Exceeded +// limit of 100000 proc ticks before terminating +// [ FAILED ] test_fifo: internal error: DEADLINE_EXCEEDED: Exceeded limit of 100000 proc +// ticks before terminating + +// Verilog generation +// const SYNTH_0_DATA_W = u32:8; +// const SYNTH_0_DATA_W_DIV8 = u32:1; +// const SYNTH_0_ID_W = u32:1; +// const SYNTH_0_DEST_W = u32:1; +// const SYNTH_0_ADDR_W = u32:4; +// const SYNTH_0_FIFO_L = u32:16; + +// proc fifo_synth { +// config( ch_fifo_read:chan> out, +// ch_fifo_write:chan> in +// ) { +// spawn FIFO< +// SYNTH_0_DATA_W, SYNTH_0_DATA_W_DIV8, SYNTH_0_ID_W, SYNTH_0_DEST_W, SYNTH_0_ADDR_W, +// SYNTH_0_FIFO_L>( +// ch_fifo_read, ch_fifo_write); +// () +// } + +// init { () } + +// next(tok: token, state: ()) { () } +// } + +// This does not affect behavior of previous test +proc fifo_synth_2 { + config() { () } + + init { () } + + next(tok: token, state: ()) { () } +} diff --git a/xls/modules/dma/frontend_reader.x b/xls/modules/dma/frontend_reader.x new file mode 100644 index 0000000000..612a6e7019 --- /dev/null +++ b/xls/modules/dma/frontend_reader.x @@ -0,0 +1,255 @@ +// Copyright 2023-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. + +// Frontend Reader +// +// Part of the main controller, which translates +// (address, length) tuples from the address generator +// into AXI Read Transactions. Data received from AXI +// is written to the AXI Stream interface (expected FIFO). +// + +import std; +import xls.modules.dma.bus.axi_pkg; +import xls.modules.dma.bus.axi_st_pkg; +import xls.modules.dma.common; +import xls.modules.dma.config; + +type TransferDescBundle = common::TransferDescBundle; +type AxiArBundle = axi_pkg::AxiArBundle; +type AxiRBundle = axi_pkg::AxiRBundle; +type AxiStreamBundle = axi_st_pkg::AxiStreamBundle; + +enum FrontendReaderStatusEnum : u3 { + IDLE = 0, + AXI_READ_REQ = 1, + AXI_READ_RSP = 2, + SEND_AXI_ST = 3, +} + +struct FrontendReaderState { + status: FrontendReaderStatusEnum, + transfer_data: TransferDescBundle, + r_bundle: AxiRBundle, + burst_counter: u32, +} + +// FIXME: Overflow issues, ensure correct back pressure. +// If we request more data from AXI than could be put into the FIFO, then +// we will overflow. To solve this problem, the FIFO should give us information +// about free space fs=(FIFO_SIZE-|ptr_w-ptr_r|). +// if fs < transfer.length, then wait +// On the other hand, we can deassert ready on the System Bus and block it until +// there is sufficient space to end the transfer. +proc FrontendReader { + ch_addr_gen_req: chan> in; + ch_addr_gen_rsp: chan<()> out; + ch_axi_ar: chan> out; + ch_axi_r: chan> in; + ch_axi_st_write: chan> out; + + config(ch_addr_gen_req: chan> in, ch_addr_gen_rsp: chan<()> out, + ch_axi_ar: chan> out, + ch_axi_r: chan> in, + ch_axi_st_write: chan> out) { + (ch_addr_gen_req, ch_addr_gen_rsp, ch_axi_ar, ch_axi_r, ch_axi_st_write) + } + + init { + (FrontendReaderState { + status: FrontendReaderStatusEnum::IDLE, + transfer_data: common::zeroTransferDescBundle(), + r_bundle: axi_pkg::zeroAxiRBundle(), + burst_counter: u32:0 + }) + } + + next(tok: token, state: FrontendReaderState) { + trace!(state); + + let (tok, next_transfer_data, goto_read_req) = recv_if_non_blocking( + tok, ch_addr_gen_req, state.status == FrontendReaderStatusEnum::IDLE, + state.transfer_data); + + if goto_read_req { + trace_fmt!("[READER] Data transfer order [AG] = {}", next_transfer_data); + } else { + + + }; + + let axi_read_req = axi_pkg::simpleAxiArBundle( + state.transfer_data.address, uN[ID_W]:0, state.transfer_data.length as u8); + let tok = send_if( + tok, ch_axi_ar, state.status == FrontendReaderStatusEnum::AXI_READ_REQ, axi_read_req); + + let (tok, axi_read_rsp, goto_stream_req) = recv_if_non_blocking( + tok, ch_axi_r, state.status == FrontendReaderStatusEnum::AXI_READ_RSP, + axi_pkg::zeroAxiRBundle()); + let next_r_bundle = if goto_stream_req { axi_read_rsp } else { state.r_bundle }; + + let next_burst_counter = + if goto_stream_req { state.burst_counter + u32:1 } else { state.burst_counter }; + + let axi_read_req = AxiStreamBundle { + tdata: next_r_bundle.rdata, + tstr: std::unsigned_max_value(), + tkeep: uN[DATA_W_DIV8]:0, + tlast: next_burst_counter == state.transfer_data.length, + tid: uN[ID_W]:0, + tdest: uN[DEST_W]:0 + }; + let tok = send_if( + tok, ch_axi_st_write, state.status == FrontendReaderStatusEnum::SEND_AXI_ST, + axi_read_req); + + let goto_idle = (state.status == FrontendReaderStatusEnum::SEND_AXI_ST) && + (next_burst_counter == state.transfer_data.length); + + let tok = send_if(tok, ch_addr_gen_rsp, goto_idle, ()); + + let next_burst_counter = if goto_idle { u32:0 } else { next_burst_counter }; + + // Next state logic + let nextStatus = if state.status == FrontendReaderStatusEnum::IDLE { + if goto_read_req { + FrontendReaderStatusEnum::AXI_READ_REQ + } else { + FrontendReaderStatusEnum::IDLE + } + } else if state.status == FrontendReaderStatusEnum::AXI_READ_REQ { + FrontendReaderStatusEnum::AXI_READ_RSP + } else if state.status == FrontendReaderStatusEnum::AXI_READ_RSP { + if goto_stream_req { + FrontendReaderStatusEnum::SEND_AXI_ST + } else { + FrontendReaderStatusEnum::AXI_READ_RSP + } + } else if state.status == FrontendReaderStatusEnum::SEND_AXI_ST { + if goto_idle { + FrontendReaderStatusEnum::IDLE + } else { + FrontendReaderStatusEnum::AXI_READ_RSP + } + } else { + FrontendReaderStatusEnum::IDLE + }; + + // trace_fmt!("Next state = {}", nextStatus); + FrontendReaderState { + status: nextStatus, + transfer_data: next_transfer_data, + r_bundle: next_r_bundle, + burst_counter: next_burst_counter + } + } +} + +const TEST_0_ADDR_W = u32:32; +const TEST_0_DATA_W = u32:32; +const TEST_0_DATA_W_DIV8 = u32:4; +const TEST_0_DEST_W = u32:4; +const TEST_0_ID_W = u32:4; + +#[test_proc] +proc testFrontendReader { + ch_addr_gen_req: chan> out; + ch_addr_gen_rsp: chan<()> in; + ch_axi_ar: chan> in; + ch_axi_r: chan> out; + ch_axi_st_write: + chan> in; + terminator: chan out; + + config(terminator: chan out) { + let (ch_addr_gen_req_s, ch_addr_gen_req_r) = chan>; + let (ch_addr_gen_rsp_s, ch_addr_gen_rsp_r) = chan<()>; + let (ch_axi_ar_s, ch_axi_ar_r) = chan>; + let (ch_axi_r_s, ch_axi_r_r) = chan>; + let (ch_axi_st_write_s, ch_axi_st_write_r) = + chan>; + spawn FrontendReader< + TEST_0_ADDR_W, TEST_0_DATA_W, TEST_0_DATA_W_DIV8, TEST_0_DEST_W, TEST_0_ID_W>( + ch_addr_gen_req_r, ch_addr_gen_rsp_s, ch_axi_ar_s, ch_axi_r_r, ch_axi_st_write_s); + ( + ch_addr_gen_req_s, ch_addr_gen_rsp_r, ch_axi_ar_r, ch_axi_r_s, ch_axi_st_write_r, + terminator, + ) + } + + init { () } + + next(tok: token, state: ()) { + let BASE_ADDR = uN[TEST_0_ADDR_W]:1000; + let BASE_DATA = uN[TEST_0_DATA_W]:200; + let NUM_TRANSFER = u32:2; + let NUM_BURST = u32:3; + let ID = uN[TEST_0_ID_W]:0; + + let tok = for (i, tok): (u32, token) in u32:0..NUM_TRANSFER { + // Configuration from the AG + let tok = send( + tok, ch_addr_gen_req, + TransferDescBundle { address: (BASE_ADDR + i), length: NUM_BURST }); + + // AXI AR + let (tok, test_axi_ar) = recv(tok, ch_axi_ar); + trace_fmt!("test_axi_ar = {}", test_axi_ar); + + // AXI R + let tok = for (j, tok): (u32, token) in uN[TEST_0_ADDR_W]:0..NUM_BURST { + let tok = send( + tok, ch_axi_r, + AxiRBundle { + rid: ID, + rdata: BASE_DATA + (i + u32:10 * j as uN[TEST_0_DATA_W]), + rresp: axi_pkg::AXI_READ_RESPONSE_CODES::OKAY, + rlast: (j == (NUM_BURST - u32:1)) as u1 + }); + tok + }(tok); + + // FIFO + let tok = for (j, tok): (u32, token) in uN[TEST_0_ADDR_W]:0..NUM_BURST { + let (tok, r_fifo_data) = recv(tok, ch_axi_st_write); + trace_fmt!("r_fifo_data = {}", r_fifo_data); + assert_eq(r_fifo_data.tdata, BASE_DATA + (i + u32:10 * j as uN[TEST_0_DATA_W])); + // Signal done + tok + }(tok); + let (tok, _) = recv(tok, ch_addr_gen_rsp); + tok + }(tok); + + let tok = send(tok, terminator, true); + } +} + +proc frontend_reader { + config(ch_addr_gen_req: chan> in, + ch_addr_gen_rsp: chan<()> out, + ch_axi_ar: chan> out, + ch_axi_r: chan> in, + ch_axi_st_write: chan> out) { + + spawn FrontendReader< + config::TOP_ADDR_W, config::TOP_DATA_W, config::TOP_DATA_W_DIV8, config::TOP_DEST_W, config::TOP_ID_W>( + ch_addr_gen_req, ch_addr_gen_rsp, ch_axi_ar, ch_axi_r, ch_axi_st_write); + () + } + + init { () } + + next(tok: token, state: ()) { } +} diff --git a/xls/modules/dma/frontend_writer.x b/xls/modules/dma/frontend_writer.x new file mode 100644 index 0000000000..5423f4712c --- /dev/null +++ b/xls/modules/dma/frontend_writer.x @@ -0,0 +1,300 @@ +// Copyright 2023-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. + +// Frontend Writer +// +// Part of the main controller, which translates +// (address, length) tuples from the address generator +// into AXI Write Transactions. Data sent to AXI +// is read from the AXI Stream interface (FIFO). +// +// + +import std; +import xls.modules.dma.bus.axi_pkg; +import xls.modules.dma.bus.axi_st_pkg; +import xls.modules.dma.common; +import xls.modules.dma.config; + +type TransferDescBundle = common::TransferDescBundle; +type AxiAwBundle = axi_pkg::AxiAwBundle; +type AxiWBundle = axi_pkg::AxiWBundle; +type AxiBBundle = axi_pkg::AxiBBundle; +type AxiStreamBundle = axi_st_pkg::AxiStreamBundle; + +enum FrontendWriterStatusEnum : u3 { + IDLE = 0, + AXI_WRITE_AW = 1, + READ_AXI_ST = 2, + AXI_WRITE_W = 3, + AXI_WRITE_B = 4, +} + +struct FrontendWriterState { + status: FrontendWriterStatusEnum, + transfer_data: TransferDescBundle, + aw_bundle: AxiAwBundle, + w_bundle: AxiWBundle, + b_bundle: AxiBBundle, + burst_counter: u32, +} + +proc FrontendWriter +{ + ch_addr_gen_req: chan> in; + ch_addr_gen_rsp: 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_addr_gen_req: chan> in, ch_addr_gen_rsp: chan<()> out, + ch_axi_aw: chan> out, + ch_axi_w: chan> out, ch_axi_b: chan> in, + ch_axi_st_read: chan> in) { + (ch_addr_gen_req, ch_addr_gen_rsp, ch_axi_aw, ch_axi_w, ch_axi_b, ch_axi_st_read) + } + + init { + (FrontendWriterState { + status: FrontendWriterStatusEnum::IDLE, + transfer_data: + TransferDescBundle { address: uN[ADDR_W]:0, length: uN[ADDR_W]:0 }, + aw_bundle: + AxiAwBundle { + awid: uN[ID_W]:0, + awaddr: uN[ADDR_W]:0, + awsize: axi_pkg::AXI_AXSIZE_ENCODING::MAX_8B_TRANSFER, + awlen: uN[8]:0, + awburst: axi_pkg::AXI_AXBURST_ENCODING::FIXED + }, + w_bundle: + AxiWBundle { wdata: uN[DATA_W]:0, wstrb: uN[STRB_W]:0, wlast: u1:0 }, + b_bundle: + AxiBBundle { bresp: axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY, bid: uN[ID_W]:0 }, + burst_counter: u32:0 + }) + } + + next(tok: token, state: FrontendWriterState) { + // Address Generator + let (tok, next_transfer_data, goto_axi_write_aw) = recv_if_non_blocking( + tok, ch_addr_gen_req, state.status == FrontendWriterStatusEnum::IDLE, + state.transfer_data); + + // Send AW + let next_aw_bundle = if goto_axi_write_aw { + trace_fmt!("[WRITER] Data transfer order [AG] = {}", next_transfer_data); + AxiAwBundle { + awid: uN[ID_W]:0, + awaddr: next_transfer_data.address, + awsize: axi_pkg::AXI_AXSIZE_ENCODING::MAX_8B_TRANSFER, + awlen: next_transfer_data.length as u8, + awburst: axi_pkg::AXI_AXBURST_ENCODING::FIXED + } + } else { + state.aw_bundle + }; + let tok = send_if(tok, ch_axi_aw, goto_axi_write_aw, next_aw_bundle); + + let goto_read_axi_st = state.status == FrontendWriterStatusEnum::AXI_WRITE_AW; + + // here + let (tok, r_data, goto_axi_write_w) = recv_if_non_blocking( + tok, ch_axi_st_read, state.status == FrontendWriterStatusEnum::READ_AXI_ST, + axi_st_pkg::zeroAxiStreamBundle()); + + let next_w_bundle = if goto_axi_write_w { + AxiWBundle { + wdata: r_data.tdata, + wstrb: std::unsigned_max_value(), + wlast: state.burst_counter == (state.transfer_data.length - u32:1) + } + } else { + state.w_bundle + }; + + // Send W + let tok = send_if( + tok, ch_axi_w, state.status == FrontendWriterStatusEnum::AXI_WRITE_W, state.w_bundle); + + let next_burst_counter = + if goto_axi_write_w { state.burst_counter + u32:1 } else { state.burst_counter }; + trace_fmt!("next burst counter = {}", next_burst_counter); + + // B + let goto_axi_write_b = (state.status == FrontendWriterStatusEnum::AXI_WRITE_W) && + (next_burst_counter == state.transfer_data.length); + + // Wait for B + let (tok, b_data, goto_idle) = recv_if_non_blocking( + tok, ch_axi_b, state.status == FrontendWriterStatusEnum::AXI_WRITE_B, + axi_pkg::zeroAxiBBundle()); + let next_b_bundle = if goto_idle { + trace_fmt!("b_data = {}", b_data); + b_data + } else { + state.b_bundle + }; + + let next_burst_counter = if goto_idle { u32:0 } else { next_burst_counter }; + + // TODO: If B channel response is not OKAY, signal an error + let tok = send_if(tok, ch_addr_gen_rsp, goto_idle, ()); + + // Next state logic + let nextStatus = if state.status == FrontendWriterStatusEnum::IDLE { + if goto_axi_write_aw { + FrontendWriterStatusEnum::AXI_WRITE_AW + } else { + FrontendWriterStatusEnum::IDLE + } + } else if state.status == FrontendWriterStatusEnum::AXI_WRITE_AW { + if goto_read_axi_st { + FrontendWriterStatusEnum::READ_AXI_ST + } else { + FrontendWriterStatusEnum::AXI_WRITE_AW + } + } else if state.status == FrontendWriterStatusEnum::READ_AXI_ST { + if goto_axi_write_w { + FrontendWriterStatusEnum::AXI_WRITE_W + } else { + FrontendWriterStatusEnum::READ_AXI_ST + } + } else if state.status == FrontendWriterStatusEnum::AXI_WRITE_W { + if goto_axi_write_b { + FrontendWriterStatusEnum::AXI_WRITE_B + } else { + FrontendWriterStatusEnum::READ_AXI_ST + } + } else if state.status == FrontendWriterStatusEnum::AXI_WRITE_B { + if goto_idle { + FrontendWriterStatusEnum::IDLE + } else { + FrontendWriterStatusEnum::AXI_WRITE_B + } + } else { + FrontendWriterStatusEnum::IDLE + }; + + // trace_fmt!("NextState = {}", nextStatus); + FrontendWriterState { + status: nextStatus, + transfer_data: next_transfer_data, + aw_bundle: next_aw_bundle, + w_bundle: next_w_bundle, + b_bundle: next_b_bundle, + burst_counter: next_burst_counter + } + } +} + +const TEST_0_ADDR_W = u32:32; +const TEST_0_DATA_W = u32:32; +const TEST_0_DATA_W_DIV8 = u32:4; +const TEST_0_DEST_W = u32:32; +const TEST_0_ID_W = u32:32; +const TEST_0_STRB_W = u32:32; + +#[test_proc] +proc testFrontendWriter { + ch_addr_gen_req: chan> out; + ch_addr_gen_rsp: chan<()> in; + ch_axi_aw: chan> in; + ch_axi_w: chan> in; + ch_axi_b: chan> out; + ch_axi_st_read: + chan> out; + terminator: chan out; + + config(terminator: chan out) { + let (ch_addr_gen_req_s, ch_addr_gen_req_r) = chan>; + let (ch_addr_gen_rsp_s, ch_addr_gen_rsp_r) = chan<()>; + let (ch_axi_aw_s, ch_axi_aw_r) = chan>; + let (ch_axi_w_s, ch_axi_w_r) = chan>; + let (ch_axi_b_s, ch_axi_b_r) = chan>; + let (ch_axi_st_read_s, ch_axi_st_read_r) = + chan>; + spawn FrontendWriter< + TEST_0_ADDR_W, TEST_0_DATA_W, TEST_0_DATA_W_DIV8, TEST_0_DEST_W, TEST_0_ID_W, TEST_0_STRB_W>( + ch_addr_gen_req_r, ch_addr_gen_rsp_s, ch_axi_aw_s, ch_axi_w_s, ch_axi_b_r, + ch_axi_st_read_r); + ( + ch_addr_gen_req_s, ch_addr_gen_rsp_r, ch_axi_aw_r, ch_axi_w_r, ch_axi_b_s, + ch_axi_st_read_s, terminator, + ) + } + + init { () } + + next(tok: token, state: ()) { + let BASE_ADDR = uN[TEST_0_ADDR_W]:1000; + let BASE_DATA = uN[TEST_0_DATA_W]:200; + let NUM_TRANSFER = u32:9; + let NUM_BURST = u32:2; + + let tok = for (i, tok): (u32, token) in u32:0..NUM_TRANSFER { + // Start transfer + let tok = send( + tok, ch_addr_gen_req, + TransferDescBundle { address: BASE_ADDR + i, length: NUM_BURST }); + // Provide stream data + let tok = for (j, tok): (u32, token) in uN[TEST_0_ADDR_W]:0..NUM_BURST { + trace_fmt!("Burst j={}", j); + let st_data = axi_st_pkg::simpleAxiStreamBundle< + TEST_0_DATA_W, TEST_0_DATA_W_DIV8, TEST_0_DEST_W, TEST_0_ID_W>( + BASE_DATA + (i + u32:10 * j as uN[TEST_0_DATA_W])); + let tok = send(tok, ch_axi_st_read, st_data); + trace_fmt!("Sent st_data = {}", st_data); + tok + }(tok); + trace_fmt!("----------------------------------------"); + // Handle AXI Write + let (tok, aw) = recv(tok, ch_axi_aw); + trace_fmt!("SBUS: AW = {}", aw); + + let tok = for (j, tok): (u32, token) in uN[TEST_0_ADDR_W]:0..NUM_BURST { + let (tok, w) = recv(tok, ch_axi_w); + trace_fmt!("SBUS: W = {}", w); + let tok = send(tok, ch_axi_b, axi_pkg::zeroAxiBBundle()); + assert_eq(w.wdata, BASE_DATA + (i + u32:10 * j as uN[TEST_0_DATA_W])); + tok + }(tok); + // End transfer + let (tok, transfer_done) = recv(tok, ch_addr_gen_rsp); + trace_fmt!("transfer_done = {}", transfer_done); + tok + }(tok); + let tok = send(tok, terminator, true); + } +} + +proc frontend_writer { + config(ch_addr_gen_req: chan> in, + ch_addr_gen_rsp: chan<()> out, + ch_axi_aw: chan> out, + ch_axi_w: chan> out, + ch_axi_b: chan> in, + ch_axi_st_read: chan> in) { + + spawn FrontendWriter< + config::TOP_ADDR_W, config::TOP_DATA_W, config::TOP_DATA_W_DIV8, config::TOP_DEST_W, config::TOP_ID_W, config::TOP_STRB_W>( + ch_addr_gen_req, ch_addr_gen_rsp, ch_axi_aw, ch_axi_w, ch_axi_b, ch_axi_st_read); + () + } + + init { () } + + next(tok: token, state: ()) { } +} diff --git a/xls/modules/dma/gpf.x b/xls/modules/dma/gpf.x new file mode 100644 index 0000000000..378760b564 --- /dev/null +++ b/xls/modules/dma/gpf.x @@ -0,0 +1,169 @@ +// Copyright 2023-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. + +// Generic Physical Function +// +// The gpf proc mocks a real accelerator function. +// Performed algorithm is meant to be simple, i.e. +// output data = input data + 1 + +import xls.modules.dma.bus.axi_st_pkg; + +enum PfBehavior : u1 { + INCREMENT = 0, + INVERT = 1, +} + +pub fn pf_incr(d: uN[N]) -> uN[N] { d + uN[N]:1 } + +pub fn pf_inv(d: uN[N]) -> uN[N] { !d & uN[N]:0xff } + +#[test] +fn test_pf() { + assert_eq(pf_incr(u32:0), u32:1); + assert_eq(pf_incr(u32:1), u32:2); + assert_eq(pf_incr(u32:100), u32:101); + + assert_eq(pf_inv(u32:0x00), u32:0xff); + assert_eq(pf_inv(u32:0x0f), u32:0xf0); + assert_eq(pf_inv(u32:0xf0), u32:0x0f); +} + +type AxiStreamBundle = axi_st_pkg::AxiStreamBundle; + +proc gpf { + ch_i: chan> in; + ch_o: chan> out; + + config(ch_i: chan> in, + ch_o: chan> out) { + (ch_i, ch_o) + } + + init { u32:0 } + + next(tok: token, state: u32) { + trace!(state); + let (tok, read_data) = recv(tok, ch_i); + let state = state + u32:1; + + let data = if PF_BEHAVIOR == PfBehavior::INCREMENT { + pf_incr(read_data.tdata) + } else if PF_BEHAVIOR == PfBehavior::INVERT { + pf_inv(read_data.tdata) + } else { + pf_incr(read_data.tdata) + }; + + let axi_packet = AxiStreamBundle { + tdata: data, + tstr: read_data.tstr, + tkeep: read_data.tkeep, + tlast: read_data.tlast, + tid: read_data.tid, + tdest: read_data.tdest + }; + let tok = send(tok, ch_o, axi_packet); + trace_fmt!("GPF: sent packet #{} to output", state); + state + } +} + +const TEST_0_DATA_W = u32:8; +const TEST_0_DATA_W_DIV8 = u32:1; +const TEST_0_ID_W = u32:1; +const TEST_0_DEST_W = u32:1; + +#[test_proc] +proc test_gpf_increment { + ch_i: chan> out; + ch_o: chan> in; + terminator: chan out; + + config(terminator: chan out) { + let (ch_i_s, ch_i_r) = + chan>; + let (ch_o_s, ch_o_r) = + chan>; + spawn gpf< + TEST_0_DATA_W, TEST_0_DATA_W_DIV8, TEST_0_ID_W, TEST_0_DEST_W, PfBehavior::INCREMENT>( + ch_i_r, ch_o_s); + (ch_i_s, ch_o_r, terminator) + } + + init { () } + + next(tok: token, state: ()) { + let data = uN[TEST_0_DATA_W]:15; + let axi_packet = AxiStreamBundle + { + tdata: data, + tstr: uN[TEST_0_DATA_W_DIV8]:0, + tkeep: uN[TEST_0_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_0_ID_W]:0, + tdest: uN[TEST_0_DEST_W]:0 + }; + + let tok = send(tok, ch_i, axi_packet); + let (tok, axi_packet_r) = recv(tok, ch_o); + let r_data = axi_packet_r.tdata; + + trace_fmt!("Data W: {}, Data R: {}", data, r_data); + assert_eq(data + uN[TEST_0_DATA_W]:1, r_data); + + let tok = send(tok, terminator, true); + } +} + +#[test_proc] +proc test_gpf_invert { + ch_i: chan> out; + ch_o: chan> in; + terminator: chan out; + + config(terminator: chan out) { + let (ch_i_s, ch_i_r) = + chan>; + let (ch_o_s, ch_o_r) = + chan>; + spawn gpf( + ch_i_r, ch_o_s); + (ch_i_s, ch_o_r, terminator) + } + + init { () } + + next(tok: token, state: ()) { + let data = uN[TEST_0_DATA_W]:15; + let axi_packet = AxiStreamBundle + { + tdata: data, + tstr: uN[TEST_0_DATA_W_DIV8]:0, + tkeep: uN[TEST_0_DATA_W_DIV8]:0, + tlast: u1:1, + tid: uN[TEST_0_ID_W]:0, + tdest: uN[TEST_0_DEST_W]:0 + }; + + let tok = send(tok, ch_i, axi_packet); + let (tok, axi_packet_r) = recv(tok, ch_o); + let r_data = axi_packet_r.tdata; + + trace_fmt!("Data W: {}, Data R: {}", data, r_data); + assert_eq(!data & uN[TEST_0_DATA_W]:0xff, r_data); + + let tok = send(tok, terminator, true); + } +} diff --git a/xls/modules/dma/main_controller.x b/xls/modules/dma/main_controller.x new file mode 100644 index 0000000000..60454bc52f --- /dev/null +++ b/xls/modules/dma/main_controller.x @@ -0,0 +1,711 @@ +// Copyright 2023-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. + +// Main Controller +// +// The Controller connects the AXI System Bus to the GPF via AXI-Stream FIFOs. +// It is controlled by the state of the CSRs. +// It is currently assumed that all buses in the design have equal data width. + +import std; +import xls.modules.dma.bus.axi_pkg; +import xls.modules.dma.bus.axi_st_pkg; +import xls.modules.dma.common; +import xls.modules.dma.config; +import xls.modules.dma.address_generator; +import xls.modules.dma.axi_csr; +import xls.modules.dma.frontend_reader; +import xls.modules.dma.frontend_writer; +import xls.modules.dma.gpf; + +type MainCtrlBundle = common::MainCtrlBundle; +type TransferDescBundle = common::TransferDescBundle; +type AxiArBundle = axi_pkg::AxiArBundle; +type AxiRBundle = axi_pkg::AxiRBundle; +type AxiAwBundle = axi_pkg::AxiAwBundle; +type AxiWBundle = axi_pkg::AxiWBundle; +type AxiBBundle = axi_pkg::AxiBBundle; +type AxiStreamBundle = axi_st_pkg::AxiStreamBundle; + +proc MainController +{ + ch_axi_ctrl_aw: chan> in; + ch_axi_ctrl_w: chan> in; + ch_axi_ctrl_b: chan> out; + ch_axi_ctrl_ar: chan> in; + ch_axi_ctrl_r: chan> out; + ch_axi_data_aw: chan> out; + ch_axi_data_w: chan> out; + ch_axi_data_b: chan> in; + ch_axi_data_ar: chan> out; + ch_axi_data_r: chan> in; + ch_axi_st_write: chan> out; + ch_axi_st_read: chan> in; + reader_sync_req: chan<()> in; + reader_sync_rsp: chan<()> out; + writer_sync_req: chan<()> in; + writer_sync_rsp: chan<()> out; + + config(ch_axi_ctrl_aw: chan> in, + ch_axi_ctrl_w: chan> in, + ch_axi_ctrl_b: chan> out, + ch_axi_ctrl_ar: chan> in, + ch_axi_ctrl_r: chan> out, + ch_axi_data_aw: chan> out, + ch_axi_data_w: chan> out, + ch_axi_data_b: chan> in, + ch_axi_data_ar: chan> out, + ch_axi_data_r: chan> in, + ch_axi_st_write: chan> out, + ch_axi_st_read: chan> in, + reader_sync_req: chan<()> in, reader_sync_rsp: chan<()> out, + writer_sync_req: chan<()> in, writer_sync_rsp: chan<()> out) { + let (ch_writer_start_s, ch_writer_start_r) = chan; + let (ch_writer_configuration_s, ch_writer_configuration_r) = chan>; + let (ch_writer_busy_s, ch_writer_busy_r) = chan; + let (ch_writer_done_s, ch_writer_done_r) = chan; + let (ch_reader_start_s, ch_reader_start_r) = chan; + let (ch_reader_configuration_s, ch_reader_configuration_r) = chan>; + let (ch_reader_busy_s, ch_reader_busy_r) = chan; + let (ch_reader_done_s, ch_reader_done_r) = chan; + let (ch_reader_addr_gen_req_s, ch_reader_addr_gen_req_r) = chan>; + let (ch_reader_addr_gen_rsp_s, ch_reader_addr_gen_rsp_r) = chan<()>; + let (ch_writer_addr_gen_req_s, ch_writer_addr_gen_req_r) = chan>; + let (ch_writer_addr_gen_rsp_s, ch_writer_addr_gen_rsp_r) = chan<()>; + + // Control path + spawn axi_csr::AxiCsr( + ch_axi_ctrl_aw, ch_axi_ctrl_w, ch_axi_ctrl_b, ch_axi_ctrl_ar, ch_axi_ctrl_r, + ch_writer_start_s, ch_writer_configuration_s, ch_writer_busy_r, ch_writer_done_r, + ch_reader_start_s, ch_reader_configuration_s, ch_reader_busy_r, ch_reader_done_r, + reader_sync_req, reader_sync_rsp, writer_sync_req, writer_sync_rsp); + + // Read data path + spawn address_generator::AddressGenerator( + ch_reader_configuration_r, ch_reader_start_r, ch_reader_busy_s, ch_reader_done_s, + ch_reader_addr_gen_req_s, ch_reader_addr_gen_rsp_r); + + spawn frontend_reader::FrontendReader( + ch_reader_addr_gen_req_r, ch_reader_addr_gen_rsp_s, ch_axi_data_ar, ch_axi_data_r, + ch_axi_st_write); + + // Write data_path + spawn address_generator::AddressGenerator( + ch_writer_configuration_r, ch_writer_start_r, ch_writer_busy_s, ch_writer_done_s, + ch_writer_addr_gen_req_s, ch_writer_addr_gen_rsp_r); + + spawn frontend_writer::FrontendWriter( + ch_writer_addr_gen_req_r, ch_writer_addr_gen_rsp_s, ch_axi_data_aw, ch_axi_data_w, + ch_axi_data_b, ch_axi_st_read); + + // TODO: Spawn IRQ CTRL + + ( + ch_axi_ctrl_aw, ch_axi_ctrl_w, ch_axi_ctrl_b, ch_axi_ctrl_ar, ch_axi_ctrl_r, + ch_axi_data_aw, ch_axi_data_w, ch_axi_data_b, ch_axi_data_ar, ch_axi_data_r, + ch_axi_st_write, ch_axi_st_read, reader_sync_req, reader_sync_rsp, writer_sync_req, + writer_sync_rsp, + ) + } + + init { () } + + next(tok: token, state: ()) { } +} + +// Start processing +// 000011 = 3 - in single mode +// 001111 = 15 - in single mode, disable sync +// 110011 = 51 - in loop mode +// 111111 = 63 - in loop mode, disable sync + +const TEST_ADDR_W = u32:32; +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_REGS_N = u32:14; +const TEST_STRB_W = TEST_DATA_W / u32:8; + +type AdrDatPair = (uN[TEST_ADDR_W], uN[TEST_DATA_W]); + +// FIXME: Tests interfere with each other! + +// #[test_proc] +// proc TestSingleMode { +// ch_axi_ctrl_aw: chan> out; +// ch_axi_ctrl_w: chan> out; +// ch_axi_ctrl_b: chan> in; +// ch_axi_ctrl_ar: chan> out; +// ch_axi_ctrl_r: chan> in; +// ch_axi_data_aw: chan> in; +// ch_axi_data_w: chan> in; +// ch_axi_data_b: chan> out; +// ch_axi_data_ar: chan> in; +// ch_axi_data_r: chan> out; +// reader_sync_req: chan<()> out; +// reader_sync_rsp: chan<()> in; +// writer_sync_req: chan<()> out; +// writer_sync_rsp: chan<()> in; +// terminator: chan out; + +// config(terminator: chan out) { +// let (ch_axi_ctrl_aw_s, ch_axi_ctrl_aw_r) = chan>; +// let (ch_axi_ctrl_w_s, ch_axi_ctrl_w_r) = chan>; +// let (ch_axi_ctrl_b_s, ch_axi_ctrl_b_r) = chan>; +// let (ch_axi_ctrl_ar_s, ch_axi_ctrl_ar_r) = chan>; +// let (ch_axi_ctrl_r_s, ch_axi_ctrl_r_r) = chan>; +// let (ch_axi_data_aw_s, ch_axi_data_aw_r) = chan>; +// let (ch_axi_data_w_s, ch_axi_data_w_r) = chan>; +// let (ch_axi_data_b_s, ch_axi_data_b_r) = chan>; +// let (ch_axi_data_ar_s, ch_axi_data_ar_r) = chan>; +// let (ch_axi_data_r_s, ch_axi_data_r_r) = chan>; +// let (reader_sync_req_s, reader_sync_req_r) = chan<()>; +// let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; +// let (writer_sync_req_s, writer_sync_req_r) = chan<()>; +// let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + +// let (ch_axi_st_write_s, ch_axi_st_write_r) = +// chan>; +// let (ch_axi_st_read_s, ch_axi_st_read_r) = +// chan>; + +// spawn MainController< +// TEST_ADDR_W, TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, TEST_REGS_N, +// TEST_STRB_W>( +// ch_axi_ctrl_aw_r, ch_axi_ctrl_w_r, ch_axi_ctrl_b_s, ch_axi_ctrl_ar_r, ch_axi_ctrl_r_s, +// ch_axi_data_aw_s, ch_axi_data_w_s, ch_axi_data_b_r, ch_axi_data_ar_s, ch_axi_data_r_r, +// ch_axi_st_write_s, ch_axi_st_read_r,reader_sync_req_r, +// reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + +// spawn gpf::gpf< +// TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, gpf::PfBehavior::INCREMENT>( +// ch_axi_st_write_r, ch_axi_st_read_s); + +// ( +// ch_axi_ctrl_aw_s, ch_axi_ctrl_w_s, ch_axi_ctrl_b_r, ch_axi_ctrl_ar_s, ch_axi_ctrl_r_r, +// ch_axi_data_aw_r, ch_axi_data_w_r, ch_axi_data_b_s, ch_axi_data_ar_r, +// ch_axi_data_r_s,reader_sync_req_s, +// reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, +// terminator, +// ) +// } + +// init { () } + +// next(tok: token, state: ()) { +// let CTRL_WORD = uN[TEST_DATA_W]:3; + +// let id = uN[TEST_ID_W]:0; +// let rw_config = MainCtrlBundle { +// start_address: u32:0x1000, line_count: u32:5, line_length: u32:6, line_stride: u32:0 +// }; +// let init_csr_values = AdrDatPair[u32:10]:[ +// (config::READER_START_ADDRESS, rw_config.start_address), +// (config::READER_LINE_LENGTH, rw_config.line_length), +// (config::READER_LINE_COUNT, rw_config.line_count), +// (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), +// (config::WRITER_START_ADDRESS, rw_config.start_address), +// (config::WRITER_LINE_LENGTH, rw_config.line_length), +// (config::WRITER_LINE_COUNT, rw_config.line_count), +// (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), +// (config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3), +// (config::CONTROL_REGISTER, CTRL_WORD), +// ]; + +// for (i, tok): (u32, token) in u32:0..u32:10 { +// let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; +// let data = (init_csr_values[i]).1; +// let w = axi_pkg::simpleAxiWBundle(data); +// let aw = axi_pkg::simpleAxiAwBundle(addr, id); +// let tok = send(tok, ch_axi_ctrl_aw, aw); +// let tok = send(tok, ch_axi_ctrl_w, w); +// let (tok, b_resp) = recv(tok, ch_axi_ctrl_b); +// assert_eq(b_resp.bresp, axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY); +// (tok) +// }(tok); + +// // Read all values and compare with writes +// for (i, tok): (u32, token) in u32:0..10 { +// let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; +// let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:1); +// let tok = send(tok, ch_axi_ctrl_ar, ar); +// let (tok, rcv) = recv(tok, ch_axi_ctrl_r); +// if i != 9 { +// assert_eq(rcv.rdata, ((init_csr_values[i]).1) as uN[TEST_DATA_W]) +// } else {}; +// (tok) +// }(tok); +// trace_fmt!("AXI Control Bus: PASS"); + +// if CTRL_WORD[2:4] == u2:0 { +// // Synchronize to external +// let tok = send(tok, reader_sync_req, ()); +// let (tok, _) = recv(tok, reader_sync_rsp); + +// let tok = send(tok, writer_sync_req, ()); +// let (tok, _) = recv(tok, writer_sync_rsp); +// } else {}; + +// // Initialize system memory +// let MEM_SIZE = rw_config.line_count * rw_config.line_length; +// let system_memory = for (i, system_memory): (u32, uN[TEST_DATA_W][MEM_SIZE]) in +// u32:0..MEM_SIZE { +// update(system_memory, i, (i + u32:1) as uN[TEST_DATA_W]) +// }(uN[TEST_DATA_W][MEM_SIZE]:[uN[TEST_DATA_W]:0, ...]); + +// // assert_eq(u32:1, u32:0); + +// let system_memory_copy = for (_, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in +// u32:0..rw_config.line_length { +// // Handle AXI Read +// let (tok, axi_ar) = recv(tok, ch_axi_data_ar); +// let addr = (axi_ar.araddr - rw_config.start_address) >> 2; +// let tok = for (i, tok): (u32, token) in u32:0..rw_config.line_count { +// let tok = send( +// tok, ch_axi_data_r, +// axi_pkg::simpleAxiRBundle( +// system_memory[addr + i], id)); +// tok +// }(tok); + +// // Handle AXI Write + +// let (tok, aw) = recv(tok, ch_axi_data_aw); + +// let mem = for (i, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in +// u32:0..rw_config.line_count +// { +// let (tok, w) = recv(tok, ch_axi_data_w); +// let addr = (aw.awaddr - rw_config.start_address) >> 2; +// let mem = update(mem, addr + i, w.wdata); +// mem +// }(mem); + +// let tok = send(tok, ch_axi_data_b, axi_pkg::zeroAxiBBundle()); +// mem +// }(uN[TEST_DATA_W][MEM_SIZE]:[uN[TEST_DATA_W]:0, ...]); + +// trace_fmt!("System memory = {}", system_memory); +// trace_fmt!("System memory copy = {}", system_memory_copy); + +// // TODO: CSR registers are not updated for a few more "next cycles" +// // I would love a while loop here, otherwise I have to rewrite the whole test +// // to use `next` as a sort-of while loop +// for (_, tok): (u32, token) in u32:0..u32:3 { +// let addr = config::STATUS_REGISTER << uN[TEST_ADDR_W]:2; +// let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:1); +// let tok = send(tok, ch_axi_ctrl_ar, ar); +// let (tok, _) = recv(tok, ch_axi_ctrl_r); +// (tok) +// }(tok); + +// // Check Interrupt Status register +// let addr = config::INTERRUPT_STATUS_REGISTER << uN[TEST_ADDR_W]:2; +// let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:1); +// let tok = send(tok, ch_axi_ctrl_ar, ar); +// let (tok, rcv) = recv(tok, ch_axi_ctrl_r); +// assert_eq(rcv.rdata, uN[TEST_DATA_W]:3); + +// // Clear interrupts +// let addr = config::INTERRUPT_STATUS_REGISTER << uN[TEST_ADDR_W]:2; +// let data = uN[TEST_DATA_W]:0; +// let w = axi_pkg::simpleAxiWBundle(data); +// let aw = axi_pkg::simpleAxiAwBundle(addr, id); +// let tok = send(tok, ch_axi_ctrl_aw, aw); +// let tok = send(tok, ch_axi_ctrl_w, w); +// let (tok, b_resp) = recv(tok, ch_axi_ctrl_b); +// assert_eq(b_resp.bresp, axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY); + +// trace_fmt!(" AXI Data Bus: PASS"); + +// let tok = send(tok, terminator, true); +// } +// } + +#[test_proc] +proc TestImageInverse { + ch_axi_ctrl_aw: chan> out; + ch_axi_ctrl_w: chan> out; + ch_axi_ctrl_b: chan> in; + ch_axi_ctrl_ar: chan> out; + ch_axi_ctrl_r: chan> in; + ch_axi_data_aw: chan> in; + ch_axi_data_w: chan> in; + ch_axi_data_b: chan> out; + ch_axi_data_ar: chan> in; + ch_axi_data_r: chan> out; + reader_sync_req: chan<()> out; + reader_sync_rsp: chan<()> in; + writer_sync_req: chan<()> out; + writer_sync_rsp: chan<()> in; + terminator: chan out; + + config(terminator: chan out) { + let (ch_axi_ctrl_aw_s, ch_axi_ctrl_aw_r) = chan>; + let (ch_axi_ctrl_w_s, ch_axi_ctrl_w_r) = chan>; + let (ch_axi_ctrl_b_s, ch_axi_ctrl_b_r) = chan>; + let (ch_axi_ctrl_ar_s, ch_axi_ctrl_ar_r) = chan>; + let (ch_axi_ctrl_r_s, ch_axi_ctrl_r_r) = chan>; + let (ch_axi_data_aw_s, ch_axi_data_aw_r) = chan>; + let (ch_axi_data_w_s, ch_axi_data_w_r) = chan>; + let (ch_axi_data_b_s, ch_axi_data_b_r) = chan>; + let (ch_axi_data_ar_s, ch_axi_data_ar_r) = chan>; + let (ch_axi_data_r_s, ch_axi_data_r_r) = chan>; + let (reader_sync_req_s, reader_sync_req_r) = chan<()>; + let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; + let (writer_sync_req_s, writer_sync_req_r) = chan<()>; + let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + + let (ch_axi_st_write_s, ch_axi_st_write_r) = + chan>; + let (ch_axi_st_read_s, ch_axi_st_read_r) = + chan>; + + spawn MainController< + TEST_ADDR_W, TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, TEST_REGS_N, TEST_STRB_W>( + ch_axi_ctrl_aw_r, ch_axi_ctrl_w_r, ch_axi_ctrl_b_s, ch_axi_ctrl_ar_r, ch_axi_ctrl_r_s, + ch_axi_data_aw_s, ch_axi_data_w_s, ch_axi_data_b_r, ch_axi_data_ar_s, ch_axi_data_r_r, + ch_axi_st_write_s, ch_axi_st_read_r, reader_sync_req_r, reader_sync_rsp_s, + writer_sync_req_r, writer_sync_rsp_s); + + spawn gpf::gpf< + TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, gpf::PfBehavior::INVERT>( + ch_axi_st_write_r, ch_axi_st_read_s); + + ( + ch_axi_ctrl_aw_s, ch_axi_ctrl_w_s, ch_axi_ctrl_b_r, ch_axi_ctrl_ar_s, ch_axi_ctrl_r_r, + ch_axi_data_aw_r, ch_axi_data_w_r, ch_axi_data_b_s, ch_axi_data_ar_r, ch_axi_data_r_s, + reader_sync_req_s, reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, terminator, + ) + } + + init { () } + + next(tok: token, state: ()) { + let CTRL_WORD = uN[TEST_DATA_W]:15; + + let id = uN[TEST_ID_W]:0; + let rw_config = MainCtrlBundle { + start_address: u32:0x1000, line_count: u32:27, line_length: u32:1, line_stride: u32:0 + }; + let init_csr_values = AdrDatPair[u32:10]:[ + (config::READER_START_ADDRESS, rw_config.start_address), + (config::READER_LINE_LENGTH, rw_config.line_length), + (config::READER_LINE_COUNT, rw_config.line_count), + (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::WRITER_START_ADDRESS, rw_config.start_address), + (config::WRITER_LINE_LENGTH, rw_config.line_length), + (config::WRITER_LINE_COUNT, rw_config.line_count), + (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), + (config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3), + (config::CONTROL_REGISTER, CTRL_WORD), + ]; + + for (i, tok): (u32, token) in u32:0..u32:10 { + let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; + let data = (init_csr_values[i]).1; + let w = axi_pkg::simpleAxiWBundle(data); + let aw = axi_pkg::simpleAxiAwBundle(addr, id); + let tok = send(tok, ch_axi_ctrl_aw, aw); + let tok = send(tok, ch_axi_ctrl_w, w); + let (tok, b_resp) = recv(tok, ch_axi_ctrl_b); + assert_eq(b_resp.bresp, axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY); + (tok) + }(tok); + + // Read all values and compare with writes + for (i, tok): (u32, token) in u32:0..10 { + let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; + let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:1); + let tok = send(tok, ch_axi_ctrl_ar, ar); + let (tok, rcv) = recv(tok, ch_axi_ctrl_r); + if i != 9 { + assert_eq(rcv.rdata, ((init_csr_values[i]).1) as uN[TEST_DATA_W]); + } else { + + + }; + (tok) + }(tok); + trace_fmt!("AXI Control Bus: PASS"); + + // Initialize system memory + let MEM_SIZE = rw_config.line_count * rw_config.line_length; + let system_memory = uN[TEST_DATA_W][MEM_SIZE]:[ + uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x82, + uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x7e, uN[TEST_DATA_W]:0x44, uN[TEST_DATA_W]:0x04, + uN[TEST_DATA_W]:0x02, uN[TEST_DATA_W]:0x28, uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x02, + uN[TEST_DATA_W]:0x10, uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x7e, uN[TEST_DATA_W]:0x28, + uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x40, uN[TEST_DATA_W]:0x44, uN[TEST_DATA_W]:0x04, + uN[TEST_DATA_W]:0x40, uN[TEST_DATA_W]:0x82, uN[TEST_DATA_W]:0x7c, uN[TEST_DATA_W]:0x7e, + uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, + ]; + + let system_memory_copy = for (_, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in + u32:0..rw_config.line_length { + // Handle AXI Read + let (tok, axi_ar) = recv(tok, ch_axi_data_ar); + let addr = (axi_ar.araddr - rw_config.start_address) >> 2; + let tok = for (i, tok): (u32, token) in u32:0..rw_config.line_count { + let tok = send( + tok, ch_axi_data_r, + axi_pkg::simpleAxiRBundle( + system_memory[addr + i], id)); + tok + }(tok); + + // Handle AXI Write + let (tok, aw) = recv(tok, ch_axi_data_aw); + let mem = for (i, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in + u32:0..rw_config.line_count { + let (tok, w) = recv(tok, ch_axi_data_w); + let addr = (aw.awaddr - rw_config.start_address) >> 2; + let mem = update(mem, addr + i, w.wdata); + mem + }(mem); + + let tok = send(tok, ch_axi_data_b, axi_pkg::zeroAxiBBundle()); + mem + }(uN[TEST_DATA_W][MEM_SIZE]:[uN[TEST_DATA_W]:0, ...]); + + trace_fmt!("System memory = {:x}", system_memory); + trace_fmt!("System memory copy = {:x}", system_memory_copy); + let golden_data = uN[TEST_DATA_W][MEM_SIZE]:[ + uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0x7d, + uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0x81, uN[TEST_DATA_W]:0xbb, uN[TEST_DATA_W]:0xfb, + uN[TEST_DATA_W]:0xfd, uN[TEST_DATA_W]:0xd7, uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0xfd, + uN[TEST_DATA_W]:0xef, uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0x81, uN[TEST_DATA_W]:0xd7, + uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0xbf, uN[TEST_DATA_W]:0xbb, uN[TEST_DATA_W]:0xfb, + uN[TEST_DATA_W]:0xbf, uN[TEST_DATA_W]:0x7d, uN[TEST_DATA_W]:0x83, uN[TEST_DATA_W]:0x81, + uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, + ]; + assert_eq(golden_data, system_memory_copy); + trace_fmt!(" AXI Data Bus: PASS"); + + let tok = send(tok, terminator, true); + } +} + +// #[test_proc] +// proc TestLoopMode { +// ch_axi_ctrl_aw: chan> out; +// ch_axi_ctrl_w: chan> out; +// ch_axi_ctrl_b: chan> in; +// ch_axi_ctrl_ar: chan> out; +// ch_axi_ctrl_r: chan> in; +// ch_axi_data_aw: chan> in; +// ch_axi_data_w: chan> in; +// ch_axi_data_b: chan> out; +// ch_axi_data_ar: chan> in; +// ch_axi_data_r: chan> out; +// reader_sync_req: chan<()> out; +// reader_sync_rsp: chan<()> in; +// writer_sync_req: chan<()> out; +// writer_sync_rsp: chan<()> in; +// terminator: chan out; + +// config(terminator: chan out) { +// let (ch_axi_ctrl_aw_s, ch_axi_ctrl_aw_r) = chan>; +// let (ch_axi_ctrl_w_s, ch_axi_ctrl_w_r) = chan>; +// let (ch_axi_ctrl_b_s, ch_axi_ctrl_b_r) = chan>; +// let (ch_axi_ctrl_ar_s, ch_axi_ctrl_ar_r) = chan>; +// let (ch_axi_ctrl_r_s, ch_axi_ctrl_r_r) = chan>; +// let (ch_axi_data_aw_s, ch_axi_data_aw_r) = chan>; +// let (ch_axi_data_w_s, ch_axi_data_w_r) = chan>; +// let (ch_axi_data_b_s, ch_axi_data_b_r) = chan>; +// let (ch_axi_data_ar_s, ch_axi_data_ar_r) = chan>; +// let (ch_axi_data_r_s, ch_axi_data_r_r) = chan>; +// let (reader_sync_req_s, reader_sync_req_r) = chan<()>; +// let (reader_sync_rsp_s, reader_sync_rsp_r) = chan<()>; +// let (writer_sync_req_s, writer_sync_req_r) = chan<()>; +// let (writer_sync_rsp_s, writer_sync_rsp_r) = chan<()>; + +// let (ch_axi_st_write_s, ch_axi_st_write_r) = +// chan>; +// let (ch_axi_st_read_s, ch_axi_st_read_r) = +// chan>; + +// spawn MainController< +// TEST_ADDR_W, TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, TEST_REGS_N, +// TEST_STRB_W>( +// ch_axi_ctrl_aw_r, ch_axi_ctrl_w_r, ch_axi_ctrl_b_s, ch_axi_ctrl_ar_r, ch_axi_ctrl_r_s, +// ch_axi_data_aw_s, ch_axi_data_w_s, ch_axi_data_b_r, ch_axi_data_ar_s, ch_axi_data_r_r, +// ch_axi_st_write_s, ch_axi_st_read_r,reader_sync_req_r, +// reader_sync_rsp_s, writer_sync_req_r, writer_sync_rsp_s); + +// spawn gpf::gpf< +// TEST_DATA_W, TEST_DATA_W_DIV8, TEST_DEST_W, TEST_ID_W, gpf::PfBehavior::INVERT>( +// ch_axi_st_write_r, ch_axi_st_read_s); + +// ( +// ch_axi_ctrl_aw_s, ch_axi_ctrl_w_s, ch_axi_ctrl_b_r, ch_axi_ctrl_ar_s, ch_axi_ctrl_r_r, +// ch_axi_data_aw_r, ch_axi_data_w_r, ch_axi_data_b_s, ch_axi_data_ar_r, +// ch_axi_data_r_s,reader_sync_req_s, +// reader_sync_rsp_r, writer_sync_req_s, writer_sync_rsp_r, +// terminator, +// ) +// } + +// init { () } + +// next(tok: token, state: ()) { +// let id = uN[TEST_ID_W]:0; +// let rw_config = MainCtrlBundle { +// start_address: u32:0x1000, line_count: u32:27, line_length: u32:1, line_stride: u32:0 +// }; +// let init_csr_values = AdrDatPair[u32:10]:[ +// (config::READER_START_ADDRESS, rw_config.start_address), +// (config::READER_LINE_LENGTH, rw_config.line_length), +// (config::READER_LINE_COUNT, rw_config.line_count), +// (config::READER_STRIDE_BETWEEN_LINES, rw_config.line_stride), +// (config::WRITER_START_ADDRESS, rw_config.start_address), +// (config::WRITER_LINE_LENGTH, rw_config.line_length), +// (config::WRITER_LINE_COUNT, rw_config.line_count), +// (config::WRITER_STRIDE_BETWEEN_LINES, rw_config.line_stride), +// (config::INTERRUPT_MASK_REGISTER, uN[TEST_DATA_W]:3), +// (config::CONTROL_REGISTER, uN[TEST_DATA_W]:63), +// ]; + +// for (i, tok): (u32, token) in u32:0..u32:10 { +// let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; +// let data = (init_csr_values[i]).1; +// let w = axi_pkg::simpleAxiWBundle(data); +// let aw = axi_pkg::simpleAxiAwBundle(addr, id); +// let tok = send(tok, ch_axi_ctrl_aw, aw); +// let tok = send(tok, ch_axi_ctrl_w, w); +// let (tok, b_resp) = recv(tok, ch_axi_ctrl_b); +// assert_eq(b_resp.bresp, axi_pkg::AXI_WRITE_RESPONSE_CODES::OKAY); +// (tok) +// }(tok); + +// // Read all values and compare with writes +// for (i, tok): (u32, token) in u32:0..10 { +// let addr = (init_csr_values[i]).0 << uN[TEST_ADDR_W]:2; +// let ar = axi_pkg::simpleAxiArBundle(addr, id, u8:1); +// let tok = send(tok, ch_axi_ctrl_ar, ar); +// let (tok, rcv) = recv(tok, ch_axi_ctrl_r); +// assert_eq(rcv.rdata, ((init_csr_values[i]).1) as uN[TEST_DATA_W]); +// (tok) +// }(tok); +// trace_fmt!("AXI Control Bus: PASS"); + +// for (_, tok): (u32, token) in u32:0..u32:3 { +// // Synchronize to external +// let tok = send(tok, reader_sync_req, ()); +// let (tok, _) = recv(tok, reader_sync_rsp); + +// let tok = send(tok, writer_sync_req, ()); +// let (tok, _) = recv(tok, writer_sync_rsp); + +// // Initialize system memory +// let MEM_SIZE = rw_config.line_count * rw_config.line_length; +// let system_memory = uN[TEST_DATA_W][MEM_SIZE]:[ +// uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, +// uN[TEST_DATA_W]:0x82, +// uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x7e, uN[TEST_DATA_W]:0x44, +// uN[TEST_DATA_W]:0x04, +// uN[TEST_DATA_W]:0x02, uN[TEST_DATA_W]:0x28, uN[TEST_DATA_W]:0x04, +// uN[TEST_DATA_W]:0x02, +// uN[TEST_DATA_W]:0x10, uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x7e, +// uN[TEST_DATA_W]:0x28, +// uN[TEST_DATA_W]:0x04, uN[TEST_DATA_W]:0x40, uN[TEST_DATA_W]:0x44, +// uN[TEST_DATA_W]:0x04, +// uN[TEST_DATA_W]:0x40, uN[TEST_DATA_W]:0x82, uN[TEST_DATA_W]:0x7c, +// uN[TEST_DATA_W]:0x7e, +// uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, uN[TEST_DATA_W]:0x00, +// ]; + +// let system_memory_copy = for (_, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in +// u32:0..rw_config.line_length { +// // Handle AXI Read +// let (tok, axi_ar) = recv(tok, ch_axi_data_ar); +// let addr = (axi_ar.araddr - rw_config.start_address) >> 2; +// let tok = for (i, tok): (u32, token) in u32:0..rw_config.line_count { +// let tok = send( +// tok, ch_axi_data_r, +// axi_pkg::simpleAxiRBundle( +// system_memory[addr + i], id)); +// tok +// }(tok); + +// // Handle AXI Write +// let (tok, aw) = recv(tok, ch_axi_data_aw); +// let mem = for (i, mem): (u32, uN[TEST_DATA_W][MEM_SIZE]) in +// u32:0..rw_config.line_count { +// let (tok, w) = recv(tok, ch_axi_data_w); +// let addr = (aw.awaddr - rw_config.start_address) >> 2; +// let mem = update(mem, addr + i, w.wdata); +// mem +// }(mem); + +// let tok = send(tok, ch_axi_data_b, axi_pkg::zeroAxiBBundle()); +// mem +// }(uN[TEST_DATA_W][MEM_SIZE]:[uN[TEST_DATA_W]:0, ...]); + +// trace_fmt!("System memory = {:x}", system_memory); +// trace_fmt!("System memory copy = {:x}", system_memory_copy); +// let golden_data = uN[TEST_DATA_W][MEM_SIZE]:[ +// uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, +// uN[TEST_DATA_W]:0x7d, +// uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0x81, uN[TEST_DATA_W]:0xbb, +// uN[TEST_DATA_W]:0xfb, +// uN[TEST_DATA_W]:0xfd, uN[TEST_DATA_W]:0xd7, uN[TEST_DATA_W]:0xfb, +// uN[TEST_DATA_W]:0xfd, +// uN[TEST_DATA_W]:0xef, uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0x81, +// uN[TEST_DATA_W]:0xd7, +// uN[TEST_DATA_W]:0xfb, uN[TEST_DATA_W]:0xbf, uN[TEST_DATA_W]:0xbb, +// uN[TEST_DATA_W]:0xfb, +// uN[TEST_DATA_W]:0xbf, uN[TEST_DATA_W]:0x7d, uN[TEST_DATA_W]:0x83, +// uN[TEST_DATA_W]:0x81, +// uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, uN[TEST_DATA_W]:0xff, +// ]; +// assert_eq(golden_data, system_memory_copy); +// trace_fmt!(" AXI Data Bus: PASS"); +// (tok) +// }(tok); +// let tok = send(tok, terminator, true); +// } +// } + +proc main_controller { + config(ch_axi_ctrl_aw: chan> in, + ch_axi_ctrl_w: chan> in, + ch_axi_ctrl_b: chan> out, + ch_axi_ctrl_ar: chan> in, + ch_axi_ctrl_r: chan> out, + ch_axi_data_aw: chan> out, + ch_axi_data_w: chan> out, + ch_axi_data_b: chan> in, + ch_axi_data_ar: chan> out, + ch_axi_data_r: chan> in, + ch_axi_st_write: chan> out, + ch_axi_st_read: chan> in, + reader_sync_req: chan<()> in, reader_sync_rsp: chan<()> out, + writer_sync_req: chan<()> in, writer_sync_rsp: chan<()> out) { + + spawn MainController< + config::TOP_ADDR_W, config::TOP_DATA_W, config::TOP_DATA_W_DIV8, config::TOP_DEST_W, config::TOP_ID_W, config::TOP_REGS_N, config::TOP_STRB_W>( + ch_axi_ctrl_aw, ch_axi_ctrl_w, ch_axi_ctrl_b, ch_axi_ctrl_ar, ch_axi_ctrl_r, + ch_axi_data_aw, ch_axi_data_w, ch_axi_data_b, ch_axi_data_ar, ch_axi_data_r, + ch_axi_st_write, ch_axi_st_read, reader_sync_req, reader_sync_rsp, writer_sync_req, + writer_sync_rsp); + () + } + + init { () } + + next(tok: token, state: ()) { } +}