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