From bccd3d37b7e40b2d3e7b7e9cfbc94b2cf3979da5 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 24 Oct 2023 12:07:54 +0200 Subject: [PATCH 01/49] modules/zstd: Add buffer library This commit adds a DSLX Buffer library that provides the Buffer struct, and helper functions that can be used to operate on it. The Buffer is meant to be a storage for data coming from the channel. It acts like a FIFO, allowing data of any length to be put in or popped out of it. Provided DSLX tests verify the correct behaviour of the library. Internal-tag: [#50221] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 39 +++++ xls/modules/zstd/buffer.x | 355 ++++++++++++++++++++++++++++++++++++++ 2 files changed, 394 insertions(+) create mode 100644 xls/modules/zstd/BUILD create mode 100644 xls/modules/zstd/buffer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD new file mode 100644 index 0000000000..d140c87ae6 --- /dev/null +++ b/xls/modules/zstd/BUILD @@ -0,0 +1,39 @@ +# Copyright 2023 The XLS Authors +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +# Build rules for XLS ZSTD codec implementation. + +load( + "//xls/build_rules:xls_build_defs.bzl", + "xls_dslx_library", + "xls_dslx_test", +) + +package( + default_applicable_licenses = ["//:license"], + default_visibility = ["//xls:xls_users"], + licenses = ["notice"], +) + +xls_dslx_library( + name = "buffer_dslx", + srcs = [ + "buffer.x", + ], +) + +xls_dslx_test( + name = "buffer_dslx_test", + library = ":buffer_dslx", +) diff --git a/xls/modules/zstd/buffer.x b/xls/modules/zstd/buffer.x new file mode 100644 index 0000000000..9d050e96ee --- /dev/null +++ b/xls/modules/zstd/buffer.x @@ -0,0 +1,355 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains implementation of a Buffer structure that acts as +// a simple FIFO. Additionally, the file provides various functions that +// can simplify access to the stored. +// +// The utility functions containing the `_checked` suffix serve two purposes: +// they perform the actual operation and return information on whether +// the operation was successful. If you are sure that the precondition is +// always true, you can use the function with the same name but without +// the `_checked` suffix. + +import std; + +// Structure to hold the buffered data +pub struct Buffer { + content: bits[CAPACITY], + length: u32 +} + +// Status values reported by the functions operating on a Buffer +pub enum BufferStatus : u2 { + OK = 0, + NO_ENOUGH_SPACE = 1, + NO_ENOUGH_DATA = 2, +} + +// Structure for returning Buffer and BufferStatus together +pub struct BufferResult { + buffer: Buffer, + status: BufferStatus +} + +// Checks whether a `buffer` can fit `data` +pub fn buffer_can_fit(buffer: Buffer, data: bits[DSIZE]) -> bool { + buffer.length + DSIZE <= CAPACITY +} + +#[test] +fn test_buffer_can_fit() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u16:0), true); + assert_eq(buffer_can_fit(buffer, u32:0), true); + assert_eq(buffer_can_fit(buffer, u33:0), false); + + let buffer = Buffer { content: u32:0, length: u32:16 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u16:0), true); + assert_eq(buffer_can_fit(buffer, u17:0), false); + assert_eq(buffer_can_fit(buffer, u32:0), false); + + let buffer = Buffer { content: u32:0, length: u32:32 }; + assert_eq(buffer_can_fit(buffer, bits[0]:0), true); + assert_eq(buffer_can_fit(buffer, u1:0), false); + assert_eq(buffer_can_fit(buffer, u16:0), false); + assert_eq(buffer_can_fit(buffer, u32:0), false); +} + +// Checks whether a `buffer` has at least `length` amount of data +pub fn buffer_has_at_least(buffer: Buffer, length: u32) -> bool { + length <= buffer.length +} + +#[test] +fn test_buffer_has_at_least() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), false); + assert_eq(buffer_has_at_least(buffer, u32:32), false); + assert_eq(buffer_has_at_least(buffer, u32:33), false); + + let buffer = Buffer { content: u32:0, length: u32:16 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), true); + assert_eq(buffer_has_at_least(buffer, u32:32), false); + assert_eq(buffer_has_at_least(buffer, u32:33), false); + + let buffer = Buffer { content: u32:0, length: u32:32 }; + assert_eq(buffer_has_at_least(buffer, u32:0), true); + assert_eq(buffer_has_at_least(buffer, u32:16), true); + assert_eq(buffer_has_at_least(buffer, u32:32), true); + assert_eq(buffer_has_at_least(buffer, u32:33), false); +} + +// Returns a new buffer with `data` appended to the original `buffer`. +// It will fail if the buffer cannot fit the data. For calls that need better +// error handling, check `buffer_append_checked` +pub fn buffer_append (buffer: Buffer, data: bits[DSIZE]) -> Buffer { + if buffer_can_fit(buffer, data) == false { + trace_fmt!("Not enough space in the buffer! {} + {} <= {}", buffer.length, DSIZE, CAPACITY); + fail!("not_enough_space", buffer) + } else { + Buffer { + content: (data as bits[CAPACITY] << buffer.length) | buffer.content, + length: DSIZE + buffer.length + } + } +} + +#[test] +fn test_buffer_append() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + let buffer = buffer_append(buffer, u16:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xBEEF, length: u32:16 }); + let buffer = buffer_append(buffer, u16:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0xDEADBEEF, length: u32:32 }); +} + +// Returns a new buffer with the `data` appended to the original `buffer` if +// the buffer has enough space. Otherwise, it returns an unmodified buffer +// along with an error. The results are stored in the BufferResult structure. +pub fn buffer_append_checked (buffer: Buffer, data: bits[DSIZE]) -> BufferResult { + if buffer_can_fit(buffer, data) == false { + BufferResult { status: BufferStatus::NO_ENOUGH_SPACE, buffer } + } else { + BufferResult { + status: BufferStatus::OK, + buffer: buffer_append(buffer, data) + } + } +} + +#[test] +fn test_buffer_append_checked() { + let buffer = Buffer { content: u32:0, length: u32:0 }; + + let result1 = buffer_append_checked(buffer, u16:0xBEEF); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xBEEF, length: u32:16 } + }); + + let result2 = buffer_append_checked(result1.buffer, u16:0xDEAD); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEADBEEF, length: u32:32 } + }); + + let result3 = buffer_append_checked(result2.buffer, u16:0xCAFE); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_SPACE, + buffer: result2.buffer + }); +} + +// Returns `length` amount of data from a `buffer` and a new buffer with +// the data removed. Since the Buffer structure acts as a simple FIFO, +// it pops the data in the same order as they were added to the buffer. +// If the buffer does not have enough data to meet the specified length, +// the function will fail. For calls that need better error handling, +// check `buffer_pop_checked`. +pub fn buffer_pop(buffer: Buffer, length: u32) -> (Buffer, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + trace_fmt!("Not enough data in the buffer!"); + fail!("not_enough_data", (buffer, bits[CAPACITY]:0)) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + ( + Buffer { + content: buffer.content >> length, + length: buffer.length - length + }, + buffer.content & mask + ) + } +} + +#[test] +fn test_buffer_pop() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (buffer, data) = buffer_pop(buffer, u32:16); + assert_eq(data, u32:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 }); + let (buffer, data) = buffer_pop(buffer, u32:16); + assert_eq(data, u32:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0, length: u32:0 }); +} + +// Returns `length` amount of data from a `buffer`, a new buffer with +// the data removed and a positive status, if the buffer contains enough data. +// Otherwise, it returns unmodified buffer, zeroed data field and error. +// Since the Buffer structure acts as a simple FIFO, it pops the data in +// the same order as they were added to the buffer. +// The results are stored in the BufferResult structure. +pub fn buffer_pop_checked (buffer: Buffer, length: u32) -> (BufferResult, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + ( + BufferResult { status: BufferStatus::NO_ENOUGH_DATA, buffer }, + bits[CAPACITY]:0 + ) + } else { + let (buffer_leftover, content) = buffer_pop(buffer, length); + ( + BufferResult { + status: BufferStatus::OK, + buffer: buffer_leftover + }, + content + ) + } +} + +#[test] +fn test_buffer_pop_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + + let (result1, data1) = buffer_pop_checked(buffer, u32:16); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEAD, length: u32:16 } + }); + assert_eq(data1, u32:0xBEEF); + + let (result2, data2) = buffer_pop_checked(result1.buffer, u32:16); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + assert_eq(data2, u32:0xDEAD); + + let (result3, data3) = buffer_pop_checked(result2.buffer, u32:16); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: result2.buffer + }); + assert_eq(data3, u32:0); +} + +// Behaves like `buffer_pop` except that the length of the popped data can be +// set using a DSIZE function parameter. For calls that need better error +// handling, check `buffer_fixed_pop_checked`. +pub fn buffer_fixed_pop (buffer: Buffer) -> (Buffer, bits[DSIZE]) { + let (buffer, value) = buffer_pop(buffer, DSIZE); + (buffer, value as bits[DSIZE]) +} + +#[test] +fn test_buffer_fixed_pop() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (buffer, data) = buffer_fixed_pop(buffer); + assert_eq(data, u16:0xBEEF); + assert_eq(buffer, Buffer { content: u32:0xDEAD, length: u32:16 }); + let (buffer, data) = buffer_fixed_pop(buffer); + assert_eq(data, u16:0xDEAD); + assert_eq(buffer, Buffer { content: u32:0, length: u32:0 }); +} + +// Behaves like `buffer_pop_checked` except that the length of the popped data +// can be set using a DSIZE function parameter. +pub fn buffer_fixed_pop_checked (buffer: Buffer) -> (BufferResult, bits[DSIZE]) { + let (result, value) = buffer_pop_checked(buffer, DSIZE); + (result, value as bits[DSIZE]) +} + +#[test] +fn test_buffer_fixed_pop_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + let (result1, data1) = buffer_fixed_pop_checked(buffer); + assert_eq(result1, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0xDEAD, length: u32:16 } + }); + assert_eq(data1, u16:0xBEEF); + + let (result2, data2) = buffer_fixed_pop_checked(result1.buffer); + assert_eq(result2, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + assert_eq(data2, u16:0xDEAD); + + let (result3, data3) = buffer_fixed_pop_checked(result2.buffer); + assert_eq(result3, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: result2.buffer + }); + assert_eq(data3, u16:0); +} + +// Returns `length` amount of data from a `buffer`. +// It will fail if the buffer has no sufficient amount of data. +// For calls that need better error handling, check `buffer_peek_checked`. +pub fn buffer_peek(buffer: Buffer, length: u32) -> bits[CAPACITY] { + if buffer_has_at_least(buffer, length) == false { + trace_fmt!("Not enough data in the buffer!"); + fail!("not_enough_data", bits[CAPACITY]:0) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + buffer.content & mask + } +} + +#[test] +fn test_buffer_peek() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + assert_eq(buffer_peek(buffer, u32:0), u32:0); + assert_eq(buffer_peek(buffer, u32:16), u32:0xBEEF); + assert_eq(buffer_peek(buffer, u32:32), u32:0xDEADBEEF); +} + +// Returns a new buffer with the `data` and a positive status if +// the buffer has enough data. Otherwise, it returns a zeroed-data and error. +// The results are stored in the BufferResult structure. +pub fn buffer_peek_checked (buffer: Buffer, length: u32) -> (BufferStatus, bits[CAPACITY]) { + if buffer_has_at_least(buffer, length) == false { + (BufferStatus::NO_ENOUGH_DATA, bits[CAPACITY]:0) + } else { + let mask = (bits[CAPACITY]:1 << length) - bits[CAPACITY]:1; + (BufferStatus::OK, buffer.content & mask) + } +} + +#[test] +fn test_buffer_peek_checked() { + let buffer = Buffer { content: u32:0xDEADBEEF, length: u32:32 }; + + let (status1, data1) = buffer_peek_checked(buffer, u32:0); + assert_eq(status1, BufferStatus::OK); + assert_eq(data1, u32:0); + + let (status2, data2) = buffer_peek_checked(buffer, u32:16); + assert_eq(status2, BufferStatus::OK); + assert_eq(data2, u32:0xBEEF); + + let (status3, data3) = buffer_peek_checked(buffer, u32:32); + assert_eq(status3, BufferStatus::OK); + assert_eq(data3, u32:0xDEADBEEF); + + let (status4, data4) = buffer_peek_checked(buffer, u32:64); + assert_eq(status4, BufferStatus::NO_ENOUGH_DATA); + assert_eq(data4, u32:0); +} + +// Creates a new buffer +pub fn buffer_new() -> Buffer { + Buffer { content: bits[CAPACITY]:0, length: u32:0 } +} + +#[test] +fn test_buffer_new() { + assert_eq(buffer_new(), Buffer { content: u32:0, length: u32:0 }); +} From 04bf3e830bbff799c0afdcfb496c37d3c07cd2e8 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 31 Oct 2023 12:34:44 +0100 Subject: [PATCH 02/49] modules/zstd: Add Buffer use-case example This commit adds a simple test that shows, how one can use the Buffer struct inside a Proc. Internal-tag: [#50221] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 88 ++++++++++++++++++++ xls/modules/zstd/window_buffer.x | 137 +++++++++++++++++++++++++++++++ 2 files changed, 225 insertions(+) create mode 100644 xls/modules/zstd/window_buffer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d140c87ae6..8575489875 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -14,10 +14,15 @@ # Build rules for XLS ZSTD codec implementation. +load("@rules_hdl//place_and_route:build_defs.bzl", "place_and_route") +load("@rules_hdl//synthesis:build_defs.bzl", "benchmark_synth", "synthesize_rtl") +load("@rules_hdl//verilog:providers.bzl", "verilog_library") load( "//xls/build_rules:xls_build_defs.bzl", + "xls_benchmark_ir", "xls_dslx_library", "xls_dslx_test", + "xls_dslx_verilog", ) package( @@ -37,3 +42,86 @@ xls_dslx_test( name = "buffer_dslx_test", library = ":buffer_dslx", ) + +xls_dslx_library( + name = "window_buffer_dslx", + srcs = [ + "window_buffer.x", + ], + deps = [ + ":buffer_dslx", + ], +) + +xls_dslx_test( + name = "window_buffer_dslx_test", + library = ":window_buffer_dslx", +) + +xls_dslx_verilog( + name = "window_buffer_verilog", + codegen_args = { + "module_name": "WindowBuffer64", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "WindowBuffer64", + library = ":window_buffer_dslx", + # TODO: 2024-01-25: Workaround for https://github.com/google/xls/issues/869 + # Force proc inlining and set last internal proc as top proc for IR optimization + opt_ir_args = { + "inline_procs": "true", + "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", + }, + verilog_file = "window_buffer.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "window_buffer_opt_ir_benchmark", + src = ":window_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "window_buffer_verilog_lib", + srcs = [ + ":window_buffer.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "window_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "WindowBuffer64", + deps = [ + ":window_buffer_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "window_buffer_benchmark_synth", + synth_target = ":window_buffer_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "window_buffer_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":window_buffer_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) + diff --git a/xls/modules/zstd/window_buffer.x b/xls/modules/zstd/window_buffer.x new file mode 100644 index 0000000000..d07bc24bf2 --- /dev/null +++ b/xls/modules/zstd/window_buffer.x @@ -0,0 +1,137 @@ +// 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 the implementation of a Proc which can be used to +// receive data through transactions of one width and output that data +// in transactions of other width. + +import std; +import xls.modules.zstd.buffer as buff; + +type Buffer = buff::Buffer; + +// WindowBuffer is a simple Proc that uses the Buffer structure to aggregate data +// in transactions of length and output it in transactions of +// length. defines the maximal size of the buffer. + +proc WindowBuffer { + input_r: chan in; + output_s: chan out; + + config( + input_r: chan in, + output_s: chan out + ) { (input_r, output_s) } + + init { buff::buffer_new() } + + next(buffer: Buffer) { + let tok = join(); + const_assert!(BUFFER_SIZE >= INPUT_WIDTH); + const_assert!(BUFFER_SIZE >= OUTPUT_WIDTH); + let (tok, recv_data, valid) = recv_non_blocking(tok, input_r, uN[INPUT_WIDTH]:0); + let buffer = if (valid) { + buff::buffer_append(buffer, recv_data) + } else { + buffer + }; + + if buffer.length >= OUTPUT_WIDTH { + let (buffer, data_to_send) = buff::buffer_fixed_pop(buffer); + let tok = send(tok, output_s, data_to_send); + buffer + } else { + buffer + } + } +} + +#[test_proc] +proc WindowBufferTest { + terminator: chan out; + data32_s: chan out; + data48_r: chan in; + + config(terminator: chan out) { + let (data32_s, data32_r) = chan("data32"); + let (data48_s, data48_r) = chan("data48"); + spawn WindowBuffer(data32_r, data48_s); + (terminator, data32_s, data48_r) + } + + init {} + + next(state: ()) { + let tok = join(); + let tok = send(tok, data32_s, u32:0xDEADBEEF); + let tok = send(tok, data32_s, u32:0xBEEFCAFE); + let tok = send(tok, data32_s, u32:0xCAFEDEAD); + + let (tok, received_data) = recv(tok, data48_r); + assert_eq(received_data, u48:0xCAFE_DEAD_BEEF); + let (tok, received_data) = recv(tok, data48_r); + assert_eq(received_data, u48:0xCAFE_DEAD_BEEF); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc WindowBufferReverseTest { + terminator: chan out; + data48_s: chan out; + data32_r: chan in; + + config(terminator: chan out) { + let (data48_s, data48_r) = chan("data48"); + let (data32_s, data32_r) = chan("data32"); + spawn WindowBuffer(data48_r, data32_s); + (terminator, data48_s, data32_r) + } + + init {} + + next(state: ()) { + let tok = join(); + let tok = send(tok, data48_s, u48:0xCAFEDEADBEEF); + let tok = send(tok, data48_s, u48:0xCAFEDEADBEEF); + + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xDEADBEEF); + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xBEEFCAFE); + let (tok, received_data) = recv(tok, data32_r); + assert_eq(received_data, u32:0xCAFEDEAD); + + send(tok, terminator, true); + } +} + +// Sample for codegen +proc WindowBuffer64 { + input_r: chan in; + output_s: chan out; + + config( + input_r: chan in, + output_s: chan out + ) { + spawn WindowBuffer(input_r, output_s); + (input_r, output_s) + } + + init {} + + next(state: ()) {} +} From a43fa9cfc232f5649c76e9d110ddea57fcbbc0e0 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 24 Oct 2023 12:40:41 +0200 Subject: [PATCH 03/49] modules/zstd: Add library for parsing magic number This commit adds the library with functions for parsing a magic number and tests that verify its correctness. Internal-tag: [#50221] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 14 +++++++ xls/modules/zstd/magic.x | 89 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 103 insertions(+) create mode 100644 xls/modules/zstd/magic.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 8575489875..d4f0d77844 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -125,3 +125,17 @@ place_and_route( tags = ["manual"], ) +xls_dslx_library( + name = "magic_dslx", + srcs = [ + "magic.x", + ], + deps = [ + ":buffer_dslx", + ] +) + +xls_dslx_test( + name = "magic_dslx_test", + library = ":magic_dslx", +) diff --git a/xls/modules/zstd/magic.x b/xls/modules/zstd/magic.x new file mode 100644 index 0000000000..787cc62503 --- /dev/null +++ b/xls/modules/zstd/magic.x @@ -0,0 +1,89 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains utilities related to ZSTD magic number parsing +// More information about the ZSTD Magic Number can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1 + +import std; +import xls.modules.zstd.buffer as buff; + +type Buffer = buff::Buffer; +type BufferStatus = buff::BufferStatus; + +// Magic number value, as in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1 +const MAGIC_NUMBER = u32:0xFD2FB528; + +// Status values reported by the magic number parsing function +pub enum MagicStatus: u2 { + OK = 0, + CORRUPTED = 1, + NO_ENOUGH_DATA = 2, +} + +// structure for returning results of magic number parsing +pub struct MagicResult { + buffer: Buffer, + status: MagicStatus, +} + +// Parses a Buffer and checks if it contains the magic number. +// The buffer is assumed to contain a valid beginning of the ZSTD file. +// The function returns MagicResult structure with the buffer after parsing +// the magic number and the status of the operation. On failure, the returned +// buffer is the same as the input buffer. +pub fn parse_magic_number(buffer: Buffer) -> MagicResult { + let (result, data) = buff::buffer_fixed_pop_checked(buffer); + + match result.status { + BufferStatus::OK => { + if data == MAGIC_NUMBER { + trace_fmt!("parse_magic_number: Magic number found!"); + MagicResult {status: MagicStatus::OK, buffer: result.buffer} + } else { + trace_fmt!("parse_magic_number: Magic number not found!"); + MagicResult {status: MagicStatus::CORRUPTED, buffer: buffer} + } + }, + _ => { + trace_fmt!("parse_frame_header: Not enough data to parse magic number!"); + MagicResult {status: MagicStatus::NO_ENOUGH_DATA, buffer: buffer} + } + } +} + +#[test] +fn test_parse_magic_number() { + let buffer = Buffer { content: MAGIC_NUMBER, length: u32:32}; + let result = parse_magic_number(buffer); + assert_eq(result, MagicResult { + status: MagicStatus::OK, + buffer: Buffer {content: u32:0, length: u32:0}, + }); + + let buffer = Buffer { content: u32:0x12345678, length: u32:32}; + let result = parse_magic_number(buffer); + assert_eq(result, MagicResult { + status: MagicStatus::CORRUPTED, + buffer: buffer + }); + + let buffer = Buffer { content: u32:0x1234, length: u32:16}; + let result = parse_magic_number(buffer); + assert_eq(result, MagicResult { + status: MagicStatus::NO_ENOUGH_DATA, + buffer: buffer, + }); +} From d3048bf17f6bb0f70c1d956cee37d3b2eebf0d6c Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 24 Oct 2023 14:53:17 +0200 Subject: [PATCH 04/49] modules/zstd: Add library for parsing frame header This commit adds the library with functions for parsing a frame header. The provided tests verify the correcness of the library. Internal-tag: [#49967] Co-authored-by: Roman Dobrodii Co-authored-by: Pawel Czarnecki Signed-off-by: Robert Winkler Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 77 ++++ xls/modules/zstd/frame_header.x | 662 ++++++++++++++++++++++++++++++++ 2 files changed, 739 insertions(+) create mode 100644 xls/modules/zstd/frame_header.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d4f0d77844..2c5b1a2cb9 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -139,3 +139,80 @@ xls_dslx_test( name = "magic_dslx_test", library = ":magic_dslx", ) + +xls_dslx_library( + name = "frame_header_dslx", + srcs = [ + "frame_header.x", + ], + deps = [ + ":buffer_dslx", + ], +) + +xls_dslx_test( + name = "zstd_frame_header_dslx_test", + library = ":zstd_frame_header_dslx", +) + +xls_dslx_verilog( + name = "frame_header_verilog", + codegen_args = { + "module_name": "FrameHeaderDecoder", + "delay_model": "asap7", + "pipeline_stages": "9", + "reset": "rst", + "reset_data_path": "false", + "use_system_verilog": "false", + }, + dslx_top = "parse_frame_header_128", + library = ":frame_header_test_dslx", + verilog_file = "frame_header.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "frame_header_opt_ir_benchmark", + src = ":frame_header_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "9", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "frame_header_verilog_lib", + srcs = [ + ":frame_header.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "frame_header_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "FrameHeaderDecoder", + deps = [ + ":frame_header_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "frame_header_benchmark_synth", + synth_target = ":frame_header_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "frame_header_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":frame_header_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x new file mode 100644 index 0000000000..a40e9dfeda --- /dev/null +++ b/xls/modules/zstd/frame_header.x @@ -0,0 +1,662 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains utilities related to ZSTD Frame Header parsing. +// More information about the ZSTD Frame Header can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.1 + +import std; +import xls.modules.zstd.buffer as buff; + +type Buffer = buff::Buffer; +type BufferStatus = buff::BufferStatus; +type BufferResult = buff::BufferResult; + +pub type WindowSize = u64; +type FrameContentSize = u64; +type DictionaryId = u32; + +// Maximal mantissa value for calculating maximal accepted window_size +// as per https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor +const MAX_MANTISSA = WindowSize:0b111; + +// Structure for holding ZSTD Frame_Header_Descriptor data, as in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.1.1 +pub struct FrameHeaderDescriptor { + frame_content_size_flag: u2, + single_segment_flag: u1, + unused: u1, + reserved: u1, + content_checksum_flag: u1, + dictionary_id_flag: u2, +} + +// Structure for data obtained from decoding the Frame_Header_Descriptor +pub struct FrameHeader { + window_size: WindowSize, + frame_content_size: FrameContentSize, + dictionary_id: DictionaryId, + content_checksum_flag: u1, +} + +// Status values reported by the frame header parsing function +pub enum FrameHeaderStatus: u2 { + OK = 0, + CORRUPTED = 1, + NO_ENOUGH_DATA = 2, + UNSUPPORTED_WINDOW_SIZE = 3, +} + +// structure for returning results of parsing a frame header +pub struct FrameHeaderResult { + status: FrameHeaderStatus, + header: FrameHeader, + buffer: Buffer, +} + +// Auxiliary constant that can be used to initialize Proc's state +// with empty FrameHeader, because `zero!` cannot be used in that context +pub const ZERO_FRAME_HEADER = zero!(); +pub const FRAME_CONTENT_SIZE_NOT_PROVIDED_VALUE = FrameContentSize::MAX; + +// Extracts Frame_Header_Descriptor fields from 8-bit chunk of data +// that is assumed to be a valid Frame_Header_Descriptor +fn extract_frame_header_descriptor(data:u8) -> FrameHeaderDescriptor { + FrameHeaderDescriptor { + frame_content_size_flag: data[6:8], + single_segment_flag: data[5:6], + unused: data[4:5], + reserved: data[3:4], + content_checksum_flag: data[2:3], + dictionary_id_flag: data[0:2], + } +} + +#[test] +fn test_extract_frame_header_descriptor() { + assert_eq( + extract_frame_header_descriptor(u8:0xA4), + FrameHeaderDescriptor { + frame_content_size_flag: u2:0x2, + single_segment_flag: u1:0x1, + unused: u1:0x0, + reserved: u1:0x0, + content_checksum_flag: u1:0x1, + dictionary_id_flag: u2:0x0 + } + ); + + assert_eq( + extract_frame_header_descriptor(u8:0x0), + FrameHeaderDescriptor { + frame_content_size_flag: u2:0x0, + single_segment_flag: u1:0x0, + unused: u1:0x0, + reserved: u1:0x0, + content_checksum_flag: u1:0x0, + dictionary_id_flag: u2:0x0 + } + ); +} + +// Parses a Buffer and extracts information from the Frame_Header_Descriptor. +// The Buffer is assumed to contain a valid Frame_Header_Descriptor. The function +// returns BufferResult with the outcome of the operations on the buffer and +// information extracted from the Frame_Header_Descriptor +fn parse_frame_header_descriptor(buffer: Buffer) -> (BufferResult, FrameHeaderDescriptor) { + let (result, data) = buff::buffer_fixed_pop_checked(buffer); + match result.status { + BufferStatus::OK => { + let frame_header_desc = extract_frame_header_descriptor(data); + (result, frame_header_desc) + }, + _ => (result, zero!()) + } +} + +#[test] +fn test_parse_frame_header_descriptor() { + let buffer = Buffer { content: u32:0xA4, length: u32:8 }; + let (result, header) = parse_frame_header_descriptor(buffer); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 }, + }); + assert_eq(header, FrameHeaderDescriptor { + frame_content_size_flag: u2:0x2, + single_segment_flag: u1:0x1, + unused: u1:0x0, + reserved: u1:0x0, + content_checksum_flag: u1:0x1, + dictionary_id_flag: u2:0x0 + }); + + let buffer = Buffer { content: u32:0x0, length: u32:8 }; + let (result, header) = parse_frame_header_descriptor(buffer); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0, length: u32:0 }, + }); + assert_eq(header, FrameHeaderDescriptor { + frame_content_size_flag: u2:0x0, + single_segment_flag: u1:0x0, + unused: u1:0x0, + reserved: u1:0x0, + content_checksum_flag: u1:0x0, + dictionary_id_flag: u2:0x0 + }); + + let buffer = Buffer { content: u32:0x0, length: u32:0 }; + let (result, header) = parse_frame_header_descriptor(buffer); + assert_eq(result, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: Buffer { content: u32:0, length: u32:0 }, + }); + assert_eq(header, zero!()); +} + +// Returns a boolean showing if the Window_Descriptor section exists +// for the frame with the given FrameHeaderDescriptor +fn window_descriptor_exists(desc: FrameHeaderDescriptor) -> bool { + desc.single_segment_flag == u1:0 +} + +#[test] +fn test_window_descriptor_exists() { + let zero_desc = zero!(); + + let desc_with_ss = FrameHeaderDescriptor {single_segment_flag: u1:1, ..zero_desc}; + assert_eq(window_descriptor_exists(desc_with_ss), false); + + let desc_without_ss = FrameHeaderDescriptor {single_segment_flag: u1:0, ..zero_desc}; + assert_eq(window_descriptor_exists(desc_without_ss), true); +} + +// Extracts window size from 8-bit chunk of data +// that is assumed to be a valid Window_Descriptor +fn extract_window_size_from_window_descriptor(data: u8) -> u64 { + let exponent = data >> u8:3; + let mantissa = data & u8:7; + + let window_base = u64:1 << (u64:10 + exponent as u64); + let window_add = (window_base >> u64:3) * (mantissa as u64); + + window_base + window_add +} + +#[test] +fn test_extract_window_size_from_window_descriptor() { + assert_eq(extract_window_size_from_window_descriptor(u8:0x0), u64:0x400); + assert_eq(extract_window_size_from_window_descriptor(u8:0x9), u64:0x900); + assert_eq(extract_window_size_from_window_descriptor(u8:0xFF), u64:0x3c000000000); +} + +// Parses a Buffer with data and extracts information from the Window_Descriptor +// The buffer is assumed to contain a valid Window_Descriptor that is related to +// the same frame as the provided FrameHeaderDescriptor. The function returns +// BufferResult with the outcome of the operations on the buffer and window size. +fn parse_window_descriptor(buffer: Buffer, desc: FrameHeaderDescriptor) -> (BufferResult, WindowSize) { + assert!(window_descriptor_exists(desc), "window_descriptor_does_not_exist"); + + let (result, data) = buff::buffer_fixed_pop_checked(buffer); + match result.status { + BufferStatus::OK => { + let window_size = extract_window_size_from_window_descriptor(data); + (result, window_size) + }, + _ => (result, u64:0) + } +} + +#[test] +fn test_parse_window_descriptor() { + let zero_desc = zero!(); + let desc_without_ss = FrameHeaderDescriptor {single_segment_flag: u1:0, ..zero_desc}; + + let buffer = Buffer { content: u32:0xF, length: u32:0x4 }; + let (result, window_size) = parse_window_descriptor(buffer, desc_without_ss); + assert_eq(result, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: Buffer { content: u32:0xF, length: u32:0x4 }, + }); + assert_eq(window_size, u64:0); + + let buffer = Buffer { content: u32:0x0, length: u32:0x8 }; + let (result, window_size) = parse_window_descriptor(buffer, desc_without_ss); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(window_size, u64:0x400); + + let buffer = Buffer { content: u32:0x9, length: u32:0x8 }; + let (result, window_size) = parse_window_descriptor(buffer, desc_without_ss); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(window_size, u64:0x900); + + let buffer = Buffer { content: u32:0xFF, length: u32:0x8 }; + let (result, window_size) = parse_window_descriptor(buffer, desc_without_ss); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(window_size, u64:0x3c000000000); +} + +// Parses a Buffer with data and extracts information from the Dictionary_ID +// The buffer is assumed to contain a valid Dictionary_ID that is related to +// the same frame as the provided FrameHeaderDescriptor. The function returns +// BufferResult with the outcome of the operations on the buffer and dictionary ID +fn parse_dictionary_id(buffer: Buffer, desc: FrameHeaderDescriptor) -> (BufferResult, DictionaryId) { + let bytes = match desc.dictionary_id_flag { + u2:0 => u32:0, + u2:1 => u32:1, + u2:2 => u32:2, + u2:3 => u32:4, + _ => fail!("not_possible", u32:0) + }; + + let (result, data) = buff::buffer_pop_checked(buffer, bytes * u32:8); + match result.status { + BufferStatus::OK => (result, data as u32), + _ => (result, u32:0) + } +} + +#[test] +fn test_parse_dictionary_id() { + let zero_desc = zero!(); + + let buffer = Buffer { content: u32:0x0, length: u32:0x0 }; + let frame_header_desc = FrameHeaderDescriptor { dictionary_id_flag: u2:0, ..zero_desc}; + let (result, dictionary_id) = parse_dictionary_id(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0x0 }, + }); + assert_eq(dictionary_id, u32:0); + + let buffer = Buffer { content: u32:0x12, length: u32:0x8 }; + let frame_header_desc = FrameHeaderDescriptor { dictionary_id_flag: u2:0x1, ..zero_desc}; + let (result, dictionary_id) = parse_dictionary_id(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(dictionary_id, u32:0x12); + + let buffer = Buffer { content: u32:0x1234, length: u32:0x10 }; + let frame_header_desc = FrameHeaderDescriptor { dictionary_id_flag: u2:0x2, ..zero_desc}; + let (result, dictionary_id) = parse_dictionary_id(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(dictionary_id, u32:0x1234); + + let buffer = Buffer { content: u32:0x12345678, length: u32:0x20 }; + let frame_header_desc = FrameHeaderDescriptor { dictionary_id_flag: u2:0x3, ..zero_desc}; + let (result, dictionary_id) = parse_dictionary_id(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u32:0x0, length: u32:0 }, + }); + assert_eq(dictionary_id, u32:0x12345678); + + let buffer = Buffer { content: u32:0x1234, length: u32:0x10 }; + let frame_header_desc = FrameHeaderDescriptor { dictionary_id_flag: u2:0x3, ..zero_desc}; + let (result, dictionary_id) = parse_dictionary_id(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: Buffer { content: u32:0x1234, length: u32:0x10 }, + }); + assert_eq(dictionary_id, u32:0x0); +} + +// Returns boolean showing if the Frame_Content_Size section exists for +// the frame with the given FrameHeaderDescriptor. +fn frame_content_size_exists(desc: FrameHeaderDescriptor) -> bool { + desc.single_segment_flag != u1:0 || desc.frame_content_size_flag != u2:0 +} + +#[test] +fn test_frame_content_size_exists() { + let zero_desc = zero!(); + + let desc = FrameHeaderDescriptor {single_segment_flag: u1:0, frame_content_size_flag: u2:0, ..zero_desc}; + assert_eq(frame_content_size_exists(desc), false); + + let desc = FrameHeaderDescriptor {single_segment_flag: u1:0, frame_content_size_flag: u2:2, ..zero_desc}; + assert_eq(frame_content_size_exists(desc), true); + + let desc = FrameHeaderDescriptor {single_segment_flag: u1:1, frame_content_size_flag: u2:0, ..zero_desc}; + assert_eq(frame_content_size_exists(desc), true); + + let desc = FrameHeaderDescriptor {single_segment_flag: u1:1, frame_content_size_flag: u2:3, ..zero_desc}; + assert_eq(frame_content_size_exists(desc), true); +} + +// Parses a Buffer with data and extracts information from the Frame_Content_Size +// The buffer is assumed to contain a valid Frame_Content_Size that is related to +// the same frame as the provided FrameHeaderDescriptor. The function returns +// BufferResult with the outcome of the operations on the buffer and frame content size. +fn parse_frame_content_size(buffer: Buffer, desc: FrameHeaderDescriptor) -> (BufferResult, FrameContentSize) { + assert!(frame_content_size_exists(desc), "frame_content_size_does_not_exist"); + + let bytes = match desc.frame_content_size_flag { + u2:0 => u32:1, + u2:1 => u32:2, + u2:2 => u32:4, + u2:3 => u32:8, + _ => fail!("not_possible", u32:0) + }; + + let (result, data) = buff::buffer_pop_checked(buffer, bytes * u32:8); + match (result.status, bytes) { + (BufferStatus::OK, u32:2) => (result, data as u64 + u64:256), + (BufferStatus::OK, _) => (result, data as u64), + (_, _) => (result, u64:0) + } +} + +#[test] +fn test_parse_frame_content_size() { + let zero_desc = zero!(); + + let buffer = Buffer { content: u64:0x12, length: u32:8 }; + let frame_header_desc = FrameHeaderDescriptor { + frame_content_size_flag: u2:0, + single_segment_flag: u1:1, + ..zero_desc + }; + let (result, frame_content_size) = parse_frame_content_size(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u64:0x0, length: u32:0x0 }, + }); + assert_eq(frame_content_size, u64:0x12); + + let buffer = Buffer { content: u64:0x1234, length: u32:0x10 }; + let frame_header_desc = FrameHeaderDescriptor { frame_content_size_flag: u2:1, ..zero_desc}; + let (result, frame_content_size) = parse_frame_content_size(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u64:0x0, length: u32:0x0 }, + }); + assert_eq(frame_content_size, u64:0x1234 + u64:256); + + let buffer = Buffer { content: u64:0x12345678, length: u32:0x20 }; + let frame_header_desc = FrameHeaderDescriptor { frame_content_size_flag: u2:2, ..zero_desc}; + let (result, frame_content_size) = parse_frame_content_size(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u64:0x0, length: u32:0x0 }, + }); + assert_eq(frame_content_size, u64:0x12345678); + + let buffer = Buffer { content: u64:0x1234567890ABCDEF, length: u32:0x40 }; + let frame_header_desc = FrameHeaderDescriptor { frame_content_size_flag: u2:3, ..zero_desc}; + let (result, frame_content_size) = parse_frame_content_size(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::OK, + buffer: Buffer { content: u64:0x0, length: u32:0x0 }, + }); + assert_eq(frame_content_size, u64:0x1234567890ABCDEF); + + let buffer = Buffer { content: u32:0x12345678, length: u32:0x20 }; + let frame_header_desc = FrameHeaderDescriptor { frame_content_size_flag: u2:0x3, ..zero_desc}; + let (result, frame_content_size) = parse_frame_content_size(buffer, frame_header_desc); + assert_eq(result, BufferResult { + status: BufferStatus::NO_ENOUGH_DATA, + buffer: Buffer { content: u32:0x12345678, length: u32:0x20 }, + }); + assert_eq(frame_content_size, u64:0x0); +} + +// Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given +// window_size should be accepted or discarded. +// Based on window_size calculation from: RFC 8878 +// https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor +fn window_size_valid(window_size: WindowSize) -> bool { + let max_window_size = (WindowSize:1 << WINDOW_LOG_MAX) + (((WindowSize:1 << WINDOW_LOG_MAX) >> WindowSize:3) * MAX_MANTISSA); + + window_size <= max_window_size +} + +// Parses a Buffer with data and extracts Frame_Header information. The buffer +// is assumed to contain a valid Frame_Header The function returns FrameHeaderResult +// with BufferResult that contains outcome of the operations on the Buffer, +// FrameHeader with the extracted frame header if the parsing was successful, +// and the status of the operation in FrameHeaderStatus. On failure, the returned +// buffer is the same as the input buffer. +// WINDOW_LOG_MAX is the base 2 logarithm used for calculating the maximal allowed +// window_size. Frame header parsing function must discard all frames that +// have window_size above the maximal allowed window_size. +// CAPACITY is the buffer capacity +pub fn parse_frame_header(buffer: Buffer) -> FrameHeaderResult { + trace_fmt!("parse_frame_header: ==== Parsing ==== \n"); + trace_fmt!("parse_frame_header: initial buffer: {:#x}", buffer); + + let (result, desc) = parse_frame_header_descriptor(buffer); + trace_fmt!("parse_frame_header: buffer after parsing header descriptor: {:#x}", result.buffer); + + let (result, header) = match result.status { + BufferStatus::OK => { + let (result, window_size) = if window_descriptor_exists(desc) { + trace_fmt!("parse_frame_header: window_descriptor exists, parse it"); + parse_window_descriptor(result.buffer, desc) + } else { + trace_fmt!("parse_frame_header: window_descriptor does not exist, skip parsing it"); + (result, u64:0) + }; + trace_fmt!("parse_frame_header: buffer after parsing window_descriptor: {:#x}", result.buffer); + + match result.status { + BufferStatus::OK => { + trace_fmt!("parse_frame_header: parse dictionary_id"); + let (result, dictionary_id) = parse_dictionary_id(result.buffer, desc); + trace_fmt!("parse_frame_header: buffer after parsing dictionary_id: {:#x}", result.buffer); + + match result.status { + BufferStatus::OK => { + let (result, frame_content_size) = if frame_content_size_exists(desc) { + trace_fmt!("parse_frame_header: frame_content_size exists, parse it"); + parse_frame_content_size(result.buffer, desc) + } else { + trace_fmt!("parse_frame_header: frame_content_size does not exist, skip parsing it"); + (result, FRAME_CONTENT_SIZE_NOT_PROVIDED_VALUE) + }; + trace_fmt!("parse_frame_header: buffer after parsing frame_content_size: {:#x}", result.buffer); + + match result.status { + BufferStatus::OK => { + trace_fmt!("parse_frame_header: calculate frame header!"); + let window_size = match window_descriptor_exists(desc) { + true => window_size, + _ => frame_content_size, + }; + + ( + result, + FrameHeader { + window_size: window_size, + frame_content_size: frame_content_size, + dictionary_id: dictionary_id, + content_checksum_flag: desc.content_checksum_flag, + } + ) + }, + _ => { + trace_fmt!("parse_frame_header: Not enough data to parse frame_content_size!"); + (result, zero!()) + } + } + }, + _ => { + trace_fmt!("parse_frame_header: Not enough data to parse dictionary_id!"); + (result, zero!()) + } + } + }, + _ => { + trace_fmt!("parse_frame_header: Not enough data to parse window_descriptor!"); + (result, zero!()) + } + } + }, + _ => { + trace_fmt!("parse_frame_header: Not enough data to parse frame_header_descriptor!"); + (result, zero!()) + } + }; + + let (status, buffer) = match result.status { + BufferStatus::OK => (FrameHeaderStatus::OK, result.buffer), + _ => (FrameHeaderStatus::NO_ENOUGH_DATA, buffer) + }; + + let frame_header_result = FrameHeaderResult { status: status, header: header, buffer: buffer }; + + // libzstd always reports NO_ENOUGH_DATA errors before CORRUPTED caused by + // reserved bit being set + if (desc.reserved == u1:1 && frame_header_result.status != FrameHeaderStatus::NO_ENOUGH_DATA) { + trace_fmt!("parse_frame_header: frame descriptor corrupted!"); + // Critical failure - requires resetting the whole decoder + FrameHeaderResult { + status: FrameHeaderStatus::CORRUPTED, + buffer: zero!(), + header: zero!(), + } + } else if (!window_size_valid(header.window_size)) { + trace_fmt!("parse_frame_header: frame discarded: window_size to big: {}", header.window_size); + FrameHeaderResult { + status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, + buffer: zero!(), + header: zero!(), + } + } else { + frame_header_result + } +} + +// The largest allowed WindowLog for DSLX tests +pub const TEST_WINDOW_LOG_MAX = WindowSize:22; + +#[test] +fn test_parse_frame_header() { + // normal cases + let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_CAFE_09_C2, length: u32:96 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::OK, + buffer: Buffer { + content: bits[128]:0x0, + length: u32:0, + }, + header: FrameHeader { + window_size: u64:0x900, + frame_content_size: u64:0x1234567890ABCDEF, + dictionary_id: u32:0xCAFE, + content_checksum_flag: u1:0, + } + }); + + // SingleSegmentFlag is set and FrameContentSize is bigger than accepted window_size + let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_CAFE_E2, length: u32:88 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, + buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, + header: zero!() + }); + + let buffer = Buffer { content: bits[128]:0xaa20, length: u32:16 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::OK, + buffer: Buffer { + content: bits[128]:0x0, + length: u32:0, + }, + header: FrameHeader { + window_size: u64:0xaa, + frame_content_size: u64:0xaa, + dictionary_id: u32:0x0, + content_checksum_flag: u1:0, + }, + }); + + // when buffer is too short + let buffer = Buffer { content: bits[128]:0x0, length: u32:0 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::NO_ENOUGH_DATA, + buffer: buffer, + header: zero!() + }); + + let buffer = Buffer { content: bits[128]:0xC2, length: u32:8 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::NO_ENOUGH_DATA, + buffer: buffer, + header: zero!() + }); + + let buffer = Buffer { content: bits[128]:0x09_C2, length: u32:16 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::NO_ENOUGH_DATA, + buffer: buffer, + header: zero!() + }); + + let buffer = Buffer { content: bits[128]:0x1234_09_C2, length: u32:32 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::NO_ENOUGH_DATA, + buffer: buffer, + header: zero!() + }); + + let buffer = Buffer { content: bits[128]:0x1234_09_C2, length: u32:32 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::NO_ENOUGH_DATA, + buffer: buffer, + header: zero!() + }); + + // when frame header descriptor is corrupted + let buffer = Buffer { content: bits[128]:0x1234567890ABCDEF_1234_09_CA, length: u32:96 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::CORRUPTED, + buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, + header: zero!() + }); + + // Frame Header is discarded because Window size required by frame is too big for given decoder + // configuration + let buffer = Buffer { content: bits[128]:0xd310, length: u32:16 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, + buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, + header: zero!() + }); +} From 86de6a7993296574357987903853eef38138af16 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 10 Jan 2024 09:33:23 +0100 Subject: [PATCH 05/49] dependency_support/libzstd: Make zstd_errors.h public Required for expected_status inference in C++ tests for ZSTD decoder components Internal-tag: [#53465] Signed-off-by: Pawel Czarnecki --- dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel | 1 + 1 file changed, 1 insertion(+) diff --git a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel index dbe80f692e..e5a74acb24 100644 --- a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel +++ b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel @@ -40,6 +40,7 @@ cc_library( ]), hdrs = [ "lib/zstd.h", + "lib/common/zstd_errors.h", ], strip_include_prefix = "lib", local_defines = [ From 3208de76c0f1a86dababa30a297537a858079e5a Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 9 Nov 2023 15:08:29 +0100 Subject: [PATCH 06/49] dependency_support: Add decodecorpus binary Internal-tag: [#50967] Signed-off-by: Robert Winkler --- .../bundled.BUILD.bazel | 39 +++++ .../decodecorpus.patch | 152 ++++++++++++++++++ dependency_support/load_external.bzl | 14 ++ xls/modules/zstd/BUILD | 9 ++ 4 files changed, 214 insertions(+) create mode 100644 dependency_support/com_github_facebook_zstd/decodecorpus.patch diff --git a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel index e5a74acb24..2538c07bfd 100644 --- a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel +++ b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel @@ -49,3 +49,42 @@ cc_library( ], visibility = ["//visibility:public"], ) + +# NOTE: Required because of direct zstd_compress.c include in decodecorpus sources +cc_library( + name = "decodecorpus_includes", + hdrs = [ + "lib/compress/zstd_compress.c", + ], +) + +cc_binary( + name = "decodecorpus", + srcs = [ + "tests/decodecorpus.c", + ] + glob( + [ + "programs/*.c", + "programs/*.h", + ], + exclude = [ + "programs/zstdcli.c", + ], + ), + deps = [ + ":zstd", + ":decodecorpus_includes", + ], + includes = [ + "lib/", + "lib/common/", + "lib/compress/", + "lib/dictBuilder/", + "lib/deprecated/", + "programs/", + ], + local_defines = [ + "XXH_NAMESPACE=ZSTD_", + ], + visibility = ["//visibility:public"], +) diff --git a/dependency_support/com_github_facebook_zstd/decodecorpus.patch b/dependency_support/com_github_facebook_zstd/decodecorpus.patch new file mode 100644 index 0000000000..fdd4cc2577 --- /dev/null +++ b/dependency_support/com_github_facebook_zstd/decodecorpus.patch @@ -0,0 +1,152 @@ +diff --git tests/decodecorpus.c tests/decodecorpus.c +index 50935d31..522b3769 100644 +--- tests/decodecorpus.c ++++ tests/decodecorpus.c +@@ -240,6 +240,12 @@ typedef enum { + gt_block, /* generate compressed blocks without block/frame headers */ + } genType_e; + ++typedef enum { ++ lt_raw, ++ lt_rle, ++ lt_compressed, ++} literalType_e; ++ + /*-******************************************************* + * Global variables (set from command line) + *********************************************************/ +@@ -252,7 +258,11 @@ U32 g_maxBlockSize = ZSTD_BLOCKSIZE_MAX; /* <= 128 KB */ + + struct { + int contentSize; /* force the content size to be present */ +-} opts; /* advanced options on generation */ ++ blockType_e *blockType; /* force specific block type */ ++ literalType_e *literalType; /* force specific literals type */ ++ int frame_header_only; /* generate only frame header */ ++ int no_magic; /* do not generate magic number */ ++} opts; + + /* Generate and write a random frame header */ + static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -317,8 +327,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + } + + /* write out the header */ +- MEM_writeLE32(op + pos, ZSTD_MAGICNUMBER); +- pos += 4; ++ if (!opts.no_magic) { ++ MEM_writeLE32(op + pos, ZSTD_MAGICNUMBER); ++ pos += 4; ++ } + + { + /* +@@ -363,8 +375,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + /* Write a literal block in either raw or RLE form, return the literals size */ + static size_t writeLiteralsBlockSimple(U32* seed, frame_t* frame, size_t contentSize) + { ++ int force_literal_type = opts.literalType != NULL; ++ int const type = (force_literal_type) ? *(opts.literalType) : RAND(seed) % 2; ++ + BYTE* op = (BYTE*)frame->data; +- int const type = RAND(seed) % 2; + int const sizeFormatDesc = RAND(seed) % 8; + size_t litSize; + size_t maxLitSize = MIN(contentSize, g_maxBlockSize); +@@ -612,8 +626,15 @@ static size_t writeLiteralsBlockCompressed(U32* seed, frame_t* frame, size_t con + + static size_t writeLiteralsBlock(U32* seed, frame_t* frame, size_t contentSize) + { +- /* only do compressed for larger segments to avoid compressibility issues */ +- if (RAND(seed) & 7 && contentSize >= 64) { ++ int select_compressed = 0; ++ if (opts.literalType) { ++ select_compressed = *(opts.literalType) == lt_compressed; ++ } else { ++ /* only do compressed for larger segments to avoid compressibility issues */ ++ select_compressed = RAND(seed) & 7 && contentSize >= 64; ++ } ++ ++ if (select_compressed) { + return writeLiteralsBlockCompressed(seed, frame, contentSize); + } else { + return writeLiteralsBlockSimple(seed, frame, contentSize); +@@ -1030,7 +1051,8 @@ static size_t writeCompressedBlock(U32* seed, frame_t* frame, size_t contentSize + static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, + int lastBlock, dictInfo info) + { +- int const blockTypeDesc = RAND(seed) % 8; ++ int force_block_type = opts.blockType != NULL; ++ int const blockTypeDesc = (force_block_type) ? *(opts.blockType) : RAND(seed) % 8; + size_t blockSize; + int blockType; + +@@ -1069,7 +1091,7 @@ static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, + + frame->data = op; + compressedSize = writeCompressedBlock(seed, frame, contentSize, info); +- if (compressedSize >= contentSize) { /* compressed block must be strictly smaller than uncompressed one */ ++ if (compressedSize >= contentSize && !force_block_type) { /* compressed block must be strictly smaller than uncompressed one */ + blockType = 0; + memcpy(op, frame->src, contentSize); + +@@ -1240,7 +1262,11 @@ static U32 generateFrame(U32 seed, frame_t* fr, dictInfo info) + DISPLAYLEVEL(3, "frame seed: %u\n", (unsigned)seed); + initFrame(fr); + ++ + writeFrameHeader(&seed, fr, info); ++ if (opts.frame_header_only) ++ return seed; ++ + writeBlocks(&seed, fr, info); + writeChecksum(fr); + +@@ -1768,6 +1794,9 @@ static void advancedUsage(const char* programName) + DISPLAY( " --max-block-size-log=# : max block size log, must be in range [2, 17]\n"); + DISPLAY( " --max-content-size-log=# : max content size log, must be <= 20\n"); + DISPLAY( " (this is ignored with gen-blocks)\n"); ++ DISPLAY( " --block-type=# : force certain block type (raw=0, rle=1, compressed=2)\n"); ++ DISPLAY( " --frame-header-only : dump only frame header\n"); ++ DISPLAY( " --no-magic : do not add magic number\n"); + } + + /*! readU32FromChar() : +@@ -1889,6 +1918,18 @@ int main(int argc, char** argv) + U32 value = readU32FromChar(&argument); + g_maxDecompressedSizeLog = + MIN(MAX_DECOMPRESSED_SIZE_LOG, value); ++ } else if (longCommandWArg(&argument, "block-type=")) { ++ U32 value = readU32FromChar(&argument); ++ opts.blockType = malloc(sizeof(blockType_e)); ++ *(opts.blockType) = value; ++ } else if (longCommandWArg(&argument, "literal-type=")) { ++ U32 value = readU32FromChar(&argument); ++ opts.literalType = malloc(sizeof(literalType_e)); ++ *(opts.literalType) = value; ++ } else if (strcmp(argument, "frame-header-only") == 0) { ++ opts.frame_header_only = 1; ++ } else if (strcmp(argument, "no-magic") == 0) { ++ opts.no_magic = 1; + } else { + advancedUsage(argv[0]); + return 1; +@@ -1900,6 +1941,18 @@ int main(int argc, char** argv) + return 1; + } } } } /* for (argNb=1; argNb Date: Tue, 14 Nov 2023 15:43:16 +0100 Subject: [PATCH 07/49] modules/zstd: Add data generator library This commit adds a binary that calls decoding to generate data and loads it into a vector of bytes. Internal-tag: [#50967] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 17 ++++- xls/modules/zstd/data_generator.cc | 110 +++++++++++++++++++++++++++++ xls/modules/zstd/data_generator.h | 42 +++++++++++ 3 files changed, 166 insertions(+), 3 deletions(-) create mode 100644 xls/modules/zstd/data_generator.cc create mode 100644 xls/modules/zstd/data_generator.h diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 19e9a9d8fb..442bfc2b83 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -141,12 +141,23 @@ xls_dslx_test( ) cc_library( - name = "zstd_test_data_generator", - srcs = ["zstd_test_data_generator.cc"], - hdrs = ["zstd_test_data_generator.h"], + name = "data_generator", + srcs = ["data_generator.cc"], + hdrs = ["data_generator.h"], data = [ "@com_github_facebook_zstd//:decodecorpus", ], + deps = [ + "//xls/common:subprocess", + "//xls/common/file:filesystem", + "//xls/common/file:get_runfile_path", + "//xls/common/status:status_macros", + "//xls/ir", + "@com_google_absl//absl/status", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/time", + "@com_google_absl//absl/types:span", + ], ) xls_dslx_library( diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc new file mode 100644 index 0000000000..00cbb6b91b --- /dev/null +++ b/xls/modules/zstd/data_generator.cc @@ -0,0 +1,110 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// + +#include "xls/modules/zstd/data_generator.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/subprocess.h" +#include "xls/ir/value.h" + +namespace xls::zstd { + +static void PrintRawDataVector(std::vector vector) { + for (int i = 0; i < vector.size(); ++i) { + std::cout << std::setfill('0') << std::setw(8) << std::hex << i << ": " + << "0x" << std::setw(2) << std::hex << int(vector[i]) + << std::endl; + } +} + +static absl::StatusOr> ReadFileAsRawData( + const std::filesystem::path& path) { + std::ifstream file(path, std::ios::binary); + if (!file.is_open()) { + return absl::NotFoundError("Unable to open a test file"); + } + + std::vector raw_data((std::istreambuf_iterator(file)), + (std::istreambuf_iterator())); + return raw_data; +} + +static std::string CreateNameForGeneratedFile( + absl::Span args, std::string_view ext, + std::optional prefix) { + std::string output; + + if (prefix.has_value()) { + output += prefix.value(); + output += "_"; + } + + for (auto const& x : args) { + output += x; + } + auto nospace_output = std::remove(output.begin(), output.end(), ' '); + output.erase(nospace_output, output.end()); + std::replace(output.begin(), output.end(), '-', '_'); + + output += ext; + + return output; +} + +static absl::StatusOr CallDecodecorpus( + absl::Span args, + std::optional cwd = std::nullopt, + std::optional timeout = std::nullopt) { + XLS_ASSIGN_OR_RETURN( + std::filesystem::path path, + xls::GetXlsRunfilePath("external/com_github_facebook_zstd/decodecorpus")); + + std::vector cmd = {path}; + cmd.insert(cmd.end(), args.begin(), args.end()); + return SubprocessErrorAsStatus(xls::InvokeSubprocess(cmd)); +} + +absl::StatusOr> GenerateFrameHeader(int seed, bool magic) { + std::array args; + args[0] = "-s" + std::to_string(seed); + args[1] = (magic) ? "" : "--no-magic"; + args[2] = "--frame-header-only"; + std::filesystem::path output_path = + std::filesystem::temp_directory_path() / + std::filesystem::path( + CreateNameForGeneratedFile(absl::MakeSpan(args), ".zstd", "fh")); + args[3] = "-p" + std::string(output_path); + + XLS_ASSIGN_OR_RETURN(auto result, CallDecodecorpus(args)); + auto raw_data = ReadFileAsRawData(output_path); + std::remove(output_path.c_str()); + return raw_data; +} + +} // namespace xls::zstd diff --git a/xls/modules/zstd/data_generator.h b/xls/modules/zstd/data_generator.h new file mode 100644 index 0000000000..06462f4872 --- /dev/null +++ b/xls/modules/zstd/data_generator.h @@ -0,0 +1,42 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +// +#ifndef XLS_MODULES_ZSTD_DATA_GENERATOR_H_ +#define XLS_MODULES_ZSTD_DATA_GENERATOR_H_ + +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "absl/time/time.h" +#include "absl/types/span.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/status/status_macros.h" +#include "xls/common/subprocess.h" +#include "xls/ir/value.h" + +namespace xls::zstd { + +absl::StatusOr> GenerateFrameHeader(int seed, bool magic); + +} // namespace xls::zstd + +#endif // XLS_MODULES_ZSTD_DATA_GENERATOR_H_ From c65b32ce518fae8230ceee4d6348e709c48d16c8 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 7 Nov 2023 12:32:42 +0100 Subject: [PATCH 08/49] modules/zstd: Add zstd frame header tests Internal-tag: [#50967] Co-authored-by: Pawel Czarnecki Signed-off-by: Robert Winkler Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 49 +++- xls/modules/zstd/frame_header_test.cc | 315 ++++++++++++++++++++++++++ xls/modules/zstd/frame_header_test.x | 30 +++ 3 files changed, 391 insertions(+), 3 deletions(-) create mode 100644 xls/modules/zstd/frame_header_test.cc create mode 100644 xls/modules/zstd/frame_header_test.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 442bfc2b83..a069f53e29 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -132,7 +132,7 @@ xls_dslx_library( ], deps = [ ":buffer_dslx", - ] + ], ) xls_dslx_test( @@ -171,8 +171,51 @@ xls_dslx_library( ) xls_dslx_test( - name = "zstd_frame_header_dslx_test", - library = ":zstd_frame_header_dslx", + name = "frame_header_dslx_test", + library = ":frame_header_dslx", +) + +xls_dslx_library( + name = "frame_header_test_dslx", + srcs = [ + "frame_header_test.x", + ], + deps = [ + ":buffer_dslx", + ":frame_header_dslx", + ], +) + +cc_test( + name = "frame_header_cc_test", + srcs = [ + "frame_header_test.cc", + ], + data = [ + ":frame_header_test_dslx", + ], + shard_count = 50, + deps = [ + ":data_generator", + "//xls/common:xls_gunit_main", + "//xls/common/file:filesystem", + "//xls/common/file:get_runfile_path", + "//xls/common/status:matchers", + "//xls/dslx:create_import_data", + "//xls/dslx:import_data", + "//xls/dslx:interp_value", + "//xls/dslx:parse_and_typecheck", + "//xls/dslx/ir_convert:ir_converter", + "//xls/dslx/type_system:parametric_env", + "//xls/ir", + "//xls/ir:ir_parser", + "//xls/ir:ir_test_base", + "@com_github_facebook_zstd//:zstd", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_fuzztest//fuzztest", + "@com_google_fuzztest//fuzztest:googletest_fixture_adapter", + "@com_google_googletest//:gtest", + ], ) xls_dslx_verilog( diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc new file mode 100644 index 0000000000..2f557b24fa --- /dev/null +++ b/xls/modules/zstd/frame_header_test.cc @@ -0,0 +1,315 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#define ZSTD_STATIC_LINKING_ONLY 1 +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "fuzztest/fuzztest.h" +#include "fuzztest/googletest_fixture_adapter.h" +#include "gtest/gtest.h" +#include "lib/common/zstd_errors.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/status/matchers.h" +#include "xls/dslx/create_import_data.h" +#include "xls/dslx/import_data.h" +#include "xls/dslx/interp_value.h" +#include "xls/dslx/ir_convert/ir_converter.h" +#include "xls/dslx/parse_and_typecheck.h" +#include "xls/dslx/type_system/parametric_env.h" +#include "xls/ir/events.h" +#include "xls/ir/ir_parser.h" +#include "xls/ir/ir_test_base.h" +#include "xls/modules/zstd/data_generator.h" + +namespace xls { +namespace { + +// Must be in sync with FrameHeaderStatus from +// xls/modules/zstd/frame_header.x +enum FrameHeaderStatus { + OK, + CORRUPTED, + NO_ENOUGH_DATA, + UNSUPPORTED_WINDOW_SIZE +}; + +class FrameHeaderTest : public xls::IrTestBase { + public: + // Prepare simulation environment + void SetUp() { + XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path path, + xls::GetXlsRunfilePath(this->file)); + XLS_ASSERT_OK_AND_ASSIGN(std::string module_text, + xls::GetFileContents(path)); + + auto import_data = xls::dslx::CreateImportDataForTest(); + XLS_ASSERT_OK_AND_ASSIGN( + xls::dslx::TypecheckedModule checked_module, + xls::dslx::ParseAndTypecheck(module_text, this->file_name, + this->module_name, &import_data)); + + auto options = xls::dslx::ConvertOptions{}; + /* FIXME: The following code should work with a parametrized version of + * the `parse_frame_header` function. However, it seems that + * the symbolic_bindings are not correctly propagated inside + * ConvertOneFunction. To leverage the problem, a simple specialization + * of the function is used (`parse_frame_header_128`). + * Once the problem is solved, we can restore the code below. + */ + // auto symbolic_bindings = xls::dslx::ParametricEnv( + // absl::flat_hash_map{ + // {"CAPACITY", xls::dslx::InterpValue::MakeUBits(/*bit_count=*/32, + // /*value=*/32)}}); + dslx::ParametricEnv* symbolic_bindings = nullptr; + XLS_ASSERT_OK_AND_ASSIGN( + this->converted, xls::dslx::ConvertOneFunction( + checked_module.module, function_name, &import_data, + symbolic_bindings, options)); + } + + // Parse Buffer contents with ZSTD library, prepare inputs for DSLX simulation + // based on the buffer contents, form the expected output from the simulation, + // run the simulation of frame header parser and compare the results against + // expected values. + void ParseAndCompareWithZstd(absl::Span buffer) { + absl::Span input_buffer; + ZSTD_frameHeader zstd_fh; + size_t result; + std::vector buffer_extended(dslx_buffer_size_bytes, 0); + + // Extend buffer contents to 128 bits if necessary + if (buffer.size() < dslx_buffer_size_bytes) { + std::copy(buffer.begin(), buffer.end(), buffer_extended.begin()); + input_buffer = absl::MakeSpan(buffer_extended); + } else { + input_buffer = buffer; + } + + // Parse input buffer with libzstd and write it as ZSTD_frameHeader + ASSERT_TRUE(!buffer.empty() && buffer.data() != nullptr); + result = ZSTD_getFrameHeader_advanced( + &zstd_fh, buffer.data(), buffer.size(), ZSTD_f_zstd1_magicless); + + // Decide on the expected status + FrameHeaderStatus expected_status = FrameHeaderStatus::OK; + if (result != 0) { + if (ZSTD_isError(result)) { + switch (ZSTD_getErrorCode(result)) { + case ZSTD_error_frameParameter_windowTooLarge: + expected_status = FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE; + break; + case ZSTD_error_frameParameter_unsupported: + // Occurs when reserved_bit == 1, should result in CORRUPTED state + default: + // Provided data is corrupted. Unable to correctly parse ZSTD frame. + expected_status = FrameHeaderStatus::CORRUPTED; + break; + } + } else { + // Provided data is to small to correctly parse ZSTD frame, should + // have `result` bytes, got `buffer.size()` bytes. + expected_status = FrameHeaderStatus::NO_ENOUGH_DATA; + } + } + + auto input = CreateDSLXSimulationInput(buffer.size(), input_buffer); + absl::flat_hash_map hashed_input = {{"buffer", input}}; + + auto expected_frame_header_result = CreateExpectedFrameHeaderResult( + &zstd_fh, input, buffer, expected_status); + + RunAndExpectEq(hashed_input, expected_frame_header_result, this->converted, + true, true); + } + + const char* file = "xls/modules/zstd/frame_header_test.x"; + const char* module_name = "frame_header_test"; + const char* file_name = "frame_header_test.x"; + const char* function_name = "parse_frame_header_128"; + std::string converted; + + private: + const uint32_t dslx_buffer_size = 128; + const uint32_t dslx_buffer_size_bytes = + (dslx_buffer_size + CHAR_BIT - 1) / CHAR_BIT; + + void PrintZSTDFrameHeader(ZSTD_frameHeader* fh) { + std::cout << std::hex; + std::cout << "zstd_fh->frameContentSize: 0x" << fh->frameContentSize + << std::endl; + std::cout << "zstd_fh->windowSize: 0x" << fh->windowSize << std::endl; + std::cout << "zstd_fh->blockSizeMax: 0x" << fh->blockSizeMax << std::endl; + std::cout << "zstd_fh->frameType: 0x" << fh->frameType << std::endl; + std::cout << "zstd_fh->headerSize: 0x" << fh->headerSize << std::endl; + std::cout << "zstd_fh->dictID: 0x" << fh->dictID << std::endl; + std::cout << "zstd_fh->checksumFlag: 0x" << fh->checksumFlag << std::endl; + } + + // Form DSLX Value representing ZSTD Frame header based on data parsed with + // ZSTD library. Represents DSLX struct `FrameHeader`. + Value CreateExpectedFrameHeader(ZSTD_frameHeader* fh) { + return Value::Tuple({ + /*window_size:*/ Value(UBits(fh->windowSize, 64)), + /*frame_content_size:*/ Value(UBits(fh->frameContentSize, 64)), + /*dictionary_id:*/ Value(UBits(fh->dictID, 32)), + /*content_checksum_flag: */ Value(UBits(fh->checksumFlag, 1)), + }); + } + + // Create DSLX Value representing Buffer contents after parsing frame header + // in simulation. Represents DSLX struct `Buffer`. + Value CreateExpectedBuffer(Value dslx_simulation_input, + absl::Span input_buffer, + uint32_t consumed_bytes_count, + FrameHeaderStatus expected_status) { + // Return original buffer contents + if (expected_status == FrameHeaderStatus::NO_ENOUGH_DATA) + return dslx_simulation_input; + // Critical failure - return empty buffer + if (expected_status == FrameHeaderStatus::CORRUPTED) + return Value::Tuple({/*contents:*/ Value(UBits(0, dslx_buffer_size)), + /*length:*/ Value(UBits(0, 32))}); + + // Frame Header parsing succeeded. Expect output buffer contents with + // removed first `consumed_bytes_count` bytes and extended to + // dslx_buffer_size if necessary + int bytes_to_extend = + dslx_buffer_size_bytes - (input_buffer.size() - consumed_bytes_count); + std::vector output_buffer(input_buffer.begin() + consumed_bytes_count, + input_buffer.end()); + for (int i = 0; i < bytes_to_extend; i++) { + output_buffer.push_back(0); + } + + auto expected_buffer_contents = + Value(Bits::FromBytes(output_buffer, dslx_buffer_size)); + auto output_buffer_size_bits = + (input_buffer.size() - consumed_bytes_count) * CHAR_BIT; + uint32_t expected_buffer_size = output_buffer_size_bits > dslx_buffer_size + ? dslx_buffer_size + : output_buffer_size_bits; + + return Value::Tuple({/*contents:*/ expected_buffer_contents, + /*length:*/ Value(UBits(expected_buffer_size, 32))}); + } + + // Prepare DSLX Value representing Full Result of frame header parsing + // simulation. It consists of expected status, parsing result and buffer + // contents after parsing. Represents DSLX struct `FrameHeaderResult`. + Value CreateExpectedFrameHeaderResult(ZSTD_frameHeader* fh, + Value dslx_simulation_input, + absl::Span input_buffer, + FrameHeaderStatus expected_status) { + auto expected_buffer = CreateExpectedBuffer( + dslx_simulation_input, input_buffer, fh->headerSize, expected_status); + auto expected_frame_header = CreateExpectedFrameHeader(fh); + return Value::Tuple({/*status:*/ Value(UBits(expected_status, 2)), + /*header:*/ expected_frame_header, + /*buffer:*/ expected_buffer}); + } + + // Return DSLX Value used as input argument for running frame header parsing + // simulation. Represents DSLX struct `Buffer`. + Value CreateDSLXSimulationInput(size_t buffer_size, + absl::Span input_buffer) { + size_t size = buffer_size; + + // ignore buffer contents that won't fit into specialized buffer + if (buffer_size > dslx_buffer_size_bytes) size = dslx_buffer_size_bytes; + + return Value::Tuple( + {/*contents:*/ Value(Bits::FromBytes(input_buffer, dslx_buffer_size)), + /*length:*/ Value(UBits(size * CHAR_BIT, 32))}); + } +}; + +/* TESTS */ + +TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.4.7"); } + +TEST_F(FrameHeaderTest, ParseFrameHeaderSuccess) { + std::vector buffer{0xC2, 0x09, 0xFE, 0xCA, 0xEF, 0xCD, + 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12}; + this->ParseAndCompareWithZstd(buffer); +} + +TEST_F(FrameHeaderTest, ParseFrameHeaderFailCorruptedReservedBit) { + std::vector buffer{0xEA, 0xFE, 0xCA, 0xEF, 0xCD, 0xAB, + 0x90, 0x78, 0x56, 0x34, 0x12}; + this->ParseAndCompareWithZstd(buffer); +} + +TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSizeTooBig) { + std::vector buffer{0x10, 0xD3}; + this->ParseAndCompareWithZstd(buffer); +} + +TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughData) { + std::vector buffer{0xD3, 0xED}; + this->ParseAndCompareWithZstd(buffer); +} + +// NO_ENOUGH_DATA has priority over CORRUPTED from reserved bit +TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughDataReservedBit) { + std::vector buffer{0xED, 0xD3}; + this->ParseAndCompareWithZstd(buffer); +} + +class FrameHeaderSeededTest : public FrameHeaderTest, + public ::testing::WithParamInterface { + public: + static const uint32_t random_headers_count = 50; +}; + +// Test `random_headers_count` instances of randomly generated valid +// frame headers, generated with `decodecorpus` tool. +TEST_P(FrameHeaderSeededTest, ParseMultipleFrameHeaders) { + auto seed = GetParam(); + auto frame_header = zstd::GenerateFrameHeader(seed, false); + ASSERT_TRUE(frame_header.ok()); + this->ParseAndCompareWithZstd(frame_header.value()); +} + +INSTANTIATE_TEST_SUITE_P( + FrameHeaderSeededTest, FrameHeaderSeededTest, + ::testing::Range(0, FrameHeaderSeededTest::random_headers_count)); + +class FrameHeaderFuzzTest + : public fuzztest::PerFuzzTestFixtureAdapter { + public: + void ParseMultipleRandomFrameHeaders(std::vector frame_header) { + this->ParseAndCompareWithZstd(frame_header); + } +}; + +// Perform UNDETERMINISTIC FuzzTests with input vectors of variable length and +// contents. Frame Headers generated by FuzzTests can be invalid. +// This test checks if negative cases are handled correctly. +FUZZ_TEST_F(FrameHeaderFuzzTest, ParseMultipleRandomFrameHeaders) + .WithDomains(fuzztest::Arbitrary>() + .WithMinSize(1) + .WithMaxSize(16)); + +} // namespace +} // namespace xls diff --git a/xls/modules/zstd/frame_header_test.x b/xls/modules/zstd/frame_header_test.x new file mode 100644 index 0000000000..6009050225 --- /dev/null +++ b/xls/modules/zstd/frame_header_test.x @@ -0,0 +1,30 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; +import xls.modules.zstd.buffer as buff; +import xls.modules.zstd.frame_header as frame_header; + +type Buffer = buff::Buffer; +type FrameHeaderResult = frame_header::FrameHeaderResult; +type WindowSize = frame_header::WindowSize; + +// Largest allowed WindowLog accepted by libzstd decompression function +// https://github.com/facebook/zstd/blob/v1.4.7/lib/decompress/zstd_decompress.c#L296 +// Use only in C++ tests when comparing DSLX ZSTD Decoder with libzstd +pub const TEST_WINDOW_LOG_MAX_LIBZSTD = WindowSize:30; + +pub fn parse_frame_header_128(buffer: Buffer<128>) -> FrameHeaderResult<128> { + frame_header::parse_frame_header(buffer) +} From 236d96e90cb6d9e8f3a1b9d2df71e30718ca3b95 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 22 Nov 2023 15:37:58 +0100 Subject: [PATCH 09/49] modules/zstd: Add common zstd definitions Internal-tag: [#51343] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 8 +++++++ xls/modules/zstd/common.x | 49 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 xls/modules/zstd/common.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index a069f53e29..924d50a376 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -170,6 +170,14 @@ xls_dslx_library( ], ) +xls_dslx_library( + name = "common_dslx", + srcs = [ + "common.x", + ], + deps = [], +) + xls_dslx_test( name = "frame_header_dslx_test", library = ":frame_header_dslx", diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x new file mode 100644 index 0000000000..8bc554f8a6 --- /dev/null +++ b/xls/modules/zstd/common.x @@ -0,0 +1,49 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +pub const DATA_WIDTH = u32:64; +pub const MAX_ID = u32::MAX; +pub const SYMBOL_WIDTH = u32:8; +pub const BLOCK_SIZE_WIDTH = u32:21; + +pub type BlockData = bits[DATA_WIDTH]; +pub type BlockPacketLength = u32; +pub type BlockSize = bits[BLOCK_SIZE_WIDTH]; +pub type CopyOrMatchContent = BlockData; +pub type CopyOrMatchLength = u64; + +pub enum BlockType : u2 { + RAW = 0, + RLE = 1, + COMPRESSED = 2, + RESERVED = 3, +} + +pub struct BlockDataPacket { + last: bool, + last_block: bool, + id: u32, + data: BlockData, + length: BlockPacketLength, +} + +pub enum SequenceExecutorMessageType : u1 { + LITERAL = 0, + SEQUENCE = 1, +} + +pub struct ExtendedBlockDataPacket { + msg_type: SequenceExecutorMessageType, + packet: BlockDataPacket, +} From 4fecaca16cd581beef32733bc5c5131e290cf24a Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 22 Nov 2023 15:45:55 +0100 Subject: [PATCH 10/49] modules/zstd: Add raw block decoder Internal-tag: [#51343] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 77 ++++++++++++++++++++ xls/modules/zstd/raw_block_dec.x | 118 +++++++++++++++++++++++++++++++ 2 files changed, 195 insertions(+) create mode 100644 xls/modules/zstd/raw_block_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 924d50a376..52cab9bf49 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -287,3 +287,80 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "raw_block_dec_dslx", + srcs = [ + "raw_block_dec.x", + ], + deps = [ + ":buffer_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "raw_block_dec_dslx_test", + library = ":raw_block_dec_dslx", +) + +xls_dslx_verilog( + name = "raw_block_dec_verilog", + codegen_args = { + "module_name": "RawBlockDecoder", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RawBlockDecoder", + library = ":raw_block_dec_dslx", + verilog_file = "raw_block_dec.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "raw_block_dec_opt_ir_benchmark", + src = ":raw_block_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "raw_block_dec_verilog_lib", + srcs = [ + ":raw_block_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "raw_block_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RawBlockDecoder", + deps = [ + ":raw_block_dec_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "raw_block_dec_benchmark_synth", + synth_target = ":raw_block_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "raw_block_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":raw_block_dec_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/raw_block_dec.x b/xls/modules/zstd/raw_block_dec.x new file mode 100644 index 0000000000..8b32f81277 --- /dev/null +++ b/xls/modules/zstd/raw_block_dec.x @@ -0,0 +1,118 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains the implementation of RawBlockDecoder responsible for decoding +// ZSTD Raw Blocks. More information about Raw Block's format can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.2.2 + +import xls.modules.zstd.common as common; + +type BlockDataPacket = common::BlockDataPacket; +type BlockPacketLength = common::BlockPacketLength; +type BlockData = common::BlockData; +type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; + +struct RawBlockDecoderState { + prev_id: u32, // ID of the previous block + prev_last: bool, // if the previous packet was the last one that makes up the whole block + prev_valid: bool, // if prev_id and prev_last contain valid data +} + +const ZERO_RAW_BLOCK_DECODER_STATE = zero!(); + +// RawBlockDecoder is responsible for decoding Raw Blocks, +// it should be a part of the ZSTD Decoder pipeline. +pub proc RawBlockDecoder { + input_r: chan in; + output_s: chan out; + + init { (ZERO_RAW_BLOCK_DECODER_STATE) } + + config( + input_r: chan in, + output_s: chan out + ) {(input_r, output_s)} + + next(state: RawBlockDecoderState) { + let tok = join(); + let (tok, data) = recv(tok, input_r); + if state.prev_valid && (data.id != state.prev_id) && (state.prev_last == false) { + trace_fmt!("ID changed but previous packet have no last!"); + fail!("no_last", ()); + } else {}; + + let output_data = ExtendedBlockDataPacket { + // Decoded RAW block is always a literal + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: data.last, + last_block: data.last_block, + id: data.id, + data: data.data as BlockData, + length: data.length as BlockPacketLength, + }, + }; + + let tok = send(tok, output_s, output_data); + + RawBlockDecoderState { + prev_valid: true, + prev_id: output_data.packet.id, + prev_last: output_data.packet.last + } + } +} + +#[test_proc] +proc RawBlockDecoderTest { + terminator: chan out; + dec_input_s: chan out; + dec_output_r: chan in; + + config(terminator: chan out) { + let (dec_input_s, dec_input_r) = chan("dec_input"); + let (dec_output_s, dec_output_r) = chan("dec_output"); + spawn RawBlockDecoder(dec_input_r, dec_output_s); + (terminator, dec_input_s, dec_output_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let data_to_send: BlockDataPacket[5] = [ + BlockDataPacket { id: u32:1, last: u1:false, last_block: u1:false, data: BlockData:1, length: BlockPacketLength:32 }, + BlockDataPacket { id: u32:1, last: u1:false, last_block: u1:false, data: BlockData:2, length: BlockPacketLength:32 }, + BlockDataPacket { id: u32:1, last: u1:true, last_block: u1:false, data: BlockData:3, length: BlockPacketLength:32 }, + BlockDataPacket { id: u32:2, last: u1:false, last_block: u1:false, data: BlockData:4, length: BlockPacketLength:32 }, + BlockDataPacket { id: u32:2, last: u1:true, last_block: u1:true, data: BlockData:5, length: BlockPacketLength:32 }, + ]; + + let tok = for ((_, data), tok): ((u32, BlockDataPacket), token) in enumerate(data_to_send) { + let tok = send(tok, dec_input_s, data); + let (tok, received_data) = recv(tok, dec_output_r); + let expected_data = ExtendedBlockDataPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + packet: data, + }; + assert_eq(expected_data, received_data); + (tok) + }(tok); + + send(tok, terminator, true); + } +} From 6f2d89b8daa3f99ef6226d595b71845ff41a0a3d Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Mon, 20 Nov 2023 17:53:42 +0100 Subject: [PATCH 11/49] modules/zstd: Add rle block decoder Adds RleBlockDecoder responsible for decoding Blocks of RLE_Block Block_Type as specified in RFC 8878, paragraph 3.1.1.2.2. https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.2.2 RleBlockDecoder communicates through BlockDataPacket channels. It reuses existing RunLengthDecoder block which is interfaced through two seprate procs: * RleDataPacker * BatchPacker Which are responsible for converting input data into format accepted by RLE decoder and for gathering RLE decoder output symbols into batches which are then send out through BlockDataPacket. Internal-tag: [#51473] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 85 ++++ xls/modules/zstd/rle_block_dec.x | 756 +++++++++++++++++++++++++++++++ 2 files changed, 841 insertions(+) create mode 100644 xls/modules/zstd/rle_block_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 52cab9bf49..32f1e5cc83 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -364,3 +364,88 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "rle_block_dec_dslx", + srcs = [ + "rle_block_dec.x", + ], + deps = [ + ":buffer_dslx", + ":common_dslx", + "//xls/modules/rle:rle_common_dslx", + "//xls/modules/rle:rle_dec_dslx", + ], +) + +xls_dslx_test( + name = "rle_block_dec_dslx_test", + library = ":rle_block_dec_dslx", +) + +xls_dslx_verilog( + name = "rle_block_dec_verilog", + codegen_args = { + "module_name": "RleBlockDecoder", + "delay_model": "asap7", + "pipeline_stages": "3", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RleBlockDecoder", + library = ":rle_block_dec_dslx", + # TODO: 2024-01-15: Workaround for https://github.com/google/xls/issues/869 + # Force proc inlining and set last internal proc as top proc for IR optimization + opt_ir_args = { + "inline_procs": "true", + "top": "__rle_block_dec__RleBlockDecoder__BatchPacker_0_next", + }, + verilog_file = "rle_block_dec.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "rle_block_dec_opt_ir_benchmark", + src = ":rle_block_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "3", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "rle_block_dec_verilog_lib", + srcs = [ + ":rle_block_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "rle_block_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RleBlockDecoder", + deps = [ + ":rle_block_dec_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "rle_block_dec_benchmark_synth", + synth_target = ":rle_block_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "rle_block_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":rle_block_dec_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/rle_block_dec.x b/xls/modules/zstd/rle_block_dec.x new file mode 100644 index 0000000000..4d1c87f43c --- /dev/null +++ b/xls/modules/zstd/rle_block_dec.x @@ -0,0 +1,756 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains the implementation of RleBlockDecoder responsible for decoding +// ZSTD RLE Blocks. More Information about Rle Block's format can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.2.2 +// +// The implementation consist of 3 procs: +// * RleDataPacker +// * RunLengthDecoder +// * BatchPacker +// Connections between those is represented on the diagram below: +// +// RleBlockDecoder +// ┌─────────────────────────────────────────────────────────────┐ +// │ RleDataPacker RunLengthDecoder BatchPacker │ +// │ ┌───────────────┐ ┌──────────────────┐ ┌─────────────┐ │ +// ───┼─►│ ├──►│ ├──►│ ├─┼──► +// │ └───────┬───────┘ └──────────────────┘ └─────────────┘ │ +// │ │ ▲ │ +// │ │ SynchronizationData │ │ +// │ └─────────────────────────────────────────┘ │ +// └─────────────────────────────────────────────────────────────┘ +// +// RleDataPacker is responsible for receiving the incoming packets of block data, converting +// those to format accepted by RunLengthDecoder and passing the data to the actual decoder block. +// It also extracts from the input packets the synchronization data like block_id and last_block +// and then passes those to BatchPacker proc. +// RunLengthDecoder decodes RLE blocks and outputs one symbol for each transaction on output +// channel. +// BatchPacker then gathers those symbols into packets, appends synchronization data received from +// RleDataPacker and passes such packets to the output of the RleBlockDecoder. + +import xls.modules.zstd.common; +import xls.modules.rle.rle_dec; +import xls.modules.rle.rle_common; + +const SYMBOL_WIDTH = common::SYMBOL_WIDTH; +const BLOCK_SIZE_WIDTH = common::BLOCK_SIZE_WIDTH; +const DATA_WIDTH = common::DATA_WIDTH; +const BATCH_SIZE = DATA_WIDTH / SYMBOL_WIDTH; + +type BlockDataPacket = common::BlockDataPacket; +type BlockPacketLength = common::BlockPacketLength; +type BlockData = common::BlockData; +type BlockSize = common::BlockSize; + +type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; + +type RleInput = rle_common::CompressedData; +type RleOutput = rle_common::PlainData; +type Symbol = bits[SYMBOL_WIDTH]; +type SymbolCount = BlockSize; + +struct BlockSyncData { + last_block: bool, + count: SymbolCount, + id: u32 +} + +proc RleDataPacker { + block_data_r: chan in; + rle_data_s: chan out; + sync_s: chan out; + + config( + block_data_r: chan in, + rle_data_s: chan out, + sync_s: chan out + ) { + (block_data_r, rle_data_s, sync_s) + } + + init { } + + next(state: ()) { + let tok = join(); + let (tok, input) = recv(tok, block_data_r); + let rle_dec_data = RleInput { + symbol: input.data as Symbol, count: input.length as SymbolCount, last: true + }; + // send RLE packet for decoding unless it has symbol count == 0 + let send_always = rle_dec_data.count != SymbolCount:0; + let data_tok = send_if(tok, rle_data_s, send_always, rle_dec_data); + let sync_data = BlockSyncData { last_block: input.last_block, count: rle_dec_data.count, id: input.id }; + // send last block packet even if it has symbol count == 0 + let sync_tok = send(data_tok, sync_s, sync_data); + } +} + +type RleTestVector = (Symbol, SymbolCount); + +#[test_proc] +proc RleDataPacker_test { + terminator: chan out; + in_s: chan out; + out_r: chan in; + sync_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("out"); + let (sync_s, sync_r) = chan("sync"); + + spawn RleDataPacker(in_r, out_s, sync_s); + + (terminator, in_s, out_r, sync_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let EncodedRleBlocks: RleTestVector[6] = [ + (Symbol:0x1, SymbolCount:0x1), + (Symbol:0x2, SymbolCount:0x2), + (Symbol:0x3, SymbolCount:0x4), + (Symbol:0x4, SymbolCount:0x8), + (Symbol:0x5, SymbolCount:0x10), + (Symbol:0x6, SymbolCount:0x1F), + ]; + let tok = for ((counter, block), tok): ((u32, RleTestVector), token) in enumerate(EncodedRleBlocks) { + let last_block = (counter == (array_size(EncodedRleBlocks) - u32:1)); + let data_in = BlockDataPacket { + last: true, + last_block, + id: counter, + data: block.0 as BlockData, + length: block.1 as BlockPacketLength + }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} raw encoded block, {:#x}", counter + u32:1, data_in); + + let data_out = RleInput { + last: true, symbol: block.0 as Symbol, count: block.1 as BlockSize + }; + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} packed rle encoded block, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, data_out); + + let sync_out = BlockSyncData { + id: counter, + count: block.1, + last_block: counter == (array_size(EncodedRleBlocks) - u32:1), + }; + let (tok, sync_output) = recv(tok, sync_r); + trace_fmt!("Received #{} synchronization data, {:#x}", counter + u32:1, sync_output); + assert_eq(sync_output, sync_out); + (tok) + }(tok); + send(tok, terminator, true); + } +} + +#[test_proc] +proc RleDataPacker_empty_blocks_test { + terminator: chan out; + in_s: chan out; + out_r: chan in; + sync_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("out"); + let (sync_s, sync_r) = chan("sync"); + + spawn RleDataPacker(in_r, out_s, sync_s); + + (terminator, in_s, out_r, sync_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let EncodedRleBlocks: RleTestVector[8] = [ + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x1, SymbolCount:0x1), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x3, SymbolCount:0x4), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x5, SymbolCount:0x10), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0xFF, SymbolCount:0x0), + ]; + let tok = for ((counter, block), tok): ((u32, RleTestVector), token) in enumerate(EncodedRleBlocks) { + let last_block = (counter == (array_size(EncodedRleBlocks) - u32:1)); + let data_in = BlockDataPacket { + last: true, + last_block, + id: counter, + data: block.0 as BlockData, + length: block.1 as BlockPacketLength + }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} raw encoded block, {:#x}", counter + u32:1, data_in); + (tok) + }(tok); + + let RleInputs: RleInput[3] = [ + RleInput {last: true, symbol: Symbol:0x1, count: BlockSize:0x1}, + RleInput {last: true, symbol: Symbol:0x3, count: BlockSize:0x4}, + RleInput {last: true, symbol: Symbol:0x5, count: BlockSize:0x10}, + ]; + let tok = for ((counter, rle_in), tok): ((u32, RleInput), token) in enumerate(RleInputs) { + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} packed rle encoded block, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, rle_in); + (tok) + }(tok); + + let BlockSyncDataInputs: BlockSyncData[8] = [ + BlockSyncData { id: 0, count: BlockSize:0x0, last_block: false }, + BlockSyncData { id: 1, count: BlockSize:0x1, last_block: false }, + BlockSyncData { id: 2, count: BlockSize:0x0, last_block: false }, + BlockSyncData { id: 3, count: BlockSize:0x4, last_block: false }, + BlockSyncData { id: 4, count: BlockSize:0x0, last_block: false }, + BlockSyncData { id: 5, count: BlockSize:0x10, last_block: false }, + BlockSyncData { id: 6, count: BlockSize:0x0, last_block: false }, + BlockSyncData { id: 7, count: BlockSize:0x0, last_block: true }, + ]; + let tok = for ((counter, sync_data), tok): ((u32, BlockSyncData), token) in enumerate(BlockSyncDataInputs) { + let (tok, sync_output) = recv(tok, sync_r); + trace_fmt!("Received #{} synchronization data, {:#x}", counter + u32:1, sync_output); + assert_eq(sync_output, sync_data); + (tok) + }(tok); + send(tok, terminator, true); + } +} + +struct BatchPackerState { + batch: BlockData, + symbols_in_batch: BlockPacketLength, + symbols_in_block: BlockPacketLength, + prev_last: bool, + prev_sync: BlockSyncData, +} + +const ZERO_BATCH_STATE = zero!(); +const ZERO_BLOCK_SYNC_DATA = zero!(); +const ZERO_RLE_OUTPUT = zero!(); +const EMPTY_RLE_OUTPUT = RleOutput {last: true, ..ZERO_RLE_OUTPUT}; + +proc BatchPacker { + rle_data_r: chan in; + sync_r: chan in; + block_data_s: chan out; + + config( + rle_data_r: chan in, + sync_r: chan in, + block_data_s: chan out + ) { + (rle_data_r, sync_r, block_data_s) + } + + // Init the state to signal new batch to process + init { (BatchPackerState { prev_last: true, ..ZERO_BATCH_STATE }) } + + next(state: BatchPackerState) { + let tok = join(); + trace_fmt!("start state: {:#x}", state); + let prev_expected_symbols_in_block = state.prev_sync.count as BlockPacketLength; + let symbols_in_batch = state.symbols_in_batch; + let symbols_in_block = state.symbols_in_block; + let block_in_progress = (symbols_in_block != prev_expected_symbols_in_block); + trace_fmt!("block_in_progress: {:#x}", block_in_progress); + + // Finished receiving RLE data of the previous block + // Proceed with receiving sync data for the next block + let start_new_block = !block_in_progress; + let (tok, sync_data) = recv_if(tok, sync_r, start_new_block, state.prev_sync); + if (start_new_block) { + trace_fmt!("received sync_data: {:#x}", sync_data); + } else { + trace_fmt!("got sync_data from the state: {:#x}", sync_data); + }; + + let expected_symbols_in_block = if (start_new_block) { sync_data.count as BlockPacketLength } else { prev_expected_symbols_in_block }; + trace_fmt!("expected_symbols_in_block: {:#x}", expected_symbols_in_block); + + let batch = state.batch; + let empty_block = (expected_symbols_in_block == BlockPacketLength:0); + trace_fmt!("batch: {:#x}", batch); + trace_fmt!("empty_block: {:#x}", empty_block); + + let do_recv_rle = !empty_block && block_in_progress; + let default_rle_output = if (empty_block) { EMPTY_RLE_OUTPUT } else { ZERO_RLE_OUTPUT }; + let (tok, decoded_data) = recv_if(tok, rle_data_r, do_recv_rle, default_rle_output); + if (do_recv_rle) { + trace_fmt!("received rle_data: {:#x}", decoded_data); + } else { + trace_fmt!("got empty rle_data: {:#x}", decoded_data); + }; + + let (batch, symbols_in_batch, symbols_in_block) = if (do_recv_rle) { + // TODO: Improve performance: remove variable shift + let shift = symbols_in_batch << u32:3; // multiply by 8 bits + let updated_batch = batch | ((decoded_data.symbol as BlockData) << shift); + let updated_symbols_in_batch = symbols_in_batch + BlockPacketLength:1; + let updated_symbols_in_block = symbols_in_block + BlockPacketLength:1; + (updated_batch, updated_symbols_in_batch, updated_symbols_in_block) + } else { + (batch, symbols_in_batch, symbols_in_block) + }; + trace_fmt!("updated batch: {:#x}", batch); + trace_fmt!("updated symbols_in_batch: {:#x}", symbols_in_batch); + trace_fmt!("updated symbols_in_block: {:#x}", symbols_in_block); + + let block_in_progress = (symbols_in_block != expected_symbols_in_block); + trace_fmt!("updated block_in_progress: {:#x}", block_in_progress); + + // Last should not occur when batch is still being processed + assert!(!(!block_in_progress ^ decoded_data.last), "corrupted_decoding_flow"); + + let batch_full = symbols_in_batch >= BATCH_SIZE; + trace_fmt!("batch_full: {:#x}", batch_full); + // Send decoded RLE packet when + // - batch size reached the maximal size + // - RLE block decoding is finished + // - Decoded RLE block is empty and is the last block in ZSTD frame + let last = decoded_data.last || (sync_data.last_block && empty_block); + let do_send_batch = (batch_full || last); + trace_fmt!("do_send_batch: {:#x}", do_send_batch); + + let decoded_batch_data = ExtendedBlockDataPacket { + // Decoded RLE block is always a literal + msg_type: SequenceExecutorMessageType::LITERAL, + packet: BlockDataPacket { + last: last, + last_block: sync_data.last_block, + id: sync_data.id, + data: batch as BlockData, + // length in bits + length: (symbols_in_batch << 3) as BlockPacketLength, + } + }; + + let data_tok = + send_if(tok, block_data_s, do_send_batch, decoded_batch_data); + if (do_send_batch) { + trace_fmt!("sent decoded_batch_data: {:#x}", decoded_batch_data); + } else { + trace_fmt!("decoded_batch_data: {:#x}", decoded_batch_data); + }; + + let (new_batch, new_symbols_in_batch) = if (do_send_batch) { + (BlockData:0, BlockPacketLength:0) + } else { + (batch, symbols_in_batch) + }; + + let (new_sync_data, new_symbols_in_block) = if (decoded_data.last || (sync_data.last_block && empty_block)) { + (ZERO_BLOCK_SYNC_DATA, BlockPacketLength:0) + } else { + (sync_data, symbols_in_block) + }; + + let new_state = BatchPackerState { + batch: new_batch, + symbols_in_batch: new_symbols_in_batch, + symbols_in_block: new_symbols_in_block, + prev_last: decoded_data.last, + prev_sync: new_sync_data + }; + + trace_fmt!("new_state: {:#x}", new_state); + + new_state + } +} + +type BatchTestVector = (Symbol, bool); + +#[test_proc] +proc BatchPacker_test { + terminator: chan out; + in_s: chan out; + sync_s: chan out; + out_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (sync_s, sync_r) = chan("sync"); + let (out_s, out_r) = chan("out"); + + spawn BatchPacker(in_r, sync_r, out_s); + + (terminator, in_s, sync_s, out_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let SyncData: BlockSyncData[6] = [ + BlockSyncData { last_block: false, count: SymbolCount:1, id: u32:0 }, + BlockSyncData { last_block: false, count: SymbolCount:2, id: u32:1 }, + BlockSyncData { last_block: false, count: SymbolCount:4, id: u32:2 }, + BlockSyncData { last_block: false, count: SymbolCount:8, id: u32:3 }, + BlockSyncData { last_block: false, count: SymbolCount:16, id: u32:4 }, + BlockSyncData { last_block: true, count: SymbolCount:31, id: u32:5 }, + ]; + let tok = for ((counter, sync_data), tok): ((u32, BlockSyncData), token) in enumerate(SyncData) { + let tok = send(tok, sync_s, sync_data); + trace_fmt!("Sent #{} synchronization data, {:#x}", counter + u32:1, sync_data); + (tok) + }(tok); + + let DecodedRleBlocks: BatchTestVector[62] = [ + // 1st block + (Symbol:0x01, bool:true), + // 2nd block + (Symbol:0x02, bool:false), (Symbol:0x02, bool:true), + // 3rd block + (Symbol:0x03, bool:false), (Symbol:0x03, bool:false), (Symbol:0x03, bool:false), + (Symbol:0x03, bool:true), + // 4th block + (Symbol:0x04, bool:false), (Symbol:0x04, bool:false), (Symbol:0x04, bool:false), + (Symbol:0x04, bool:false), (Symbol:0x04, bool:false), (Symbol:0x04, bool:false), + (Symbol:0x04, bool:false), (Symbol:0x04, bool:true), + // 5th block + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:true), + // 6th block + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), (Symbol:0x06, bool:false), + (Symbol:0x06, bool:true), + ]; + let tok = for ((counter, test_data), tok): ((u32, BatchTestVector), token) in enumerate(DecodedRleBlocks) { + let symbol = test_data.0 as Symbol; + let last = test_data.1; + let data_in = RleOutput { symbol, last }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} decoded rle symbol, {:#x}", counter + u32:1, data_in); + (tok) + }(tok); + + let BatchedDecodedRleSymbols: ExtendedBlockDataPacket[10] = [ + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:0, data: BlockData:0x01, length: BlockPacketLength:8}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:1, data: BlockData:0x0202, length: BlockPacketLength:16}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:2, data: BlockData:0x03030303, length: BlockPacketLength:32}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:3, data: BlockData:0x0404040404040404, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:false, id: u32:4, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:4, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:true, id: u32:5, data: BlockData:0x06060606060606, length: BlockPacketLength:56}}, + ]; + + let tok = for ((counter, expected), tok): ((u32, ExtendedBlockDataPacket), token) in enumerate(BatchedDecodedRleSymbols) { + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} batched decoded rle symbols, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + } +} + +#[test_proc] +proc BatchPacker_empty_blocks_test { + terminator: chan out; + in_s: chan out; + sync_s: chan out; + out_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (sync_s, sync_r) = chan("sync"); + let (out_s, out_r) = chan("out"); + + spawn BatchPacker(in_r, sync_r, out_s); + + (terminator, in_s, sync_s, out_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let SyncData: BlockSyncData[8] = [ + BlockSyncData { last_block: false, count: SymbolCount:0, id: u32:0 }, + BlockSyncData { last_block: false, count: SymbolCount:1, id: u32:1 }, + BlockSyncData { last_block: false, count: SymbolCount:0, id: u32:2 }, + BlockSyncData { last_block: false, count: SymbolCount:4, id: u32:3 }, + BlockSyncData { last_block: false, count: SymbolCount:0, id: u32:4 }, + BlockSyncData { last_block: false, count: SymbolCount:16, id: u32:5 }, + BlockSyncData { last_block: false, count: SymbolCount:0, id: u32:6 }, + BlockSyncData { last_block: true, count: SymbolCount:0, id: u32:7 }, + ]; + let tok = for ((counter, sync_data), tok): ((u32, BlockSyncData), token) in enumerate(SyncData) { + let tok = send(tok, sync_s, sync_data); + trace_fmt!("Sent #{} synchronization data, {:#x}", counter + u32:1, sync_data); + (tok) + }(tok); + + let DecodedRleBlocks: BatchTestVector[21] = [ + // 0 block + // EMPTY + // 1st block + (Symbol:0x01, bool:true), + // 2nd block + // EMPTY + // 3rd block + (Symbol:0x03, bool:false), (Symbol:0x03, bool:false), (Symbol:0x03, bool:false), + (Symbol:0x03, bool:true), + // 4th block + // EMPTY + // 5th block + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), (Symbol:0x05, bool:false), + (Symbol:0x05, bool:true), + // 6th block + // EMPTY + // 7th block + // EMPTY + ]; + let tok = for ((counter, test_data), tok): ((u32, BatchTestVector), token) in enumerate(DecodedRleBlocks) { + let symbol = test_data.0 as Symbol; + let last = test_data.1; + let data_in = RleOutput { symbol, last }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} decoded rle symbol, {:#x}", counter + u32:1, data_in); + (tok) + }(tok); + + let BatchedDecodedRleSymbols: ExtendedBlockDataPacket[9] = [ + // 0 block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:0, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 1st block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:1, data: BlockData:0x01, length: BlockPacketLength:8}}, + // 2nd block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:2, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 3rd block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:3, data: BlockData:0x03030303, length: BlockPacketLength:32}}, + // 4th block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:4, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 5th block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:false, id: u32:5, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:5, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + // 6th block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:6, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 7th block + // EMPTY with LAST_BLOCK + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:true, id: u32:7, data: BlockData:0x0, length: BlockPacketLength:0}}, + ]; + + let tok = for ((counter, expected), tok): ((u32, ExtendedBlockDataPacket), token) in enumerate(BatchedDecodedRleSymbols) { + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} batched decoded rle symbols, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + } +} + +pub proc RleBlockDecoder { + input_r: chan in; + output_s: chan out; + + config(input_r: chan in, output_s: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("out"); + let (sync_s, sync_r) = chan("sync"); + + spawn RleDataPacker(input_r, in_s, sync_s); + spawn rle_dec::RunLengthDecoder( + in_r, out_s); + spawn BatchPacker(out_r, sync_r, output_s); + + (input_r, output_s) + } + + init { } + + next(state: ()) { } +} + +#[test_proc] +proc RleBlockDecoder_test { + 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 RleBlockDecoder(in_r, out_s); + + (terminator, in_s, out_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let EncodedRleBlocks: RleTestVector[6] = [ + (Symbol:0x1, SymbolCount:0x1), + (Symbol:0x2, SymbolCount:0x2), + (Symbol:0x3, SymbolCount:0x4), + (Symbol:0x4, SymbolCount:0x8), + (Symbol:0x5, SymbolCount:0x10), + (Symbol:0x6, SymbolCount:0x1F), + ]; + let tok = for ((counter, block), tok): ((u32, RleTestVector), token) in enumerate(EncodedRleBlocks) { + let last_block = (counter == (array_size(EncodedRleBlocks) - u32:1)); + let data_in = BlockDataPacket { + last: true, // RLE block fits into single packet, each will be last for given block + last_block, + id: counter, + data: block.0 as BlockData, + length: block.1 as BlockPacketLength + }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} raw encoded block, {:#x}", counter + u32:1, data_in); + (tok) + }(tok); + + let BatchedDecodedRleSymbols: ExtendedBlockDataPacket[10] = [ + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:false, id: u32:0, data: BlockData:0x01, length: BlockPacketLength:8}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:false, id: u32:1, data: BlockData:0x0202, length: BlockPacketLength:16}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:false, id: u32:2, data: BlockData:0x03030303, length: BlockPacketLength:32}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:false, id: u32:3, data: BlockData:0x0404040404040404, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:false, last_block: bool:false, id: u32:4, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:false, id: u32:4, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:false, last_block: bool:true, id: u32:5, data: BlockData:0x0606060606060606, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { last: bool:true, last_block: bool:true, id: u32:5, data: BlockData:0x06060606060606, length: BlockPacketLength:56}}, + ]; + + let tok = for ((counter, expected), tok): ((u32, ExtendedBlockDataPacket), token) in enumerate(BatchedDecodedRleSymbols) { + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} batched decoded rle symbols, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + } +} + +#[test_proc] +proc RleBlockDecoder_empty_blocks_test { + 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 RleBlockDecoder(in_r, out_s); + + (terminator, in_s, out_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let EncodedRleBlocks: RleTestVector[8] = [ + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x1, SymbolCount:0x1), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x3, SymbolCount:0x4), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0x5, SymbolCount:0x10), + (Symbol:0xFF, SymbolCount:0x0), + (Symbol:0xFF, SymbolCount:0x0), + ]; + let tok = for ((counter, block), tok): ((u32, RleTestVector), token) in enumerate(EncodedRleBlocks) { + let last_block = (counter == (array_size(EncodedRleBlocks) - u32:1)); + let data_in = BlockDataPacket { + last: true, // RLE block fits into single packet, each will be last for given block + last_block, + id: counter, + data: block.0 as BlockData, + length: block.1 as BlockPacketLength + }; + let tok = send(tok, in_s, data_in); + trace_fmt!("Sent #{} raw encoded block, {:#x}", counter + u32:1, data_in); + (tok) + }(tok); + + let BatchedDecodedRleSymbols: ExtendedBlockDataPacket[9] = [ + // 0 block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:0, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 1st block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:1, data: BlockData:0x01, length: BlockPacketLength:8}}, + // 2nd block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:2, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 3rd block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:3, data: BlockData:0x03030303, length: BlockPacketLength:32}}, + // 4th block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:4, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 5th block + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:false, last_block: bool:false, id: u32:5, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:5, data: BlockData:0x0505050505050505, length: BlockPacketLength:64}}, + // 6th block + // EMPTY + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:false, id: u32:6, data: BlockData:0x0, length: BlockPacketLength:0}}, + // 7th block + // EMPTY with LAST_BLOCK + ExtendedBlockDataPacket {msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket {last: bool:true, last_block: bool:true, id: u32:7, data: BlockData:0x0, length: BlockPacketLength:0}}, + ]; + + let tok = for ((counter, expected), tok): ((u32, ExtendedBlockDataPacket), token) in enumerate(BatchedDecodedRleSymbols) { + let (tok, dec_output) = recv(tok, out_r); + trace_fmt!("Received #{} batched decoded rle symbols, {:#x}", counter + u32:1, dec_output); + assert_eq(dec_output, expected); + (tok) + }(tok); + send(tok, terminator, true); + } +} From 4bd9c4035553dd1e3b5e19626a7fbba2a996db28 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 14 Nov 2023 17:42:28 +0100 Subject: [PATCH 12/49] modules/zstd: Add block header parsing library Internal-tag: [#51343] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 16 +++++ xls/modules/zstd/block_header.x | 108 ++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+) create mode 100644 xls/modules/zstd/block_header.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 32f1e5cc83..f2ffca8dac 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -449,3 +449,19 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "block_header_dslx", + srcs = [ + "block_header.x", + ], + deps = [ + ":buffer_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "block_header_dslx_test", + library = ":block_header_dslx", +) diff --git a/xls/modules/zstd/block_header.x b/xls/modules/zstd/block_header.x new file mode 100644 index 0000000000..b3a0e9966f --- /dev/null +++ b/xls/modules/zstd/block_header.x @@ -0,0 +1,108 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains utilities related to ZSTD Block Header parsing. +// More information about the ZSTD Block Header can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.2 + +import std; +import xls.modules.zstd.buffer as buff; +import xls.modules.zstd.common as common; + +type Buffer = buff::Buffer; +type BufferStatus = buff::BufferStatus; +type BlockType = common::BlockType; + +// Status values reported by the block header parsing function +pub enum BlockHeaderStatus: u2 { + OK = 0, + CORRUPTED = 1, + NO_ENOUGH_DATA = 2, +} + +// Structure for data obtained from decoding Block_Header +pub struct BlockHeader { + last: bool, + btype: BlockType, + size: u21, +} + +// Structure for returning results of block header parsing +pub struct BlockHeaderResult { + buffer: Buffer, + status: BlockHeaderStatus, + header: BlockHeader, +} + +// Auxiliary constant that can be used to initialize Proc's state +// with empty FrameHeader, because `zero!` cannot be used in that context +pub const ZERO_BLOCK_HEADER = zero!(); + +// Extracts Block_Header fields from 24-bit chunk of data +// that is assumed to be a valid Block_Header +pub fn extract_block_header(data:u24) -> BlockHeader { + BlockHeader { + size: data[3:24], + btype: data[1:3] as BlockType, + last: data[0:1], + } +} + +// Parses a Buffer and extracts information from a Block_Header. Returns BufferResult +// with outcome of operations on buffer and information extracted from the Block_Header. +pub fn parse_block_header(buffer: Buffer) -> BlockHeaderResult { + let (result, data) = buff::buffer_fixed_pop_checked(buffer); + + match result.status { + BufferStatus::OK => { + let block_header = extract_block_header(data); + if (block_header.btype != BlockType::RESERVED) { + BlockHeaderResult {status: BlockHeaderStatus::OK, header: block_header, buffer: result.buffer} + } else { + BlockHeaderResult {status: BlockHeaderStatus::CORRUPTED, header: zero!(), buffer: buffer} + } + }, + _ => { + trace_fmt!("parse_block_header: Not enough data to parse block header! {}", buffer.length); + BlockHeaderResult {status: BlockHeaderStatus::NO_ENOUGH_DATA, header: zero!(), buffer: buffer} + } + } +} + +#[test] +fn test_parse_block_header() { + let buffer = Buffer { content: u32:0x8001 , length: u32:24}; + let result = parse_block_header(buffer); + assert_eq(result, BlockHeaderResult { + status: BlockHeaderStatus::OK, + header: BlockHeader { last: u1:1, btype: BlockType::RAW, size: u21:0x1000 }, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + + let buffer = Buffer { content: u32:0x91A2, length: u32:24}; + let result = parse_block_header(buffer); + assert_eq(result, BlockHeaderResult { + status: BlockHeaderStatus::OK, + header: BlockHeader { last: u1:0, btype: BlockType::RLE, size: u21:0x1234 }, + buffer: Buffer { content: u32:0, length: u32:0 } + }); + + let buffer = Buffer { content: u32:0x001, length: u32:16}; + let result = parse_block_header(buffer); + assert_eq(result, BlockHeaderResult { + status: BlockHeaderStatus::NO_ENOUGH_DATA, + header: zero!(), + buffer: Buffer { content: u32:0x001, length: u32:16 } + }); +} From 3de695e584fde62d31ef8501852a3057c4f4fef8 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 4 Jan 2024 10:43:26 +0100 Subject: [PATCH 13/49] modules/zstd: Add SequenceExecutorPacket to common definitions Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/common.x | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 8bc554f8a6..fcc0116e8a 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -47,3 +47,10 @@ pub struct ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType, packet: BlockDataPacket, } + +pub struct SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType, + length: CopyOrMatchLength, // Literal length or match length + content: CopyOrMatchContent, // Literal data or match offset + last: bool, // Last packet in frame +} From 1c525a61582505319190fed839d04f1b4e908b08 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Wed, 22 Nov 2023 15:47:49 +0100 Subject: [PATCH 14/49] modules/zstd: Add block data muxer library This commit adds DecoderMux Proc, which collects data from specialized Raw, RLE, and Compressed Block decoders and re-sends them in the correct order. Internal-tag: [#51343] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 76 ++++++ xls/modules/zstd/dec_mux.x | 494 +++++++++++++++++++++++++++++++++++++ 2 files changed, 570 insertions(+) create mode 100644 xls/modules/zstd/dec_mux.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f2ffca8dac..1fce207ac0 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -465,3 +465,79 @@ xls_dslx_test( name = "block_header_dslx_test", library = ":block_header_dslx", ) + +xls_dslx_library( + name = "dec_mux_dslx", + srcs = [ + "dec_mux.x", + ], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "dec_mux_dslx_test", + library = ":dec_mux_dslx", +) + +xls_dslx_verilog( + name = "dec_mux_verilog", + codegen_args = { + "module_name": "DecoderMux", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "DecoderMux", + library = ":dec_mux_dslx", + verilog_file = "dec_mux.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "dec_mux_opt_ir_benchmark", + src = ":dec_mux_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "dec_mux_verilog_lib", + srcs = [ + ":dec_mux.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "dec_mux_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "DecoderMux", + deps = [ + ":dec_mux_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "dec_mux_benchmark_synth", + synth_target = ":dec_mux_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "dec_mux_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":dec_mux_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x new file mode 100644 index 0000000000..423c0e36e1 --- /dev/null +++ b/xls/modules/zstd/dec_mux.x @@ -0,0 +1,494 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains the DecoderMux Proc, which collects data from +// specialized Raw, RLE, and Compressed Block decoders and re-sends them in +// the correct order. + +import std; +import xls.modules.zstd.common as common; + +type BlockDataPacket = common::BlockDataPacket; +type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; +type BlockData = common::BlockData; +type BlockPacketLength = common::BlockPacketLength; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; + +const MAX_ID = common::DATA_WIDTH; +const DATA_WIDTH = common::DATA_WIDTH; + +struct DecoderMuxState { + prev_id: u32, + prev_last: bool, + prev_last_block: bool, + prev_valid: bool, + raw_data: ExtendedBlockDataPacket, + raw_data_valid: bool, + raw_data_valid_next_frame: bool, + rle_data: ExtendedBlockDataPacket, + rle_data_valid: bool, + rle_data_valid_next_frame: bool, + compressed_data: ExtendedBlockDataPacket, + compressed_data_valid: bool, + compressed_data_valid_next_frame: bool, +} + +const ZERO_DECODER_MUX_STATE = zero!(); + +pub proc DecoderMux { + raw_r: chan in; + rle_r: chan in; + cmp_r: chan in; + output_s: chan out; + + init {( DecoderMuxState { prev_id: u32:0xFFFFFFFF, prev_last: true, prev_last_block: true, ..ZERO_DECODER_MUX_STATE } )} + + config ( + raw_r: chan in, + rle_r: chan in, + cmp_r: chan in, + output_s: chan out, + ) {(raw_r, rle_r, cmp_r, output_s)} + + next (state: DecoderMuxState) { + let tok = join(); + let (tok, raw_data, raw_data_valid) = recv_if_non_blocking( + tok, raw_r, !state.raw_data_valid && !state.raw_data_valid_next_frame, zero!()); + let state = if (raw_data_valid) { + let state = if (raw_data.packet.id <= state.prev_id && state.prev_last && state.prev_valid && !state.prev_last_block) { + // received ID the same as previous, but `last` occurred + // this might be a packet from the next frame + let raw_data_valid_next_frame = raw_data_valid; + DecoderMuxState {raw_data, raw_data_valid_next_frame, ..state} + } else { + DecoderMuxState {raw_data, raw_data_valid, ..state} + }; + state + } else { state }; + + let (tok, rle_data, rle_data_valid) = recv_if_non_blocking( + tok, rle_r, !state.rle_data_valid && !state.rle_data_valid_next_frame, zero!()); + let state = if (rle_data_valid) { + trace_fmt!("DecoderMux: received RLE data packet {:#x}", rle_data); + let state = if (rle_data.packet.id <= state.prev_id && state.prev_last && state.prev_valid && !state.prev_last_block) { + // received ID the same as previous, but `last` occurred + // this might be a packet from the next frame + let rle_data_valid_next_frame = rle_data_valid; + DecoderMuxState {rle_data, rle_data_valid_next_frame, ..state} + } else { + DecoderMuxState {rle_data, rle_data_valid, ..state} + }; + state + } else { state }; + + let (tok, compressed_data, compressed_data_valid) = recv_if_non_blocking( + tok, cmp_r, !state.compressed_data_valid && !state.compressed_data_valid_next_frame, zero!()); + let state = if (compressed_data_valid) { + trace_fmt!("DecoderMux: received compressed data packet {:#x}", compressed_data); + let state = if (compressed_data.packet.id <= state.prev_id && state.prev_last && state.prev_valid && !state.prev_last_block) { + // received ID the same as previous, but `last` occurred + // this might be a packet from the next frame + let compressed_data_valid_next_frame = compressed_data_valid; + DecoderMuxState {compressed_data, compressed_data_valid_next_frame, ..state} + } else { + DecoderMuxState {compressed_data, compressed_data_valid, ..state} + }; + state + } else { state }; + + let raw_id = if state.raw_data_valid { state.raw_data.packet.id } else { MAX_ID }; + let rle_id = if state.rle_data_valid { state.rle_data.packet.id } else { MAX_ID }; + let compressed_id = if state.compressed_data_valid { state.compressed_data.packet.id } else { MAX_ID }; + let any_valid = state.raw_data_valid || state.rle_data_valid || state.compressed_data_valid; + let all_valid = state.raw_data_valid && state.rle_data_valid && state.compressed_data_valid; + + let state = if (any_valid) { + let min_id = std::umin(std::umin(rle_id, raw_id), compressed_id); + trace_fmt!("DecoderMux: rle_id: {}, raw_id: {}, compressed_id: {}", rle_id, raw_id, compressed_id); + trace_fmt!("DecoderMux: min_id: {}", min_id); + + assert!((state.prev_id <= min_id) || !state.prev_valid || state.prev_last_block, "wrong_id"); + assert!(!state.prev_last_block || !state.prev_last || min_id == u32:0, "wrong_id_expected_0"); + assert!(state.prev_last_block || !state.prev_last || !all_valid || (min_id == (state.prev_id + u32:1)) || (min_id == state.prev_id), "id_continuity_failure"); + + let (do_send, data_to_send, state) = + if (state.raw_data_valid && + (((state.raw_data.packet.id == (state.prev_id + u32:1)) && state.prev_last) || + ((state.raw_data.packet.id == state.prev_id) && !state.prev_last))) { + assert!(!state.raw_data_valid_next_frame, "raw_packet_valid_in_current_and_next_frame"); + (true, + SequenceExecutorPacket { + msg_type: state.raw_data.msg_type, + length: state.raw_data.packet.length as CopyOrMatchLength, + content: state.raw_data.packet.data as CopyOrMatchContent, + last: state.raw_data.packet.last && state.raw_data.packet.last_block, + }, + DecoderMuxState { + raw_data_valid: false, + raw_data_valid_next_frame: if (state.raw_data.packet.last_block) {false} else {state.raw_data_valid_next_frame}, + rle_data_valid: if (state.raw_data.packet.last_block) {state.rle_data_valid_next_frame} else {state.rle_data_valid}, + rle_data_valid_next_frame: if (state.raw_data.packet.last_block) {false} else {state.rle_data_valid_next_frame}, + compressed_data_valid: if (state.raw_data.packet.last_block) {state.compressed_data_valid_next_frame} else {state.compressed_data_valid}, + compressed_data_valid_next_frame: if (state.raw_data.packet.last_block) {false} else {state.compressed_data_valid_next_frame}, + prev_valid : true, + prev_id: if (state.raw_data.packet.last_block && state.raw_data.packet.last) {u32:0xffffffff} else {state.raw_data.packet.id}, + prev_last: state.raw_data.packet.last, + prev_last_block: state.raw_data.packet.last_block, + ..state}) + } else if (state.rle_data_valid && + (((state.rle_data.packet.id == (state.prev_id + u32:1)) && state.prev_last) || + ((state.rle_data.packet.id == state.prev_id) && !state.prev_last))) { + assert!(!state.rle_data_valid_next_frame, "rle_packet_valid_in_current_and_next_frame"); + (true, + SequenceExecutorPacket { + msg_type: state.rle_data.msg_type, + length: state.rle_data.packet.length as CopyOrMatchLength, + content: state.rle_data.packet.data as CopyOrMatchContent, + last: state.rle_data.packet.last && state.rle_data.packet.last_block, + }, + DecoderMuxState { + raw_data_valid: if (state.rle_data.packet.last_block) {state.raw_data_valid_next_frame} else {state.raw_data_valid}, + raw_data_valid_next_frame: if (state.rle_data.packet.last_block) {false} else {state.raw_data_valid_next_frame}, + rle_data_valid: false, + rle_data_valid_next_frame: if (state.rle_data.packet.last_block) {false} else {state.rle_data_valid_next_frame}, + compressed_data_valid: if (state.rle_data.packet.last_block) {state.compressed_data_valid_next_frame} else {state.compressed_data_valid}, + compressed_data_valid_next_frame: if (state.rle_data.packet.last_block) {false} else {state.compressed_data_valid_next_frame}, + prev_valid : true, + prev_id: if (state.rle_data.packet.last_block && state.rle_data.packet.last) {u32:0xffffffff} else {state.rle_data.packet.id}, + prev_last: state.rle_data.packet.last, + prev_last_block: state.rle_data.packet.last_block, + ..state}) + } else if (state.compressed_data_valid && + (((state.compressed_data.packet.id == (state.prev_id + u32:1)) && state.prev_last) || + ((state.compressed_data.packet.id == state.prev_id) && !state.prev_last))) { + assert!(!state.compressed_data_valid_next_frame, "compressed_packet_valid_in_current_and_next_frame"); + (true, + SequenceExecutorPacket { + msg_type: state.compressed_data.msg_type, + length: state.compressed_data.packet.length as CopyOrMatchLength, + content: state.compressed_data.packet.data as CopyOrMatchContent, + last: state.compressed_data.packet.last && state.compressed_data.packet.last_block, + }, + DecoderMuxState { + raw_data_valid: if (state.compressed_data.packet.last_block) {state.raw_data_valid_next_frame} else {state.raw_data_valid}, + raw_data_valid_next_frame: if (state.compressed_data.packet.last_block) {false} else {state.raw_data_valid_next_frame}, + rle_data_valid: if (state.compressed_data.packet.last_block) {state.rle_data_valid_next_frame} else {state.rle_data_valid}, + rle_data_valid_next_frame: if (state.compressed_data.packet.last_block) {false} else {state.rle_data_valid_next_frame}, + compressed_data_valid: false, + compressed_data_valid_next_frame: if (state.compressed_data.packet.last_block) {false} else {state.compressed_data_valid_next_frame}, + prev_valid : true, + prev_id: if (state.compressed_data.packet.last_block && state.compressed_data.packet.last) {u32:0xffffffff} else {state.compressed_data.packet.id}, + prev_last: state.compressed_data.packet.last, + prev_last_block: state.compressed_data.packet.last_block, + ..state}) + } else { + (false, zero!(), state) + }; + + let tok = send_if(tok, output_s, do_send, data_to_send); + if (do_send) { + trace_fmt!("DecoderMux: sent {:#x}", data_to_send); + } else {()}; + state + } else { + state + }; + + state + } +} + +#[test_proc] +proc DecoderMuxTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: BlockData:0xAAAAAAAA, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: true, data: BlockData:0x00000000, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0xBBBBBBBB, length: BlockPacketLength:32 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:32 }); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc DecoderMuxEmptyRawBlocksTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:4, last: bool: true, last_block: bool: true, data: BlockData:0x0, length: BlockPacketLength:0 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0, length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc DecoderMuxEmptyRleBlocksTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:4, last: bool: true, last_block: bool: true, data: BlockData:0x0, length: BlockPacketLength:0 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0, length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0, length: CopyOrMatchLength:0 }); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc DecoderMuxEmptyBlockBetweenRegularBlocksOnTheSameInputChannelTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: false, last_block: bool: false, data: BlockData:0xAAAAAAAA, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0xBBBBBBBB, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: true, data: BlockData:0x00000000, length: BlockPacketLength:32 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0, length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:32 }); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc DecoderMuxEmptyBlockBetweenRegularBlocksOnDifferentInputChannelsTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: false, last_block: bool: false, data: BlockData:0xAAAAAAAA, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: true, data: BlockData:0x00000000, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0xBBBBBBBB, length: BlockPacketLength:32 }}); + + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0, length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x00000000, length: CopyOrMatchLength:32 }); + + send(tok, terminator, true); + } +} + +#[test_proc] +proc DecoderMuxMultipleFramesTest { + terminator: chan out; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (output_s, output_r) = chan("output"); + + spawn DecoderMux(raw_r, rle_r, cmp_r, output_s); + (terminator, raw_s, rle_s, cmp_s, output_r) + } + + next(state: ()) { + let tok = join(); + // Frame #1 + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x11111111, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: BlockData:0x22222222, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x33333333, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: BlockData:0xAAAAAAAA, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0xBBBBBBBB, length: BlockPacketLength:32 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0xCCCCCCCC, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: false, data: BlockData:0xDDDDDDDD, length: BlockPacketLength:32 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:4, last: bool: true, last_block: bool: false, data: BlockData:0xEEEEEEEE, length: BlockPacketLength:32 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:5, last: bool: true, last_block: bool: true, data: BlockData:0xFFFFFFFF, length: BlockPacketLength:32 }}); + // Frame #2 + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x44444444, length: BlockPacketLength:32 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, cmp_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, rle_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: true, data: BlockData:0x0, length: BlockPacketLength:0 }}); + // Frame #3 + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: BlockData:0x55555555, length: BlockPacketLength:32 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:4, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:5, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:6, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:7, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:8, last: bool: true, last_block: bool: false, data: BlockData:0x0, length: BlockPacketLength:0 }}); + let tok = send(tok, raw_s, ExtendedBlockDataPacket { msg_type: SequenceExecutorMessageType::LITERAL, packet: BlockDataPacket { id: u32:9, last: bool: true, last_block: bool: true, data: BlockData:0x0, length: BlockPacketLength:0 }}); + + // Frame #1 + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x11111111, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x22222222, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x33333333, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xAAAAAAAA, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xBBBBBBBB, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xCCCCCCCC, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDDDDDDDD, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xEEEEEEEE, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xFFFFFFFF, length: CopyOrMatchLength:32 }); + // Frame #2 + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x44444444, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + // Frame #3 + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x55555555, length: CopyOrMatchLength:32 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + let (tok, data) = recv(tok, output_r); assert_eq(data, SequenceExecutorPacket {last: bool: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x0 , length: CopyOrMatchLength:0 }); + + send(tok, terminator, true); + } +} + From 27573e218b7830057ade3850798220467a06c7c0 Mon Sep 17 00:00:00 2001 From: Maciej Dudek Date: Mon, 27 Nov 2023 10:18:35 +0100 Subject: [PATCH 15/49] modules/zstd: Add block demuxer library This DSLX proc responsibility is to dispatch encoded blocks to a correct decoder: RAW, RLE, COMPRESSED. It tracks and assigns block IDs. The ID counter is reset on the frame's last block on the last data packet. Internal-tag: [#51736] Co-authored-by: Robert Winkler Signed-off-by: Maciej Dudek --- xls/modules/zstd/BUILD | 77 ++++++++++ xls/modules/zstd/dec_demux.x | 273 +++++++++++++++++++++++++++++++++++ 2 files changed, 350 insertions(+) create mode 100644 xls/modules/zstd/dec_demux.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 1fce207ac0..b74e119039 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -541,3 +541,80 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "dec_demux_dslx", + srcs = [ + "dec_demux.x", + ], + deps = [ + ":block_header_dslx", + ":common_dslx", + ], +) + +xls_dslx_test( + name = "dec_demux_dslx_test", + library = ":dec_demux_dslx", +) + +xls_dslx_verilog( + name = "dec_demux_verilog", + codegen_args = { + "module_name": "DecoderDemux", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "DecoderDemux", + library = ":dec_demux_dslx", + verilog_file = "dec_demux.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "dec_demux_opt_ir_benchmark", + src = ":dec_demux_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "dec_demux_verilog_lib", + srcs = [ + ":dec_demux.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "dec_demux_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "DecoderDemux", + deps = [ + ":dec_demux_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "dec_demux_benchmark_synth", + synth_target = ":dec_demux_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "dec_demux_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":dec_demux_synth_asap7", + target_die_utilization_percentage = "5", + tags = ["manual"], +) diff --git a/xls/modules/zstd/dec_demux.x b/xls/modules/zstd/dec_demux.x new file mode 100644 index 0000000000..d032a3a223 --- /dev/null +++ b/xls/modules/zstd/dec_demux.x @@ -0,0 +1,273 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains DecoderDemux Proc, which is responsible for +// parsing Block_Header and sending the obtained data to the Raw, RLE, +// or Compressed Block decoders. + +import std; +import xls.modules.zstd.common as common; +import xls.modules.zstd.block_header as block_header; + +type BlockDataPacket = common::BlockDataPacket; + +const DATA_WIDTH = common::DATA_WIDTH; + +enum DecoderDemuxStatus : u2 { + IDLE = 0, + PASS_RAW = 1, + PASS_RLE = 2, + PASS_COMPRESSED = 3, +} + +struct DecoderDemuxState { + status: DecoderDemuxStatus, + byte_to_pass: u21, + send_data: u21, + id: u32, + last_packet: BlockDataPacket, +} + +// It's safe to assume that data contains full header and some extra data. +// Previous stage aligns block header and data, it also guarantees +// new block headers in new packets. +fn handle_idle_state(data: BlockDataPacket, state: DecoderDemuxState) + -> DecoderDemuxState { + let header = block_header::extract_block_header(data.data[0:24] as u24); + let data = BlockDataPacket { + data: data.data[24:] as bits[DATA_WIDTH], + length: data.length - u32:24, + id: state.id, + ..data + }; + match header.btype { + common::BlockType::RAW => { + DecoderDemuxState { + status: DecoderDemuxStatus::PASS_RAW, + byte_to_pass: header.size, + send_data: u21:0, + last_packet: data, + ..state + } + }, + common::BlockType::RLE => { + DecoderDemuxState { + status: DecoderDemuxStatus::PASS_RLE, + byte_to_pass: header.size, + send_data: u21:0, + last_packet: data, + ..state + } + }, + common::BlockType::COMPRESSED => { + DecoderDemuxState { + status: DecoderDemuxStatus::PASS_COMPRESSED, + byte_to_pass: header.size, + send_data: u21:0, + last_packet: data, + ..state + } + }, + _ => { + fail!("Should_never_happen", state) + } + } +} + +const ZERO_DECODER_DEMUX_STATE = zero!(); +const ZERO_DATA = zero!(); + +pub proc DecoderDemux { + input_r: chan in; + raw_s: chan out; + rle_s: chan out; + cmp_s: chan out; + + init {(ZERO_DECODER_DEMUX_STATE)} + + config ( + input_r: chan in, + raw_s: chan out, + rle_s: chan out, + cmp_s: chan out, + ) {( + input_r, + raw_s, + rle_s, + cmp_s + )} + + next (state: DecoderDemuxState) { + let tok = join(); + let (tok, data) = recv_if(tok, input_r, !state.last_packet.last, ZERO_DATA); + if (!state.last_packet.last) { + trace_fmt!("DecoderDemux: recv: {:#x}", data); + } else {}; + let (send_raw, send_rle, send_cmp, new_state) = match state.status { + DecoderDemuxStatus::IDLE => + (false, false, false, handle_idle_state(data, state)), + DecoderDemuxStatus::PASS_RAW => { + let new_state = DecoderDemuxState { + send_data: state.send_data + (state.last_packet.length >> 3) as u21, + last_packet: data, + ..state + }; + (true, false, false, new_state) + }, + DecoderDemuxStatus::PASS_RLE => { + let new_state = DecoderDemuxState { + send_data: state.send_data + state.byte_to_pass, + last_packet: data, + ..state + }; + (false, true, false, new_state) + }, + DecoderDemuxStatus::PASS_COMPRESSED => { + let new_state = DecoderDemuxState { + send_data: state.send_data +(state.last_packet.length >> 3) as u21, + last_packet: data, + ..state + }; + (false, false, true, new_state) + }, + _ => fail!("IDLE_STATE_IMPOSSIBLE", (false, false, false, state)) + }; + + let end_state = if (send_raw || send_rle || send_cmp) { + let max_packet_width = DATA_WIDTH; + let block_size_bits = u32:24 + (state.byte_to_pass as u32 << 3); + if (!send_rle) && ((block_size_bits <= max_packet_width) && + ((block_size_bits) != state.last_packet.length) && !state.last_packet.last) { + // Demuxer expect that blocks would be received in a separate packets, + // even if 2 block would fit entirely or even partially in a single packet. + // It is the job of top-level ZSTD decoder to split each block into at least one + // BlockDataPacket. + // For Raw and Compressed blocks it is illegal to have block of size smaller than + // max size of packet and have packet length greater than this size. + fail!("Should_never_happen", state) + } else { + state + }; + let data_to_send = BlockDataPacket {id: state.id, ..state.last_packet}; + let tok = send_if(tok, raw_s, send_raw, data_to_send); + if (send_raw) { + trace_fmt!("DecoderDemux: send_raw: {:#x}", data_to_send); + } else {}; + // RLE module expects single byte in data field + // and block length in length field. This is different from + // Raw and Compressed modules. + let rle_data = BlockDataPacket{ + data: state.last_packet.data[0:8] as bits[DATA_WIDTH], + length: state.byte_to_pass as u32, + id: state.id, + ..state.last_packet + }; + let tok = send_if(tok, rle_s, send_rle, rle_data); + if (send_rle) { + trace_fmt!("DecoderDemux: send_rle: {:#x}", rle_data); + } else {}; + let tok = send_if(tok, cmp_s, send_cmp, data_to_send); + if (send_cmp) { + trace_fmt!("DecoderDemux: send_cmp: {:#x}", data_to_send); + } else {}; + let end_state = if (new_state.send_data == new_state.byte_to_pass) { + let next_id = if (state.last_packet.last && state.last_packet.last_block) { + u32: 0 + } else { + state.id + u32:1 + }; + DecoderDemuxState { + status: DecoderDemuxStatus::IDLE, + byte_to_pass: u21:0, + send_data: u21:0, + id: next_id, + last_packet: ZERO_DATA, + } + } else { + new_state + }; + end_state + } else { + new_state + }; + + end_state + } +} + +#[test_proc] +proc DecoderDemuxTest { + terminator: chan out; + input_s: chan out; + raw_r: chan in; + rle_r: chan in; + cmp_r: chan in; + + init {} + + config (terminator: chan out) { + let (raw_s, raw_r) = chan("raw"); + let (rle_s, rle_r) = chan("rle"); + let (cmp_s, cmp_r) = chan("cmp"); + let (input_s, input_r) = chan("input"); + + spawn DecoderDemux(input_r, raw_s, rle_s, cmp_s); + (terminator, input_s, raw_r, rle_r, cmp_r) + } + + next(state: ()) { + let tok = join(); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x11111111110000c0, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x2222222222111111, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x3333333333222222, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000000000333333, length: u32:24 }); + + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0xAAAAAAAAAA000100, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0xBBBBBBBBBBAAAAAA, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0xCCCCCCCCCCBBBBBB, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000000000CCCCCC, length: u32:24 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0xDDDDDDDDDDDDDDDD, length: u32:64 }); + + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000000FF000102, length: u32:32 }); + + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x4444444444000145, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x5555555555444444, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x6666666666555555, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x7777777777666666, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x8888888888777777, length: u32:64 }); + let tok = send(tok, input_s, BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: true, data: bits[DATA_WIDTH]:0x0000000000888888, length: u32:24 }); + + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000001111111111, length: u32:40 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x2222222222111111, length: u32:64 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:0, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x3333333333222222, length: u32:64 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:0, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000000000333333, length: u32:24 }); + + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x000000AAAAAAAAAA, length: u32:40 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0xBBBBBBBBBBAAAAAA, length: u32:64 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0xCCCCCCCCCCBBBBBB, length: u32:64 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:1, last: bool: false, last_block: bool: false, data: bits[DATA_WIDTH]:0x0000000000CCCCCC, length: u32:24 }); + let (tok, data) = recv(tok, raw_r); assert_eq(data, BlockDataPacket { id: u32:1, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0xDDDDDDDDDDDDDDDD, length: u32:64 }); + + let (tok, data) = recv(tok, rle_r); assert_eq(data, BlockDataPacket { id: u32:2, last: bool: true, last_block: bool: false, data: bits[DATA_WIDTH]:0xFF, length: u32:32 }); + + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x0000004444444444, length: u32:40 }); + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x5555555555444444, length: u32:64 }); + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x6666666666555555, length: u32:64 }); + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x7777777777666666, length: u32:64 }); + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: false, last_block: bool: true, data: bits[DATA_WIDTH]:0x8888888888777777, length: u32:64 }); + let (tok, data) = recv(tok, cmp_r); assert_eq(data, BlockDataPacket { id: u32:3, last: bool: true, last_block: bool: true, data: bits[DATA_WIDTH]:0x0000000000888888, length: u32:24 }); + + send(tok, terminator, true); + } +} From 323214e4dea246fe8f4f4032fd152d8d8d41481f Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 28 Nov 2023 15:10:34 +0100 Subject: [PATCH 16/49] modules/zstd: Add block decoder module This adds a decoder of block data. It decodes block header and demuxes remaining input data into one of specific block decoders depending on the type of the parsed block. Then it muxes outputs from those decoders into single output channel. Internal-tag: [#51873] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 86 ++++++++++++++++++ xls/modules/zstd/block_dec.x | 170 +++++++++++++++++++++++++++++++++++ 2 files changed, 256 insertions(+) create mode 100644 xls/modules/zstd/block_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index b74e119039..f1c998c4d7 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -618,3 +618,89 @@ place_and_route( target_die_utilization_percentage = "5", tags = ["manual"], ) + +xls_dslx_library( + name = "block_dec_dslx", + srcs = [ + "block_dec.x", + ], + deps = [ + ":common_dslx", + ":dec_demux_dslx", + ":dec_mux_dslx", + ":raw_block_dec_dslx", + ":rle_block_dec_dslx", + ], +) + +xls_dslx_test( + name = "block_dec_dslx_test", + library = ":block_dec_dslx", +) + +xls_dslx_verilog( + name = "block_dec_verilog", + codegen_args = { + "module_name": "BlockDecoder", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "BlockDecoder", + library = ":block_dec_dslx", + # TODO: 2024-01-15: Workaround for https://github.com/google/xls/issues/869 + # Force proc inlining and set last internal proc as top proc for IR optimization + opt_ir_args = { + "inline_procs": "true", + "top": "__xls_modules_zstd_dec_mux__BlockDecoder__DecoderMux_0_next", + }, + verilog_file = "block_dec.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "block_dec_opt_ir_benchmark", + src = ":block_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "block_dec_verilog_lib", + srcs = [ + ":block_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "block_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "BlockDecoder", + deps = [ + ":block_dec_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "block_dec_benchmark_synth", + synth_target = ":block_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "block_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":block_dec_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/block_dec.x b/xls/modules/zstd/block_dec.x new file mode 100644 index 0000000000..30fced5c56 --- /dev/null +++ b/xls/modules/zstd/block_dec.x @@ -0,0 +1,170 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import xls.modules.zstd.common; +import xls.modules.zstd.dec_demux as demux; +import xls.modules.zstd.raw_block_dec as raw; +import xls.modules.zstd.rle_block_dec as rle; +import xls.modules.zstd.dec_mux as mux; + +type BlockDataPacket = common::BlockDataPacket; +type BlockData = common::BlockData; +type BlockPacketLength = common::BlockPacketLength; +type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; + +// Proc responsible for connecting internal procs used in Block data decoding. +// It handles incoming block data packets by redirecting those to demuxer which passes those to +// block decoder procs specific for given block type. Results are then gathered by mux which +// transfers decoded data further. The connections are visualised on the following diagram: +// +// Block Decoder +// ┌───────────────────────────────────────┐ +// │ Raw Block Decoder │ +// │ ┌───────────────────┐ │ +// │ ┌─► ├┐ │ +// │ Demux │ └───────────────────┘│ Mux │ +// │┌─────┐│ Rle Block Decoder │ ┌─────┐│ +// ││ ├┘ ┌───────────────────┐└─► ││ +// ──┼► ├──► ├──► ├┼─► +// ││ ├┐ └───────────────────┘┌─► ││ +// │└─────┘│ Cmp Block Decoder │ └─────┘│ +// │ │ ┌───────────────────┐│ │ +// │ └─► ├┘ │ +// │ └───────────────────┘ │ +// └───────────────────────────────────────┘ + +proc BlockDecoder { + input_r: chan in; + output_s: chan out; + + config (input_r: chan in, output_s: chan out) { + let (demux_raw_s, demux_raw_r) = chan("demux_raw"); + let (demux_rle_s, demux_rle_r) = chan("demux_rle"); + let (demux_cmp_s, demux_cmp_r) = chan("demux_cmp"); + let (mux_raw_s, mux_raw_r) = chan("mux_raw"); + let (mux_rle_s, mux_rle_r) = chan("mux_rle"); + let (mux_cmp_s, mux_cmp_r) = chan("mux_cmp"); + + spawn demux::DecoderDemux(input_r, demux_raw_s, demux_rle_s, demux_cmp_s); + spawn raw::RawBlockDecoder(demux_raw_r, mux_raw_s); + spawn rle::RleBlockDecoder(demux_rle_r, mux_rle_s); + // TODO(lpawelcz): 2023-11-28 change to compressed block decoder proc + spawn raw::RawBlockDecoder(demux_cmp_r, mux_cmp_s); + spawn mux::DecoderMux(mux_raw_r, mux_rle_r, mux_cmp_r, output_s); + + (input_r, output_s) + } + + init { } + + next(state: ()) { } +} + +#[test_proc] +proc BlockDecoderTest { + terminator: chan out; + input_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (input_s, input_r) = chan("input"); + let (output_s, output_r) = chan("output"); + + spawn BlockDecoder(input_r, output_s); + + (terminator, input_s, output_r) + } + + next(state: ()) { + let tok = join(); + let EncodedDataBlocksPackets: BlockDataPacket[13] = [ + // RAW Block 1 byte + BlockDataPacket { id: u32:0, last: true, last_block: false, data: BlockData:0xDE000008, length: BlockPacketLength:32 }, + // RAW Block 2 bytes + BlockDataPacket { id: u32:1, last: true, last_block: false, data: BlockData:0xDEAD000010, length: BlockPacketLength:40 }, + // RAW Block 4 bytes + BlockDataPacket { id: u32:2, last: true, last_block: false, data: BlockData:0xDEADBEEF000020, length: BlockPacketLength:56 }, + // RAW Block 5 bytes (block header takes one full packet) + BlockDataPacket { id: u32:3, last: true, last_block: false, data: BlockData:0xDEADBEEFEF000028, length: BlockPacketLength:64 }, + // RAW Block 24 bytes (multi-packet block header with unaligned data in the last packet) + BlockDataPacket { id: u32:4, last: false, last_block: false, data: BlockData:0x12345678900000C0, length: BlockPacketLength:64 }, + BlockDataPacket { id: u32:4, last: false, last_block: false, data: BlockData:0x1234567890ABCDEF, length: BlockPacketLength:64 }, + BlockDataPacket { id: u32:4, last: false, last_block: false, data: BlockData:0xFEDCBA0987654321, length: BlockPacketLength:64 }, + BlockDataPacket { id: u32:4, last: true, last_block: false, data: BlockData:0xF0F0F0, length: BlockPacketLength:24 }, + + // RLE Block 1 byte + BlockDataPacket { id: u32:5, last: true, last_block: false, data: BlockData:0x6700000a, length: BlockPacketLength:32 }, + // RLE Block 2 bytes + BlockDataPacket { id: u32:6, last: true, last_block: false, data: BlockData:0x45000012, length: BlockPacketLength:32 }, + // RLE Block 4 bytes + BlockDataPacket { id: u32:7, last: true, last_block: false, data: BlockData:0x23000022, length: BlockPacketLength:32 }, + // RLE Block 8 bytes (block takes one full packet) + BlockDataPacket { id: u32:8, last: true, last_block: false, data: BlockData:0x10000042, length: BlockPacketLength:32 }, + // RLE Block 26 bytes (multi-packet block header with unaligned data in the last packet) + BlockDataPacket { id: u32:9, last: true, last_block: true, data: BlockData:0xDE0000d2, length: BlockPacketLength:32 }, + ]; + + let tok = for ((counter, block_packet), tok): ((u32, BlockDataPacket), token) in enumerate(EncodedDataBlocksPackets) { + let tok = send(tok, input_s, block_packet); + trace_fmt!("Sent #{} encoded block packet, {:#x}", counter + u32:1, block_packet); + (tok) + }(tok); + + let DecodedDataBlocksPackets: SequenceExecutorPacket[16] = [ + // RAW Block 1 byte + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDE, length: CopyOrMatchLength:8 }, + // RAW Block 2 bytes + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEAD, length: CopyOrMatchLength:16 }, + // RAW Block 4 bytes + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEADBEEF, length: CopyOrMatchLength:32 }, + // RAW Block 5 bytes (block header takes one full packet) + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEADBEEFEF, length: CopyOrMatchLength:40 }, + // RAW Block 24 bytes (multi-packet block header with unaligned data in the last packet) + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x1234567890, length: CopyOrMatchLength:40 }, + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x1234567890ABCDEF, length: CopyOrMatchLength:64 }, + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xFEDCBA0987654321, length: CopyOrMatchLength:64 }, + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xF0F0F0, length: CopyOrMatchLength:24 }, + + // RLE Block 1 byte + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x67, length: CopyOrMatchLength:8 }, + // RLE Block 2 bytes + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x4545, length: CopyOrMatchLength:16 }, + // RLE Block 4 bytes + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x23232323, length: CopyOrMatchLength:32 }, + // RLE Block 8 bytes (block takes one full packet) + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0x1010101010101010, length: CopyOrMatchLength:64 }, + // RLE Block 26 bytes (multi-packet block header with unaligned data in the last packet) + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEDEDEDEDEDEDEDE, length: CopyOrMatchLength:64 }, + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEDEDEDEDEDEDEDE, length: CopyOrMatchLength:64 }, + SequenceExecutorPacket { last: false, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEDEDEDEDEDEDEDE, length: CopyOrMatchLength:64 }, + SequenceExecutorPacket { last: true, msg_type: SequenceExecutorMessageType::LITERAL, content: CopyOrMatchContent:0xDEDE, length: CopyOrMatchLength:16 }, + ]; + + let tok = for ((counter, expected_block_packet), tok): ((u32, SequenceExecutorPacket), token) in enumerate(DecodedDataBlocksPackets) { + let (tok, decoded_block_packet) = recv(tok, output_r); + trace_fmt!("Received #{} decoded block packet, data: 0x{:x}", counter + u32:1, decoded_block_packet); + trace_fmt!("Expected #{} decoded block packet, data: 0x{:x}", counter + u32:1, expected_block_packet); + assert_eq(decoded_block_packet, expected_block_packet); + (tok) + }(tok); + + send(tok, terminator, true); + } +} From 4f19952195be8a07fc2a220725ac46744d2a4de6 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jan 2024 15:36:34 +0100 Subject: [PATCH 17/49] modules/zstd/common: Specify decoder output format Internal-tag: [#52954] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/common.x | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index fcc0116e8a..7d530d1f35 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -54,3 +54,10 @@ pub struct SequenceExecutorPacket { content: CopyOrMatchContent, // Literal data or match offset last: bool, // Last packet in frame } + +// Defines output format of the ZSTD Decoder +pub struct ZstdDecodedPacket { + data: BlockData, + length: BlockPacketLength, // valid bits in data + last: bool, // Last decoded packet in frame +} From 410d78c6407535dbd65eead8146d53df6c1c924e Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Fri, 12 Jan 2024 17:16:52 +0100 Subject: [PATCH 18/49] examples/ram: Export internal RAM API to other modules This commit marks SimultaneousReadWriteBehavior enum and num_partitions function as public to allow for creating simpler tests that interact with RAM models. Internal-tag: [#53241] Signed-off-by: Robert Winkler --- xls/examples/ram.x | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/xls/examples/ram.x b/xls/examples/ram.x index f42865ae7f..674f4d7898 100644 --- a/xls/examples/ram.x +++ b/xls/examples/ram.x @@ -55,7 +55,7 @@ pub fn ReadWordReq(addr:uN[ADDR_WIDTH]) -> } // Behavior of reads and writes to the same address in the same "tick". -enum SimultaneousReadWriteBehavior : u2 { +pub enum SimultaneousReadWriteBehavior : u2 { // The read shows the contents at the address before the write. READ_BEFORE_WRITE = 0, // The read shows the contents at the address after the write. @@ -160,7 +160,7 @@ fn write_word_test() { // Function to compute num partitions (e.g. mask width) for a data_width-wide // word divided into word_partition_size-chunks. -fn num_partitions(word_partition_size: u32, data_width: u32) -> u32 { +pub fn num_partitions(word_partition_size: u32, data_width: u32) -> u32 { match word_partition_size { u32:0 => u32:0, _ => (word_partition_size + data_width - u32:1) / word_partition_size, From 0b613d25cc67932e9021c556b71762f393d59909 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 8 Feb 2024 15:34:27 +0100 Subject: [PATCH 19/49] modules/zstd: Add Offset type to common zstd definitions Internal-tag: [#54705] Signed-off-by: Robert Winkler --- xls/modules/zstd/common.x | 2 ++ 1 file changed, 2 insertions(+) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 7d530d1f35..05cf6db431 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -16,12 +16,14 @@ pub const DATA_WIDTH = u32:64; pub const MAX_ID = u32::MAX; pub const SYMBOL_WIDTH = u32:8; pub const BLOCK_SIZE_WIDTH = u32:21; +pub const OFFSET_WIDTH = u32:22; pub type BlockData = bits[DATA_WIDTH]; pub type BlockPacketLength = u32; pub type BlockSize = bits[BLOCK_SIZE_WIDTH]; pub type CopyOrMatchContent = BlockData; pub type CopyOrMatchLength = u64; +pub type Offset = bits[OFFSET_WIDTH]; pub enum BlockType : u2 { RAW = 0, From 0ca18c47f9580bade706fe48f835aeb6109c948c Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 8 Feb 2024 15:44:19 +0100 Subject: [PATCH 20/49] modules/zstd: Add RamPrinter Proc This commit adds RAM printer block usefull for debugging HistoryBuffer inside SequenceExecutor. Internal-tag: [#54705] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 13 +++ xls/modules/zstd/ram_printer.x | 145 +++++++++++++++++++++++++++++++++ 2 files changed, 158 insertions(+) create mode 100644 xls/modules/zstd/ram_printer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index f1c998c4d7..d10bb4311f 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -704,3 +704,16 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "ram_printer_dslx", + srcs = ["ram_printer.x"], + deps = [ + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "ram_printer_dslx_test", + library = ":ram_printer_dslx", +) diff --git a/xls/modules/zstd/ram_printer.x b/xls/modules/zstd/ram_printer.x new file mode 100644 index 0000000000..9fd91e5a6e --- /dev/null +++ b/xls/modules/zstd/ram_printer.x @@ -0,0 +1,145 @@ +import std; +import xls.examples.ram; + +enum RamPrinterStatus : u2 { + IDLE = 0, + BUSY = 1, +} + +struct RamPrinterState { status: RamPrinterStatus, addr: bits[ADDR_WIDTH] } + +proc RamPrinter +{ + print_r: chan<()> in; + finish_s: chan<()> out; + rd_req_s: chan>[NUM_MEMORIES] out; + rd_resp_r: chan>[NUM_MEMORIES] in; + + config(print_r: chan<()> in, finish_s: chan<()> out, + rd_req_s: chan>[NUM_MEMORIES] out, + rd_resp_r: chan>[NUM_MEMORIES] in) { + (print_r, finish_s, rd_req_s, rd_resp_r) + } + + init { RamPrinterState { status: RamPrinterStatus::IDLE, addr: bits[ADDR_WIDTH]:0 } } + + next(state: RamPrinterState) { + let tok = join(); + let is_idle = state.status == RamPrinterStatus::IDLE; + let (tok, _) = recv_if(tok, print_r, is_idle, ()); + + let (tok, row) = for (i, (tok, row)): (u32, (token, bits[DATA_WIDTH][NUM_MEMORIES])) in + range(u32:0, NUM_MEMORIES) { + let tok = send(tok, rd_req_s[i], ram::ReadWordReq(state.addr)); + let (tok, resp) = recv(tok, rd_resp_r[i]); + let row = update(row, i, resp.data); + (tok, row) + }((tok, bits[DATA_WIDTH][NUM_MEMORIES]:[bits[DATA_WIDTH]:0, ...])); + + let is_start = state.addr == bits[ADDR_WIDTH]:0; + let is_last = state.addr == (SIZE - u32:1) as bits[ADDR_WIDTH]; + + if is_start { trace_fmt!(" ========= RAM content ========= "); } else { }; + trace_fmt!(" {}: {:x} ", state.addr, array_rev(row)); + + let tok = send_if(tok, finish_s, is_last, ()); + + if is_last { + RamPrinterState { addr: bits[ADDR_WIDTH]:0, status: RamPrinterStatus::IDLE } + } else { + RamPrinterState { + addr: state.addr + bits[ADDR_WIDTH]:1, status: RamPrinterStatus::BUSY + } + } + } +} + +const TEST_NUM_MEMORIES = u32:8; +const TEST_SIZE = u32:10; +const TEST_DATA_WIDTH = u32:8; +const TEST_WORD_PARTITION_SIZE = u32:1; +const TEST_NUM_PARTITIONS = ram::num_partitions(TEST_WORD_PARTITION_SIZE, TEST_DATA_WIDTH); +const TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_ADDR_WIDTH = std::clog2(TEST_SIZE); +const TEST_INITIALIZED = true; + +type TestAddr = uN[TEST_ADDR_WIDTH]; +type TestData = uN[TEST_DATA_WIDTH]; + +fn TestWriteWordReq + (addr: TestAddr, data: TestData) + -> ram::WriteReq { + ram::WriteWordReq(addr, data) +} + +fn TestReadWordReq(addr: TestAddr) -> ram::ReadReq { + ram::ReadWordReq(addr) +} + +#[test_proc] +proc RamPrinterTest { + terminator: chan out; + rd_req_s: chan>[TEST_NUM_MEMORIES] out; + rd_resp_r: chan>[TEST_NUM_MEMORIES] in; + wr_req_s: chan>[TEST_NUM_MEMORIES] out; + wr_resp_r: chan[TEST_NUM_MEMORIES] in; + print_s: chan<()> out; + finish_r: chan<()> in; + + config(terminator: chan out) { + let (rd_req_s, rd_req_r) = chan>[TEST_NUM_MEMORIES]("rd_req"); + let (rd_resp_s, rd_resp_r) = chan>[TEST_NUM_MEMORIES]("rd_resp"); + let (wr_req_s, wr_req_r) = chan>[TEST_NUM_MEMORIES]("wr_req"); + let (wr_resp_s, wr_resp_r) = chan[TEST_NUM_MEMORIES]("wr_resp"); + let (print_s, print_r) = chan<()>("print"); + let (finish_s, finish_r) = chan<()>("finish"); + + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[0], rd_resp_s[0], wr_req_r[0], wr_resp_s[0]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[1], rd_resp_s[1], wr_req_r[1], wr_resp_s[1]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[2], rd_resp_s[2], wr_req_r[2], wr_resp_s[2]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[3], rd_resp_s[3], wr_req_r[3], wr_resp_s[3]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[4], rd_resp_s[4], wr_req_r[4], wr_resp_s[4]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[5], rd_resp_s[5], wr_req_r[5], wr_resp_s[5]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[6], rd_resp_s[6], wr_req_r[6], wr_resp_s[6]); + spawn ram::RamModel< + TEST_DATA_WIDTH, TEST_SIZE, TEST_WORD_PARTITION_SIZE, TEST_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_INITIALIZED>( + rd_req_r[7], rd_resp_s[7], wr_req_r[7], wr_resp_s[7]); + + spawn RamPrinter< + TEST_DATA_WIDTH, TEST_SIZE, TEST_NUM_PARTITIONS, TEST_ADDR_WIDTH, TEST_NUM_MEMORIES>( + print_r, finish_s, rd_req_s, rd_resp_r); + + (terminator, rd_req_s, rd_resp_r, wr_req_s, wr_resp_r, print_s, finish_r) + } + + init { } + + next(state: ()) { + let tok = join(); + let tok = send(tok, wr_req_s[0], TestWriteWordReq(TestAddr:2, TestData:0x10)); + let tok = send(tok, wr_req_s[1], TestWriteWordReq(TestAddr:2, TestData:0x20)); + let tok = send(tok, wr_req_s[2], TestWriteWordReq(TestAddr:2, TestData:0x30)); + let tok = send(tok, wr_req_s[3], TestWriteWordReq(TestAddr:2, TestData:0x40)); + let tok = send(tok, wr_req_s[4], TestWriteWordReq(TestAddr:2, TestData:0x50)); + let tok = send(tok, wr_req_s[5], TestWriteWordReq(TestAddr:2, TestData:0x60)); + let tok = send(tok, wr_req_s[6], TestWriteWordReq(TestAddr:2, TestData:0x70)); + let tok = send(tok, wr_req_s[7], TestWriteWordReq(TestAddr:2, TestData:0x80)); + let tok = send(tok, print_s, ()); + let (tok, _) = recv(tok, finish_r); + let tok = send(tok, terminator, true); + } +} From 02fc78cb31b3e6352640e89ea7bd0dd553db32e7 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Thu, 8 Feb 2024 15:44:52 +0100 Subject: [PATCH 21/49] modules/zstd: Add SequenceExecutor Proc Add Proc responsible for handling ZSTD Sequence Execution step, which is described in: https://datatracker.ietf.org/doc/html/rfc8878#name-sequence-execution Internal-tag: [#54705] Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 111 ++ xls/modules/zstd/common.x | 1 + xls/modules/zstd/sequence_executor.x | 1716 ++++++++++++++++++++++++++ 3 files changed, 1828 insertions(+) create mode 100644 xls/modules/zstd/sequence_executor.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index d10bb4311f..c9cceb43dc 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -20,6 +20,7 @@ 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", @@ -717,3 +718,113 @@ xls_dslx_test( name = "ram_printer_dslx_test", library = ":ram_printer_dslx", ) + +xls_dslx_library( + name = "sequence_executor_dslx", + srcs = [ + "sequence_executor.x", + ], + deps = [ + ":common_dslx", + ":ram_printer_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "sequence_executor_dslx_test", + dslx_test_args = { + "compare": "none", + }, + library = ":sequence_executor_dslx", +) + +xls_dslx_verilog( + name = "sequence_executor_verilog", + codegen_args = { + "module_name": "sequence_executor", + "generator": "pipeline", + "delay_model": "asap7", + "ram_configurations": ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram{}".format(num), + rd_req = "sequence_executor__rd_req_m{}_s".format(num), + rd_resp = "sequence_executor__rd_resp_m{}_r".format(num), + wr_req = "sequence_executor__wr_req_m{}_s".format(num), + wr_resp = "sequence_executor__wr_resp_m{}_r".format(num), + ) + for num in range(7) + ]), + "pipeline_stages": "8", + "reset": "rst", + "reset_data_path": "true", + "reset_active_low": "false", + "reset_asynchronous": "true", + "flop_inputs": "false", + "flop_single_value_channels": "false", + "flop_outputs": "false", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "SequenceExecutorZstd", + library = ":sequence_executor_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__sequence_executor__SequenceExecutorZstd__SequenceExecutor_0__64_0_0_0_13_8192_65536_next", + }, + verilog_file = "sequence_executor.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "sequence_executor_ir_benchmark", + src = ":sequence_executor_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "8", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +xls_benchmark_verilog( + name = "sequence_executor_verilog_benchmark", + verilog_target = "sequence_executor_verilog", + tags = ["manual"], +) + +verilog_library( + name = "sequence_executor_lib", + srcs = [ + ":sequence_executor.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "sequence_executor_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "sequence_executor", + deps = [ + ":sequence_executor_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "sequence_executor_benchmark_synth", + synth_target = ":sequence_executor_asap7", + tags = ["manual"], +) + +place_and_route( + name = "sequence_executor_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":sequence_executor_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 05cf6db431..582ac54b29 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -17,6 +17,7 @@ pub const MAX_ID = u32::MAX; pub const SYMBOL_WIDTH = u32:8; pub const BLOCK_SIZE_WIDTH = u32:21; pub const OFFSET_WIDTH = u32:22; +pub const HISTORY_BUFFER_SIZE_KB = u32:64; pub type BlockData = bits[DATA_WIDTH]; pub type BlockPacketLength = u32; diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x new file mode 100644 index 0000000000..8057c9831c --- /dev/null +++ b/xls/modules/zstd/sequence_executor.x @@ -0,0 +1,1716 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import std; +import xls.modules.zstd.common as common; +import xls.modules.zstd.ram_printer as ram_printer; +import xls.examples.ram; + +type BlockData = common::BlockData; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type ZstdDecodedPacket = common::ZstdDecodedPacket; +type BlockPacketLength = common::BlockPacketLength; +type Offset = common::Offset; + +fn calculate_ram_addr_width(hb_size_kb: u32, ram_data_width: u32, ram_num: u32) -> u32 { + ((hb_size_kb * u32:1024 * u32:8) / ram_data_width) / ram_num +} + +// Configurable RAM parameters +pub const RAM_DATA_WIDTH = common::SYMBOL_WIDTH; +const RAM_NUM = u32:8; + +type RamData = bits[RAM_DATA_WIDTH]; + +// Constants calculated from RAM parameters +const RAM_NUM_WIDTH = std::clog2(RAM_NUM); +pub const RAM_WORD_PARTITION_SIZE = RAM_DATA_WIDTH; +const RAM_ORDER_WIDTH = std::clog2(RAM_DATA_WIDTH); +pub const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); +const RAM_REQ_MASK_ALL = std::unsigned_max_value(); +const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; + +type RamNumber = bits[RAM_NUM_WIDTH]; +type RamOrder = bits[RAM_ORDER_WIDTH]; + +pub fn ram_size(hb_size_kb: u32) -> u32 { (hb_size_kb * u32:1024 * u32:8) / RAM_DATA_WIDTH / RAM_NUM } + +fn ram_addr_width(hb_size_kb: u32) -> u32 { std::clog2(ram_size(hb_size_kb)) } + +// RAM related constants common for tests +const TEST_HISTORY_BUFFER_SIZE_KB = u32:1; +const TEST_RAM_SIZE = ram_size(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_ADDR_WIDTH = ram_addr_width(TEST_HISTORY_BUFFER_SIZE_KB); +pub const TEST_RAM_INITIALIZED = true; +pub const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; + +type TestRamAddr = bits[TEST_RAM_ADDR_WIDTH]; +type TestWriteReq = ram::WriteReq; +type TestWriteResp = ram::WriteResp; +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; + +struct HistoryBufferPtr { number: RamNumber, addr: bits[RAM_ADDR_WIDTH] } + +type HistoryBufferLength = u32; + +enum SequenceExecutorStatus : u2 { + IDLE = 0, + LITERAL_WRITE = 1, + SEQUENCE_READ = 2, + SEQUENCE_WRITE = 3, +} + +struct SequenceExecutorState { + status: SequenceExecutorStatus, + // Packet handling + packet: SequenceExecutorPacket, + packet_valid: bool, + // History Buffer handling + hyp_ptr: HistoryBufferPtr, + real_ptr: HistoryBufferPtr, + hb_len: HistoryBufferLength, + // Repeat Offset handling + repeat_offsets: Offset[3], + repeat_req: bool, + seq_cnt: bool, +} + +fn decode_literal_packet(packet: SequenceExecutorPacket) -> ZstdDecodedPacket { + ZstdDecodedPacket { + data: packet.content, length: packet.length as BlockPacketLength, last: packet.last + } +} + +#[test] +fn test_decode_literal_packet() { + let content = CopyOrMatchContent:0xAA00BB11CC22DD33; + let length = CopyOrMatchLength:64; + let last = false; + + assert_eq( + decode_literal_packet( + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length, content, last + }), + ZstdDecodedPacket { + length: length as BlockPacketLength, + data: content, last + }) +} + +fn round_up_to_pow2(x: uN[N]) -> uN[N] { + let base = x[Y_CLOG2 as s32:]; + let reminder = x[0:Y_CLOG2 as s32] != bits[Y_CLOG2]:0; + (base as uN[N] + reminder as uN[N]) << Y_CLOG2 +} + +#[test] +fn test_round_up_to_pow2() { + assert_eq(round_up_to_pow2(u16:0), u16:0); + assert_eq(round_up_to_pow2(u16:1), u16:8); + assert_eq(round_up_to_pow2(u16:7), u16:8); + assert_eq(round_up_to_pow2(u16:8), u16:8); + assert_eq(round_up_to_pow2(u16:9), u16:16); + assert_eq(round_up_to_pow2(u16:9), u16:16); +} + +fn hb_ptr_from_offset_back + + (ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { + + const_assert!(common::OFFSET_WIDTH < u32:32); + type RamAddr = bits[RAM_ADDR_WIDTH]; + + let buff_change = std::mod_pow2(offset as u32, RAM_NUM) as RamNumber; + let rounded_offset = round_up_to_pow2(offset as u32 + u32:1); + let max_row_span = std::div_pow2(rounded_offset, RAM_NUM) as RamAddr; + let (number, addr_change) = if ptr.number >= buff_change { + (ptr.number - buff_change, max_row_span - RamAddr:1) + } else { + ((RAM_NUM + ptr.number as u32 - buff_change as u32) as RamNumber, max_row_span) + }; + let addr = if ptr.addr > addr_change { + ptr.addr - addr_change + } else { + (RAM_SIZE + ptr.addr as u32 - addr_change as u32) as RamAddr + }; + HistoryBufferPtr { number, addr } +} + +#[test] +fn test_hb_ptr_from_offset_back() { + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), + HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), + HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), + HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), + HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:0 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }, Offset:1), + HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }); +} + +fn hb_ptr_from_offset_forw + + (ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { + + type RamAddr = bits[RAM_ADDR_WIDTH]; + const MAX_ADDR = (RAM_SIZE - u32:1) as RamAddr; + + let buff_change = std::mod_pow2(offset as u32, RAM_NUM) as RamNumber; + let rounded_offset = round_up_to_pow2(offset as u32 + u32:1); + let max_row_span = std::div_pow2(rounded_offset, RAM_NUM) as RamAddr; + let (number, addr_change) = if ptr.number as u32 + buff_change as u32 < RAM_NUM { + (ptr.number + buff_change, max_row_span - RamAddr:1) + } else { + ((buff_change as u32 - (RAM_NUM - ptr.number as u32)) as RamNumber, max_row_span) + }; + + let addr = if ptr.addr + addr_change <= MAX_ADDR { + ptr.addr + addr_change + } else { + (addr_change - (MAX_ADDR - ptr.addr)) + }; + + HistoryBufferPtr { number, addr } +} + +#[test] +fn test_hb_ptr_from_offset_forw() { + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), + HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), + HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), + HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), + HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:4 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }, + Offset:1), HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }); +} + +fn literal_packet_to_single_write_req + + (ptr: HistoryBufferPtr, literal: SequenceExecutorPacket, number: RamNumber) + -> ram::WriteReq { + + let offset = std::mod_pow2(RAM_NUM - ptr.number as u32 + number as u32, RAM_NUM) as Offset; + let we = literal.length >= (offset as CopyOrMatchLength + CopyOrMatchLength:1) << CopyOrMatchLength:3; + let hb = hb_ptr_from_offset_forw(ptr, offset); + + if we { + ram::WriteReq { + data: literal.content[offset as u32 << u32:3+:RamData] as RamData, + addr: hb.addr, + mask: std::unsigned_max_value() + } + } else { + ram::WriteReq { + addr: bits[RAM_ADDR_WIDTH]:0, + data: bits[RAM_DATA_WIDTH]:0, + mask: bits[RAM_NUM_PARTITIONS]:0 + } + } +} + +#[test] +fn test_literal_packet_to_single_write_req() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | | o|77|66|55|44|33|22| + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: CopyOrMatchContent:0x77_6655_4433_2211, + length: CopyOrMatchLength:56, + last: false + }; + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:0), + TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }); + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:3), + TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }); + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:6), + zero!()); +} + +fn literal_packet_to_write_reqs + + (ptr: HistoryBufferPtr, literal: SequenceExecutorPacket) + -> (ram::WriteReq[RAM_NUM], HistoryBufferPtr) { + type WriteReq = ram::WriteReq; + let result = WriteReq[RAM_NUM]:[ + literal_packet_to_single_write_req(ptr, literal, RamNumber:0), + literal_packet_to_single_write_req(ptr, literal, RamNumber:1), + literal_packet_to_single_write_req(ptr, literal, RamNumber:2), + literal_packet_to_single_write_req(ptr, literal, RamNumber:3), + literal_packet_to_single_write_req(ptr, literal, RamNumber:4), + literal_packet_to_single_write_req(ptr, literal, RamNumber:5), + literal_packet_to_single_write_req(ptr, literal, RamNumber:6), + literal_packet_to_single_write_req(ptr, literal, RamNumber:7), + ]; + + let ptr_offset = literal.length >> CopyOrMatchLength:3; + (result, hb_ptr_from_offset_forw(ptr, ptr_offset as Offset)) +} + +#[test] +fn test_literal_packet_to_write_reqs() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | | | | | | | | o| + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:0x2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: CopyOrMatchContent:0x11, + length: CopyOrMatchLength:8, + last: false + }; + assert_eq( + literal_packet_to_write_reqs(ptr, literals), + ( + TestWriteReq[RAM_NUM]:[ + zero!(), zero!(), zero!(), + zero!(), zero!(), zero!(), + zero!(), + TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, + ], HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x3 }, + )); + + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | o|88|77|66|55|44|33|22| + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: CopyOrMatchContent:0x8877_6655_4433_2211, + length: CopyOrMatchLength:64, + last: false + }; + assert_eq( + literal_packet_to_write_reqs(ptr, literals), + ( + TestWriteReq[RAM_NUM]:[ + TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x33, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x44, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x66, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x77, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x88, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, + ], HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:3 }, + )); +} + +fn max_hb_ptr_for_sequence_packet + + (ptr: HistoryBufferPtr, seq: SequenceExecutorPacket) + -> HistoryBufferPtr { + hb_ptr_from_offset_back(ptr, seq.content as Offset) +} + +fn sequence_packet_to_single_read_req + + (ptr: HistoryBufferPtr, max_ptr: HistoryBufferPtr, + seq: SequenceExecutorPacket, number: RamNumber) + -> (ram::ReadReq, RamOrder) { + type ReadReq = ram::ReadReq; + let offset_change = if max_ptr.number > number { + RAM_NUM - max_ptr.number as u32 + number as u32 + } else { + number as u32 - max_ptr.number as u32 + }; + let offset = (seq.content as u32 - offset_change) as Offset; + let re = (offset_change as CopyOrMatchLength) < seq.length; + let hb = hb_ptr_from_offset_back(ptr, offset); + + if re { + (ReadReq { addr: hb.addr, mask: RAM_REQ_MASK_ALL }, offset_change as RamOrder) + } else { + (zero!(), RamOrder:0) + } +} + +#[test] +fn test_sequence_packet_to_single_read_req() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | x| x| | | | | | | 1 | | | | | | | | | + // 2 | | | | | | | x| x| 2 | | | | | | | | | + // 3 | | | | | | | o| | 3 | | | o| y| y| y| y| | + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; + let sequence = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:11, + length: CopyOrMatchLength:4, + last: false + }; + let max_ptr = max_hb_ptr_for_sequence_packet(ptr, sequence); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:0), + (TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, RamOrder:2)); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:1), + (TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, RamOrder:3)); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:2), (zero!(), RamOrder:0)); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:7), + (TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, RamOrder:1)); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:6), + (TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, RamOrder:0)); +} + +fn sequence_packet_to_read_reqs + + (ptr: HistoryBufferPtr, seq: SequenceExecutorPacket, hb_len: HistoryBufferLength) + -> (ram::ReadReq[RAM_NUM], RamOrder[RAM_NUM], SequenceExecutorPacket, bool) { + type ReadReq = ram::ReadReq; + + let max_len = std::umin(seq.length as u32, std::umin(RAM_NUM, hb_len)); + + let (next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { + ( + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: seq.length - max_len as CopyOrMatchLength, + content: seq.content, + last: seq.last + }, true, + ) + } else { + (zero!(), false) + }; + + let max_ptr = max_hb_ptr_for_sequence_packet(ptr, seq); + let (req0, order0) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:0); + let (req1, order1) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:1); + let (req2, order2) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:2); + let (req3, order3) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:3); + let (req4, order4) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:4); + let (req5, order5) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:5); + let (req6, order6) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:6); + let (req7, order7) = + sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:7); + + let reqs = ReadReq[RAM_NUM]:[req0, req1, req2, req3, req4, req5, req6, req7]; + let orders = RamOrder[RAM_NUM]:[order0, order1, order2, order3, order4, order5, order6, order7]; + (reqs, orders, next_seq, next_seq_valid) +} + +#[test] +fn test_sequence_packet_to_read_reqs() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | x| x| | | | | | | 1 | | | | | | | | | + // 2 | | | | | | | x| x| 2 | | | | | | | | | + // 3 | | | | | | | o| | 3 | | | | | | | o| | + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; + let sequence = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:11, + length: CopyOrMatchLength:4, + last: false + }; + let result = sequence_packet_to_read_reqs( + ptr, sequence, HistoryBufferLength:20); + let expected = ( + TestReadReq[RAM_NUM]:[ + TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, zero!(), + zero!(), zero!(), zero!(), + TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, + ], + RamOrder[RAM_NUM]:[ + RamOrder:2, RamOrder:3, zero!(), zero!(), zero!(), + zero!(), RamOrder:0, RamOrder:1, + ], zero!(), false, + ); + assert_eq(result, expected); + + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | x| x| | | | | | | 2 | | | | | | | | | + // 3 | | | x| x| x| x| x| x| 3 | | x| | | | | | | + // 4 | | | | | | | | o| 4 | | | | | | | | o| + + let ptr = HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x4 }; + let sequence = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:10, + length: CopyOrMatchLength:9, + last: false + }; + let result = sequence_packet_to_read_reqs( + ptr, sequence, HistoryBufferLength:20); + let expected = ( + TestReadReq[RAM_NUM]:[ + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, + ], + RamOrder[RAM_NUM]:[ + RamOrder:2, RamOrder:3, RamOrder:4, RamOrder:5, RamOrder:6, RamOrder:7, RamOrder:0, + RamOrder:1, + ], + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:10, + length: CopyOrMatchLength:1, + last: false + }, true, + ); + assert_eq(result, expected); +} + +struct RamWrRespHandlerData { + resp: bool[RAM_NUM], + ptr: HistoryBufferPtr, +} + +fn create_ram_wr_data + (reqs: ram::WriteReq[RAM_NUM], ptr: HistoryBufferPtr) -> (bool, RamWrRespHandlerData) { + let do_write = for (i, do_write): (u32, bool) in range(u32:0, RAM_NUM) { + do_write || reqs[i].mask + }(false); + + let resp = bool[RAM_NUM]:[ + ((reqs[0]).mask != RAM_REQ_MASK_NONE), + ((reqs[1]).mask != RAM_REQ_MASK_NONE), + ((reqs[2]).mask != RAM_REQ_MASK_NONE), + ((reqs[3]).mask != RAM_REQ_MASK_NONE), + ((reqs[4]).mask != RAM_REQ_MASK_NONE), + ((reqs[5]).mask != RAM_REQ_MASK_NONE), + ((reqs[6]).mask != RAM_REQ_MASK_NONE), + ((reqs[7]).mask != RAM_REQ_MASK_NONE), + ]; + + (do_write, RamWrRespHandlerData { resp, ptr }) +} + +proc RamWrRespHandler { + input_r: chan in; + output_s: chan out; + wr_resp_m0_r: chan in; + wr_resp_m1_r: chan in; + wr_resp_m2_r: chan in; + wr_resp_m3_r: chan in; + wr_resp_m4_r: chan in; + wr_resp_m5_r: chan in; + wr_resp_m6_r: chan in; + wr_resp_m7_r: chan in; + + config(input_r: chan> in, + output_s: chan> out, + wr_resp_m0_r: chan in, wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, wr_resp_m7_r: chan in) { + ( + input_r, output_s, wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, wr_resp_m4_r, + wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ) + } + + init { } + + next(state: ()) { + let tok0 = join(); + let (tok1, input) = recv(tok0, input_r); + + let (tok2_0, _) = recv_if(tok1, wr_resp_m0_r, input.resp[0], zero!()); + let (tok2_1, _) = recv_if(tok1, wr_resp_m1_r, input.resp[1], zero!()); + let (tok2_2, _) = recv_if(tok1, wr_resp_m2_r, input.resp[2], zero!()); + let (tok2_3, _) = recv_if(tok1, wr_resp_m3_r, input.resp[3], zero!()); + let (tok2_4, _) = recv_if(tok1, wr_resp_m4_r, input.resp[4], zero!()); + let (tok2_5, _) = recv_if(tok1, wr_resp_m5_r, input.resp[5], zero!()); + let (tok2_6, _) = recv_if(tok1, wr_resp_m6_r, input.resp[6], zero!()); + let (tok2_7, _) = recv_if(tok1, wr_resp_m7_r, input.resp[7], zero!()); + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + let tok3 = send(tok2, output_s, input.ptr); + } +} + +struct RamRdRespHandlerData { + resp: bool[RAM_NUM], + order: RamOrder[RAM_NUM], + last: bool +} + +fn create_ram_rd_data + (reqs: ram::ReadReq[RAM_NUM], order: RamOrder[RAM_NUM], last: bool, next_packet_valid: bool) -> (bool, RamRdRespHandlerData) { + let do_read = for (i, do_read): (u32, bool) in range(u32:0, RAM_NUM) { + do_read || reqs[i].mask + }(false); + + let resp = bool[RAM_NUM]:[ + ((reqs[0]).mask != RAM_REQ_MASK_NONE), + ((reqs[1]).mask != RAM_REQ_MASK_NONE), + ((reqs[2]).mask != RAM_REQ_MASK_NONE), + ((reqs[3]).mask != RAM_REQ_MASK_NONE), + ((reqs[4]).mask != RAM_REQ_MASK_NONE), + ((reqs[5]).mask != RAM_REQ_MASK_NONE), + ((reqs[6]).mask != RAM_REQ_MASK_NONE), + ((reqs[7]).mask != RAM_REQ_MASK_NONE), + ]; + + let last = if next_packet_valid { false } else { last }; + (do_read, RamRdRespHandlerData { resp, order, last }) +} + +proc RamRdRespHandler { + input_r: chan in; + output_s: chan out; + rd_resp_m0_r: chan> in; + rd_resp_m1_r: chan> in; + rd_resp_m2_r: chan> in; + rd_resp_m3_r: chan> in; + rd_resp_m4_r: chan> in; + rd_resp_m5_r: chan> in; + rd_resp_m6_r: chan> in; + rd_resp_m7_r: chan> in; + + config(input_r: chan in, output_s: chan out, + rd_resp_m0_r: chan> in, + rd_resp_m1_r: chan> in, + rd_resp_m2_r: chan> in, + rd_resp_m3_r: chan> in, + rd_resp_m4_r: chan> in, + rd_resp_m5_r: chan> in, + rd_resp_m6_r: chan> in, + rd_resp_m7_r: chan> in) { + ( + input_r, output_s, rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, rd_resp_m4_r, + rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + ) + } + + init { } + + next(state: ()) { + let tok0 = join(); + type ReadResp = ram::ReadResp; + + let (tok1, input) = recv(tok0, input_r); + + let (tok2_0, resp_0) = recv_if(tok1, rd_resp_m0_r, input.resp[0], zero!()); + let (tok2_1, resp_1) = recv_if(tok1, rd_resp_m1_r, input.resp[1], zero!()); + let (tok2_2, resp_2) = recv_if(tok1, rd_resp_m2_r, input.resp[2], zero!()); + let (tok2_3, resp_3) = recv_if(tok1, rd_resp_m3_r, input.resp[3], zero!()); + let (tok2_4, resp_4) = recv_if(tok1, rd_resp_m4_r, input.resp[4], zero!()); + let (tok2_5, resp_5) = recv_if(tok1, rd_resp_m5_r, input.resp[5], zero!()); + let (tok2_6, resp_6) = recv_if(tok1, rd_resp_m6_r, input.resp[6], zero!()); + let (tok2_7, resp_7) = recv_if(tok1, rd_resp_m7_r, input.resp[7], zero!()); + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + let content = (resp_0.data as CopyOrMatchContent) << (input.order[0] as CopyOrMatchContent << 3) | + (resp_1.data as CopyOrMatchContent) << (input.order[1] as CopyOrMatchContent << 3) | + (resp_2.data as CopyOrMatchContent) << (input.order[2] as CopyOrMatchContent << 3) | + (resp_3.data as CopyOrMatchContent) << (input.order[3] as CopyOrMatchContent << 3) | + (resp_4.data as CopyOrMatchContent) << (input.order[4] as CopyOrMatchContent << 3) | + (resp_5.data as CopyOrMatchContent) << (input.order[5] as CopyOrMatchContent << 3) | + (resp_6.data as CopyOrMatchContent) << (input.order[6] as CopyOrMatchContent << 3) | + (resp_7.data as CopyOrMatchContent) << (input.order[7] as CopyOrMatchContent << 3); + + let converted = std::convert_to_bits_msb0(input.resp); + let length = std::popcount(converted) << 3; + + let output_data = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: length as CopyOrMatchLength, + content: content as CopyOrMatchContent, + last: input.last, + }; + + let tok3 = send(tok2, output_s, output_data); + } +} + +fn handle_reapeated_offset_for_sequences + (seq: SequenceExecutorPacket, repeat_offsets: Offset[3], repeat_req: bool) + -> (SequenceExecutorPacket, Offset[3]) { + let modified_repeat_offsets = if repeat_req { + Offset[3]:[repeat_offsets[1], repeat_offsets[2], repeat_offsets[0] - Offset:1] + } else { + repeat_offsets + }; + + let (seq, final_repeat_offsets) = if seq.content == CopyOrMatchContent:0 { + fail!( + "match_offset_zero_not_allowed", + (zero!(), Offset[3]:[Offset:0, ...])) + } else if seq.content == CopyOrMatchContent:1 { + let offset = modified_repeat_offsets[0]; + ( + SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Offset[3]:[ + offset, repeat_offsets[1], repeat_offsets[2], + ], + ) + } else if seq.content == CopyOrMatchContent:2 { + let offset = modified_repeat_offsets[1]; + ( + SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Offset[3]:[ + offset, repeat_offsets[0], repeat_offsets[2], + ], + ) + } else if seq.content == CopyOrMatchContent:3 { + let offset = modified_repeat_offsets[2]; + ( + SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Offset[3]:[ + offset, repeat_offsets[0], repeat_offsets[1], + ], + ) + } else { + let offset = seq.content as Offset - Offset:3; + ( + SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Offset[3]:[ + offset, repeat_offsets[0], repeat_offsets[1], + ], + ) + }; + (seq, final_repeat_offsets) +} + +proc SequenceExecutor +{ + input_r: chan in; + output_s: chan out; + ram_comp_input_s: chan> out; + ram_comp_output_r: chan> in; + ram_resp_input_s: chan out; + ram_resp_output_r: chan in; + rd_req_m0_s: chan> out; + rd_req_m1_s: chan> out; + rd_req_m2_s: chan> out; + rd_req_m3_s: chan> out; + rd_req_m4_s: chan> out; + rd_req_m5_s: chan> out; + rd_req_m6_s: chan> out; + rd_req_m7_s: chan> out; + wr_req_m0_s: chan> out; + wr_req_m1_s: chan> out; + wr_req_m2_s: chan> out; + wr_req_m3_s: chan> out; + wr_req_m4_s: chan> out; + wr_req_m5_s: chan> out; + wr_req_m6_s: chan> out; + wr_req_m7_s: chan> out; + + config( + input_r: chan in, + output_s: chan out, + ram_resp_output_r: chan in, + ram_resp_output_s: chan out, + rd_req_m0_s: chan> out, + rd_req_m1_s: chan> out, + rd_req_m2_s: chan> out, + rd_req_m3_s: chan> out, + rd_req_m4_s: chan> out, + rd_req_m5_s: chan> out, + rd_req_m6_s: chan> out, + rd_req_m7_s: chan> out, + rd_resp_m0_r: chan> in, + rd_resp_m1_r: chan> in, + rd_resp_m2_r: chan> in, + rd_resp_m3_r: chan> in, + rd_resp_m4_r: chan> in, + rd_resp_m5_r: chan> in, + rd_resp_m6_r: chan> in, + rd_resp_m7_r: chan> in, + wr_req_m0_s: chan> out, + wr_req_m1_s: chan> out, + wr_req_m2_s: chan> out, + wr_req_m3_s: chan> out, + wr_req_m4_s: chan> out, + wr_req_m5_s: chan> out, + wr_req_m6_s: chan> out, + wr_req_m7_s: chan> out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + let (ram_comp_input_s, ram_comp_input_r) = chan, u32:1>("ram_comp_input"); + let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); + let (ram_resp_input_s, ram_resp_input_r) = chan("ram_resp_input"); + + spawn RamWrRespHandler( + ram_comp_input_r, ram_comp_output_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r); + + spawn RamRdRespHandler( + ram_resp_input_r, ram_resp_output_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, + rd_resp_m3_r, rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r); + + ( + input_r, output_s, + ram_comp_input_s, ram_comp_output_r, + ram_resp_input_s, ram_resp_output_r, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + ) + } + + init { + const_assert!(INIT_HB_PTR_RAM < RAM_NUM); + const_assert!(INIT_HB_PTR_ADDR <= (std::unsigned_max_value() as u32)); + + type RamAddr = bits[RAM_ADDR_WIDTH]; + let INIT_HB_PTR = HistoryBufferPtr { + number: INIT_HB_PTR_RAM as RamNumber, addr: INIT_HB_PTR_ADDR as RamAddr + }; + SequenceExecutorState { + status: SequenceExecutorStatus::IDLE, + packet: zero!(), + packet_valid: false, + hyp_ptr: INIT_HB_PTR, + real_ptr: INIT_HB_PTR, + hb_len: INIT_HB_LENGTH, + repeat_offsets: Offset[3]:[Offset:1, Offset:4, Offset:8], + repeat_req: false, + seq_cnt: false + } + } + + next(state: SequenceExecutorState) { + let tok0 = join(); + type Status = SequenceExecutorStatus; + type State = SequenceExecutorState; + type MsgType = SequenceExecutorMessageType; + type Packet = SequenceExecutorPacket; + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + const ZERO_READ_REQS = ReadReq[RAM_NUM]:[zero!(), ...]; + const ZERO_WRITE_REQS = WriteReq[RAM_NUM]:[zero!(), ...]; + const ZERO_ORDER = RamOrder[RAM_NUM]:[RamOrder:0, ...]; + + // Recieve literals and sequences from the input channel ... + let do_recv_input = !state.packet_valid && state.status != Status::SEQUENCE_READ && + state.status != Status::SEQUENCE_WRITE; + let (tok1_0, input_packet, input_packet_valid) = + recv_if_non_blocking(tok0, input_r, do_recv_input, zero!()); + + // ... or our own sequences from the looped channel + let do_recv_ram = + (state.status == Status::SEQUENCE_READ || state.status == Status::SEQUENCE_WRITE); + let (tok1_1, ram_packet, ram_packet_valid) = + recv_if_non_blocking(tok0, ram_resp_output_r, do_recv_ram, zero!()); + + // Read RAM write completion, used for monitoring the real state + // of the RAM and eventually changing the state to IDLE. + // Going through the IDLE state is required for changing between + // Literals and Sequences (and the other way around) and between every + // Sequence read from the input (original sequence from the ZSTD stream). + let (tok1_2, real_ptr, real_ptr_valid) = + recv_non_blocking(tok0, ram_comp_output_r, zero!()); + if real_ptr_valid { + trace_fmt!("SequenceExecutor:: Received completion update"); + } else { }; + + let real_ptr = if real_ptr_valid { real_ptr } else { state.real_ptr }; + let tok1 = join(tok1_0, tok1_1, tok1_2); + + // Since we either get data from input, from frame, or from state, + // we are always working on a single packet. The current state + // can be use to determine the source of the packet. + let (packet, packet_valid) = if input_packet_valid { + (input_packet, true) + } else if ram_packet_valid { + (ram_packet, true) + } else { + (state.packet, state.packet_valid) + }; + + // if we are in the IDLE state and have a valid packet stored in the state, + // or we have a new packet from the input go to the corresponding + // processing step immediately. (added to be able to process a single + // literal in one next() evaluation) + let status = match (state.status, packet_valid, packet.msg_type) { + (Status::IDLE, true, MsgType::LITERAL) => Status::LITERAL_WRITE, + (Status::IDLE, true, MsgType::SEQUENCE) => Status::SEQUENCE_READ, + _ => state.status, + }; + + let NO_VALID_PACKET_STATE = State { packet, packet_valid, real_ptr, ..state }; + let (write_reqs, read_reqs, order, new_state) = match ( + status, packet_valid, packet.msg_type + ) { + // Handling LITERAL_WRITE + (Status::LITERAL_WRITE, true, MsgType::LITERAL) => { + trace_fmt!("SequenceExecutor:: Handling LITERAL packet in LITERAL_WRITE step"); + let (write_reqs, new_hyp_ptr) = + literal_packet_to_write_reqs(state.hyp_ptr, packet); + let new_repeat_req = packet.length == CopyOrMatchLength:0; + let hb_add = (packet.length >> 3) as HistoryBufferLength; + let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL); + ( + write_reqs, ZERO_READ_REQS, ZERO_ORDER, + State { + status: Status::LITERAL_WRITE, + packet: zero!(), + packet_valid: false, + hyp_ptr: new_hyp_ptr, + real_ptr, + repeat_offsets: state.repeat_offsets, + repeat_req: new_repeat_req, + hb_len: new_hb_len, + seq_cnt: false + }, + ) + }, + (Status::LITERAL_WRITE, _, _) => { + let status = + if real_ptr == state.hyp_ptr { Status::IDLE } else { Status::LITERAL_WRITE }; + ( + ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, + State { status, ..NO_VALID_PACKET_STATE }, + ) + }, + // Handling SEQUENCE_READ + (Status::SEQUENCE_READ, true, MsgType::SEQUENCE) => { + trace_fmt!("Handling SEQUENCE in SEQUENCE_READ state"); + let (packet, new_repeat_offsets) = if !state.seq_cnt { + handle_reapeated_offset_for_sequences( + packet, state.repeat_offsets, state.repeat_req) + } else { + (packet, state.repeat_offsets) + }; + let (read_reqs, order, packet, packet_valid) = sequence_packet_to_read_reqs< + HISTORY_BUFFER_SIZE_KB>( + state.hyp_ptr, packet, state.hb_len); + + ( + ZERO_WRITE_REQS, read_reqs, order, + SequenceExecutorState { + status: Status::SEQUENCE_WRITE, + packet, + packet_valid, + hyp_ptr: state.hyp_ptr, + real_ptr, + repeat_offsets: new_repeat_offsets, + repeat_req: false, + hb_len: state.hb_len, + seq_cnt: packet_valid + }, + ) + }, + (Status::SEQUENCE_READ, _, _) => { + let ZERO_RETURN = (ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, zero!()); + fail!("should_no_happen", (ZERO_RETURN)) + }, + // Handling SEQUENCE_WRITE + (Status::SEQUENCE_WRITE, true, MsgType::LITERAL) => { + trace_fmt!("Handling LITERAL in SEQUENCE_WRITE state: {}", status); + let (write_reqs, new_hyp_ptr) = + literal_packet_to_write_reqs(state.hyp_ptr, packet); + let hb_add = packet.length as HistoryBufferLength; + let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL); + + ( + write_reqs, ZERO_READ_REQS, ZERO_ORDER, + SequenceExecutorState { + status: zero!(), + packet: state.packet, + packet_valid: state.packet_valid, + hyp_ptr: new_hyp_ptr, + real_ptr, + repeat_offsets: state.repeat_offsets, + repeat_req: state.repeat_req, + hb_len: new_hb_len, + seq_cnt: state.seq_cnt + }, + ) + }, + (Status::SEQUENCE_WRITE, _, _) => { + let status = if real_ptr == state.hyp_ptr { + Status::IDLE + } else if state.seq_cnt { + Status::SEQUENCE_READ + } else { + Status::SEQUENCE_WRITE + }; + ( + ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, + State { status, ..NO_VALID_PACKET_STATE }, + ) + }, + // Handling IDLE + _ => { + let status = Status::IDLE; + ( + ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, + State { status, ..NO_VALID_PACKET_STATE }, + ) + }, + }; + + let tok2_1 = send_if(tok1, wr_req_m0_s, (write_reqs[0]).mask != RAM_REQ_MASK_NONE, write_reqs[0]); + let tok2_2 = send_if(tok1, wr_req_m1_s, (write_reqs[1]).mask != RAM_REQ_MASK_NONE, write_reqs[1]); + let tok2_3 = send_if(tok1, wr_req_m2_s, (write_reqs[2]).mask != RAM_REQ_MASK_NONE, write_reqs[2]); + let tok2_4 = send_if(tok1, wr_req_m3_s, (write_reqs[3]).mask != RAM_REQ_MASK_NONE, write_reqs[3]); + let tok2_5 = send_if(tok1, wr_req_m4_s, (write_reqs[4]).mask != RAM_REQ_MASK_NONE, write_reqs[4]); + let tok2_6 = send_if(tok1, wr_req_m5_s, (write_reqs[5]).mask != RAM_REQ_MASK_NONE, write_reqs[5]); + let tok2_7 = send_if(tok1, wr_req_m6_s, (write_reqs[6]).mask != RAM_REQ_MASK_NONE, write_reqs[6]); + let tok2_8 = send_if(tok1, wr_req_m7_s, (write_reqs[7]).mask != RAM_REQ_MASK_NONE, write_reqs[7]); + + // Write to output ask for completion + let (do_write, wr_resp_handler_data) = create_ram_wr_data(write_reqs, new_state.hyp_ptr); + if do_write { + trace_fmt!("Sending request to RamWrRespHandler: {:#x}", wr_resp_handler_data); + } else { }; + let tok2_9 = send_if(tok1, ram_comp_input_s, do_write, wr_resp_handler_data); + + let output_data = decode_literal_packet(packet); + let do_write_output = do_write || (packet.last && packet.msg_type == SequenceExecutorMessageType::LITERAL); + if do_write_output { trace_fmt!("Sending output data: {:#x}", output_data); } else { }; + let tok2_10 = send_if(tok1, output_s, do_write_output, output_data); + + // Ask for response + let tok2_11 = send_if(tok1, rd_req_m0_s, (read_reqs[0]).mask != RAM_REQ_MASK_NONE, read_reqs[0]); + let tok2_12 = send_if(tok1, rd_req_m1_s, (read_reqs[1]).mask != RAM_REQ_MASK_NONE, read_reqs[1]); + let tok2_13 = send_if(tok1, rd_req_m2_s, (read_reqs[2]).mask != RAM_REQ_MASK_NONE, read_reqs[2]); + let tok2_14 = send_if(tok1, rd_req_m3_s, (read_reqs[3]).mask != RAM_REQ_MASK_NONE, read_reqs[3]); + let tok2_15 = send_if(tok1, rd_req_m4_s, (read_reqs[4]).mask != RAM_REQ_MASK_NONE, read_reqs[4]); + let tok2_16 = send_if(tok1, rd_req_m5_s, (read_reqs[5]).mask != RAM_REQ_MASK_NONE, read_reqs[5]); + let tok2_17 = send_if(tok1, rd_req_m6_s, (read_reqs[6]).mask != RAM_REQ_MASK_NONE, read_reqs[6]); + let tok2_18 = send_if(tok1, rd_req_m7_s, (read_reqs[7]).mask != RAM_REQ_MASK_NONE, read_reqs[7]); + + let (do_read, rd_resp_handler_data) = + create_ram_rd_data + (read_reqs, order, packet.last, new_state.packet_valid); + if do_read { + trace_fmt!("Sending request to RamRdRespHandler: {:#x}", rd_resp_handler_data); + } else { }; + let tok2_19 = send_if(tok1, ram_resp_input_s, do_read, rd_resp_handler_data); + + new_state + } +} + +pub const ZSTD_HISTORY_BUFFER_SIZE_KB: u32 = u32:64; +const ZSTD_RAM_SIZE = ram_size(ZSTD_HISTORY_BUFFER_SIZE_KB); +pub const ZSTD_RAM_ADDR_WIDTH = ram_addr_width(ZSTD_HISTORY_BUFFER_SIZE_KB); + +pub proc SequenceExecutorZstd { + + init { } + + config( + input_r: chan in, + output_s: chan out, + looped_channel_r: chan in, + looped_channel_s: chan out, + rd_req_m0_s: chan> out, + rd_req_m1_s: chan> out, + rd_req_m2_s: chan> out, + rd_req_m3_s: chan> out, + rd_req_m4_s: chan> out, + rd_req_m5_s: chan> out, + rd_req_m6_s: chan> out, + rd_req_m7_s: chan> out, + rd_resp_m0_r: chan> in, + rd_resp_m1_r: chan> in, + rd_resp_m2_r: chan> in, + rd_resp_m3_r: chan> in, + rd_resp_m4_r: chan> in, + rd_resp_m5_r: chan> in, + rd_resp_m6_r: chan> in, + rd_resp_m7_r: chan> in, + wr_req_m0_s: chan> out, + wr_req_m1_s: chan> out, + wr_req_m2_s: chan> out, + wr_req_m3_s: chan> out, + wr_req_m4_s: chan> out, + wr_req_m5_s: chan> out, + wr_req_m6_s: chan> out, + wr_req_m7_s: chan> out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + spawn SequenceExecutor ( + input_r, output_s, + looped_channel_r, looped_channel_s, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r + ); + } + + next (state: ()) { } +} + +const LITERAL_TEST_INPUT_DATA = SequenceExecutorPacket[8]:[ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:64, + content: CopyOrMatchContent:0xAA00BB11CC22DD33, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:64, + content: CopyOrMatchContent:0x447733220088CCFF, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:32, + content: CopyOrMatchContent:0x88AA0022, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:32, + content: CopyOrMatchContent:0xFFEEDD11, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:64, + content: CopyOrMatchContent:0x9DAF8B41C913EFDA, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:64, + content: CopyOrMatchContent:0x157D8C7EB8B97CA3, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:0, + content: CopyOrMatchContent:0x0, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:0, + content: CopyOrMatchContent:0x0, + last: true + }, +]; + +const LITERAL_TEST_MEMORY_CONTENT:(TestRamAddr, RamData)[3][RAM_NUM] = [ + [ + (TestRamAddr:127, RamData:0x33), + (TestRamAddr:0, RamData:0xFF), + (TestRamAddr:1, RamData:0x22) + ], + [ + (TestRamAddr:127, RamData:0xDD), + (TestRamAddr:0, RamData:0xCC), + (TestRamAddr:1, RamData:0x00) + ], + [ + (TestRamAddr:127, RamData:0x22), + (TestRamAddr:0, RamData:0x88), + (TestRamAddr:1, RamData:0xAA) + ], + [ + (TestRamAddr:127, RamData:0xCC), + (TestRamAddr:0, RamData:0x00), + (TestRamAddr:1, RamData:0x88) + ], + [ + (TestRamAddr:127, RamData:0x11), + (TestRamAddr:0, RamData:0x22), + (TestRamAddr:1, RamData:0x11) + ], + [ + (TestRamAddr:127, RamData:0xBB), + (TestRamAddr:0, RamData:0x33), + (TestRamAddr:1, RamData:0xDD) + ], + [ + (TestRamAddr:127, RamData:0x00), + (TestRamAddr:0, RamData:0x77), + (TestRamAddr:1, RamData:0xEE) + ], + [ + (TestRamAddr:127, RamData:0xAA), + (TestRamAddr:0, RamData:0x44), + (TestRamAddr:1, RamData:0xFF) + ], +]; + +#[test_proc] +proc SequenceExecutorLiteralsTest { + terminator: chan out; + + input_s: chan> out; + output_r: chan in; + + print_start_s: chan<()> out; + print_finish_r: chan<()> in; + + ram_rd_req_s: chan[RAM_NUM] out; + ram_rd_resp_r: chan[RAM_NUM] in; + ram_wr_req_s: chan[RAM_NUM] out; + ram_wr_resp_r: chan[RAM_NUM] in; + + config(terminator: chan out) { + let (input_s, input_r) = chan>("input"); + let (output_s, output_r) = chan("output"); + + let (looped_channel_s, looped_channel_r) = chan("looped_channels"); + + let (print_start_s, print_start_r) = chan<()>("print_start"); + let (print_finish_s, print_finish_r) = chan<()>("print_finish"); + + let (ram_rd_req_s, ram_rd_req_r) = chan[RAM_NUM]("ram_rd_req"); + let (ram_rd_resp_s, ram_rd_resp_r) = chan[RAM_NUM]("ram_rd_resp"); + let (ram_wr_req_s, ram_wr_req_r) = chan[RAM_NUM]("ram_wr_req"); + let (ram_wr_resp_s, ram_wr_resp_r) = chan[RAM_NUM]("ram_wr_resp"); + + let INIT_HB_PTR_ADDR = u32:127; + spawn SequenceExecutor< + TEST_HISTORY_BUFFER_SIZE_KB, + TEST_RAM_SIZE, + TEST_RAM_ADDR_WIDTH, + INIT_HB_PTR_ADDR, + > ( + input_r, output_s, + looped_channel_r, looped_channel_s, + ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], + ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], + ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], + ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], + ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], + ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], + ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], + ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] + ); + + spawn ram_printer::RamPrinter< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_NUM_PARTITIONS, + TEST_RAM_ADDR_WIDTH, RAM_NUM> + (print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r); + + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + + ( + terminator, + input_s, output_r, + print_start_s, print_finish_r, + ram_rd_req_s, ram_rd_resp_r, + ram_wr_req_s, ram_wr_resp_r + ) + } + + init { } + + next(state: ()) { + let tok = join(); + for (i, ()): (u32, ()) in range(u32:0, array_size(LITERAL_TEST_INPUT_DATA)) { + let tok = send(tok, input_s, LITERAL_TEST_INPUT_DATA[i]); + // Don't receive when there's an empty literals packet which is not last + if (LITERAL_TEST_INPUT_DATA[i].msg_type != SequenceExecutorMessageType::LITERAL || + LITERAL_TEST_INPUT_DATA[i].length != CopyOrMatchLength:0 || + LITERAL_TEST_INPUT_DATA[i].last) { + let (tok, recv_data) = recv(tok, output_r); + let expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); + assert_eq(expected, recv_data); + } else {} + }(()); + + for (i, ()): (u32, ()) in range(u32:0, RAM_NUM) { + for (j, ()): (u32, ()) in range(u32:0, array_size(LITERAL_TEST_MEMORY_CONTENT[0])) { + let addr = LITERAL_TEST_MEMORY_CONTENT[i][j].0; + let tok = send(tok, ram_rd_req_s[i], TestReadReq { addr, mask: RAM_REQ_MASK_ALL }); + let (tok, resp) = recv(tok, ram_rd_resp_r[i]); + let expected = LITERAL_TEST_MEMORY_CONTENT[i][j].1; + assert_eq(expected, resp.data); + }(()); + }(()); + + // Print RAM content + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + send(tok, terminator, true); + } +} + +const SEQUENCE_TEST_INPUT_SEQUENCES = SequenceExecutorPacket[11]: [ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:9, + content: CopyOrMatchContent:13, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:7, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:8, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:5, + content: CopyOrMatchContent:13, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:3, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:1, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:0, + content: CopyOrMatchContent:0, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:3, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:0, + content: CopyOrMatchContent:0, + last:false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:10, + content: CopyOrMatchContent:2, + last:true, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:3, + last:false + }, +]; + +const SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS:ZstdDecodedPacket[11] = [ + ZstdDecodedPacket { + data: BlockData:0x8C_7E_B8_B9_7C_A3_9D_AF, + length: BlockPacketLength:64, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0x7D, + length: BlockPacketLength:8, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB8, + length: BlockPacketLength:8, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB8, + length: BlockPacketLength:8, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB8_B9_7C_A3_9D, + length: BlockPacketLength:40, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB9_7C_A3, + length: BlockPacketLength:24, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB8, + length: BlockPacketLength:8, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0x7C, + length: BlockPacketLength:8, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0xB9_7C_A3_B8_B9_7C_A3_9D, + length: BlockPacketLength:64, + last: false + }, + ZstdDecodedPacket { + data: BlockData:0x7C_B8, + length: BlockPacketLength:16, + last: true + }, + ZstdDecodedPacket { + data: BlockData:0x9D, + length: BlockPacketLength:8, + last: false + } +]; + +#[test_proc] +proc SequenceExecutorSequenceTest { + terminator: chan out; + + input_s: chan out; + output_r: chan in; + + print_start_s: chan<()> out; + print_finish_r: chan<()> in; + + ram_rd_req_s: chan[RAM_NUM] out; + ram_rd_resp_r: chan[RAM_NUM] in; + ram_wr_req_s: chan[RAM_NUM] out; + ram_wr_resp_r: chan[RAM_NUM] in; + + config(terminator: chan out) { + let (input_s, input_r) = chan("input"); + let (output_s, output_r) = chan("output"); + + let (looped_channel_s, looped_channel_r) = chan("looped_channel"); + + let (print_start_s, print_start_r) = chan<()>("print_start"); + let (print_finish_s, print_finish_r) = chan<()>("print_finish"); + + let (ram_rd_req_s, ram_rd_req_r) = chan[RAM_NUM]("ram_rd_req"); + let (ram_rd_resp_s, ram_rd_resp_r) = chan[RAM_NUM]("ram_rd_resp"); + let (ram_wr_req_s, ram_wr_req_r) = chan[RAM_NUM]("ram_wr_req"); + let (ram_wr_resp_s, ram_wr_resp_r) = chan[RAM_NUM]("ram_wr_resp"); + + let INIT_HB_PTR_ADDR = u32:127; + spawn SequenceExecutor< + TEST_HISTORY_BUFFER_SIZE_KB, + TEST_RAM_SIZE, + TEST_RAM_ADDR_WIDTH, + INIT_HB_PTR_ADDR, + > ( + input_r, output_s, + looped_channel_r, looped_channel_s, + ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], + ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], + ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], + ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], + ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], + ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], + ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], + ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] + ); + + spawn ram_printer::RamPrinter< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_NUM_PARTITIONS, + TEST_RAM_ADDR_WIDTH, RAM_NUM> + (print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r); + + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + + ( + terminator, + input_s, output_r, + print_start_s, print_finish_r, + ram_rd_req_s, ram_rd_resp_r, ram_wr_req_s, ram_wr_resp_r + ) + } + + init { } + + next(state: ()) { + let tok = join(); + for (i, ()): (u32, ()) in range(u32:0, array_size(LITERAL_TEST_INPUT_DATA)) { + let tok = send(tok, input_s, LITERAL_TEST_INPUT_DATA[i]); + // Don't receive when there's an empty literal packet which is not last + if (LITERAL_TEST_INPUT_DATA[i].msg_type != SequenceExecutorMessageType::LITERAL || + LITERAL_TEST_INPUT_DATA[i].length != CopyOrMatchLength:0 || + LITERAL_TEST_INPUT_DATA[i].last) { + let (tok, recv_data) = recv(tok, output_r); + let expected = decode_literal_packet(LITERAL_TEST_INPUT_DATA[i]); + assert_eq(expected, recv_data); + } else {} + }(()); + + // Print RAM content + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[0]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[0], recv_data); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[1], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[1]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[2], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[2]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[3], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[3]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[4], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[4]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[5], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[5]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[6], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[6]); + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[7]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[7], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[8]); + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[9]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[8], recv_data); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[9], recv_data); + + let tok = send(tok, input_s, SEQUENCE_TEST_INPUT_SEQUENCES[10]); + let (tok, recv_data) = recv(tok, output_r); + assert_eq(SEQUENCE_TEST_EXPECTED_SEQUENCE_RESULTS[10], recv_data); + + // Print RAM content + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + send(tok, terminator, true); + } +} From 43a637db839377c1f1e580cda92fab9263bf6637 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jan 2024 16:48:27 +0100 Subject: [PATCH 22/49] modules/zstd: Add repacketizer Internal-tag: [#52954] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 76 +++++++++++ xls/modules/zstd/repacketizer.x | 215 ++++++++++++++++++++++++++++++++ 2 files changed, 291 insertions(+) create mode 100644 xls/modules/zstd/repacketizer.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index c9cceb43dc..57d07f435b 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -828,3 +828,79 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "repacketizer_dslx", + srcs = [ + "repacketizer.x", + ], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "repacketizer_dslx_test", + library = ":repacketizer_dslx", +) + +xls_dslx_verilog( + name = "repacketizer_verilog", + codegen_args = { + "module_name": "Repacketizer", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "Repacketizer", + library = ":repacketizer_dslx", + verilog_file = "repacketizer.v", + tags = ["manual"], +) + +xls_benchmark_ir( + name = "repacketizer_opt_ir_benchmark", + src = ":repacketizer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "repacketizer_verilog_lib", + srcs = [ + ":repacketizer.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "repacketizer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "Repacketizer", + deps = [ + ":repacketizer_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "repacketizer_benchmark_synth", + synth_target = ":repacketizer_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "repacketizer_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":repacketizer_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/repacketizer.x b/xls/modules/zstd/repacketizer.x new file mode 100644 index 0000000000..3123cd67d0 --- /dev/null +++ b/xls/modules/zstd/repacketizer.x @@ -0,0 +1,215 @@ +// 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. + +// Repacketizer +// +// Remove invalid bytes from input packets, +// form new packets with all bits valid if possible. + +import std; +import xls.modules.zstd.common as common; + +type ZstdDecodedPacket = common::ZstdDecodedPacket; +type BlockData = common::BlockData; +type BlockPacketLength = common::BlockPacketLength; + +const DATA_WIDTH = common::DATA_WIDTH; + +struct RepacketizerState { + repacked_data: BlockData, + valid_length: BlockPacketLength, + to_fill: BlockPacketLength, + send_last_leftover: bool +} + +const ZERO_ZSTD_DECODED_PACKET = zero!(); +const ZERO_REPACKETIZER_STATE = zero!(); +const INIT_REPACKETIZER_STATE = RepacketizerState {to_fill: DATA_WIDTH, ..ZERO_REPACKETIZER_STATE}; + +proc Repacketizer { + input_r: chan in; + output_s: chan out; + + init {(INIT_REPACKETIZER_STATE)} + + config ( + input_r: chan in, + output_s: chan out, + ) { + (input_r, output_s) + } + + next (state: RepacketizerState) { + let tok = join(); + // Don't receive if we process leftovers + let (tok, decoded_packet) = recv_if(tok, input_r, !state.send_last_leftover, ZERO_ZSTD_DECODED_PACKET); + + // Will be able to send repacketized packet in current next() evaluation + let send_now = state.to_fill <= decoded_packet.length || decoded_packet.last || state.send_last_leftover; + // Received last packet in frame which won't fit into currently processed repacketized packet. + // Set flag indicating that Repacketizer will send another packet to finish the frame in + // next evaluation. + let next_send_last_leftover = decoded_packet.last && state.to_fill < decoded_packet.length; + + let combined_length = state.valid_length + decoded_packet.length; + let leftover_length = (combined_length - DATA_WIDTH) as s32; + let next_valid_length = if leftover_length >= s32:0 {leftover_length as BlockPacketLength} else {combined_length}; + let next_to_fill = DATA_WIDTH - next_valid_length; + + let current_valid_length = if leftover_length >= s32:0 {DATA_WIDTH} else {combined_length}; + let bits_to_take_length = if leftover_length >= s32:0 {state.to_fill} else {decoded_packet.length}; + + // Append lest signifiant bits of received packet to most significant positions of repacked data buffer + let masked_data = ((BlockData:1 << bits_to_take_length) - BlockData:1) & decoded_packet.data; + let repacked_data = state.repacked_data | (masked_data << state.valid_length); + + // Prepare buffer state for the next evaluation - take leftover most significant bits of + // received packet + let leftover_mask = (BlockData:1 << (decoded_packet.length - bits_to_take_length)) - BlockData:1; + let leftover_masked_data = (decoded_packet.data >> bits_to_take_length) & leftover_mask; + let next_repacked_data = if (send_now) {leftover_masked_data} else {repacked_data}; + + let packet_to_send = ZstdDecodedPacket { + data: repacked_data, + length: current_valid_length, + last: state.send_last_leftover || (decoded_packet.last && !next_send_last_leftover), + }; + let tok = send_if(tok, output_s, send_now, packet_to_send); + + let next_state = if (state.send_last_leftover || (decoded_packet.last && !next_send_last_leftover)) { + INIT_REPACKETIZER_STATE + } else { + RepacketizerState { + repacked_data: next_repacked_data, + valid_length: next_valid_length, + to_fill: next_to_fill, + send_last_leftover: next_send_last_leftover, + } + }; + + trace_fmt!("Repacketizer: state: {:#x}", state); + if (!state.send_last_leftover) { + trace_fmt!("Repacketizer: Received packet: {:#x}", decoded_packet); + } else {}; + trace_fmt!("Repacketizer: send_now: {}", send_now); + trace_fmt!("Repacketizer: next_send_last_leftover: {}", next_send_last_leftover); + trace_fmt!("Repacketizer: combined_length: {}", combined_length); + trace_fmt!("Repacketizer: leftover_length: {}", leftover_length); + trace_fmt!("Repacketizer: next_valid_length: {}", next_valid_length); + trace_fmt!("Repacketizer: next_to_fill: {}", next_to_fill); + trace_fmt!("Repacketizer: current_valid_length: {}", current_valid_length); + trace_fmt!("Repacketizer: bits_to_take_length: {}", bits_to_take_length); + trace_fmt!("Repacketizer: masked_data: {:#x}", masked_data); + trace_fmt!("Repacketizer: repacked_data: {:#x}", repacked_data); + trace_fmt!("Repacketizer: leftover_mask: {:#x}", leftover_mask); + trace_fmt!("Repacketizer: leftover_masked_data: {:#x}", leftover_masked_data); + trace_fmt!("Repacketizer: next_repacked_data: {:#x}", next_repacked_data); + if (send_now) { + trace_fmt!("Repacketizer: Sent repacketized packet: {:#x}", packet_to_send); + } else {}; + trace_fmt!("Repacketizer: next_state: {:#x}", next_state); + + next_state + } +} + +#[test_proc] +proc RepacketizerTest { + terminator: chan out; + input_s: chan out; + output_r: chan in; + + init {} + + config (terminator: chan out) { + let (input_s, input_r) = chan("input"); + let (output_s, output_r) = chan("output"); + + spawn Repacketizer(input_r, output_s); + (terminator, input_s, output_r) + } + + next(state: ()) { + let tok = join(); + let DecodedInputs: ZstdDecodedPacket[24] = [ + // Full packet - no need for removing alignment zeros + ZstdDecodedPacket {data: BlockData:0xDEADBEEF12345678, length: BlockPacketLength:64, last:false}, + // Data in 4 packets - should be batched together into one full output packet + ZstdDecodedPacket {data: BlockData:0x78, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x56, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x1234, length: BlockPacketLength:16, last:false}, + ZstdDecodedPacket {data: BlockData:0xDEADBEEF, length: BlockPacketLength:32, last:false}, + // Small last packet - should be send out separatelly + ZstdDecodedPacket {data: BlockData:0x9A, length: BlockPacketLength:8, last:true}, + // One not-full packet and consecutive last packet packet in frame which completes previous packet and + // starts new one which should be marked as last + ZstdDecodedPacket {data: BlockData:0xADBEEF12345678, length: BlockPacketLength:56, last:false}, + ZstdDecodedPacket {data: BlockData:0x9ADE, length: BlockPacketLength:16, last:true}, + // 8 1-byte packets forming single output packet + ZstdDecodedPacket {data: BlockData:0xEF, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0xCD, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0xAB, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x89, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x67, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x45, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x23, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x01, length: BlockPacketLength:8, last:false}, + // 7 1-byte packets and 1 8-byte packet forming 1 full and 1 7-byte output packet + // marked as last + ZstdDecodedPacket {data: BlockData:0xEF, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0xCD, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0xAB, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x89, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x67, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x45, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0x23, length: BlockPacketLength:8, last:false}, + ZstdDecodedPacket {data: BlockData:0xFEDCBA9876543201, length: BlockPacketLength:64, last:true}, + ]; + + let DecodedOutputs: ZstdDecodedPacket[8] = [ + // Full packet - no need for removing alignment zeros + ZstdDecodedPacket {data: BlockData:0xDEADBEEF12345678, length: BlockPacketLength:64, last:false}, + // Data in 4 packets - should be batched together into one full output packet + ZstdDecodedPacket {data: BlockData:0xDEADBEEF12345678, length: BlockPacketLength:64, last:false}, + // Small last packet - should be send out separatelly + ZstdDecodedPacket {data: BlockData:0x9A, length: BlockPacketLength:8, last:true}, + // One not-full packet and consecutive last packet packet in frame which completes previous packet and + // starts new one which should be marked as last + ZstdDecodedPacket {data: BlockData:0xDEADBEEF12345678, length: BlockPacketLength:64, last:false}, + ZstdDecodedPacket {data: BlockData:0x9A, length: BlockPacketLength:8, last:true}, + // 8 1-byte packets forming single output packet + ZstdDecodedPacket {data: BlockData:0x0123456789ABCDEF, length: BlockPacketLength:64, last:false}, + // 7 1-byte packets and 1 8-byte packet forming 1 full and 1 7-byte output packet + // marked as last + ZstdDecodedPacket {data: BlockData:0x0123456789ABCDEF, length: BlockPacketLength:64, last:false}, + ZstdDecodedPacket {data: BlockData:0xFEDCBA98765432, length: BlockPacketLength:56, last:true}, + ]; + + let tok = for ((counter, decoded_input), tok): ((u32, ZstdDecodedPacket), token) in enumerate(DecodedInputs) { + let tok = send(tok, input_s, decoded_input); + trace_fmt!("Sent #{} decoded zero-filled packet, {:#x}", counter + u32:1, decoded_input); + (tok) + } (tok); + + let tok = for ((counter, expected_output), tok): ((u32, ZstdDecodedPacket), token) in enumerate(DecodedOutputs) { + let (tok, decoded_output) = recv(tok, output_r); + trace_fmt!("Received #{} decoded non-zero-filled packet, {:#x}", counter + u32:1, decoded_output); + trace_fmt!("Expected #{} decoded non-zero-filled packet, {:#x}", counter + u32:1, expected_output); + assert_eq(decoded_output, expected_output); + (tok) + } (tok); + + send(tok, terminator, true); + } +} From 96b6efd3f20887a3531431c314e519dfd6cea785 Mon Sep 17 00:00:00 2001 From: Robert Winkler Date: Tue, 24 Oct 2023 15:29:57 +0200 Subject: [PATCH 23/49] modules/zstd: Add ZSTD decoder This commit adds a ZSTD decoder module that parses ZSTD frames. The provided tests examine the model using C++ API, which is a prerequisite for detailed tests using zstd library. Internal-tag: [#50221] Co-authored-by: Maciej Dudek Co-authored-by: Pawel Czarnecki Signed-off-by: Maciej Dudek Signed-off-by: Pawel Czarnecki Signed-off-by: Robert Winkler --- xls/modules/zstd/BUILD | 141 ++++++++ xls/modules/zstd/common.x | 1 + xls/modules/zstd/data_generator.cc | 20 ++ xls/modules/zstd/data_generator.h | 8 + xls/modules/zstd/zstd_dec.x | 499 +++++++++++++++++++++++++++++ xls/modules/zstd/zstd_dec_test.cc | 267 +++++++++++++++ 6 files changed, 936 insertions(+) create mode 100644 xls/modules/zstd/zstd_dec.x create mode 100644 xls/modules/zstd/zstd_dec_test.cc diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 57d07f435b..1048201c0b 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -20,9 +20,11 @@ load("@rules_hdl//verilog:providers.bzl", "verilog_library") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", + "xls_dslx_ir", "xls_benchmark_verilog", "xls_dslx_library", "xls_dslx_test", + "xls_ir_opt_ir", "xls_dslx_verilog", ) @@ -904,3 +906,142 @@ place_and_route( target_die_utilization_percentage = "10", tags = ["manual"], ) + +xls_dslx_library( + name = "zstd_dec_dslx", + srcs = [ + "zstd_dec.x", + ], + deps = [ + ":block_dec_dslx", + ":block_header_dslx", + ":buffer_dslx", + ":common_dslx", + ":frame_header_dslx", + ":frame_header_test_dslx", + ":magic_dslx", + ":ram_printer_dslx", + ":repacketizer_dslx", + ":sequence_executor_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_verilog( + name = "zstd_dec_verilog", + codegen_args = { + "module_name": "ZstdDecoder", + "generator": "pipeline", + "delay_model": "asap7", + "ram_configurations": ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram{}".format(num), + rd_req = "zstd_dec__ram_rd_req_{}_s".format(num), + rd_resp = "zstd_dec__ram_rd_resp_{}_r".format(num), + wr_req = "zstd_dec__ram_wr_req_{}_s".format(num), + wr_resp = "zstd_dec__ram_wr_resp_{}_r".format(num), + ) + for num in range(7) + ]), + "pipeline_stages": "10", + "reset": "rst", + "reset_data_path": "true", + "reset_active_low": "false", + "reset_asynchronous": "true", + "flop_inputs": "false", + "flop_single_value_channels": "false", + "flop_outputs": "false", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "ZstdDecoder", + library = ":zstd_dec_dslx", + # TODO: 2024-01-15: Workaround for https://github.com/google/xls/issues/869 + # Force proc inlining for IR optimization + opt_ir_args = { + "inline_procs": "true", + }, + verilog_file = "zstd_dec.v", + tags = ["manual"], +) + +xls_dslx_ir( + name = "zstd_dec_test_ir", + dslx_top = "ZstdDecoderTest", + ir_file = "zstd_dec_test.ir", + library = ":zstd_dec_dslx", +) + +cc_test( + name = "zstd_dec_cc_test", + srcs = [ + "zstd_dec_test.cc", + ], + data = [ + ":zstd_dec_test.ir", + ], + shard_count = 50, + size = "large", + deps = [ + ":data_generator", + "//xls/common:xls_gunit_main", + "//xls/common/file:filesystem", + "//xls/common/file:get_runfile_path", + "//xls/common/status:matchers", + "//xls/interpreter:interpreter_proc_runtime", + "//xls/jit:jit_proc_runtime", + "//xls/ir:events", + "//xls/ir:ir_parser", + "//xls/ir:value", + "@com_github_facebook_zstd//:zstd", + "@com_google_googletest//:gtest", + ], +) + +xls_benchmark_ir( + name = "zstd_dec_opt_ir_benchmark", + src = ":zstd_dec_verilog.opt.ir", + benchmark_ir_args = { + #TODO: rewrite ram in opt_ir step to perform valid IR benchmark + "pipeline_stages": "1", + "delay_model": "asap7", + }, + tags = ["manual"], +) + +verilog_library( + name = "zstd_dec_verilog_lib", + srcs = [ + ":zstd_dec.v", + ], + tags = ["manual"], +) + +synthesize_rtl( + name = "zstd_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "ZstdDecoder", + deps = [ + ":zstd_dec_verilog_lib", + ], + tags = ["manual"], +) + +benchmark_synth( + name = "zstd_dec_benchmark_synth", + synth_target = ":zstd_dec_synth_asap7", + tags = ["manual"], +) + +place_and_route( + name = "zstd_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":zstd_dec_synth_asap7", + target_die_utilization_percentage = "10", + tags = ["manual"], +) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 582ac54b29..1fc676539f 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -18,6 +18,7 @@ pub const SYMBOL_WIDTH = u32:8; pub const BLOCK_SIZE_WIDTH = u32:21; pub const OFFSET_WIDTH = u32:22; pub const HISTORY_BUFFER_SIZE_KB = u32:64; +pub const BUFFER_WIDTH = u32:128; pub type BlockData = bits[DATA_WIDTH]; pub type BlockPacketLength = u32; diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc index 00cbb6b91b..243136fc1d 100644 --- a/xls/modules/zstd/data_generator.cc +++ b/xls/modules/zstd/data_generator.cc @@ -107,4 +107,24 @@ absl::StatusOr> GenerateFrameHeader(int seed, bool magic) { return raw_data; } +absl::StatusOr> GenerateFrame(int seed, BlockType btype) { + std::vector args; + args.push_back("-s" + std::to_string(seed)); + std::filesystem::path output_path = + std::filesystem::temp_directory_path() / + std::filesystem::path( + CreateNameForGeneratedFile(absl::MakeSpan(args), ".zstd", "fh")); + args.push_back("-p" + std::string(output_path)); + if (btype != BlockType::RANDOM) + args.push_back("--block-type=" + std::to_string(btype)); + if (btype == BlockType::RLE) args.push_back("--content-size"); + // Test payloads up to 16KB + args.push_back("--max-content-size-log=14"); + + XLS_ASSIGN_OR_RETURN(auto result, CallDecodecorpus(args)); + auto raw_data = ReadFileAsRawData(output_path); + std::remove(output_path.c_str()); + return raw_data; +} + } // namespace xls::zstd diff --git a/xls/modules/zstd/data_generator.h b/xls/modules/zstd/data_generator.h index 06462f4872..feb7c14b83 100644 --- a/xls/modules/zstd/data_generator.h +++ b/xls/modules/zstd/data_generator.h @@ -35,7 +35,15 @@ namespace xls::zstd { +enum BlockType { + RAW, + RLE, + COMPRESSED, + RANDOM, +}; + absl::StatusOr> GenerateFrameHeader(int seed, bool magic); +absl::StatusOr> GenerateFrame(int seed, BlockType btype); } // namespace xls::zstd diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x new file mode 100644 index 0000000000..f0148ca8c9 --- /dev/null +++ b/xls/modules/zstd/zstd_dec.x @@ -0,0 +1,499 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// This file contains work-in-progress ZSTD decoder implementation +// More information about ZSTD decoding can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878 + +import std; +import xls.modules.zstd.block_header; +import xls.modules.zstd.block_dec; +import xls.modules.zstd.sequence_executor; +import xls.modules.zstd.buffer as buff; +import xls.modules.zstd.common; +import xls.modules.zstd.frame_header; +import xls.modules.zstd.frame_header_test; +import xls.modules.zstd.magic; +import xls.modules.zstd.repacketizer; +import xls.examples.ram; + +type Buffer = buff::Buffer; +type BlockDataPacket = common::BlockDataPacket; +type BlockData = common::BlockData; +type BlockSize = common::BlockSize; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type ZstdDecodedPacket = common::ZstdDecodedPacket; + +// TODO: all of this porboably should be in common.x +const TEST_WINDOW_LOG_MAX_LIBZSTD = frame_header_test::TEST_WINDOW_LOG_MAX_LIBZSTD; + +const ZSTD_RAM_ADDR_WIDTH = sequence_executor::ZSTD_RAM_ADDR_WIDTH; +const RAM_DATA_WIDTH = sequence_executor::RAM_DATA_WIDTH; +const RAM_NUM_PARTITIONS = sequence_executor::RAM_NUM_PARTITIONS; +const ZSTD_HISTORY_BUFFER_SIZE_KB = sequence_executor::ZSTD_HISTORY_BUFFER_SIZE_KB; + +const BUFFER_WIDTH = common::BUFFER_WIDTH; +const DATA_WIDTH = common::DATA_WIDTH; +const ZERO_FRAME_HEADER = frame_header::ZERO_FRAME_HEADER; +const ZERO_BLOCK_HEADER = block_header::ZERO_BLOCK_HEADER; + +enum ZstdDecoderStatus : u8 { + DECODE_MAGIC_NUMBER = 0, + DECODE_FRAME_HEADER = 1, + DECODE_BLOCK_HEADER = 2, + FEED_BLOCK_DECODER = 3, + DECODE_CHECKSUM = 4, + ERROR = 255, +} + +struct ZstdDecoderState { + status: ZstdDecoderStatus, + buffer: Buffer, + frame_header: frame_header::FrameHeader, + block_size_bytes: BlockSize, + last: bool, + bytes_sent: BlockSize, +} + +const ZERO_DECODER_STATE = zero!(); + +fn decode_magic_number(state: ZstdDecoderState) -> (bool, BlockDataPacket, ZstdDecoderState) { + trace_fmt!("zstd_dec: decode_magic_number: DECODING NEW FRAME"); + trace_fmt!("zstd_dec: decode_magic_number: state: {:#x}", state); + trace_fmt!("zstd_dec: decode_magic_number: Decoding magic number"); + let magic_result = magic::parse_magic_number(state.buffer); + trace_fmt!("zstd_dec: decode_magic_number: magic_result: {:#x}", magic_result); + let new_state = match magic_result.status { + magic::MagicStatus::OK => ZstdDecoderState { + status: ZstdDecoderStatus::DECODE_FRAME_HEADER, + buffer: magic_result.buffer, + ..state + }, + magic::MagicStatus::CORRUPTED => ZstdDecoderState { + status: ZstdDecoderStatus::ERROR, + ..ZERO_DECODER_STATE + }, + magic::MagicStatus::NO_ENOUGH_DATA => state, + _ => state, + }; + trace_fmt!("zstd_dec: decode_magic_number: new_state: {:#x}", new_state); + + (false, zero!(), new_state) +} + +fn decode_frame_header(state: ZstdDecoderState) -> (bool, BlockDataPacket, ZstdDecoderState) { + trace_fmt!("zstd_dec: decode_frame_header: DECODING FRAME HEADER"); + trace_fmt!("zstd_dec: decode_frame_header: state: {:#x}", state); + let frame_header_result = frame_header::parse_frame_header(state.buffer); + trace_fmt!("zstd_dec: decode_frame_header: frame_header_result: {:#x}", frame_header_result); + let new_state = match frame_header_result.status { + frame_header::FrameHeaderStatus::OK => ZstdDecoderState { + status: ZstdDecoderStatus::DECODE_BLOCK_HEADER, + buffer: frame_header_result.buffer, + frame_header: frame_header_result.header, + ..state + }, + frame_header::FrameHeaderStatus::CORRUPTED => ZstdDecoderState { + status: ZstdDecoderStatus::ERROR, + ..ZERO_DECODER_STATE + }, + frame_header::FrameHeaderStatus::NO_ENOUGH_DATA => state, + frame_header::FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE => ZstdDecoderState { + status: ZstdDecoderStatus::ERROR, + ..ZERO_DECODER_STATE + }, + _ => state, + }; + trace_fmt!("zstd_dec: decode_frame_header: new_state: {:#x}", new_state); + + (false, zero!(), new_state) +} + +fn decode_block_header(state: ZstdDecoderState) -> (bool, BlockDataPacket, ZstdDecoderState) { + trace_fmt!("zstd_dec: decode_block_header: DECODING BLOCK HEADER"); + trace_fmt!("zstd_dec: decode_block_header: state: {:#x}", state); + let block_header_result = block_header::parse_block_header(state.buffer); + trace_fmt!("zstd_dec: decode_block_header: block_header_result: {:#x}", block_header_result); + let new_state = match block_header_result.status { + block_header::BlockHeaderStatus::OK => { + trace_fmt!("zstd_dec: BlockHeader: {:#x}", block_header_result.header); + match block_header_result.header.btype { + common::BlockType::RAW => ZstdDecoderState { + status: ZstdDecoderStatus::FEED_BLOCK_DECODER, + buffer: state.buffer, + block_size_bytes: block_header_result.header.size as BlockSize + BlockSize:3, + last: block_header_result.header.last, + bytes_sent: BlockSize:0, + ..state + }, + common::BlockType::RLE => ZstdDecoderState { + status: ZstdDecoderStatus::FEED_BLOCK_DECODER, + buffer: state.buffer, + block_size_bytes: BlockSize:4, + last: block_header_result.header.last, + bytes_sent: BlockSize:0, + ..state + }, + common::BlockType::COMPRESSED => ZstdDecoderState { + status: ZstdDecoderStatus::FEED_BLOCK_DECODER, + buffer: state.buffer, + block_size_bytes: block_header_result.header.size as BlockSize + BlockSize:3, + last: block_header_result.header.last, + bytes_sent: BlockSize:0, + ..state + }, + _ => { + fail!("impossible_case", state) + } + } + }, + block_header::BlockHeaderStatus::CORRUPTED => ZstdDecoderState { + status: ZstdDecoderStatus::ERROR, + ..ZERO_DECODER_STATE + }, + block_header::BlockHeaderStatus::NO_ENOUGH_DATA => state, + _ => state, + }; + trace_fmt!("zstd_dec: decode_block_header: new_state: {:#x}", new_state); + + (false, zero!(), new_state) +} + +fn feed_block_decoder(state: ZstdDecoderState) -> (bool, BlockDataPacket, ZstdDecoderState) { + trace_fmt!("zstd_dec: feed_block_decoder: FEEDING BLOCK DECODER"); + trace_fmt!("zstd_dec: feed_block_decoder: state: {:#x}", state); + let remaining_bytes_to_send = state.block_size_bytes - state.bytes_sent; + trace_fmt!("zstd_dec: feed_block_decoder: remaining_bytes_to_send: {}", remaining_bytes_to_send); + let buffer_length_bytes = state.buffer.length >> 3; + trace_fmt!("zstd_dec: feed_block_decoder: buffer_length_bytes: {}", buffer_length_bytes); + let data_width_bytes = (DATA_WIDTH >> 3) as BlockSize; + trace_fmt!("zstd_dec: feed_block_decoder: data_width_bytes: {}", data_width_bytes); + let remaining_bytes_to_send_now = std::umin(remaining_bytes_to_send, data_width_bytes); + trace_fmt!("zstd_dec: feed_block_decoder: remaining_bytes_to_send_now: {}", remaining_bytes_to_send_now); + if (buffer_length_bytes >= remaining_bytes_to_send_now as u32) { + let remaining_bits_to_send_now = (remaining_bytes_to_send_now as u32) << 3; + trace_fmt!("zstd_dec: feed_block_decoder: remaining_bits_to_send_now: {}", remaining_bits_to_send_now); + let last_packet = (remaining_bytes_to_send == remaining_bytes_to_send_now); + trace_fmt!("zstd_dec: feed_block_decoder: last_packet: {}", last_packet); + let (buffer_result, data_to_send) = buff::buffer_pop_checked(state.buffer, remaining_bits_to_send_now); + match buffer_result.status { + buff::BufferStatus::OK => { + let decoder_channel_data = BlockDataPacket { + last: last_packet, + last_block: state.last, + id: u32:0, + data: data_to_send[0: DATA_WIDTH as s32], + length: remaining_bits_to_send_now, + }; + let new_fsm_status = if (last_packet) { + if (state.last) { + if (state.frame_header.content_checksum_flag) { + ZstdDecoderStatus::DECODE_CHECKSUM + } else { + ZstdDecoderStatus::DECODE_MAGIC_NUMBER + } + } else { + ZstdDecoderStatus::DECODE_BLOCK_HEADER + } + } else { + ZstdDecoderStatus::FEED_BLOCK_DECODER + }; + trace_fmt!("zstd_dec: feed_block_decoder: packet to decode: {:#x}", decoder_channel_data); + let new_state = (true, decoder_channel_data, ZstdDecoderState { + bytes_sent: state.bytes_sent + remaining_bytes_to_send_now, + buffer: buffer_result.buffer, + status: new_fsm_status, + ..state + }); + trace_fmt!("zstd_dec: feed_block_decoder: new_state: {:#x}", new_state); + new_state + }, + _ => { + fail!("should_not_happen_1", (false, zero!(), state)) + } + } + } else { + trace_fmt!("zstd_dec: feed_block_decoder: Not enough data for intermediate FEED_BLOCK_DECODER block dump"); + (false, zero!(), state) + } +} + +fn decode_checksum(state: ZstdDecoderState) -> (bool, BlockDataPacket, ZstdDecoderState) { + trace_fmt!("zstd_dec: decode_checksum: DECODE CHECKSUM"); + trace_fmt!("zstd_dec: decode_checksum: state: {:#x}", state); + // Pop fixed checksum size of 4 bytes + let (buffer_result, _) = buff::buffer_pop_checked(state.buffer, u32:32); + + let new_state = ZstdDecoderState { + status: ZstdDecoderStatus::DECODE_MAGIC_NUMBER, + buffer: buffer_result.buffer, + ..state + }; + trace_fmt!("zstd_dec: decode_checksum: new_state: {:#x}", new_state); + + (false, zero!(), new_state) +} + +pub proc ZstdDecoder { + input_r: chan in; + block_dec_in_s: chan out; + output_s: chan out; + looped_channel_r: chan in; + looped_channel_s: chan out; + ram_rd_req_0_s: chan> out; + ram_rd_req_1_s: chan> out; + ram_rd_req_2_s: chan> out; + ram_rd_req_3_s: chan> out; + ram_rd_req_4_s: chan> out; + ram_rd_req_5_s: chan> out; + ram_rd_req_6_s: chan> out; + ram_rd_req_7_s: chan> out; + ram_rd_resp_0_r: chan> in; + ram_rd_resp_1_r: chan> in; + ram_rd_resp_2_r: chan> in; + ram_rd_resp_3_r: chan> in; + ram_rd_resp_4_r: chan> in; + ram_rd_resp_5_r: chan> in; + ram_rd_resp_6_r: chan> in; + ram_rd_resp_7_r: chan> in; + ram_wr_req_0_s: chan> out; + ram_wr_req_1_s: chan> out; + ram_wr_req_2_s: chan> out; + ram_wr_req_3_s: chan> out; + ram_wr_req_4_s: chan> out; + ram_wr_req_5_s: chan> out; + ram_wr_req_6_s: chan> out; + ram_wr_req_7_s: chan> out; + ram_wr_resp_0_r: chan in; + ram_wr_resp_1_r: chan in; + ram_wr_resp_2_r: chan in; + ram_wr_resp_3_r: chan in; + ram_wr_resp_4_r: chan in; + ram_wr_resp_5_r: chan in; + ram_wr_resp_6_r: chan in; + ram_wr_resp_7_r: chan in; + + init {(ZERO_DECODER_STATE)} + + config ( + input_r: chan in, + output_s: chan out, + looped_channel_r: chan in, + looped_channel_s: chan out, + ram_rd_req_0_s: chan> out, + ram_rd_req_1_s: chan> out, + ram_rd_req_2_s: chan> out, + ram_rd_req_3_s: chan> out, + ram_rd_req_4_s: chan> out, + ram_rd_req_5_s: chan> out, + ram_rd_req_6_s: chan> out, + ram_rd_req_7_s: chan> out, + ram_rd_resp_0_r: chan> in, + ram_rd_resp_1_r: chan> in, + ram_rd_resp_2_r: chan> in, + ram_rd_resp_3_r: chan> in, + ram_rd_resp_4_r: chan> in, + ram_rd_resp_5_r: chan> in, + ram_rd_resp_6_r: chan> in, + ram_rd_resp_7_r: chan> in, + ram_wr_req_0_s: chan> out, + ram_wr_req_1_s: chan> out, + ram_wr_req_2_s: chan> out, + ram_wr_req_3_s: chan> out, + ram_wr_req_4_s: chan> out, + ram_wr_req_5_s: chan> out, + ram_wr_req_6_s: chan> out, + ram_wr_req_7_s: chan> out, + ram_wr_resp_0_r: chan in, + ram_wr_resp_1_r: chan in, + ram_wr_resp_2_r: chan in, + ram_wr_resp_3_r: chan in, + ram_wr_resp_4_r: chan in, + ram_wr_resp_5_r: chan in, + ram_wr_resp_6_r: chan in, + ram_wr_resp_7_r: chan in, + ) { + let (block_dec_in_s, block_dec_in_r) = chan("block_dec_in"); + let (seq_exec_in_s, seq_exec_in_r) = chan("seq_exec_in"); + let (repacketizer_in_s, repacketizer_in_r) = chan("repacketizer_in"); + + spawn block_dec::BlockDecoder(block_dec_in_r, seq_exec_in_s); + + spawn sequence_executor::SequenceExecutor( + seq_exec_in_r, repacketizer_in_s, + looped_channel_r, looped_channel_s, + ram_rd_req_0_s, ram_rd_req_1_s, ram_rd_req_2_s, ram_rd_req_3_s, + ram_rd_req_4_s, ram_rd_req_5_s, ram_rd_req_6_s, ram_rd_req_7_s, + ram_rd_resp_0_r, ram_rd_resp_1_r, ram_rd_resp_2_r, ram_rd_resp_3_r, + ram_rd_resp_4_r, ram_rd_resp_5_r, ram_rd_resp_6_r, ram_rd_resp_7_r, + ram_wr_req_0_s, ram_wr_req_1_s, ram_wr_req_2_s, ram_wr_req_3_s, + ram_wr_req_4_s, ram_wr_req_5_s, ram_wr_req_6_s, ram_wr_req_7_s, + ram_wr_resp_0_r, ram_wr_resp_1_r, ram_wr_resp_2_r, ram_wr_resp_3_r, + ram_wr_resp_4_r, ram_wr_resp_5_r, ram_wr_resp_6_r, ram_wr_resp_7_r, + ); + + spawn repacketizer::Repacketizer(repacketizer_in_r, output_s); + + (input_r, block_dec_in_s, output_s, looped_channel_r, looped_channel_s, + ram_rd_req_0_s, ram_rd_req_1_s, ram_rd_req_2_s, ram_rd_req_3_s, + ram_rd_req_4_s, ram_rd_req_5_s, ram_rd_req_6_s, ram_rd_req_7_s, + ram_rd_resp_0_r, ram_rd_resp_1_r, ram_rd_resp_2_r, ram_rd_resp_3_r, + ram_rd_resp_4_r, ram_rd_resp_5_r, ram_rd_resp_6_r, ram_rd_resp_7_r, + ram_wr_req_0_s, ram_wr_req_1_s, ram_wr_req_2_s, ram_wr_req_3_s, + ram_wr_req_4_s, ram_wr_req_5_s, ram_wr_req_6_s, ram_wr_req_7_s, + ram_wr_resp_0_r, ram_wr_resp_1_r, ram_wr_resp_2_r, ram_wr_resp_3_r, + ram_wr_resp_4_r, ram_wr_resp_5_r, ram_wr_resp_6_r, ram_wr_resp_7_r) + } + + next (state: ZstdDecoderState) { + let tok = join(); + trace_fmt!("zstd_dec: next(): state: {:#x}", state); + let can_fit = buff::buffer_can_fit(state.buffer, BlockData:0); + trace_fmt!("zstd_dec: next(): can_fit: {}", can_fit); + let (tok, data, recv_valid) = recv_if_non_blocking(tok, input_r, can_fit, BlockData:0); + let state = if (can_fit && recv_valid) { + let buffer = buff::buffer_append(state.buffer, data); + trace_fmt!("zstd_dec: next(): received more data: {:#x}", data); + ZstdDecoderState {buffer, ..state} + } else { + state + }; + trace_fmt!("zstd_dec: next(): state after receive: {:#x}", state); + + let (do_send, data_to_send, state) = match state.status { + ZstdDecoderStatus::DECODE_MAGIC_NUMBER => + decode_magic_number(state), + ZstdDecoderStatus::DECODE_FRAME_HEADER => + decode_frame_header(state), + ZstdDecoderStatus::DECODE_BLOCK_HEADER => + decode_block_header(state), + ZstdDecoderStatus::FEED_BLOCK_DECODER => + feed_block_decoder(state), + ZstdDecoderStatus::DECODE_CHECKSUM => + decode_checksum(state), + _ => (false, zero!(), state) + }; + + trace_fmt!("zstd_dec: next(): do_send: {:#x}, data_to_send: {:#x}, state: {:#x}", do_send, data_to_send, state); + let tok = send_if(tok, block_dec_in_s, do_send, data_to_send); + + state + } +} + +const TEST_RAM_SIZE = sequence_executor::ram_size(ZSTD_HISTORY_BUFFER_SIZE_KB); +const RAM_WORD_PARTITION_SIZE = sequence_executor::RAM_WORD_PARTITION_SIZE; +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = sequence_executor::TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR; +const TEST_RAM_INITIALIZED = sequence_executor::TEST_RAM_INITIALIZED; +const TEST_RAM_ASSERT_VALID_READ:bool = {false}; + +pub proc ZstdDecoderTest { + input_r: chan in; + output_s: chan out; + + init {()} + + config ( + input_r: chan in, + output_s: chan out, + ) { + let (looped_channel_s, looped_channel_r) = chan("looped_channel"); + + let (ram_rd_req_0_s, ram_rd_req_0_r) = chan, u32:1>("ram_rd_req_0"); + let (ram_rd_req_1_s, ram_rd_req_1_r) = chan, u32:1>("ram_rd_req_1"); + let (ram_rd_req_2_s, ram_rd_req_2_r) = chan, u32:1>("ram_rd_req_2"); + let (ram_rd_req_3_s, ram_rd_req_3_r) = chan, u32:1>("ram_rd_req_3"); + let (ram_rd_req_4_s, ram_rd_req_4_r) = chan, u32:1>("ram_rd_req_4"); + let (ram_rd_req_5_s, ram_rd_req_5_r) = chan, u32:1>("ram_rd_req_5"); + let (ram_rd_req_6_s, ram_rd_req_6_r) = chan, u32:1>("ram_rd_req_6"); + let (ram_rd_req_7_s, ram_rd_req_7_r) = chan, u32:1>("ram_rd_req_7"); + + let (ram_rd_resp_0_s, ram_rd_resp_0_r) = chan, u32:1>("ram_rd_resp_0"); + let (ram_rd_resp_1_s, ram_rd_resp_1_r) = chan, u32:1>("ram_rd_resp_1"); + let (ram_rd_resp_2_s, ram_rd_resp_2_r) = chan, u32:1>("ram_rd_resp_2"); + let (ram_rd_resp_3_s, ram_rd_resp_3_r) = chan, u32:1>("ram_rd_resp_3"); + let (ram_rd_resp_4_s, ram_rd_resp_4_r) = chan, u32:1>("ram_rd_resp_4"); + let (ram_rd_resp_5_s, ram_rd_resp_5_r) = chan, u32:1>("ram_rd_resp_5"); + let (ram_rd_resp_6_s, ram_rd_resp_6_r) = chan, u32:1>("ram_rd_resp_6"); + let (ram_rd_resp_7_s, ram_rd_resp_7_r) = chan, u32:1>("ram_rd_resp_7"); + + let (ram_wr_req_0_s, ram_wr_req_0_r) = chan, u32:1>("ram_wr_req_0"); + let (ram_wr_req_1_s, ram_wr_req_1_r) = chan, u32:1>("ram_wr_req_1"); + let (ram_wr_req_2_s, ram_wr_req_2_r) = chan, u32:1>("ram_wr_req_2"); + let (ram_wr_req_3_s, ram_wr_req_3_r) = chan, u32:1>("ram_wr_req_3"); + let (ram_wr_req_4_s, ram_wr_req_4_r) = chan, u32:1>("ram_wr_req_4"); + let (ram_wr_req_5_s, ram_wr_req_5_r) = chan, u32:1>("ram_wr_req_5"); + let (ram_wr_req_6_s, ram_wr_req_6_r) = chan, u32:1>("ram_wr_req_6"); + let (ram_wr_req_7_s, ram_wr_req_7_r) = chan, u32:1>("ram_wr_req_7"); + + let (ram_wr_resp_0_s, ram_wr_resp_0_r) = chan("ram_wr_resp_0"); + let (ram_wr_resp_1_s, ram_wr_resp_1_r) = chan("ram_wr_resp_1"); + let (ram_wr_resp_2_s, ram_wr_resp_2_r) = chan("ram_wr_resp_2"); + let (ram_wr_resp_3_s, ram_wr_resp_3_r) = chan("ram_wr_resp_3"); + let (ram_wr_resp_4_s, ram_wr_resp_4_r) = chan("ram_wr_resp_4"); + let (ram_wr_resp_5_s, ram_wr_resp_5_r) = chan("ram_wr_resp_5"); + let (ram_wr_resp_6_s, ram_wr_resp_6_r) = chan("ram_wr_resp_6"); + let (ram_wr_resp_7_s, ram_wr_resp_7_r) = chan("ram_wr_resp_7"); + + spawn ZstdDecoder( + input_r, output_s, + looped_channel_r, looped_channel_s, + ram_rd_req_0_s, ram_rd_req_1_s, ram_rd_req_2_s, ram_rd_req_3_s, + ram_rd_req_4_s, ram_rd_req_5_s, ram_rd_req_6_s, ram_rd_req_7_s, + ram_rd_resp_0_r, ram_rd_resp_1_r, ram_rd_resp_2_r, ram_rd_resp_3_r, + ram_rd_resp_4_r, ram_rd_resp_5_r, ram_rd_resp_6_r, ram_rd_resp_7_r, + ram_wr_req_0_s, ram_wr_req_1_s, ram_wr_req_2_s, ram_wr_req_3_s, + ram_wr_req_4_s, ram_wr_req_5_s, ram_wr_req_6_s, ram_wr_req_7_s, + ram_wr_resp_0_r, ram_wr_resp_1_r, ram_wr_resp_2_r, ram_wr_resp_3_r, + ram_wr_resp_4_r, ram_wr_resp_5_r, ram_wr_resp_6_r, ram_wr_resp_7_r, + ); + + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_0_r, ram_rd_resp_0_s, ram_wr_req_0_r, ram_wr_resp_0_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_1_r, ram_rd_resp_1_s, ram_wr_req_1_r, ram_wr_resp_1_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_2_r, ram_rd_resp_2_s, ram_wr_req_2_r, ram_wr_resp_2_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_3_r, ram_rd_resp_3_s, ram_wr_req_3_r, ram_wr_resp_3_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_4_r, ram_rd_resp_4_s, ram_wr_req_4_r, ram_wr_resp_4_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_5_r, ram_rd_resp_5_s, ram_wr_req_5_r, ram_wr_resp_5_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_6_r, ram_rd_resp_6_s, ram_wr_req_6_r, ram_wr_resp_6_s); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED, TEST_RAM_ASSERT_VALID_READ> + (ram_rd_req_7_r, ram_rd_resp_7_s, ram_wr_req_7_r, ram_wr_resp_7_s); + + (input_r, output_s) + } + + next (state: ()) {} +} diff --git a/xls/modules/zstd/zstd_dec_test.cc b/xls/modules/zstd/zstd_dec_test.cc new file mode 100644 index 0000000000..a5399e2015 --- /dev/null +++ b/xls/modules/zstd/zstd_dec_test.cc @@ -0,0 +1,267 @@ +// Copyright 2020 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. +// + +#include +#include + +#include "gtest/gtest.h" +#include "xls/common/file/filesystem.h" +#include "xls/common/file/get_runfile_path.h" +#include "xls/common/status/matchers.h" +#include "xls/interpreter/interpreter_proc_runtime.h" +#include "xls/interpreter/serial_proc_runtime.h" +#include "xls/jit/jit_proc_runtime.h" +#include "xls/ir/events.h" +#include "xls/ir/ir_parser.h" +#include "xls/modules/zstd/data_generator.h" +#include "zstd.h" + +namespace xls { +namespace { + +class ZstdDecodedPacket { + public: + static std::optional MakeZstdDecodedPacket(Value packet) { + // Expect tuple + if (!packet.IsTuple()) return std::nullopt; + // Expect exactly 3 fields + if (packet.size() != 3) return std::nullopt; + for (int i = 0; i < 3; i++) { + // Expect fields to be Bits + if (!packet.element(i).IsBits()) return std::nullopt; + // All fields must fit in 64bits + if (!packet.element(i).bits().FitsInUint64()) return std::nullopt; + } + + std::vector data = packet.element(0).bits().ToBytes(); + absl::StatusOr len = packet.element(1).bits().ToUint64(); + if (!len.ok()) return std::nullopt; + uint64_t length = *len; + bool last = packet.element(2).bits().IsOne(); + + return ZstdDecodedPacket(data, length, last); + } + + std::vector GetData() { return data; } + + uint64_t GetLength() { return length; } + + bool IsLast() { return last; } + + const std::string PrintData() const { + std::stringstream s; + for (int j = 0; j < sizeof(uint64_t) && j < data.size(); j++) { + s << "0x" << std::setw(2) << std::setfill('0') << std::right << std::hex + << (unsigned int)data[j] << std::dec << ", "; + } + return s.str(); + } + + friend std::ostream& operator<<(std::ostream& os, + const ZstdDecodedPacket& packet) { + return os << "ZstdDecodedPacket { data: {" << packet.PrintData() + << "}, length: " << packet.length << " last: " << packet.last + << "}" << std::endl; + } + + private: + ZstdDecodedPacket(std::vector data, uint64_t length, bool last) + : data(data), length(length), last(last) {} + + std::vector data; + uint64_t length; + bool last; +}; + +class ZstdDecoderTest : public ::testing::Test { + public: + void SetUp() { + XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path ir_path, + xls::GetXlsRunfilePath(this->ir_file)); + XLS_ASSERT_OK_AND_ASSIGN(std::string ir_text, + xls::GetFileContents(ir_path)); + XLS_ASSERT_OK_AND_ASSIGN(this->package, xls::Parser::ParsePackage(ir_text)); + XLS_ASSERT_OK_AND_ASSIGN( + this->interpreter, + CreateJitSerialProcRuntime(this->package.get())); + + auto& queue_manager = this->interpreter->queue_manager(); + XLS_ASSERT_OK_AND_ASSIGN(this->recv_queue, queue_manager.GetQueueByName( + this->recv_channel_name)); + XLS_ASSERT_OK_AND_ASSIGN(this->send_queue, queue_manager.GetQueueByName( + this->send_channel_name)); + } + + void PrintTraceMessages(std::string pname) { + XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, this->package->GetProc(pname)); + const InterpreterEvents& events = + this->interpreter->GetInterpreterEvents(proc); + + if (!events.trace_msgs.empty()) { + for (const auto& tm : events.trace_msgs) { + std::cout << "[TRACE] " << tm.message << std::endl; + } + } + } + + const char* proc_name = "__zstd_dec__ZstdDecoderTest_0_next"; + const char* recv_channel_name = "zstd_dec__output_s"; + const char* send_channel_name = "zstd_dec__input_r"; + + const char* ir_file = "xls/modules/zstd/zstd_dec_test.ir"; + + std::unique_ptr package; + std::unique_ptr interpreter; + ChannelQueue *recv_queue, *send_queue; + + void PrintVector(absl::Span vec) { + for (int i = 0; i < vec.size(); i += 8) { + std::cout << "0x" << std::hex << std::setw(3) << std::left << i + << std::dec << ": "; + for (int j = 0; j < sizeof(uint64_t) && (i + j) < vec.size(); j++) { + std::cout << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)vec[i + j] + << std::dec << " "; + } + std::cout << std::endl; + } + } + + void DecompressWithLibZSTD(std::vector encoded_frame, std::vector &decoded_frame) { + size_t buff_out_size = ZSTD_DStreamOutSize(); + void* const buff_out = new uint8_t[buff_out_size]; + + ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + ASSERT_FALSE(dctx == NULL); + + void* const frame = static_cast(encoded_frame.data()); + size_t const frame_size = encoded_frame.size(); + // Put the whole frame in the buffer + ZSTD_inBuffer input_buffer = {frame, frame_size, 0}; + + while (input_buffer.pos < input_buffer.size) { + ZSTD_outBuffer output_buffer = {buff_out, buff_out_size, 0}; + size_t decomp_result = ZSTD_decompressStream(dctx, &output_buffer, &input_buffer); + bool decomp_success = ZSTD_isError(decomp_result); + ASSERT_FALSE(decomp_success); + + // Append output buffer contents to output vector + decoded_frame.insert(decoded_frame.end(), (uint8_t*)output_buffer.dst, ((uint8_t*)output_buffer.dst + output_buffer.pos)); + + ASSERT_TRUE(decomp_result == 0 && output_buffer.pos < output_buffer.size); + } + + ZSTD_freeDCtx(dctx); + delete[] buff_out; + } + + void ParseAndCompareWithZstd(std::vector frame) { + std::vector lib_decomp; + DecompressWithLibZSTD(frame, lib_decomp); + size_t lib_decomp_size = lib_decomp.size(); + std::cout << "lib_decomp_size: " << lib_decomp_size << std::endl; + + std::vector sim_decomp; + size_t sim_decomp_size_words = + (lib_decomp_size + sizeof(uint64_t) - 1) / sizeof(uint64_t); + size_t sim_decomp_size_bytes = + (lib_decomp_size + sizeof(uint64_t) - 1) * sizeof(uint64_t); + sim_decomp.reserve(sim_decomp_size_bytes); + + // Send compressed frame to decoder simulation + for (int i = 0; i < frame.size(); i += 8) { + auto span = absl::MakeSpan(frame.data() + i, 8); + auto value = Value(Bits::FromBytes(span, 64)); + XLS_EXPECT_OK(this->send_queue->Write(value)); + XLS_EXPECT_OK(this->interpreter->Tick()); + } + PrintTraceMessages("__zstd_dec__ZstdDecoderTest_0_next"); + + // Tick decoder simulation until we get expected amount of output data + // batches on output channel queue + std::optional ticks_timeout = std::nullopt; + absl::flat_hash_map output_counts = { + {this->recv_queue->channel(), sim_decomp_size_words}}; + XLS_EXPECT_OK( + this->interpreter->TickUntilOutput(output_counts, ticks_timeout)); + + // Read decompressed data from output channel queue + for (int i = 0; i < sim_decomp_size_words; i++) { + auto read_value = this->recv_queue->Read(); + EXPECT_EQ(read_value.has_value(), true); + auto packet = + ZstdDecodedPacket::MakeZstdDecodedPacket(read_value.value()); + EXPECT_EQ(packet.has_value(), true); + auto word_vec = packet->GetData(); + auto valid_length = packet->GetLength() / CHAR_BIT; + std::copy(begin(word_vec), begin(word_vec) + valid_length, + back_inserter(sim_decomp)); + } + + EXPECT_EQ(lib_decomp_size, sim_decomp.size()); + for (int i = 0; i < lib_decomp_size; i++) { + EXPECT_EQ(lib_decomp[i], sim_decomp[i]); + } + } +}; + +/* TESTS */ + +TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.4.7"); } + +TEST_F(ZstdDecoderTest, ParseFrameWithRawBlocks) { + int seed = 3; // Arbitrary seed value for small ZSTD frame + auto frame = zstd::GenerateFrame(seed, zstd::BlockType::RAW); + EXPECT_TRUE(frame.ok()); + this->ParseAndCompareWithZstd(frame.value()); +} + +TEST_F(ZstdDecoderTest, ParseFrameWithRleBlocks) { + int seed = 3; // Arbitrary seed value for small ZSTD frame + auto frame = zstd::GenerateFrame(seed, zstd::BlockType::RLE); + EXPECT_TRUE(frame.ok()); + this->ParseAndCompareWithZstd(frame.value()); +} + +class ZstdDecoderSeededTest : public ZstdDecoderTest, + public ::testing::WithParamInterface { + public: + static const uint32_t seed_generator_start = 0; + static const uint32_t random_frames_count = 100; +}; + +// Test `random_frames_count` instances of randomly generated valid +// frames, generated with `decodecorpus` tool. + +TEST_P(ZstdDecoderSeededTest, ParseMultipleFramesWithRawBlocks) { + auto seed = GetParam(); + auto frame = zstd::GenerateFrame(seed, zstd::BlockType::RAW); + EXPECT_TRUE(frame.ok()); + this->ParseAndCompareWithZstd(frame.value()); +} + +TEST_P(ZstdDecoderSeededTest, ParseMultipleFramesWithRleBlocks) { + auto seed = GetParam(); + auto frame = zstd::GenerateFrame(seed, zstd::BlockType::RLE); + EXPECT_TRUE(frame.ok()); + this->ParseAndCompareWithZstd(frame.value()); +} + +INSTANTIATE_TEST_SUITE_P( + ZstdDecoderSeededTest, ZstdDecoderSeededTest, + ::testing::Range(ZstdDecoderSeededTest::seed_generator_start, + ZstdDecoderSeededTest::seed_generator_start + ZstdDecoderSeededTest::random_frames_count)); + +} // namespace +} // namespace xls From 4c3b07c2ceef5efee5e115b29c78680dc3065ce1 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 29 Feb 2024 16:38:42 +0100 Subject: [PATCH 24/49] modules/zstd: Add ZSTD Decoder documentation Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/README.md | 352 ++++++++++++++++++ .../ZSTD_compressed_block_Huffman_decoder.png | Bin 0 -> 19782 bytes .../img/ZSTD_compressed_block_decoder.png | Bin 0 -> 21331 bytes ...ZSTD_compressed_block_literals_decoder.png | Bin 0 -> 24760 bytes ...ZSTD_compressed_block_sequence_decoder.png | Bin 0 -> 23882 bytes xls/modules/zstd/img/ZSTD_decoder.png | Bin 0 -> 28375 bytes 6 files changed, 352 insertions(+) create mode 100644 xls/modules/zstd/README.md create mode 100644 xls/modules/zstd/img/ZSTD_compressed_block_Huffman_decoder.png create mode 100644 xls/modules/zstd/img/ZSTD_compressed_block_decoder.png create mode 100644 xls/modules/zstd/img/ZSTD_compressed_block_literals_decoder.png create mode 100644 xls/modules/zstd/img/ZSTD_compressed_block_sequence_decoder.png create mode 100644 xls/modules/zstd/img/ZSTD_decoder.png diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md new file mode 100644 index 0000000000..b0b9daee62 --- /dev/null +++ b/xls/modules/zstd/README.md @@ -0,0 +1,352 @@ +# ZSTD decoder + +The ZSTD decoder decompresses the correctly formed ZSTD frames and blocks. +It implements the [RFC 8878](https://www.rfc-editor.org/rfc/rfc8878.html) decompression algorithm. +Overview of the decoder architecture is presented on the diagram below. +The decoder comprises: +* frame decoder, +* block dispatcher, +* 3 types of processing units: RAW, RLE, and compressed, +* command aggregator, +* history buffer, +* repacketizer. + +Incoming ZSTD frames are processed in the following order: +1. magic number is detected, +2. frame header is parsed, +3. ZSTD data blocks are being redirected to correct processing unit based on the block header, +4. processing unit results are aggregated in correct order into a stream +and routed to the history buffer, +5. data block outputs are assembled based on the history buffer contents and update history, +6. decoded data is processed by repacketizer in order to prepare the final output of the decoder, +7. (optional) calculated checksum is compared against frame checksum. + +![](img/ZSTD_decoder.png) + +## ZSTD decoder architecture + +### Top level Proc +This state machine is responsible for receiving encoded ZSTD frames, buffering the input and passing it to decoder's internal components based on the state of the proc. +The states defined for the processing of ZSTD frame are as follows: +* DECODE_MAGIC_NUMBER +* DECODE_FRAME_HEADER +* DECODE_BLOCK_HEADER +* FEED_BLOCK_DECODER +* DECODE_CHECKSUM + +After going through initial stages of decoding magic number and frame header, decoder starts the block division process. +It decodes block headers to calculate how many bytes must be sent to the block dispatcher and when the current frame's last data block is being processed. +Knowing that, it starts feeding the block decoder with data required for decoding current block. +After transmitting all data required for current block, it loops around to the block header decoding state and when next block header is not found it decodes checksum when it was requested in frame header or finishes ZSTD frame decoding and loops around to magic number decoding. + +### ZSTD frame header decoder +This part of the design starts with detecting the ZSTD magic number. +Then it parses and decodes the frame header's content and checks the header's correctness. +If the frame header has the checksum option enabled, this will enable `DECODE_CHECKSUM` stage at the end of the frame decoding where the frame's checksum will be computed and compared with the checksum embedded at the end of the frame stream. + +### Block dispatcher (demux) +At this stage, block headers are parsed and removed from the block data stream. +Based on parse values, it directs the block data stream to either RAW, RLE or compressed block sections. +For this task it uses an 8 byte native interface: a 64-bit data bus and a 64-bit length field that contains the number of correct bits on the data bus. +It also attaches a unique block ID value to each processed data block. +The IDs are sequential starting from 0 and are reset only after receiving and processing the current frame's last data block. + +### RAW +This proc passes the received data directly to its output channel. +It preserves the block ID and attaches a tag, stating that the data contains literals and should be placed in the history buffer unchanged, to each data output. + +### RLE decoder +This proc receives a tuple (s, N), where s is an 8 bit symbol and N is an accompanying `symbol_count`. +The proc produces `N*s` repeats of the given symbol. +This step preserves the block ID and attaches the literals tag to all its outputs. + +### Compressed block decoder +This part of the design is responsible for decoding the compressed data blocks. +It ingests the bytes stream, internally translates and interprets incoming data. +Only this part of the design creates data chunks tagged both with `literals` and/or `copy`. +This step preserves the block ID. +More in depth description can be found in [Compressed block decoder architecture](#compressed-block-decoder-architecture) paragraph of this doc. + +### Commands aggregator (mux) +This stage takes the output from either RAW, RLE or Command constructor and sends it to the History buffer and command execution stage. +This stage orders streams based on the ID value assigned by the block dispatcher. +It is expected that single base decoders (RAW, RLE, compressed block decoder) will be continuously transmitting a single ID to the point of sending the `last` signal which marks the last packet of currently decoded block. +That ID can change only when mux receives the `last` signal or `last` and `last_block` signals. + +It works as a priority mux that waits for a stream with the expected ID. +It continues to read that stream until the `last` signal is set, then it switches to the next stream ID. + +The command aggregator starts by waiting for `ID = 0`, after receiving the `last` signal it expects `ID = 1` and so on. +Only when both `last` and `last_block` are set the command aggregator will wait for `ID = 0`. + +### History buffer and command execution +This stage receives data which is tagged either `literals` or `copy`. +This stage will show the following behavior, depending on the tag: +* `literals` + * Packet contents placed as newest in the history buffer, + * Packet contents copied to the decoder's output, +* `copy` + * Wait for all previous writes to be completed, + * Copy `copy_length` literals starting `offset _length` from the newest in history buffer to the decoder's output, + * Copy `copy_length` literals starting `offset _length` from the newest in history buffer to the history buffer as the newest. + +### Compressed block decoder architecture +This part of the design is responsible for processing the compressed blocks up to the `literals`/`copy` command sequence. +This sequence is then processed by the history buffer to generate expected data output. +Overview of the architecture is provided on the diagram below. +The architecture is split into 2 paths: literals path and sequence path. +Architecture is split into 3 paths: literals path, FSE encoded Huffman trees and sequence path. +Literals path uses Hufman trees to decode some types of compressed blocks: Compressed and Treeless blocks. + +![](img/ZSTD_compressed_block_decoder.png) + +#### Compressed block dispatcher +This proc parses literals section headers to calculate block compression format, Huffmman tree size (if applicable based on compression format), compressed and regenerated sizes for literals. +If compressed block format is `Compressed_Literals_Block`, dispatcher reads Huffman tree header byte from Huffman bitstream, and directs expected number of bytes to the Huffman tree decoder. +Following this step, the proc sends an appropriate number of bytes to the literals decoder dispatcher. + +After sending literals to literals decompression, it redirects the remaining bytes to the sequence parsing stages. + +#### Command Constructor +This stage takes literals length, offset length and copy length. +When `literals length` is greater than 0, it will send a request to the literals buffer to obtain `literals length` literals and then send them to the history buffer. +Then based on the offset and copy length it either creates a match command using the provided offset and match lengths, or uses repeated offset and updates the repeated offset memory. +Formed commands are sent to the Commands aggregator (mux). + +### Literals path architecture + +![](img/ZSTD_compressed_block_literals_decoder.png) + +#### Literals decoder dispatcher +This proc parses and consumes the literals section header. +Based on the received values it passes the remaining bytes to RAW/RLE/Huffman tree/Huffman code decoders. +It also controls the 4 stream operation mode [4-stream mode in RFC](https://www.rfc-editor.org/rfc/rfc8878.html#name-jump_table). + +All packets sent to the Huffman bitstream buffer will be tagged either `in_progress` or `finished`. +If the compressed literals use the 4 streams encoding, the dispatcher will send the `finished` tag 4 times, each time a fully compressed stream is sent to the bitstream buffer. + +#### RAW Literals +This stage simply passes the incoming bytes as literals to the literals buffer. + +#### RLE Literals +This stage works similarly to the [RLE stage](#rle-decoder) for RLE data blocks. + +#### Huffman bitstream buffer +This stage takes data from the literals decoder dispatcher and stores it in the buffer memory. +Once the data with the `finished` tag set is received, this stage sends a tuple containing (start, end) positions for the current bitstream to the Huffman codes decoder. +This stage receives a response from the Huffman codes decoder when decoding is done and all bits got processed. +Upon receiving this message, the buffer will reclaim free space. + +#### Huffman codes decoder +This stage receives bitstream pointers from the Huffman bitstream buffer and Huffman tree configuration from the Huffman tree builder. +It accesses the bitstream buffers memory to retrieve bitstream data in reversed byte order and runs it through an array of comparators to decode Huffman code to correct literals values. + +#### Literals buffer +This stage receives data either from RAW, RLE or Huffman decoder and stores it. +Upon receiving the literals copy command from the Command Constructor for `N` number of bytes, it provides a reply with `N` literals. + +### FSE Huffman decoder architecture + +![](img/ZSTD_compressed_block_Huffman_decoder.png) + +#### Huffman tree decoder dispatcher +This stage parses and consumes the Huffman tree description header. +Based on the value of the Huffman descriptor header, it passes the tree description to the FSE decoder or to direct weight extraction. + +#### FSE weight decoder +This stage performs multiple functions. +1. It decodes and builds the FSE distribution table. +2. It stores all remaining bitstream data. +3. After receiving the last byte, it translates the bitstream to Huffman weights using 2 interleaved FSE streams. + +#### Direct weight decoder +This stage takes the incoming bytes and translates them to the stream of Huffman tree weights. +The first byte of the transfer defines the number of symbols to be decoded. + +#### Weight aggregator +This stage receives tree weights either from the FSE decoder or the direct decoder and transfers them to Huffman tree builder. +This stage also resolves the number of bits of the final weight and the max number of bits required in the tree representation. +This stage will emit the weights and number of symbols of the same weight before the current symbol for all possible byte values. + +#### Huffman tree builder +This stage takes `max_number_of_bits` (maximal length of Huffman code) as the first value, then the number of symbols with lower weight for each possible weight (11 bytes), followed by a tuple (number of preceding symbols with the same weight, symbol's_weight). +It's expected to receive weights for all possible byte values in the correct order. +Based on this information, this stage will configure the Huffman codes decoder. + +### Sequence path architecture + +![](img/ZSTD_compressed_block_sequence_decoder.png) + +#### Sequence Header parser and dispatcher +This stage parses and consumes `Sequences_Section_Header`. +Based on the parsed data, it redirects FSE description to the FSE table decoder and triggers Literals FSE, Offset FSE or Match FSE decoder to reconfigure its values based on the FSE table decoder. +After parsing the FSE tables, this stage buffers bitstream and starts sending bytes, starting from the last one received as per ZSTD format. +Bytes are sent to all decoders at the same time. +This stage monitors and triggers sequence decoding phases starting from initialization, followed by decode and state advance. +FSE decoders send each other the number of bits they read. + +#### Literals FSE decoder +This stage reconfigures its FSE table when triggered from [sequence header parse and dispatcher](#sequence-header-parser-and-dispatcher). +It initializes its state as the first FSE decoder. +In the decode phase, this stage is the last one to decode extra raw bits from the bitstream, and the number of ingested bits is transmitted to all other decoders. +This stage is the first stage to get a new FSE state from the bitstream, and it transmits the number of bits it used. + +#### Offset FSE decoder +This stage reconfigures its FSE table when triggered from [sequence header parse and dispatcher](#sequence-header-parser-and-dispatcher). +It initializes its state as the second FSE decoder. +In the decode phase, this stage is the first one to decode extra raw bits from bitstream, and the number of ingested bits is transmitted to all other decoders. +This stage is the last decoder to update its FSE state after the decode phase, and it transmits the number of used bits to other decoders. + +#### Match FSE decoder +This stage reconfigures its FSE table when triggered from [sequence header parse and dispatcher](#sequence-header-parser-and-dispatcher). +It initializes its state as the last FSE decoder. +In the decode phase, this stage is the second one to decode extra raw bits from the bitstream, and the number of ingested bits is transmitted to all other decoders. +This stage is the second stage to update its state after the decode phase, and the number of used bits is sent to all other decoders. + +### Repacketizer +This proc is used at the end of the processing flow in the ZSTD decoder. +It gathers the output of `SequenceExecutor` proc and processes it to form final output packets of the ZSTD decoder. +Input packets coming from the `SequenceExecutor` consist of: + +* data - bit vector of constant length +* length - field describing how many bits in bit vector are valid +* last - flag which marks the last packet in currently decoded ZSTD frame. + +It is not guaranteed that all bits in data bit vectors in packets received from `SequenceExecutor` are valid as those can include padding bits which were added in previous decoding steps and now have to be removed. +Repacketizer buffers input packets, removes the padding bits and forms new packets with all bits of the bit vector valid, meaning that all bits are decoded data. +Newly formed packets are then sent out to the output of the whole ZSTD decoder. + +## Testing methodology + +Testing of the `ZSTD decoder` is carried out on two levels: + +* Decoder components +* Integrated decoder + +Each component of the decoder is tested individually in DSLX tests. +Testing on the DSLX level allows the creation of small test cases that test for both positive and negative outcomes of a given part of the design. +When need be, those test cases can be also modified by the user to better understand how the component operates. + +Tests of the integrated ZSTD decoder are written in C++. +The objective of those is to verify the functionality of the decoder as a whole. +Testing setup for the ZSTD decoder is based on comparing the simulated decoding results against the decoding of the reference library. +Currently, due to the restrictions from the ZSTD frame generator, it is possible to test only the positive cases (decoding valid ZSTD frames). + +### Failure points + +#### User-facing decoder errors + +The design will fail the tests under the following conditions: + +* Straightforward failures: + * Top Level State Machine transitions to `ERROR` state + * Simulation encounters `assert!()` or `fail!()` statements + * The decoding result from the simulation has a different size than the results from the reference library + * The decoding result from the simulation has different contents than the results from the reference library +* Caveats: + * Timeout occurred while waiting for a valid `Magic Number` to start the decoding process + * Other timeouts occurring while waiting on channel operations (To be fixed) + +Currently, all mentioned conditions lead to an eventual test failure. +Most of those cases are handled properly while some are yet to be reworked to finish faster or to provide more information about the error. +For example, in case of transitioning to the `ERROR` state, the test will timeout on channel operations waiting to read from the decoder output. +In case of waiting for a valid `Magic Number`, the decoder will transition to an `ERROR` state without registering the correct `Magic Number` on the input channel which will lead to a similar timeout. + +Those cases should be handled in a way that allows for early failure of the test. +It can be done through a Proc parameter enabled for tests that change the behavior of the logic, e.g. launching `assert!()` when the decoder enters the `ERROR` state. +Another idea is to use a special output channel for signaling internal states and errors to monitor the decoder for the errors encountered during decoding. +For example, in an invalid `Magic Number`, the test case should expect a certain type of error reported on this channel at the very beginning of the simulation. + +#### Failures in ZSTD Decoder components + +It is important to note that some of the errors (e.g. errors in magic number or frame header decoding) are easy to trigger in the integration test cases by manual modification of the generated ZSTD frames. +However, the majority of the errors require modification of the deeper parts of the raw ZSTD frame which is significantly harder. +Because of that, it is better to rely on DSLX tests for the individual components where inputs for the test cases are smaller, easier to understand and modify when needed. + +The components of the ZSTD decoder can fail on `assert!()` and `fail!()` statements or propagate specific error states to the Top Level Proc and cause it to transition to the `ERROR` state. +The following enumeration will describe how to trigger each possible ZSTD Decoder error. + +The `ERROR` state can be encountered under the following conditions when running Top Level Proc C++ tests but also in DSLX tests for the specific components: +* Corrupted data on the `Magic Number` decoding stage + * Provide data for the decoding with the first 4 bytes not being the valid `Magic Number` (0xFD2FB528) +* Corrupted data during frame header decoding + * Set the `Reserved bit` in the frame header descriptor +* Unsupported Window Size during frame header decoding + * Set `Window Size` in frame header to value greater than `max window size` calculated from current `WINDOW_LOG_MAX` (by default in Top Level Proc tests `Window Size` must be greater than `0x78000000` to trigger the error) +* Corrupted data during Block Header decoding + * Set the `Block Type` of any block in the ZSTD frame to `RESERVED` + +The `assert!()` or `fail!()` will occur in: +* Buffer + * Add data to the buffer with `buffer_append()` when it is already full or unable to fit the whole length of the data + * Fetch data from the buffer with `buffer_pop()` when it is empty or have not enough data +* DecoderDemux + * Receive more than one `raw` or `compressed` block in a single `BlockDataPacket` +* RawBlockDecoder + * Receive `BlockDataPacket` with `ID` different than the previous packet which did not have the `last` flag set +* DecoderMux + * At the beginning of the simulation or after receiving `ExtendedBlockDataPacket` with `last` and `last_block` (decoding new ZSTD frame) set receive on channels `raw_r`, `rle_r` and `cmp_r` `ExtendedBlockDataPackets` without any of those having `ID==0` + * Receive `ExtendedBlockDataPacket` with a smaller `ID` than any of the previously processed packets during the current ZSTD frame decoding +* SequenceExecutor + * Receive `SequenceExecutorPacket` with `msg_type==SEQUENCE` and `content` field with value: `0` + +There are also several `impossible cases` covered by `assert!()` and `fail!()`: + +* Frame header decoder + * `Window Descriptor` does not exist after checking that it is available in the frame header + * `Frame Content Size` does not exist after checking that it is available in the frame header + * `Dictionary ID Flag` has an illegal value + * `Frame Content Size Flag` has an illegal value +* DecoderDemux + * Data packet has a different `Block Type` than `RAW`, `RLE` or `COMPRESSED` +* SequenceExecutor + * Proc transitions to `SEQUENCE_READ` state after receiving `SequenceExecutorPacket` with `msg_type` different than `SEQUENCE` or the message was invalid +* Top Level Proc + * Block header type is different than `RAW`, `RLE`, `COMPRESSED` + * There is not enough data to feed the `BlockDecoder`, even though the previous check indicated a valid amount of data in the buffer + +### Testing against [libzstd](https://github.com/facebook/zstd) + +Design is verified by comparing decoding results to the reference library `libzstd`. +ZSTD frames used for testing are generated with [decodecorpus](https://github.com/facebook/zstd/blob/dev/tests/decodecorpus.c) utility. +The generated frame is then decoded with `libzstd`. + +#### Positive test cases + +If the results of decoding with `libzstd` are valid, the test runs the same encoded frame through the simulation of DSLX design. +The output of the simulation is gathered and compared with the results of `libzstd` in terms of its size and contents. + +Encoded ZSTD frame is generated with the function `GenerateFrame(int seed, BlockType btype)` from [data_generator](https://github.com/antmicro/xls/blob/52186-zstd-top/xls/modules/zstd/data_generator.cc) library. +This function takes as arguments the seed for the generator and enum which codes the type of blocks that should be generated in a given frame. +The available block types are: + +* RAW +* RLE +* COMPRESSED +* RANDOM + +The function returns a vector of bytes representing a valid encoded ZSTD frame. +Such generated frame can be passed to `ParseAndCompareWithZstd(std::vector frame)` which is responsible for decoding the frame, running simulation and comparing the results. + +Tests are available in the `zstd_dec_test.cc` file and can be launched with the following Bazel command: + +``` +bazel test //xls/modules/zstd:zstd_dec_cc_test +``` + +#### Negative test cases + +Currently, `decodecorpus` does not support generating ZSTD frames with subtle errors that trigger failure points provided in the ZSTD Decoder. +Because of that, it is not possible to efficiently provide valuable negative tests for the integrated ZSTD Decoder. + +The alternatives for writing negative tests include: + +* Generating a well-known valid ZSTD frame from a specific generator seed and then tweaking the raw bits in this frame to trigger the error response from the decoder +* Using [FuzzTest](https://github.com/google/fuzztest) to create multiple randomized test cases for the decoder and then compare `libzstd` decoder failure with `ZSTD Decoder` failure. + +### Known Limitations + +* **[WIP]** Bugs in the current flow cause failures in some of the test cases for RAW and RLE block types +* **[WIP]** Compressed block type is not supported +* Checksum is not being verified + diff --git a/xls/modules/zstd/img/ZSTD_compressed_block_Huffman_decoder.png b/xls/modules/zstd/img/ZSTD_compressed_block_Huffman_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..625fa98e949fd42a94635c7d1da0443bcab5004b GIT binary patch literal 19782 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
|peVmH!Zj4eV+<{Oyv49Ngn#}t|CaG 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 Nkf*Dk%Q~loCIH&>JEQ;r literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/img/ZSTD_compressed_block_decoder.png b/xls/modules/zstd/img/ZSTD_compressed_block_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..a790965f7cfd32735cca4049a4e4029d54e323d5 GIT binary patch literal 21331 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 HdB^+{0F^G6 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/img/ZSTD_compressed_block_literals_decoder.png b/xls/modules/zstd/img/ZSTD_compressed_block_literals_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..d89c12c93605fc0b6cc5cf4300fc11b45fde509c GIT binary patch literal 24760 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 X#zmFp^B(ar(8ZXZw>n2M!rc8Y_>arb literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/img/ZSTD_compressed_block_sequence_decoder.png b/xls/modules/zstd/img/ZSTD_compressed_block_sequence_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..4c6bfb7720b22784d1998c5e2a7d1758f5e6e9e5 GIT binary patch literal 23882 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 literal 0 HcmV?d00001 diff --git a/xls/modules/zstd/img/ZSTD_decoder.png b/xls/modules/zstd/img/ZSTD_decoder.png new file mode 100644 index 0000000000000000000000000000000000000000..f157751512745dc923beec4b242377fe3962eb89 GIT binary patch literal 28375 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 fe`JP`|1bW3`ds>vf( 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 SCL)x7c>GZNLHT`)@c#k)`e{o5 literal 0 HcmV?d00001 From 57afc0f20717deb35ffc0cd9afef648e2942d28b Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 24 Apr 2024 17:43:35 +0200 Subject: [PATCH 25/49] examples/ram: use assert!() instead of assert_eq() in RamModel Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/examples/ram.x | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/xls/examples/ram.x b/xls/examples/ram.x index 674f4d7898..73a931b2fc 100644 --- a/xls/examples/ram.x +++ b/xls/examples/ram.x @@ -251,7 +251,7 @@ proc RamModel mem[read_req.addr], SimultaneousReadWriteBehavior::WRITE_BEFORE_READ => value_to_write, - SimultaneousReadWriteBehavior::ASSERT_NO_CONFLICT => { - // Assertion failure, we have a conflicting read and write. - assert_eq(true, false); - mem[read_req.addr] // Need to return something. - }, + SimultaneousReadWriteBehavior::ASSERT_NO_CONFLICT => fail!("conflicting_read_and_write", mem[read_req.addr]), + _ => fail!("impossible_case", uN[DATA_WIDTH]:0), } } else { mem[read_req.addr] }; let read_resp_value = ReadResp { From ab5fb05708ef9cbf950819adf6afcc9d0825670e Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Thu, 6 Jun 2024 11:47:00 +0200 Subject: [PATCH 26/49] CI: Add custom ZSTD module workflow Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- .github/workflows/modules-zstd.yml | 68 ++++++++++++++++++++++++++++++ 1 file changed, 68 insertions(+) create mode 100644 .github/workflows/modules-zstd.yml diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml new file mode 100644 index 0000000000..db31a4d0b8 --- /dev/null +++ b/.github/workflows/modules-zstd.yml @@ -0,0 +1,68 @@ +name: Modules (ZSTD) +on: + # Avoid triggering on pushes to /all/ open PR branches. + push: + branches: + - main + paths: + - 'xls/modules/zstd/**' + pull_request: + branches: + - main + paths: + - 'xls/modules/zstd/**' + # This lets us trigger manually from the UI. + workflow_dispatch: + +jobs: + test: + name: Test ZSTD module (opt) + runs-on: + labels: ubuntu-22.04-64core + timeout-minutes: 600 + continue-on-error: true + steps: + - uses: actions/checkout@v2 + + - name: Restore Nightly Bazel Cache + uses: actions/cache/restore@v4 + with: + path: "~/.cache/bazel" + key: bazel-cache-nightly-${{ runner.os }}-${{ github.sha }} + restore-keys: bazel-cache-nightly-${{ runner.os }}- + + - name: Install dependencies via apt + run: sudo apt-get install python3-distutils python3-dev python-is-python3 libtinfo5 build-essential liblapack-dev libblas-dev gfortran + + - name: Bazel Build Tools (opt) + run: | + bazel build -c opt --test_output=errors -- //xls/dslx:interpreter_main //xls/dslx/ir_convert:ir_converter_main //xls/tools:opt_main //xls/tools:codegen_main + + - name: Build ZSTD Module (opt) + run: | + bazel build -c opt --test_output=errors -- //xls/modules/zstd:all + + - name: Test ZSTD Module (opt) + if: ${{ !cancelled() }} + run: | + bazel test -c opt --test_output=errors -- //xls/modules/zstd:all + + - name: Build ZSTD verilog targets (opt) + if: ${{ !cancelled() }} + run: | + bazel build -c opt -- $(bazel query 'filter(".*_verilog", kind(rule, //xls/modules/zstd/...))') + + - 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/...))') + + - 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/...))') + + - name: Build ZSTD place and route targets (opt) + if: ${{ !cancelled() }} + run: | + bazel build -c opt -- $(bazel query 'filter(".*_place_and_route", kind(rule, //xls/modules/zstd/...))') From 495353dafa8fb97de54cdb1944f290eb42961ddc Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 07:49:45 +0200 Subject: [PATCH 27/49] [FIXUP] Update copyrights notice and add missing license header Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 2 +- xls/modules/zstd/block_dec.x | 2 +- xls/modules/zstd/block_header.x | 2 +- xls/modules/zstd/buffer.x | 2 +- xls/modules/zstd/common.x | 2 +- xls/modules/zstd/data_generator.cc | 2 +- xls/modules/zstd/data_generator.h | 2 +- xls/modules/zstd/dec_demux.x | 2 +- xls/modules/zstd/dec_mux.x | 2 +- xls/modules/zstd/frame_header.x | 2 +- xls/modules/zstd/frame_header_test.cc | 2 +- xls/modules/zstd/frame_header_test.x | 2 +- xls/modules/zstd/magic.x | 2 +- xls/modules/zstd/ram_printer.x | 14 ++++++++++++++ xls/modules/zstd/raw_block_dec.x | 2 +- xls/modules/zstd/rle_block_dec.x | 2 +- xls/modules/zstd/sequence_executor.x | 2 +- xls/modules/zstd/zstd_dec.x | 2 +- xls/modules/zstd/zstd_dec_test.cc | 2 +- 19 files changed, 32 insertions(+), 18 deletions(-) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 1048201c0b..33f34800cc 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1,4 +1,4 @@ -# Copyright 2023 The XLS Authors +# 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. diff --git a/xls/modules/zstd/block_dec.x b/xls/modules/zstd/block_dec.x index 30fced5c56..6797484d59 100644 --- a/xls/modules/zstd/block_dec.x +++ b/xls/modules/zstd/block_dec.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/block_header.x b/xls/modules/zstd/block_header.x index b3a0e9966f..455b3295e1 100644 --- a/xls/modules/zstd/block_header.x +++ b/xls/modules/zstd/block_header.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/buffer.x b/xls/modules/zstd/buffer.x index 9d050e96ee..d4f9acfa20 100644 --- a/xls/modules/zstd/buffer.x +++ b/xls/modules/zstd/buffer.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 1fc676539f..8c6b1f1c5d 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc index 243136fc1d..ae05ea2395 100644 --- a/xls/modules/zstd/data_generator.cc +++ b/xls/modules/zstd/data_generator.cc @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/data_generator.h b/xls/modules/zstd/data_generator.h index feb7c14b83..b62a519940 100644 --- a/xls/modules/zstd/data_generator.h +++ b/xls/modules/zstd/data_generator.h @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/dec_demux.x b/xls/modules/zstd/dec_demux.x index d032a3a223..5bcd380f91 100644 --- a/xls/modules/zstd/dec_demux.x +++ b/xls/modules/zstd/dec_demux.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x index 423c0e36e1..59778ff304 100644 --- a/xls/modules/zstd/dec_mux.x +++ b/xls/modules/zstd/dec_mux.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x index a40e9dfeda..81335cfc46 100644 --- a/xls/modules/zstd/frame_header.x +++ b/xls/modules/zstd/frame_header.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index 2f557b24fa..e1f753f8e8 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/frame_header_test.x b/xls/modules/zstd/frame_header_test.x index 6009050225..9216dfab8d 100644 --- a/xls/modules/zstd/frame_header_test.x +++ b/xls/modules/zstd/frame_header_test.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/magic.x b/xls/modules/zstd/magic.x index 787cc62503..196f2f528f 100644 --- a/xls/modules/zstd/magic.x +++ b/xls/modules/zstd/magic.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/ram_printer.x b/xls/modules/zstd/ram_printer.x index 9fd91e5a6e..d887a05bcc 100644 --- a/xls/modules/zstd/ram_printer.x +++ b/xls/modules/zstd/ram_printer.x @@ -1,3 +1,17 @@ +// 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.examples.ram; diff --git a/xls/modules/zstd/raw_block_dec.x b/xls/modules/zstd/raw_block_dec.x index 8b32f81277..a3656011b0 100644 --- a/xls/modules/zstd/raw_block_dec.x +++ b/xls/modules/zstd/raw_block_dec.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/rle_block_dec.x b/xls/modules/zstd/rle_block_dec.x index 4d1c87f43c..232d9a6381 100644 --- a/xls/modules/zstd/rle_block_dec.x +++ b/xls/modules/zstd/rle_block_dec.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 8057c9831c..7d5e7a8d08 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index f0148ca8c9..259361de8f 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -1,4 +1,4 @@ -// Copyright 2023 The XLS Authors +// 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. diff --git a/xls/modules/zstd/zstd_dec_test.cc b/xls/modules/zstd/zstd_dec_test.cc index a5399e2015..87f246e642 100644 --- a/xls/modules/zstd/zstd_dec_test.cc +++ b/xls/modules/zstd/zstd_dec_test.cc @@ -1,4 +1,4 @@ -// Copyright 2020 The XLS Authors +// 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. From 593e40ca1534cfca5173324b1837795c9ab9bea8 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 07:50:02 +0200 Subject: [PATCH 28/49] [FIXUP] Update README Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index b0b9daee62..29b18e4d8b 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -346,7 +346,7 @@ The alternatives for writing negative tests include: ### Known Limitations -* **[WIP]** Bugs in the current flow cause failures in some of the test cases for RAW and RLE block types +* **[WIP]** Bugs in the current flow cause failures in some of the test cases of decoding ZSTD frame with RLE block types * **[WIP]** Compressed block type is not supported * Checksum is not being verified From dc6cb3c34dc2335de09109c8fc993701c8eac6d9 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 07:53:57 +0200 Subject: [PATCH 29/49] [FIXUP] Add manual tags to test rules Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/BUILD | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 33f34800cc..ea9d2ec3bf 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -44,6 +44,7 @@ xls_dslx_library( xls_dslx_test( name = "buffer_dslx_test", library = ":buffer_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -59,6 +60,7 @@ xls_dslx_library( xls_dslx_test( name = "window_buffer_dslx_test", library = ":window_buffer_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -141,6 +143,7 @@ xls_dslx_library( xls_dslx_test( name = "magic_dslx_test", library = ":magic_dslx", + tags = ["manual"], ) cc_library( @@ -184,6 +187,7 @@ xls_dslx_library( xls_dslx_test( name = "frame_header_dslx_test", library = ":frame_header_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -227,6 +231,7 @@ cc_test( "@com_google_fuzztest//fuzztest:googletest_fixture_adapter", "@com_google_googletest//:gtest", ], + tags = ["manual"], ) xls_dslx_verilog( @@ -305,6 +310,7 @@ xls_dslx_library( xls_dslx_test( name = "raw_block_dec_dslx_test", library = ":raw_block_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -384,6 +390,7 @@ xls_dslx_library( xls_dslx_test( name = "rle_block_dec_dslx_test", library = ":rle_block_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -467,6 +474,7 @@ xls_dslx_library( xls_dslx_test( name = "block_header_dslx_test", library = ":block_header_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -482,6 +490,7 @@ xls_dslx_library( xls_dslx_test( name = "dec_mux_dslx_test", library = ":dec_mux_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -559,6 +568,7 @@ xls_dslx_library( xls_dslx_test( name = "dec_demux_dslx_test", library = ":dec_demux_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -639,6 +649,7 @@ xls_dslx_library( xls_dslx_test( name = "block_dec_dslx_test", library = ":block_dec_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -719,6 +730,7 @@ xls_dslx_library( xls_dslx_test( name = "ram_printer_dslx_test", library = ":ram_printer_dslx", + tags = ["manual"], ) xls_dslx_library( @@ -739,6 +751,7 @@ xls_dslx_test( "compare": "none", }, library = ":sequence_executor_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -844,6 +857,7 @@ xls_dslx_library( xls_dslx_test( name = "repacketizer_dslx_test", library = ":repacketizer_dslx", + tags = ["manual"], ) xls_dslx_verilog( @@ -971,6 +985,7 @@ xls_dslx_ir( dslx_top = "ZstdDecoderTest", ir_file = "zstd_dec_test.ir", library = ":zstd_dec_dslx", + tags = ["manual"], ) cc_test( @@ -997,6 +1012,7 @@ cc_test( "@com_github_facebook_zstd//:zstd", "@com_google_googletest//:gtest", ], + tags = ["manual"], ) xls_benchmark_ir( From d7463ba60fc158aca62868f26f2f3fa98794a985 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 07:58:48 +0200 Subject: [PATCH 30/49] [FIXUP] restore original main CI workflow Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- .github/workflows/continuous-integration.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index a779dc5b3b..3c24746ef6 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -41,4 +41,4 @@ jobs: - name: Bazel Test All (opt) run: | - bazel test -c opt --noshow_progress --test_output=errors -- //... + bazel test -c opt --noshow_progress --test_output=errors -- //xls/... From b46ee8c55d8bd03e5ebeb27ac131f1b6ef3488e2 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 07:59:25 +0200 Subject: [PATCH 31/49] [FIXUP] Explicitly run test targets in ZSTD CI workflow Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- .github/workflows/modules-zstd.yml | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.github/workflows/modules-zstd.yml b/.github/workflows/modules-zstd.yml index db31a4d0b8..a2daaa681e 100644 --- a/.github/workflows/modules-zstd.yml +++ b/.github/workflows/modules-zstd.yml @@ -42,10 +42,15 @@ jobs: run: | bazel build -c opt --test_output=errors -- //xls/modules/zstd:all - - name: Test ZSTD Module (opt) + - name: Test ZSTD Module - DSLX Tests (opt) if: ${{ !cancelled() }} run: | - bazel test -c opt --test_output=errors -- //xls/modules/zstd:all + bazel test -c opt --test_output=errors -- $(bazel query 'filter(".*_dslx_test", kind(rule, //xls/modules/zstd/...))') + + - name: Test ZSTD Module - CC Tests (opt) + if: ${{ !cancelled() }} + run: | + bazel test -c opt --test_output=errors -- $(bazel query 'filter(".*_cc_test", kind(rule, //xls/modules/zstd/...))') - name: Build ZSTD verilog targets (opt) if: ${{ !cancelled() }} From 9ddd7cebf627ffd8821166fb40f8df3e17923171 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 08:06:36 +0200 Subject: [PATCH 32/49] [FIXUP] document the usage of libzstd dependency Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- dependency_support/load_external.bzl | 1 + 1 file changed, 1 insertion(+) diff --git a/dependency_support/load_external.bzl b/dependency_support/load_external.bzl index abd3c795ae..04da64c876 100644 --- a/dependency_support/load_external.bzl +++ b/dependency_support/load_external.bzl @@ -331,6 +331,7 @@ def load_external_repositories(): sha256 = "cad05f864a32799f6f9022891de91ac78f30e0fa07dc68abac92a628121b5b11", ) + # Used in C++ tests of the ZSTD Module # Version 1.4.7 released on 17.12.2020 # https://github.com/facebook/zstd/releases/tag/v1.4.7 # Updated 27.06.2024 From 9483420fd631e9c81700f35a69b13bde1dbf4f71 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 10:57:33 +0200 Subject: [PATCH 33/49] [FIXUP] return reference to the data vector Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/zstd_dec_test.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/xls/modules/zstd/zstd_dec_test.cc b/xls/modules/zstd/zstd_dec_test.cc index 87f246e642..423a0e5597 100644 --- a/xls/modules/zstd/zstd_dec_test.cc +++ b/xls/modules/zstd/zstd_dec_test.cc @@ -54,7 +54,7 @@ class ZstdDecodedPacket { return ZstdDecodedPacket(data, length, last); } - std::vector GetData() { return data; } + std::vector &GetData() { return data; } uint64_t GetLength() { return length; } From a16c27a3233c6f7b3f3b531477a6955c13d697cd Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 12:02:28 +0200 Subject: [PATCH 34/49] [FIXUP] fix RLE block handling in decodecorpus Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- .../com_github_facebook_zstd/decodecorpus.patch | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/dependency_support/com_github_facebook_zstd/decodecorpus.patch b/dependency_support/com_github_facebook_zstd/decodecorpus.patch index fdd4cc2577..852eb0e26f 100644 --- a/dependency_support/com_github_facebook_zstd/decodecorpus.patch +++ b/dependency_support/com_github_facebook_zstd/decodecorpus.patch @@ -1,5 +1,5 @@ diff --git tests/decodecorpus.c tests/decodecorpus.c -index 50935d31..522b3769 100644 +index 50935d31..269bca86 100644 --- tests/decodecorpus.c +++ tests/decodecorpus.c @@ -240,6 +240,12 @@ typedef enum { @@ -28,6 +28,15 @@ index 50935d31..522b3769 100644 /* Generate and write a random frame header */ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -285,7 +295,7 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + if (RAND(seed) & 7 && g_maxDecompressedSizeLog > 7) { + /* do content of at least 128 bytes */ + highBit = 1ULL << RAND_range(seed, 7, g_maxDecompressedSizeLog); +- } else if (RAND(seed) & 3) { ++ } else if ((RAND(seed) & 3) || (*(opts.blockType) == bt_rle)) { + /* do small content */ + highBit = 1ULL << RAND_range(seed, 0, MIN(7, 1U << g_maxDecompressedSizeLog)); + } else { @@ -317,8 +327,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) } From 8c350b5cbb743f36b66f2616bada5ab149c6de63 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Tue, 2 Jul 2024 12:55:30 +0200 Subject: [PATCH 35/49] [FIXUP] fix generating data when we don't force the block type Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- .../decodecorpus.patch | 39 ++++++++++++------- 1 file changed, 25 insertions(+), 14 deletions(-) diff --git a/dependency_support/com_github_facebook_zstd/decodecorpus.patch b/dependency_support/com_github_facebook_zstd/decodecorpus.patch index 852eb0e26f..99d724fdec 100644 --- a/dependency_support/com_github_facebook_zstd/decodecorpus.patch +++ b/dependency_support/com_github_facebook_zstd/decodecorpus.patch @@ -1,5 +1,5 @@ diff --git tests/decodecorpus.c tests/decodecorpus.c -index 50935d31..269bca86 100644 +index 50935d31..c529fbfe 100644 --- tests/decodecorpus.c +++ tests/decodecorpus.c @@ -240,6 +240,12 @@ typedef enum { @@ -28,16 +28,27 @@ index 50935d31..269bca86 100644 /* Generate and write a random frame header */ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) -@@ -285,7 +295,7 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -281,10 +291,19 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + + { + /* Generate random content size */ ++ int force_block_type = opts.blockType != NULL; + size_t highBit; if (RAND(seed) & 7 && g_maxDecompressedSizeLog > 7) { /* do content of at least 128 bytes */ highBit = 1ULL << RAND_range(seed, 7, g_maxDecompressedSizeLog); -- } else if (RAND(seed) & 3) { -+ } else if ((RAND(seed) & 3) || (*(opts.blockType) == bt_rle)) { ++ } else if (force_block_type) { ++ if ((RAND(seed) & 3) || (*(opts.blockType) == bt_rle)) { ++ /* do small content */ ++ highBit = 1ULL << RAND_range(seed, 0, MIN(7, 1U << g_maxDecompressedSizeLog)); ++ } else { ++ /* 0 size frame */ ++ highBit = 0; ++ } + } else if (RAND(seed) & 3) { /* do small content */ highBit = 1ULL << RAND_range(seed, 0, MIN(7, 1U << g_maxDecompressedSizeLog)); - } else { -@@ -317,8 +327,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -317,8 +336,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) } /* write out the header */ @@ -50,7 +61,7 @@ index 50935d31..269bca86 100644 { /* -@@ -363,8 +375,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -363,8 +384,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) /* Write a literal block in either raw or RLE form, return the literals size */ static size_t writeLiteralsBlockSimple(U32* seed, frame_t* frame, size_t contentSize) { @@ -62,7 +73,7 @@ index 50935d31..269bca86 100644 int const sizeFormatDesc = RAND(seed) % 8; size_t litSize; size_t maxLitSize = MIN(contentSize, g_maxBlockSize); -@@ -612,8 +626,15 @@ static size_t writeLiteralsBlockCompressed(U32* seed, frame_t* frame, size_t con +@@ -612,8 +635,15 @@ static size_t writeLiteralsBlockCompressed(U32* seed, frame_t* frame, size_t con static size_t writeLiteralsBlock(U32* seed, frame_t* frame, size_t contentSize) { @@ -80,7 +91,7 @@ index 50935d31..269bca86 100644 return writeLiteralsBlockCompressed(seed, frame, contentSize); } else { return writeLiteralsBlockSimple(seed, frame, contentSize); -@@ -1030,7 +1051,8 @@ static size_t writeCompressedBlock(U32* seed, frame_t* frame, size_t contentSize +@@ -1030,7 +1060,8 @@ static size_t writeCompressedBlock(U32* seed, frame_t* frame, size_t contentSize static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, int lastBlock, dictInfo info) { @@ -90,7 +101,7 @@ index 50935d31..269bca86 100644 size_t blockSize; int blockType; -@@ -1069,7 +1091,7 @@ static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, +@@ -1069,7 +1100,7 @@ static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, frame->data = op; compressedSize = writeCompressedBlock(seed, frame, contentSize, info); @@ -99,7 +110,7 @@ index 50935d31..269bca86 100644 blockType = 0; memcpy(op, frame->src, contentSize); -@@ -1240,7 +1262,11 @@ static U32 generateFrame(U32 seed, frame_t* fr, dictInfo info) +@@ -1240,7 +1271,11 @@ static U32 generateFrame(U32 seed, frame_t* fr, dictInfo info) DISPLAYLEVEL(3, "frame seed: %u\n", (unsigned)seed); initFrame(fr); @@ -111,7 +122,7 @@ index 50935d31..269bca86 100644 writeBlocks(&seed, fr, info); writeChecksum(fr); -@@ -1768,6 +1794,9 @@ static void advancedUsage(const char* programName) +@@ -1768,6 +1803,9 @@ static void advancedUsage(const char* programName) DISPLAY( " --max-block-size-log=# : max block size log, must be in range [2, 17]\n"); DISPLAY( " --max-content-size-log=# : max content size log, must be <= 20\n"); DISPLAY( " (this is ignored with gen-blocks)\n"); @@ -121,7 +132,7 @@ index 50935d31..269bca86 100644 } /*! readU32FromChar() : -@@ -1889,6 +1918,18 @@ int main(int argc, char** argv) +@@ -1889,6 +1927,18 @@ int main(int argc, char** argv) U32 value = readU32FromChar(&argument); g_maxDecompressedSizeLog = MIN(MAX_DECOMPRESSED_SIZE_LOG, value); @@ -140,7 +151,7 @@ index 50935d31..269bca86 100644 } else { advancedUsage(argv[0]); return 1; -@@ -1900,6 +1941,18 @@ int main(int argc, char** argv) +@@ -1900,6 +1950,18 @@ int main(int argc, char** argv) return 1; } } } } /* for (argNb=1; argNb Date: Tue, 2 Jul 2024 14:19:44 +0200 Subject: [PATCH 36/49] modules/zstd/frame_header_test: Reproduce bugs Internal-tag: [#60906] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header_test.cc | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index e1f753f8e8..1d03755348 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -276,6 +276,16 @@ TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughDataReservedBit) { this->ParseAndCompareWithZstd(buffer); } +TEST_F(FrameHeaderTest, ParseFrameHeaderPendingBug1) { + std::vector buffer{0261, 015, 91, 91, 91, 0364}; + this->ParseAndCompareWithZstd(buffer); +} + +TEST_F(FrameHeaderTest, ParseFrameHeaderPendingBug2) { + std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; + this->ParseAndCompareWithZstd(buffer); +} + class FrameHeaderSeededTest : public FrameHeaderTest, public ::testing::WithParamInterface { public: From cc33b0b1f3f4d587c56ab2b68a4d29475c522e24 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 3 Jul 2024 09:38:11 +0200 Subject: [PATCH 37/49] [FIXUP] return expect empty buffer on the unsupported windows size error Internal-tag: [#60906] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header_test.cc | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index 1d03755348..ff8fb45841 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -187,7 +187,8 @@ class FrameHeaderTest : public xls::IrTestBase { if (expected_status == FrameHeaderStatus::NO_ENOUGH_DATA) return dslx_simulation_input; // Critical failure - return empty buffer - if (expected_status == FrameHeaderStatus::CORRUPTED) + if (expected_status == FrameHeaderStatus::CORRUPTED || + expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) return Value::Tuple({/*contents:*/ Value(UBits(0, dslx_buffer_size)), /*length:*/ Value(UBits(0, 32))}); @@ -281,7 +282,7 @@ TEST_F(FrameHeaderTest, ParseFrameHeaderPendingBug1) { this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderPendingBug2) { +TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSize) { std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; this->ParseAndCompareWithZstd(buffer); } From 2aef289a2af80dc7981580d2fd2d49a38fb1f7e8 Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 3 Jul 2024 12:09:26 +0200 Subject: [PATCH 38/49] [FIXUP] Handle special case of difference between libzstd and ZSTD decoder Internal-tag: [#60906] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header_test.cc | 50 ++++++++++++++++++++++----- 1 file changed, 41 insertions(+), 9 deletions(-) diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index ff8fb45841..35318b3ee6 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -131,6 +131,10 @@ class FrameHeaderTest : public xls::IrTestBase { // have `result` bytes, got `buffer.size()` bytes. expected_status = FrameHeaderStatus::NO_ENOUGH_DATA; } + // Make sure that the FCS does not exceed max window buffer size + // Frame Header decoding failed - Special case - difference between the reference library and the decoder + } else if (!window_size_valid(zstd_fh.windowSize)) { + expected_status = FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE; } auto input = CreateDSLXSimulationInput(buffer.size(), input_buffer); @@ -153,6 +157,25 @@ class FrameHeaderTest : public xls::IrTestBase { const uint32_t dslx_buffer_size = 128; const uint32_t dslx_buffer_size_bytes = (dslx_buffer_size + CHAR_BIT - 1) / CHAR_BIT; + // Largest allowed WindowLog accepted by libzstd decompression function + // https://github.com/facebook/zstd/blob/v1.4.7/lib/decompress/zstd_decompress.c#L296 + // Use only in C++ tests when comparing DSLX ZSTD Decoder with libzstd + // Must be in sync with TEST_WINDOW_LOG_MAX_LIBZSTD in frame_header_test.x + const uint32_t TEST_WINDOW_LOG_MAX_LIBZSTD = 30; + + // Maximal mantissa value for calculating maximal accepted window_size + // as per https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor + const uint32_t MAX_MANTISSA = 0b111; + + // Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given + // window_size should be accepted or discarded. + // Based on window_size calculation from: RFC 8878 + // https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor + bool window_size_valid(uint32_t window_size) { + auto max_window_size = (1 << TEST_WINDOW_LOG_MAX_LIBZSTD) + (((1 << TEST_WINDOW_LOG_MAX_LIBZSTD) >> 3) * MAX_MANTISSA); + + return window_size <= max_window_size; + } void PrintZSTDFrameHeader(ZSTD_frameHeader* fh) { std::cout << std::hex; @@ -168,13 +191,22 @@ class FrameHeaderTest : public xls::IrTestBase { // Form DSLX Value representing ZSTD Frame header based on data parsed with // ZSTD library. Represents DSLX struct `FrameHeader`. - Value CreateExpectedFrameHeader(ZSTD_frameHeader* fh) { - return Value::Tuple({ - /*window_size:*/ Value(UBits(fh->windowSize, 64)), - /*frame_content_size:*/ Value(UBits(fh->frameContentSize, 64)), - /*dictionary_id:*/ Value(UBits(fh->dictID, 32)), - /*content_checksum_flag: */ Value(UBits(fh->checksumFlag, 1)), - }); + Value CreateExpectedFrameHeader(ZSTD_frameHeader* fh, FrameHeaderStatus expected_status) { + if (expected_status == FrameHeaderStatus::CORRUPTED || + expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) + return Value::Tuple({ + /*window_size:*/ Value(UBits(0, 64)), + /*frame_content_size:*/ Value(UBits(0, 64)), + /*dictionary_id:*/ Value(UBits(0, 32)), + /*content_checksum_flag: */ Value(UBits(0, 1)), + }); + else + return Value::Tuple({ + /*window_size:*/ Value(UBits(fh->windowSize, 64)), + /*frame_content_size:*/ Value(UBits(fh->frameContentSize, 64)), + /*dictionary_id:*/ Value(UBits(fh->dictID, 32)), + /*content_checksum_flag: */ Value(UBits(fh->checksumFlag, 1)), + }); } // Create DSLX Value representing Buffer contents after parsing frame header @@ -224,7 +256,7 @@ class FrameHeaderTest : public xls::IrTestBase { FrameHeaderStatus expected_status) { auto expected_buffer = CreateExpectedBuffer( dslx_simulation_input, input_buffer, fh->headerSize, expected_status); - auto expected_frame_header = CreateExpectedFrameHeader(fh); + auto expected_frame_header = CreateExpectedFrameHeader(fh, expected_status); return Value::Tuple({/*status:*/ Value(UBits(expected_status, 2)), /*header:*/ expected_frame_header, /*buffer:*/ expected_buffer}); @@ -277,7 +309,7 @@ TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughDataReservedBit) { this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderPendingBug1) { +TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedFrameContentSizeThroughSingleSegment) { std::vector buffer{0261, 015, 91, 91, 91, 0364}; this->ParseAndCompareWithZstd(buffer); } From 14fe3f05cac2699f62716ea906c32a337c3bbbef Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 3 Jul 2024 12:12:56 +0200 Subject: [PATCH 39/49] [FIXUP] Add DSLX test case for unsupported FCS Internal-tag: [#60906] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header.x | 15 +++++++++++++++ 1 file changed, 15 insertions(+) diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x index 81335cfc46..e0f53830cd 100644 --- a/xls/modules/zstd/frame_header.x +++ b/xls/modules/zstd/frame_header.x @@ -659,4 +659,19 @@ fn test_parse_frame_header() { buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, header: zero!() }); + + // Frame Header is discarded because Frame Content Size required by frame is too big for given decoder + // configuration + let buffer = Buffer { content: bits[128]:0xf45b5b5b0db1, length: u32:48 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, + buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, + header: FrameHeader { + window_size: u64:0x0, + frame_content_size: u64:0x0, + dictionary_id: u32:0x0, + content_checksum_flag: u1:0, + }, + }); } From e3804ff5bde4a282650ec231ba7d85a5ad50b48f Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 3 Jul 2024 13:52:08 +0200 Subject: [PATCH 40/49] [FIXUP] fix max_window_size calculation types Internal-tag: [#52186] Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header.x | 15 +++++++++++++++ xls/modules/zstd/frame_header_test.cc | 11 ++++++++--- 2 files changed, 23 insertions(+), 3 deletions(-) diff --git a/xls/modules/zstd/frame_header.x b/xls/modules/zstd/frame_header.x index e0f53830cd..858d64ac53 100644 --- a/xls/modules/zstd/frame_header.x +++ b/xls/modules/zstd/frame_header.x @@ -674,4 +674,19 @@ fn test_parse_frame_header() { content_checksum_flag: u1:0, }, }); + + // Frame Header is discarded because Frame Content Size required by frame is too big (above 64bits) for given decoder + // configuration + let buffer = Buffer { content: bits[128]:0xc0659db6813a16b33f3da53a79e4, length: u32:112 }; + let frame_header_result = parse_frame_header(buffer); + assert_eq(frame_header_result, FrameHeaderResult { + status: FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE, + buffer: Buffer { content: bits[128]:0x0, length: u32:0 }, + header: FrameHeader { + window_size: u64:0x0, + frame_content_size: u64:0x0, + dictionary_id: u32:0x0, + content_checksum_flag: u1:0, + }, + }); } diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index 35318b3ee6..e9a36e7111 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -161,17 +161,17 @@ class FrameHeaderTest : public xls::IrTestBase { // https://github.com/facebook/zstd/blob/v1.4.7/lib/decompress/zstd_decompress.c#L296 // Use only in C++ tests when comparing DSLX ZSTD Decoder with libzstd // Must be in sync with TEST_WINDOW_LOG_MAX_LIBZSTD in frame_header_test.x - const uint32_t TEST_WINDOW_LOG_MAX_LIBZSTD = 30; + const uint64_t TEST_WINDOW_LOG_MAX_LIBZSTD = 30; // Maximal mantissa value for calculating maximal accepted window_size // as per https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor - const uint32_t MAX_MANTISSA = 0b111; + const uint64_t MAX_MANTISSA = 0b111; // Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given // window_size should be accepted or discarded. // Based on window_size calculation from: RFC 8878 // https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor - bool window_size_valid(uint32_t window_size) { + bool window_size_valid(uint64_t window_size) { auto max_window_size = (1 << TEST_WINDOW_LOG_MAX_LIBZSTD) + (((1 << TEST_WINDOW_LOG_MAX_LIBZSTD) >> 3) * MAX_MANTISSA); return window_size <= max_window_size; @@ -314,6 +314,11 @@ TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedFrameContentSizeThroughSi this->ParseAndCompareWithZstd(buffer); } +TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { + std::vector buffer{0344, 'y', ':', 0245, '=', '?', 0263, 0026, ':', 0201, 0266, 0235, 'e', 0300}; + this->ParseAndCompareWithZstd(buffer); +} + TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSize) { std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; this->ParseAndCompareWithZstd(buffer); From cdeff692eb93fdd097c1a14d508ab3e3981b29ca Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 17 Jul 2024 11:30:19 +0200 Subject: [PATCH 41/49] [FIXUP] dependencies cleanup Signed-off-by: Pawel Czarnecki --- .../bundled.BUILD.bazel | 12 +- dependency_support/load_external.bzl | 1 - xls/modules/zstd/BUILD | 91 ++++++------ xls/modules/zstd/README.md | 41 +++++- xls/modules/zstd/data_generator.cc | 26 ++-- xls/modules/zstd/data_generator.h | 17 +-- xls/modules/zstd/frame_header_test.cc | 109 ++++++++------ xls/modules/zstd/zstd_dec_test.cc | 136 +++++++++++------- 8 files changed, 252 insertions(+), 181 deletions(-) diff --git a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel index 2538c07bfd..f175f30cc0 100644 --- a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel +++ b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel @@ -12,15 +12,17 @@ # See the License for the specific language governing permissions and # limitations under the License. +""" +Builds everything together similarly to the zstd cmake file: +https://github.com/facebook/zstd/blob/dev/build/cmake/lib/CMakeLists.txt +but with legacy support and defines as in the zstd BUCK file: +https://github.com/facebook/zstd/blob/dev/lib/BUCK +""" + package(default_visibility = ["//visibility:public"]) licenses(["notice"]) -# Builds everything together similarly to the zstd cmake file: -# https://github.com/facebook/zstd/blob/dev/build/cmake/lib/CMakeLists.txt -# but with legacy support and defines as in the zstd BUCK file: -# https://github.com/facebook/zstd/blob/dev/lib/BUCK - cc_library( name = "zstd", srcs = glob([ diff --git a/dependency_support/load_external.bzl b/dependency_support/load_external.bzl index 04da64c876..eb49065ab8 100644 --- a/dependency_support/load_external.bzl +++ b/dependency_support/load_external.bzl @@ -344,4 +344,3 @@ def load_external_repositories(): # Modify decodecorpus to allow generation of zstd frame headers only patches = ["//dependency_support/com_github_facebook_zstd:decodecorpus.patch"], ) - diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index ea9d2ec3bf..e83e34ccf0 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -20,11 +20,10 @@ load("@rules_hdl//verilog:providers.bzl", "verilog_library") load( "//xls/build_rules:xls_build_defs.bzl", "xls_benchmark_ir", - "xls_dslx_ir", "xls_benchmark_verilog", + "xls_dslx_ir", "xls_dslx_library", "xls_dslx_test", - "xls_ir_opt_ir", "xls_dslx_verilog", ) @@ -80,8 +79,8 @@ xls_dslx_verilog( "inline_procs": "true", "top": "__window_buffer__WindowBuffer64__WindowBuffer_0__64_32_48_next", }, - verilog_file = "window_buffer.v", tags = ["manual"], + verilog_file = "window_buffer.v", ) xls_benchmark_ir( @@ -105,11 +104,11 @@ verilog_library( synthesize_rtl( name = "window_buffer_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "WindowBuffer64", deps = [ ":window_buffer_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -126,8 +125,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":window_buffer_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -155,10 +154,8 @@ cc_library( ], deps = [ "//xls/common:subprocess", - "//xls/common/file:filesystem", "//xls/common/file:get_runfile_path", "//xls/common/status:status_macros", - "//xls/ir", "@com_google_absl//absl/status", "@com_google_absl//absl/status:statusor", "@com_google_absl//absl/time", @@ -210,6 +207,7 @@ cc_test( ":frame_header_test_dslx", ], shard_count = 50, + tags = ["manual"], deps = [ ":data_generator", "//xls/common:xls_gunit_main", @@ -218,20 +216,20 @@ cc_test( "//xls/common/status:matchers", "//xls/dslx:create_import_data", "//xls/dslx:import_data", - "//xls/dslx:interp_value", "//xls/dslx:parse_and_typecheck", + "//xls/dslx/ir_convert:convert_options", "//xls/dslx/ir_convert:ir_converter", "//xls/dslx/type_system:parametric_env", - "//xls/ir", - "//xls/ir:ir_parser", + "//xls/ir:bits", "//xls/ir:ir_test_base", + "//xls/ir:value", "@com_github_facebook_zstd//:zstd", "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/types:span", "@com_google_fuzztest//fuzztest", "@com_google_fuzztest//fuzztest:googletest_fixture_adapter", "@com_google_googletest//:gtest", ], - tags = ["manual"], ) xls_dslx_verilog( @@ -246,8 +244,8 @@ xls_dslx_verilog( }, dslx_top = "parse_frame_header_128", library = ":frame_header_test_dslx", - verilog_file = "frame_header.v", tags = ["manual"], + verilog_file = "frame_header.v", ) xls_benchmark_ir( @@ -271,11 +269,11 @@ verilog_library( synthesize_rtl( name = "frame_header_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "FrameHeaderDecoder", deps = [ ":frame_header_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -292,8 +290,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":frame_header_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -324,8 +322,8 @@ xls_dslx_verilog( }, dslx_top = "RawBlockDecoder", library = ":raw_block_dec_dslx", - verilog_file = "raw_block_dec.v", tags = ["manual"], + verilog_file = "raw_block_dec.v", ) xls_benchmark_ir( @@ -349,11 +347,11 @@ verilog_library( synthesize_rtl( name = "raw_block_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RawBlockDecoder", deps = [ ":raw_block_dec_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -370,8 +368,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":raw_block_dec_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -410,8 +408,8 @@ xls_dslx_verilog( "inline_procs": "true", "top": "__rle_block_dec__RleBlockDecoder__BatchPacker_0_next", }, - verilog_file = "rle_block_dec.v", tags = ["manual"], + verilog_file = "rle_block_dec.v", ) xls_benchmark_ir( @@ -435,11 +433,11 @@ verilog_library( synthesize_rtl( name = "rle_block_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "RleBlockDecoder", deps = [ ":rle_block_dec_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -456,8 +454,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":rle_block_dec_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -504,8 +502,8 @@ xls_dslx_verilog( }, dslx_top = "DecoderMux", library = ":dec_mux_dslx", - verilog_file = "dec_mux.v", tags = ["manual"], + verilog_file = "dec_mux.v", ) xls_benchmark_ir( @@ -529,11 +527,11 @@ verilog_library( synthesize_rtl( name = "dec_mux_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "DecoderMux", deps = [ ":dec_mux_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -550,8 +548,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":dec_mux_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -582,8 +580,8 @@ xls_dslx_verilog( }, dslx_top = "DecoderDemux", library = ":dec_demux_dslx", - verilog_file = "dec_demux.v", tags = ["manual"], + verilog_file = "dec_demux.v", ) xls_benchmark_ir( @@ -607,11 +605,11 @@ verilog_library( synthesize_rtl( name = "dec_demux_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "DecoderDemux", deps = [ ":dec_demux_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -628,8 +626,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":dec_demux_synth_asap7", - target_die_utilization_percentage = "5", tags = ["manual"], + target_die_utilization_percentage = "5", ) xls_dslx_library( @@ -669,8 +667,8 @@ xls_dslx_verilog( "inline_procs": "true", "top": "__xls_modules_zstd_dec_mux__BlockDecoder__DecoderMux_0_next", }, - verilog_file = "block_dec.v", tags = ["manual"], + verilog_file = "block_dec.v", ) xls_benchmark_ir( @@ -694,11 +692,11 @@ verilog_library( synthesize_rtl( name = "block_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "BlockDecoder", deps = [ ":block_dec_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -715,8 +713,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":block_dec_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -788,8 +786,8 @@ xls_dslx_verilog( "inline_procs": "true", "top": "__sequence_executor__SequenceExecutorZstd__SequenceExecutor_0__64_0_0_0_13_8192_65536_next", }, - verilog_file = "sequence_executor.v", tags = ["manual"], + verilog_file = "sequence_executor.v", ) xls_benchmark_ir( @@ -804,8 +802,8 @@ xls_benchmark_ir( xls_benchmark_verilog( name = "sequence_executor_verilog_benchmark", - verilog_target = "sequence_executor_verilog", tags = ["manual"], + verilog_target = "sequence_executor_verilog", ) verilog_library( @@ -819,11 +817,11 @@ verilog_library( synthesize_rtl( name = "sequence_executor_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "sequence_executor", deps = [ ":sequence_executor_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -840,8 +838,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":sequence_executor_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -871,8 +869,8 @@ xls_dslx_verilog( }, dslx_top = "Repacketizer", library = ":repacketizer_dslx", - verilog_file = "repacketizer.v", tags = ["manual"], + verilog_file = "repacketizer.v", ) xls_benchmark_ir( @@ -896,11 +894,11 @@ verilog_library( synthesize_rtl( name = "repacketizer_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "Repacketizer", deps = [ ":repacketizer_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -917,8 +915,8 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":repacketizer_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) xls_dslx_library( @@ -976,8 +974,8 @@ xls_dslx_verilog( opt_ir_args = { "inline_procs": "true", }, - verilog_file = "zstd_dec.v", tags = ["manual"], + verilog_file = "zstd_dec.v", ) xls_dslx_ir( @@ -990,6 +988,7 @@ xls_dslx_ir( cc_test( name = "zstd_dec_cc_test", + size = "large", srcs = [ "zstd_dec_test.cc", ], @@ -997,22 +996,28 @@ cc_test( ":zstd_dec_test.ir", ], shard_count = 50, - size = "large", + tags = ["manual"], deps = [ ":data_generator", "//xls/common:xls_gunit_main", "//xls/common/file:filesystem", "//xls/common/file:get_runfile_path", "//xls/common/status:matchers", - "//xls/interpreter:interpreter_proc_runtime", - "//xls/jit:jit_proc_runtime", + "//xls/interpreter:channel_queue", + "//xls/interpreter:serial_proc_runtime", + "//xls/ir", + "//xls/ir:bits", + "//xls/ir:channel", "//xls/ir:events", "//xls/ir:ir_parser", "//xls/ir:value", + "//xls/jit:jit_proc_runtime", "@com_github_facebook_zstd//:zstd", + "@com_google_absl//absl/container:flat_hash_map", + "@com_google_absl//absl/status:statusor", + "@com_google_absl//absl/types:span", "@com_google_googletest//:gtest", ], - tags = ["manual"], ) xls_benchmark_ir( @@ -1037,11 +1042,11 @@ verilog_library( synthesize_rtl( name = "zstd_dec_synth_asap7", standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + tags = ["manual"], top_module = "ZstdDecoder", deps = [ ":zstd_dec_verilog_lib", ], - tags = ["manual"], ) benchmark_synth( @@ -1058,6 +1063,6 @@ place_and_route( placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":zstd_dec_synth_asap7", - target_die_utilization_percentage = "10", tags = ["manual"], + target_die_utilization_percentage = "10", ) diff --git a/xls/modules/zstd/README.md b/xls/modules/zstd/README.md index 29b18e4d8b..eff9097fbe 100644 --- a/xls/modules/zstd/README.md +++ b/xls/modules/zstd/README.md @@ -28,11 +28,42 @@ and routed to the history buffer, ### Top level Proc This state machine is responsible for receiving encoded ZSTD frames, buffering the input and passing it to decoder's internal components based on the state of the proc. The states defined for the processing of ZSTD frame are as follows: -* DECODE_MAGIC_NUMBER -* DECODE_FRAME_HEADER -* DECODE_BLOCK_HEADER -* FEED_BLOCK_DECODER -* DECODE_CHECKSUM + +```mermaid +stateDiagram + direction LR + + [*] --> DECODE_MAGIC_NUMBER + + DECODE_MAGIC_NUMBER --> DECODE_MAGIC_NUMBER: Not enough data + DECODE_MAGIC_NUMBER --> DECODE_FRAME_HEADER: Got magic number + DECODE_MAGIC_NUMBER --> ERROR: Corrupted + + DECODE_FRAME_HEADER --> DECODE_FRAME_HEADER: Not enough data + DECODE_FRAME_HEADER --> DECODE_BLOCK_HEADER: Header decoded + DECODE_FRAME_HEADER --> ERROR: Unsupported window size + DECODE_FRAME_HEADER --> ERROR: Corrupted + + DECODE_BLOCK_HEADER --> DECODE_BLOCK_HEADER: Not enough data + DECODE_BLOCK_HEADER --> FEED_BLOCK_DECODER: Feed raw data + DECODE_BLOCK_HEADER --> FEED_BLOCK_DECODER: Feed RLE data + DECODE_BLOCK_HEADER --> FEED_BLOCK_DECODER: Feed compressed data + DECODE_BLOCK_HEADER --> ERROR: Corrupted + + state if_decode_checksum <> + state if_block_done <> + + FEED_BLOCK_DECODER --> if_decode_checksum: Is the checksum available? + if_decode_checksum --> DECODE_CHECKSUM: True + if_decode_checksum --> DECODE_MAGIC_NUMBER: False + FEED_BLOCK_DECODER --> if_block_done: Is the block decoding done? + if_block_done --> DECODE_BLOCK_HEADER: Decode next block + if_block_done --> FEED_BLOCK_DECODER: Continue feeding + + DECODE_CHECKSUM --> DECODE_MAGIC_NUMBER: Frame decoded + + ERROR --> [*] +``` After going through initial stages of decoding magic number and frame header, decoder starts the block division process. It decodes block headers to calculate how many bytes must be sent to the block dispatcher and when the current frame's last data block is being processed. diff --git a/xls/modules/zstd/data_generator.cc b/xls/modules/zstd/data_generator.cc index ae05ea2395..b1cae8ff7c 100644 --- a/xls/modules/zstd/data_generator.cc +++ b/xls/modules/zstd/data_generator.cc @@ -16,33 +16,28 @@ #include "xls/modules/zstd/data_generator.h" #include +#include +#include +#include #include #include -#include #include #include #include #include +#include #include +#include "absl/status/status.h" +#include "absl/status/statusor.h" #include "absl/time/time.h" #include "absl/types/span.h" -#include "xls/common/file/filesystem.h" #include "xls/common/file/get_runfile_path.h" #include "xls/common/status/status_macros.h" #include "xls/common/subprocess.h" -#include "xls/ir/value.h" namespace xls::zstd { -static void PrintRawDataVector(std::vector vector) { - for (int i = 0; i < vector.size(); ++i) { - std::cout << std::setfill('0') << std::setw(8) << std::hex << i << ": " - << "0x" << std::setw(2) << std::hex << int(vector[i]) - << std::endl; - } -} - static absl::StatusOr> ReadFileAsRawData( const std::filesystem::path& path) { std::ifstream file(path, std::ios::binary); @@ -79,7 +74,7 @@ static std::string CreateNameForGeneratedFile( static absl::StatusOr CallDecodecorpus( absl::Span args, - std::optional cwd = std::nullopt, + const std::optional& cwd = std::nullopt, std::optional timeout = std::nullopt) { XLS_ASSIGN_OR_RETURN( std::filesystem::path path, @@ -115,9 +110,12 @@ absl::StatusOr> GenerateFrame(int seed, BlockType btype) { std::filesystem::path( CreateNameForGeneratedFile(absl::MakeSpan(args), ".zstd", "fh")); args.push_back("-p" + std::string(output_path)); - if (btype != BlockType::RANDOM) + if (btype != BlockType::RANDOM) { args.push_back("--block-type=" + std::to_string(btype)); - if (btype == BlockType::RLE) args.push_back("--content-size"); + } + if (btype == BlockType::RLE) { + args.push_back("--content-size"); + } // Test payloads up to 16KB args.push_back("--max-content-size-log=14"); diff --git a/xls/modules/zstd/data_generator.h b/xls/modules/zstd/data_generator.h index b62a519940..7ba5437bc9 100644 --- a/xls/modules/zstd/data_generator.h +++ b/xls/modules/zstd/data_generator.h @@ -15,23 +15,10 @@ #ifndef XLS_MODULES_ZSTD_DATA_GENERATOR_H_ #define XLS_MODULES_ZSTD_DATA_GENERATOR_H_ -#include -#include -#include -#include -#include -#include -#include -#include +#include #include -#include "absl/time/time.h" -#include "absl/types/span.h" -#include "xls/common/file/filesystem.h" -#include "xls/common/file/get_runfile_path.h" -#include "xls/common/status/status_macros.h" -#include "xls/common/subprocess.h" -#include "xls/ir/value.h" +#include "absl/status/statusor.h" namespace xls::zstd { diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index e9a36e7111..f2345e8c7b 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -13,33 +13,37 @@ // limitations under the License. #define ZSTD_STATIC_LINKING_ONLY 1 -#include -#include +#include #include +#include #include -#include -#include -#include -#include +#include +#include +#include +#include +#include +#include #include "absl/container/flat_hash_map.h" +#include "absl/types/span.h" +#include "external/com_github_facebook_zstd/lib/common/zstd_errors.h" +#include "external/com_github_facebook_zstd/lib/zstd.h" #include "fuzztest/fuzztest.h" #include "fuzztest/googletest_fixture_adapter.h" #include "gtest/gtest.h" -#include "lib/common/zstd_errors.h" #include "xls/common/file/filesystem.h" #include "xls/common/file/get_runfile_path.h" #include "xls/common/status/matchers.h" #include "xls/dslx/create_import_data.h" #include "xls/dslx/import_data.h" -#include "xls/dslx/interp_value.h" +#include "xls/dslx/ir_convert/convert_options.h" #include "xls/dslx/ir_convert/ir_converter.h" #include "xls/dslx/parse_and_typecheck.h" #include "xls/dslx/type_system/parametric_env.h" -#include "xls/ir/events.h" -#include "xls/ir/ir_parser.h" +#include "xls/ir/bits.h" #include "xls/ir/ir_test_base.h" +#include "xls/ir/value.h" #include "xls/modules/zstd/data_generator.h" namespace xls { @@ -57,7 +61,7 @@ enum FrameHeaderStatus { class FrameHeaderTest : public xls::IrTestBase { public: // Prepare simulation environment - void SetUp() { + void SetUp() override { XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path path, xls::GetXlsRunfilePath(this->file)); XLS_ASSERT_OK_AND_ASSIGN(std::string module_text, @@ -131,10 +135,11 @@ class FrameHeaderTest : public xls::IrTestBase { // have `result` bytes, got `buffer.size()` bytes. expected_status = FrameHeaderStatus::NO_ENOUGH_DATA; } - // Make sure that the FCS does not exceed max window buffer size - // Frame Header decoding failed - Special case - difference between the reference library and the decoder + // Make sure that the FCS does not exceed max window buffer size + // Frame Header decoding failed - Special case - difference between the + // reference library and the decoder } else if (!window_size_valid(zstd_fh.windowSize)) { - expected_status = FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE; + expected_status = FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE; } auto input = CreateDSLXSimulationInput(buffer.size(), input_buffer); @@ -167,46 +172,49 @@ class FrameHeaderTest : public xls::IrTestBase { // as per https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor const uint64_t MAX_MANTISSA = 0b111; - // Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return whether given - // window_size should be accepted or discarded. - // Based on window_size calculation from: RFC 8878 + // Calculate maximal accepted window_size for given WINDOW_LOG_MAX and return + // whether given window_size should be accepted or discarded. Based on + // window_size calculation from: RFC 8878 // https://datatracker.ietf.org/doc/html/rfc8878#name-window-descriptor bool window_size_valid(uint64_t window_size) { - auto max_window_size = (1 << TEST_WINDOW_LOG_MAX_LIBZSTD) + (((1 << TEST_WINDOW_LOG_MAX_LIBZSTD) >> 3) * MAX_MANTISSA); + auto max_window_size = + (1 << TEST_WINDOW_LOG_MAX_LIBZSTD) + + (((1 << TEST_WINDOW_LOG_MAX_LIBZSTD) >> 3) * MAX_MANTISSA); - return window_size <= max_window_size; + return window_size <= max_window_size; } void PrintZSTDFrameHeader(ZSTD_frameHeader* fh) { std::cout << std::hex; std::cout << "zstd_fh->frameContentSize: 0x" << fh->frameContentSize - << std::endl; - std::cout << "zstd_fh->windowSize: 0x" << fh->windowSize << std::endl; - std::cout << "zstd_fh->blockSizeMax: 0x" << fh->blockSizeMax << std::endl; - std::cout << "zstd_fh->frameType: 0x" << fh->frameType << std::endl; - std::cout << "zstd_fh->headerSize: 0x" << fh->headerSize << std::endl; - std::cout << "zstd_fh->dictID: 0x" << fh->dictID << std::endl; - std::cout << "zstd_fh->checksumFlag: 0x" << fh->checksumFlag << std::endl; + << "\n"; + std::cout << "zstd_fh->windowSize: 0x" << fh->windowSize << "\n"; + std::cout << "zstd_fh->blockSizeMax: 0x" << fh->blockSizeMax << "\n"; + std::cout << "zstd_fh->frameType: 0x" << fh->frameType << "\n"; + std::cout << "zstd_fh->headerSize: 0x" << fh->headerSize << "\n"; + std::cout << "zstd_fh->dictID: 0x" << fh->dictID << "\n"; + std::cout << "zstd_fh->checksumFlag: 0x" << fh->checksumFlag << "\n"; } // Form DSLX Value representing ZSTD Frame header based on data parsed with // ZSTD library. Represents DSLX struct `FrameHeader`. - Value CreateExpectedFrameHeader(ZSTD_frameHeader* fh, FrameHeaderStatus expected_status) { + Value CreateExpectedFrameHeader(ZSTD_frameHeader* fh, + FrameHeaderStatus expected_status) { if (expected_status == FrameHeaderStatus::CORRUPTED || - expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) + expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) { return Value::Tuple({ /*window_size:*/ Value(UBits(0, 64)), /*frame_content_size:*/ Value(UBits(0, 64)), /*dictionary_id:*/ Value(UBits(0, 32)), /*content_checksum_flag: */ Value(UBits(0, 1)), }); - else - return Value::Tuple({ - /*window_size:*/ Value(UBits(fh->windowSize, 64)), - /*frame_content_size:*/ Value(UBits(fh->frameContentSize, 64)), - /*dictionary_id:*/ Value(UBits(fh->dictID, 32)), - /*content_checksum_flag: */ Value(UBits(fh->checksumFlag, 1)), - }); + } + return Value::Tuple({ + /*window_size:*/ Value(UBits(fh->windowSize, 64)), + /*frame_content_size:*/ Value(UBits(fh->frameContentSize, 64)), + /*dictionary_id:*/ Value(UBits(fh->dictID, 32)), + /*content_checksum_flag: */ Value(UBits(fh->checksumFlag, 1)), + }); } // Create DSLX Value representing Buffer contents after parsing frame header @@ -216,13 +224,15 @@ class FrameHeaderTest : public xls::IrTestBase { uint32_t consumed_bytes_count, FrameHeaderStatus expected_status) { // Return original buffer contents - if (expected_status == FrameHeaderStatus::NO_ENOUGH_DATA) + if (expected_status == FrameHeaderStatus::NO_ENOUGH_DATA) { return dslx_simulation_input; + } // Critical failure - return empty buffer if (expected_status == FrameHeaderStatus::CORRUPTED || - expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) + expected_status == FrameHeaderStatus::UNSUPPORTED_WINDOW_SIZE) { return Value::Tuple({/*contents:*/ Value(UBits(0, dslx_buffer_size)), /*length:*/ Value(UBits(0, 32))}); + } // Frame Header parsing succeeded. Expect output buffer contents with // removed first `consumed_bytes_count` bytes and extended to @@ -254,8 +264,9 @@ class FrameHeaderTest : public xls::IrTestBase { Value dslx_simulation_input, absl::Span input_buffer, FrameHeaderStatus expected_status) { - auto expected_buffer = CreateExpectedBuffer( - dslx_simulation_input, input_buffer, fh->headerSize, expected_status); + auto expected_buffer = + CreateExpectedBuffer(std::move(dslx_simulation_input), input_buffer, + fh->headerSize, expected_status); auto expected_frame_header = CreateExpectedFrameHeader(fh, expected_status); return Value::Tuple({/*status:*/ Value(UBits(expected_status, 2)), /*header:*/ expected_frame_header, @@ -269,7 +280,9 @@ class FrameHeaderTest : public xls::IrTestBase { size_t size = buffer_size; // ignore buffer contents that won't fit into specialized buffer - if (buffer_size > dslx_buffer_size_bytes) size = dslx_buffer_size_bytes; + if (buffer_size > dslx_buffer_size_bytes) { + size = dslx_buffer_size_bytes; + } return Value::Tuple( {/*contents:*/ Value(Bits::FromBytes(input_buffer, dslx_buffer_size)), @@ -309,18 +322,23 @@ TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughDataReservedBit) { this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedFrameContentSizeThroughSingleSegment) { +TEST_F(FrameHeaderTest, + ParseFrameHeaderFailUnsupportedFrameContentSizeThroughSingleSegment) { std::vector buffer{0261, 015, 91, 91, 91, 0364}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { - std::vector buffer{0344, 'y', ':', 0245, '=', '?', 0263, 0026, ':', 0201, 0266, 0235, 'e', 0300}; +TEST_F( + FrameHeaderTest, + ParseFrameHeaderFailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { + std::vector buffer{0344, 'y', ':', 0245, '=', '?', 0263, + 0026, ':', 0201, 0266, 0235, 'e', 0300}; this->ParseAndCompareWithZstd(buffer); } TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSize) { - std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; + std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', + 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; this->ParseAndCompareWithZstd(buffer); } @@ -346,7 +364,8 @@ INSTANTIATE_TEST_SUITE_P( class FrameHeaderFuzzTest : public fuzztest::PerFuzzTestFixtureAdapter { public: - void ParseMultipleRandomFrameHeaders(std::vector frame_header) { + void ParseMultipleRandomFrameHeaders( + const std::vector& frame_header) { this->ParseAndCompareWithZstd(frame_header); } }; diff --git a/xls/modules/zstd/zstd_dec_test.cc b/xls/modules/zstd/zstd_dec_test.cc index 423a0e5597..6d3f57f130 100644 --- a/xls/modules/zstd/zstd_dec_test.cc +++ b/xls/modules/zstd/zstd_dec_test.cc @@ -13,72 +13,98 @@ // limitations under the License. // -#include +#include +#include +#include +#include +#include +#include +#include +#include +#include #include - +#include +#include +#include +#include +#include +#include + +#include "absl/container/flat_hash_map.h" +#include "absl/status/statusor.h" +#include "absl/types/span.h" +#include "external/com_github_facebook_zstd/lib/zstd.h" #include "gtest/gtest.h" #include "xls/common/file/filesystem.h" #include "xls/common/file/get_runfile_path.h" #include "xls/common/status/matchers.h" -#include "xls/interpreter/interpreter_proc_runtime.h" +#include "xls/interpreter/channel_queue.h" #include "xls/interpreter/serial_proc_runtime.h" -#include "xls/jit/jit_proc_runtime.h" +#include "xls/ir/bits.h" +#include "xls/ir/channel.h" #include "xls/ir/events.h" #include "xls/ir/ir_parser.h" +#include "xls/ir/package.h" +#include "xls/ir/proc.h" +#include "xls/ir/value.h" +#include "xls/jit/jit_proc_runtime.h" #include "xls/modules/zstd/data_generator.h" -#include "zstd.h" namespace xls { namespace { class ZstdDecodedPacket { public: - static std::optional MakeZstdDecodedPacket(Value packet) { + static std::optional MakeZstdDecodedPacket( + const Value& packet) { // Expect tuple - if (!packet.IsTuple()) return std::nullopt; + if (!packet.IsTuple()) { + return std::nullopt; + } // Expect exactly 3 fields - if (packet.size() != 3) return std::nullopt; + if (packet.size() != 3) { + return std::nullopt; + } for (int i = 0; i < 3; i++) { // Expect fields to be Bits - if (!packet.element(i).IsBits()) return std::nullopt; + if (!packet.element(i).IsBits()) { + return std::nullopt; + } // All fields must fit in 64bits - if (!packet.element(i).bits().FitsInUint64()) return std::nullopt; + if (!packet.element(i).bits().FitsInUint64()) { + return std::nullopt; + } } std::vector data = packet.element(0).bits().ToBytes(); absl::StatusOr len = packet.element(1).bits().ToUint64(); - if (!len.ok()) return std::nullopt; + if (!len.ok()) { + return std::nullopt; + } uint64_t length = *len; bool last = packet.element(2).bits().IsOne(); return ZstdDecodedPacket(data, length, last); } - std::vector &GetData() { return data; } + std::vector& GetData() { return data; } uint64_t GetLength() { return length; } bool IsLast() { return last; } - const std::string PrintData() const { + std::string PrintData() const { std::stringstream s; for (int j = 0; j < sizeof(uint64_t) && j < data.size(); j++) { s << "0x" << std::setw(2) << std::setfill('0') << std::right << std::hex - << (unsigned int)data[j] << std::dec << ", "; + << static_cast(data[j]) << std::dec << ", "; } return s.str(); } - friend std::ostream& operator<<(std::ostream& os, - const ZstdDecodedPacket& packet) { - return os << "ZstdDecodedPacket { data: {" << packet.PrintData() - << "}, length: " << packet.length << " last: " << packet.last - << "}" << std::endl; - } - private: ZstdDecodedPacket(std::vector data, uint64_t length, bool last) - : data(data), length(length), last(last) {} + : data(std::move(data)), length(length), last(last) {} std::vector data; uint64_t length; @@ -87,15 +113,14 @@ class ZstdDecodedPacket { class ZstdDecoderTest : public ::testing::Test { public: - void SetUp() { + void SetUp() override { XLS_ASSERT_OK_AND_ASSIGN(std::filesystem::path ir_path, xls::GetXlsRunfilePath(this->ir_file)); XLS_ASSERT_OK_AND_ASSIGN(std::string ir_text, xls::GetFileContents(ir_path)); XLS_ASSERT_OK_AND_ASSIGN(this->package, xls::Parser::ParsePackage(ir_text)); - XLS_ASSERT_OK_AND_ASSIGN( - this->interpreter, - CreateJitSerialProcRuntime(this->package.get())); + XLS_ASSERT_OK_AND_ASSIGN(this->interpreter, + CreateJitSerialProcRuntime(this->package.get())); auto& queue_manager = this->interpreter->queue_manager(); XLS_ASSERT_OK_AND_ASSIGN(this->recv_queue, queue_manager.GetQueueByName( @@ -104,14 +129,14 @@ class ZstdDecoderTest : public ::testing::Test { this->send_channel_name)); } - void PrintTraceMessages(std::string pname) { + void PrintTraceMessages(const std::string& pname) { XLS_ASSERT_OK_AND_ASSIGN(Proc * proc, this->package->GetProc(pname)); const InterpreterEvents& events = this->interpreter->GetInterpreterEvents(proc); if (!events.trace_msgs.empty()) { for (const auto& tm : events.trace_msgs) { - std::cout << "[TRACE] " << tm.message << std::endl; + std::cout << "[TRACE] " << tm.message << "\n"; } } } @@ -131,46 +156,50 @@ class ZstdDecoderTest : public ::testing::Test { std::cout << "0x" << std::hex << std::setw(3) << std::left << i << std::dec << ": "; for (int j = 0; j < sizeof(uint64_t) && (i + j) < vec.size(); j++) { - std::cout << std::setfill('0') << std::setw(2) << std::hex << (unsigned int)vec[i + j] - << std::dec << " "; + std::cout << std::setfill('0') << std::setw(2) << std::hex + << static_cast(vec[i + j]) << std::dec << " "; } - std::cout << std::endl; + std::cout << "\n"; } } - void DecompressWithLibZSTD(std::vector encoded_frame, std::vector &decoded_frame) { - size_t buff_out_size = ZSTD_DStreamOutSize(); - void* const buff_out = new uint8_t[buff_out_size]; + void DecompressWithLibZSTD(std::vector encoded_frame, + std::vector& decoded_frame) { + size_t buff_out_size = ZSTD_DStreamOutSize(); + uint8_t* const buff_out = new uint8_t[buff_out_size]; - ZSTD_DCtx* const dctx = ZSTD_createDCtx(); - ASSERT_FALSE(dctx == NULL); + ZSTD_DCtx* const dctx = ZSTD_createDCtx(); + EXPECT_FALSE(dctx == nullptr); - void* const frame = static_cast(encoded_frame.data()); - size_t const frame_size = encoded_frame.size(); - // Put the whole frame in the buffer - ZSTD_inBuffer input_buffer = {frame, frame_size, 0}; + void* const frame = static_cast(encoded_frame.data()); + size_t const frame_size = encoded_frame.size(); + // Put the whole frame in the buffer + ZSTD_inBuffer input_buffer = {frame, frame_size, 0}; - while (input_buffer.pos < input_buffer.size) { - ZSTD_outBuffer output_buffer = {buff_out, buff_out_size, 0}; - size_t decomp_result = ZSTD_decompressStream(dctx, &output_buffer, &input_buffer); - bool decomp_success = ZSTD_isError(decomp_result); - ASSERT_FALSE(decomp_success); + while (input_buffer.pos < input_buffer.size) { + ZSTD_outBuffer output_buffer = {buff_out, buff_out_size, 0}; + size_t decomp_result = + ZSTD_decompressStream(dctx, &output_buffer, &input_buffer); + bool decomp_success = ZSTD_isError(decomp_result) != 0u; + EXPECT_FALSE(decomp_success); - // Append output buffer contents to output vector - decoded_frame.insert(decoded_frame.end(), (uint8_t*)output_buffer.dst, ((uint8_t*)output_buffer.dst + output_buffer.pos)); + // Append output buffer contents to output vector + decoded_frame.insert( + decoded_frame.end(), static_cast(output_buffer.dst), + (static_cast(output_buffer.dst) + output_buffer.pos)); - ASSERT_TRUE(decomp_result == 0 && output_buffer.pos < output_buffer.size); - } + EXPECT_TRUE(decomp_result == 0 && output_buffer.pos < output_buffer.size); + } - ZSTD_freeDCtx(dctx); - delete[] buff_out; + ZSTD_freeDCtx(dctx); + delete[] buff_out; } void ParseAndCompareWithZstd(std::vector frame) { std::vector lib_decomp; DecompressWithLibZSTD(frame, lib_decomp); size_t lib_decomp_size = lib_decomp.size(); - std::cout << "lib_decomp_size: " << lib_decomp_size << std::endl; + std::cout << "lib_decomp_size: " << lib_decomp_size << "\n"; std::vector sim_decomp; size_t sim_decomp_size_words = @@ -261,7 +290,8 @@ TEST_P(ZstdDecoderSeededTest, ParseMultipleFramesWithRleBlocks) { INSTANTIATE_TEST_SUITE_P( ZstdDecoderSeededTest, ZstdDecoderSeededTest, ::testing::Range(ZstdDecoderSeededTest::seed_generator_start, - ZstdDecoderSeededTest::seed_generator_start + ZstdDecoderSeededTest::random_frames_count)); + ZstdDecoderSeededTest::seed_generator_start + + ZstdDecoderSeededTest::random_frames_count)); } // namespace } // namespace xls From 210e3227719f9104ac568f08b93f84f3e1a6748d Mon Sep 17 00:00:00 2001 From: Pawel Czarnecki Date: Wed, 17 Jul 2024 11:47:07 +0200 Subject: [PATCH 42/49] [FIXUP] shorten test case names in frame_header tests Signed-off-by: Pawel Czarnecki --- xls/modules/zstd/frame_header_test.cc | 20 +++++++++----------- 1 file changed, 9 insertions(+), 11 deletions(-) diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index f2345e8c7b..7562a7e3f4 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -294,49 +294,47 @@ class FrameHeaderTest : public xls::IrTestBase { TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.4.7"); } -TEST_F(FrameHeaderTest, ParseFrameHeaderSuccess) { +TEST_F(FrameHeaderTest, Success) { std::vector buffer{0xC2, 0x09, 0xFE, 0xCA, 0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailCorruptedReservedBit) { +TEST_F(FrameHeaderTest, FailCorruptedReservedBit) { std::vector buffer{0xEA, 0xFE, 0xCA, 0xEF, 0xCD, 0xAB, 0x90, 0x78, 0x56, 0x34, 0x12}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSizeTooBig) { +TEST_F(FrameHeaderTest, FailUnsupportedWindowSizeTooBig) { std::vector buffer{0x10, 0xD3}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughData) { +TEST_F(FrameHeaderTest, FailNoEnoughData) { std::vector buffer{0xD3, 0xED}; this->ParseAndCompareWithZstd(buffer); } // NO_ENOUGH_DATA has priority over CORRUPTED from reserved bit -TEST_F(FrameHeaderTest, ParseFrameHeaderFailNoEnoughDataReservedBit) { +TEST_F(FrameHeaderTest, FailNoEnoughDataReservedBit) { std::vector buffer{0xED, 0xD3}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, - ParseFrameHeaderFailUnsupportedFrameContentSizeThroughSingleSegment) { +TEST_F(FrameHeaderTest, FailUnsupportedFrameContentSizeThroughSingleSegment) { std::vector buffer{0261, 015, 91, 91, 91, 0364}; this->ParseAndCompareWithZstd(buffer); } -TEST_F( - FrameHeaderTest, - ParseFrameHeaderFailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { +TEST_F(FrameHeaderTest, + FailUnsupportedVeryLargeFrameContentSizeThroughSingleSegment) { std::vector buffer{0344, 'y', ':', 0245, '=', '?', 0263, 0026, ':', 0201, 0266, 0235, 'e', 0300}; this->ParseAndCompareWithZstd(buffer); } -TEST_F(FrameHeaderTest, ParseFrameHeaderFailUnsupportedWindowSize) { +TEST_F(FrameHeaderTest, FailUnsupportedWindowSize) { std::vector buffer{'S', 0301, 'i', 0320, 0, 0256, 'd', 'D', 0226, 'F', 'Z', 'Z', 0332, 0370, 'A'}; this->ParseAndCompareWithZstd(buffer); From ba7eda4505b1d5029c86bda42a2b18f60da9b55a Mon Sep 17 00:00:00 2001 From: Maciej Dudek Date: Fri, 19 Jul 2024 10:15:15 +0200 Subject: [PATCH 43/49] [FIXUP] Update to zstd-top Signed-off-by: Maciej Dudek --- .github/workflows/continuous-integration.yml | 2 +- .../bundled.BUILD.bazel | 169 +++++++++++++++--- .../decodecorpus.patch | 58 +++--- dependency_support/load_external.bzl | 25 +-- xls/modules/zstd/BUILD | 9 +- xls/modules/zstd/data_generator.cc | 7 +- xls/modules/zstd/frame_header_test.cc | 13 +- xls/modules/zstd/zstd_dec_test.cc | 6 +- 8 files changed, 201 insertions(+), 88 deletions(-) diff --git a/.github/workflows/continuous-integration.yml b/.github/workflows/continuous-integration.yml index 3c24746ef6..a779dc5b3b 100644 --- a/.github/workflows/continuous-integration.yml +++ b/.github/workflows/continuous-integration.yml @@ -41,4 +41,4 @@ jobs: - name: Bazel Test All (opt) run: | - bazel test -c opt --noshow_progress --test_output=errors -- //xls/... + bazel test -c opt --noshow_progress --test_output=errors -- //... diff --git a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel index f175f30cc0..037ba422b7 100644 --- a/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel +++ b/dependency_support/com_github_facebook_zstd/bundled.BUILD.bazel @@ -1,4 +1,4 @@ -# Copyright 2023 The XLS Authors +# 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. @@ -12,44 +12,167 @@ # See the License for the specific language governing permissions and # limitations under the License. -""" -Builds everything together similarly to the zstd cmake file: -https://github.com/facebook/zstd/blob/dev/build/cmake/lib/CMakeLists.txt -but with legacy support and defines as in the zstd BUCK file: -https://github.com/facebook/zstd/blob/dev/lib/BUCK +""" Builds zstd. """ -package(default_visibility = ["//visibility:public"]) +load("@rules_cc//cc:defs.bzl", "cc_binary", "cc_library", "cc_test") -licenses(["notice"]) +package(default_visibility = ["//visibility:public"]) -cc_library( - name = "zstd", +filegroup( + name = "common_sources", srcs = glob([ - "lib/common/*.h", "lib/common/*.c", - "lib/compress/*.h", + "lib/common/*.h", + ]), +) + +filegroup( + name = "compress_sources", + srcs = glob([ "lib/compress/*.c", - "lib/decompress/*.h", + "lib/compress/*.h", + ]), +) + +filegroup( + name = "decompress_sources", + srcs = glob([ "lib/decompress/*.c", - "lib/decompress/*.S", - "lib/deprecated/*.h", - "lib/deprecated/*.c", - "lib/dictBuilder/*.h", + "lib/decompress/*.h", + ]) + select({ + "@platforms//os:windows": [], + "//conditions:default": glob(["lib/decompress/*.S"]), + }), +) + +filegroup( + name = "dictbuilder_sources", + srcs = glob([ "lib/dictBuilder/*.c", - "lib/legacy/*.h", - "lib/legacy/*.c", + "lib/dictBuilder/*.h", ]), +) + +cc_library( + name = "zstd", + srcs = [ + ":common_sources", + ":compress_sources", + ":decompress_sources", + ":dictbuilder_sources", + ], hdrs = [ + "lib/zdict.h", "lib/zstd.h", - "lib/common/zstd_errors.h", + "lib/zstd_errors.h", ], - strip_include_prefix = "lib", + includes = ["lib"], + linkopts = ["-pthread"], + linkstatic = True, local_defines = [ - "ZSTD_LEGACY_SUPPORT=4", "XXH_NAMESPACE=ZSTD_", + "ZSTD_MULTITHREAD", + "ZSTD_BUILD_SHARED=OFF", + "ZSTD_BUILD_STATIC=ON", + ] + select({ + "@platforms//os:windows": ["ZSTD_DISABLE_ASM"], + "//conditions:default": [], + }), +) + +cc_binary( + name = "zstd_cli", + srcs = glob( + include = [ + "programs/*.c", + "programs/*.h", + ], + exclude = [ + "programs/datagen.c", + "programs/datagen.h", + "programs/platform.h", + "programs/util.h", + ], + ), + deps = [ + ":datagen", + ":util", + ":zstd", + ], +) + +cc_library( + name = "util", + srcs = [ + "programs/platform.h", + "programs/util.c", + ], + hdrs = [ + "lib/common/compiler.h", + "lib/common/debug.h", + "lib/common/mem.h", + "lib/common/portability_macros.h", + "lib/common/zstd_deps.h", + "programs/util.h", + ], +) + +cc_library( + name = "datagen", + srcs = [ + "programs/datagen.c", + "programs/platform.h", + ], + hdrs = ["programs/datagen.h"], + deps = [":util"], +) + +cc_binary( + name = "datagen_cli", + srcs = [ + "programs/lorem.c", + "programs/lorem.h", + "tests/datagencli.c", + "tests/loremOut.c", + "tests/loremOut.h", + ], + includes = [ + "programs", + "tests", + ], + deps = [":datagen"], +) + +cc_test( + name = "fullbench", + srcs = [ + "lib/decompress/zstd_decompress_internal.h", + "programs/benchfn.c", + "programs/benchfn.h", + "programs/benchzstd.c", + "programs/benchzstd.h", + "programs/lorem.c", + "programs/lorem.h", + "programs/timefn.c", + "programs/timefn.h", + "tests/fullbench.c", + "tests/loremOut.c", + "tests/loremOut.h", + ], + copts = select({ + "@platforms//os:windows": [], + "//conditions:default": ["-Wno-deprecated-declarations"], + }), + includes = [ + "lib/common", + "programs", + "tests", + ], + deps = [ + ":datagen", + ":zstd", ], - visibility = ["//visibility:public"], ) # NOTE: Required because of direct zstd_compress.c include in decodecorpus sources diff --git a/dependency_support/com_github_facebook_zstd/decodecorpus.patch b/dependency_support/com_github_facebook_zstd/decodecorpus.patch index 99d724fdec..131e186b16 100644 --- a/dependency_support/com_github_facebook_zstd/decodecorpus.patch +++ b/dependency_support/com_github_facebook_zstd/decodecorpus.patch @@ -1,11 +1,11 @@ diff --git tests/decodecorpus.c tests/decodecorpus.c -index 50935d31..c529fbfe 100644 +index 1abc7df8..f25e5dc7 100644 --- tests/decodecorpus.c +++ tests/decodecorpus.c -@@ -240,6 +240,12 @@ typedef enum { - gt_block, /* generate compressed blocks without block/frame headers */ - } genType_e; - +@@ -247,6 +247,12 @@ typedef enum { + #define MIN(a, b) ((a) < (b) ? (a) : (b)) + #endif + +typedef enum { + lt_raw, + lt_rle, @@ -15,8 +15,8 @@ index 50935d31..c529fbfe 100644 /*-******************************************************* * Global variables (set from command line) *********************************************************/ -@@ -252,7 +258,11 @@ U32 g_maxBlockSize = ZSTD_BLOCKSIZE_MAX; /* <= 128 KB */ - +@@ -259,7 +265,11 @@ U32 g_maxBlockSize = ZSTD_BLOCKSIZE_MAX; /* <= 128 KB */ + struct { int contentSize; /* force the content size to be present */ -} opts; /* advanced options on generation */ @@ -25,11 +25,11 @@ index 50935d31..c529fbfe 100644 + int frame_header_only; /* generate only frame header */ + int no_magic; /* do not generate magic number */ +} opts; - + /* Generate and write a random frame header */ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) -@@ -281,10 +291,19 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) - +@@ -288,10 +298,19 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) + { /* Generate random content size */ + int force_block_type = opts.blockType != NULL; @@ -48,9 +48,9 @@ index 50935d31..c529fbfe 100644 } else if (RAND(seed) & 3) { /* do small content */ highBit = 1ULL << RAND_range(seed, 0, MIN(7, 1U << g_maxDecompressedSizeLog)); -@@ -317,8 +336,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -324,8 +343,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) } - + /* write out the header */ - MEM_writeLE32(op + pos, ZSTD_MAGICNUMBER); - pos += 4; @@ -58,10 +58,10 @@ index 50935d31..c529fbfe 100644 + MEM_writeLE32(op + pos, ZSTD_MAGICNUMBER); + pos += 4; + } - + { /* -@@ -363,8 +384,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) +@@ -370,8 +391,10 @@ static void writeFrameHeader(U32* seed, frame_t* frame, dictInfo info) /* Write a literal block in either raw or RLE form, return the literals size */ static size_t writeLiteralsBlockSimple(U32* seed, frame_t* frame, size_t contentSize) { @@ -73,8 +73,8 @@ index 50935d31..c529fbfe 100644 int const sizeFormatDesc = RAND(seed) % 8; size_t litSize; size_t maxLitSize = MIN(contentSize, g_maxBlockSize); -@@ -612,8 +635,15 @@ static size_t writeLiteralsBlockCompressed(U32* seed, frame_t* frame, size_t con - +@@ -619,8 +642,15 @@ static size_t writeLiteralsBlockCompressed(U32* seed, frame_t* frame, size_t con + static size_t writeLiteralsBlock(U32* seed, frame_t* frame, size_t contentSize) { - /* only do compressed for larger segments to avoid compressibility issues */ @@ -91,7 +91,7 @@ index 50935d31..c529fbfe 100644 return writeLiteralsBlockCompressed(seed, frame, contentSize); } else { return writeLiteralsBlockSimple(seed, frame, contentSize); -@@ -1030,7 +1060,8 @@ static size_t writeCompressedBlock(U32* seed, frame_t* frame, size_t contentSize +@@ -1034,7 +1064,8 @@ static size_t writeCompressedBlock(U32* seed, frame_t* frame, size_t contentSize static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, int lastBlock, dictInfo info) { @@ -100,20 +100,20 @@ index 50935d31..c529fbfe 100644 + int const blockTypeDesc = (force_block_type) ? *(opts.blockType) : RAND(seed) % 8; size_t blockSize; int blockType; - -@@ -1069,7 +1100,7 @@ static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, - + +@@ -1073,7 +1104,7 @@ static void writeBlock(U32* seed, frame_t* frame, size_t contentSize, + frame->data = op; compressedSize = writeCompressedBlock(seed, frame, contentSize, info); - if (compressedSize >= contentSize) { /* compressed block must be strictly smaller than uncompressed one */ + if (compressedSize >= contentSize && !force_block_type) { /* compressed block must be strictly smaller than uncompressed one */ blockType = 0; memcpy(op, frame->src, contentSize); - -@@ -1240,7 +1271,11 @@ static U32 generateFrame(U32 seed, frame_t* fr, dictInfo info) + +@@ -1244,7 +1275,11 @@ static U32 generateFrame(U32 seed, frame_t* fr, dictInfo info) DISPLAYLEVEL(3, "frame seed: %u\n", (unsigned)seed); initFrame(fr); - + + writeFrameHeader(&seed, fr, info); + if (opts.frame_header_only) @@ -121,8 +121,8 @@ index 50935d31..c529fbfe 100644 + writeBlocks(&seed, fr, info); writeChecksum(fr); - -@@ -1768,6 +1803,9 @@ static void advancedUsage(const char* programName) + +@@ -1772,6 +1807,9 @@ static void advancedUsage(const char* programName) DISPLAY( " --max-block-size-log=# : max block size log, must be in range [2, 17]\n"); DISPLAY( " --max-content-size-log=# : max content size log, must be <= 20\n"); DISPLAY( " (this is ignored with gen-blocks)\n"); @@ -130,9 +130,9 @@ index 50935d31..c529fbfe 100644 + DISPLAY( " --frame-header-only : dump only frame header\n"); + DISPLAY( " --no-magic : do not add magic number\n"); } - + /*! readU32FromChar() : -@@ -1889,6 +1927,18 @@ int main(int argc, char** argv) +@@ -1893,6 +1931,18 @@ int main(int argc, char** argv) U32 value = readU32FromChar(&argument); g_maxDecompressedSizeLog = MIN(MAX_DECOMPRESSED_SIZE_LOG, value); @@ -151,10 +151,10 @@ index 50935d31..c529fbfe 100644 } else { advancedUsage(argv[0]); return 1; -@@ -1900,6 +1950,18 @@ int main(int argc, char** argv) +@@ -1904,6 +1954,18 @@ int main(int argc, char** argv) return 1; } } } } /* for (argNb=1; argNb #include #include -#include +#include // NOLINT #include #include #include @@ -76,9 +76,8 @@ static absl::StatusOr CallDecodecorpus( absl::Span args, const std::optional& cwd = std::nullopt, std::optional timeout = std::nullopt) { - XLS_ASSIGN_OR_RETURN( - std::filesystem::path path, - xls::GetXlsRunfilePath("external/com_github_facebook_zstd/decodecorpus")); + XLS_ASSIGN_OR_RETURN(std::filesystem::path path, + xls::GetXlsRunfilePath("external/zstd/decodecorpus")); std::vector cmd = {path}; cmd.insert(cmd.end(), args.begin(), args.end()); diff --git a/xls/modules/zstd/frame_header_test.cc b/xls/modules/zstd/frame_header_test.cc index 7562a7e3f4..6785c38121 100644 --- a/xls/modules/zstd/frame_header_test.cc +++ b/xls/modules/zstd/frame_header_test.cc @@ -18,7 +18,7 @@ #include #include #include -#include +#include // NOLINT #include #include #include @@ -27,13 +27,12 @@ #include "absl/container/flat_hash_map.h" #include "absl/types/span.h" -#include "external/com_github_facebook_zstd/lib/common/zstd_errors.h" -#include "external/com_github_facebook_zstd/lib/zstd.h" -#include "fuzztest/fuzztest.h" -#include "fuzztest/googletest_fixture_adapter.h" +#include "external/zstd/lib/zstd.h" +#include "external/zstd/lib/zstd_errors.h" #include "gtest/gtest.h" #include "xls/common/file/filesystem.h" #include "xls/common/file/get_runfile_path.h" +#include "xls/common/fuzzing/fuzztest.h" #include "xls/common/status/matchers.h" #include "xls/dslx/create_import_data.h" #include "xls/dslx/import_data.h" @@ -163,7 +162,7 @@ class FrameHeaderTest : public xls::IrTestBase { const uint32_t dslx_buffer_size_bytes = (dslx_buffer_size + CHAR_BIT - 1) / CHAR_BIT; // Largest allowed WindowLog accepted by libzstd decompression function - // https://github.com/facebook/zstd/blob/v1.4.7/lib/decompress/zstd_decompress.c#L296 + // https://github.com/facebook/zstd/blob/v1.5.6/lib/decompress/zstd_decompress.c#L515 // Use only in C++ tests when comparing DSLX ZSTD Decoder with libzstd // Must be in sync with TEST_WINDOW_LOG_MAX_LIBZSTD in frame_header_test.x const uint64_t TEST_WINDOW_LOG_MAX_LIBZSTD = 30; @@ -292,7 +291,7 @@ class FrameHeaderTest : public xls::IrTestBase { /* TESTS */ -TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.4.7"); } +TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.5.6"); } TEST_F(FrameHeaderTest, Success) { std::vector buffer{0xC2, 0x09, 0xFE, 0xCA, 0xEF, 0xCD, diff --git a/xls/modules/zstd/zstd_dec_test.cc b/xls/modules/zstd/zstd_dec_test.cc index 6d3f57f130..0ed0b91fb8 100644 --- a/xls/modules/zstd/zstd_dec_test.cc +++ b/xls/modules/zstd/zstd_dec_test.cc @@ -17,7 +17,7 @@ #include #include #include -#include +#include // NOLINT #include #include #include @@ -33,7 +33,7 @@ #include "absl/container/flat_hash_map.h" #include "absl/status/statusor.h" #include "absl/types/span.h" -#include "external/com_github_facebook_zstd/lib/zstd.h" +#include "external/zstd/lib/zstd.h" #include "gtest/gtest.h" #include "xls/common/file/filesystem.h" #include "xls/common/file/get_runfile_path.h" @@ -247,7 +247,7 @@ class ZstdDecoderTest : public ::testing::Test { /* TESTS */ -TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.4.7"); } +TEST(ZstdLib, Version) { ASSERT_EQ(ZSTD_VERSION_STRING, "1.5.6"); } TEST_F(ZstdDecoderTest, ParseFrameWithRawBlocks) { int seed = 3; // Arbitrary seed value for small ZSTD frame From 7a56110971e94a0fad3dce8b7fc8823a87cac468 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Fri, 10 May 2024 15:21:33 +0200 Subject: [PATCH 44/49] modules/zstd: Add literals dispatcher Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 82 ++++++++++ xls/modules/zstd/common.x | 48 ++++++ xls/modules/zstd/literals_dispatcher.x | 216 +++++++++++++++++++++++++ 3 files changed, 346 insertions(+) create mode 100644 xls/modules/zstd/literals_dispatcher.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 3b0a3dc197..7206878f78 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1065,3 +1065,85 @@ place_and_route( tags = ["manual"], target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "literals_dispatcher_dslx", + srcs = [ + "literals_dispatcher.x", + ], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "literals_dispatcher_dslx_test", + library = ":literals_dispatcher_dslx", +) + +xls_dslx_verilog( + name = "literals_dispatcher_verilog", + codegen_args = { + "module_name": "LiteralsDispatcher", + "delay_model": "asap7", + "pipeline_stages": "1", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "LiteralsDispatcher", + library = ":literals_dispatcher_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__literals_dispatcher__LiteralsDispatcher_0_next", + }, + verilog_file = "literals_dispatcher.v", +) + +xls_benchmark_ir( + name = "literals_dispatcher_opt_ir_benchmark", + src = ":literals_dispatcher_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "1", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "literals_dispatcher_verilog_benchmark", + verilog_target = "literals_dispatcher_verilog", +) + +verilog_library( + name = "literals_dispatcher_verilog_lib", + srcs = [ + ":literals_dispatcher.v", + ], +) + +synthesize_rtl( + name = "literals_dispatcher_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "LiteralsDispatcher", + deps = [ + ":literals_dispatcher_verilog_lib", + ], +) + +benchmark_synth( + name = "literals_dispatcher_benchmark_synth", + synth_target = ":literals_dispatcher_synth_asap7", +) + +place_and_route( + name = "literals_dispatcher_place_and_route", + clock_period = "750", + core_padding_microns = 2, + die_height_microns = 50, + die_width_microns = 50, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":literals_dispatcher_synth_asap7", + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 8c6b1f1c5d..899c852d95 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -12,6 +12,8 @@ // See the License for the specific language governing permissions and // limitations under the License. +import std; + pub const DATA_WIDTH = u32:64; pub const MAX_ID = u32::MAX; pub const SYMBOL_WIDTH = u32:8; @@ -65,3 +67,49 @@ pub struct ZstdDecodedPacket { length: BlockPacketLength, // valid bits in data last: bool, // Last decoded packet in frame } + +// Literals decoding + +pub const RLE_LITERALS_DATA_WIDTH = u32:8; +pub const RLE_LITERALS_REPEAT_WIDTH = u32:20; +pub const LITERALS_DATA_WIDTH = u32:64; +pub const LITERALS_LENGTH_WIDTH = std::clog2( + std::ceil_div(LITERALS_DATA_WIDTH, RLE_LITERALS_DATA_WIDTH) + u32:1 +); + +pub type RleLitData = uN[RLE_LITERALS_DATA_WIDTH]; +pub type RleLitRepeat = uN[RLE_LITERALS_REPEAT_WIDTH]; +pub type LitData = uN[LITERALS_DATA_WIDTH]; +pub type LitLength = uN[LITERALS_LENGTH_WIDTH]; + +pub enum LiteralType: u3 { + RAW = 0, + RLE = 1, + COMP = 2, + COMP_4 = 3, + TREELESS = 4, + TREELESS_4 = 5, +} + +pub struct Streams { + count: bits[2], + stream_lengths: bits[20][4], +} + +pub struct LiteralsPathCtrl { + data_conf: Streams, + decompressed_size: u20, + literals_type: LiteralType, +} + +pub struct RleLiteralsData { + data: RleLitData, + repeat: RleLitRepeat, + last: bool, +} + +pub struct LiteralsData { + data: LitData, + length: LitLength, + last: bool, +} diff --git a/xls/modules/zstd/literals_dispatcher.x b/xls/modules/zstd/literals_dispatcher.x new file mode 100644 index 0000000000..e10181d1b8 --- /dev/null +++ b/xls/modules/zstd/literals_dispatcher.x @@ -0,0 +1,216 @@ +// 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 the implementation of LiteralsDispatcher responsible for +// dispatching ZSTD Literals. More information about Literals' format can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.1 + +import xls.modules.zstd.common; + +type LiteralsPathCtrl = common::LiteralsPathCtrl; +type LiteralsData = common::LiteralsData; +type RleLiteralsData = common::RleLiteralsData; +type LiteralType = common::LiteralType; +type Streams = common::Streams; + +type RleLitData = common::RleLitData; +type RleLitRepeat = common::RleLitRepeat; +type LitData = common::LitData; +type LitLength = common::LitLength; + +struct LiteralsDispatcherState { + // literals type received from ctrl channel + literals_type: LiteralType, + // number of literals to be read. The initial value is received + // from ctrl channel and decreased after each read from literals channel + left_to_read: u20, +} + +pub proc LiteralsDispatcher { + literals_ctrl_r: chan in; + literals_data_r: chan in; + raw_literals_s: chan out; + rle_literals_s: chan out; + huff_literals_s: chan out; + + config ( + literals_ctrl_r: chan in, + literals_data_r: chan in, + raw_literals_s: chan out, + rle_literals_s: chan out, + huff_literals_s: chan out, + ) { + ( + literals_ctrl_r, + literals_data_r, + raw_literals_s, + rle_literals_s, + huff_literals_s, + ) + } + + init { zero!() } + + next(tok: token, state: LiteralsDispatcherState ) { + + let do_recv_ctrl = (state.left_to_read == u20:0); + let (tok, literals_path_ctrl) = recv_if(tok, literals_ctrl_r, do_recv_ctrl, zero!()); + + let (literals_type, left_to_read) = if do_recv_ctrl { + (literals_path_ctrl.literals_type, literals_path_ctrl.decompressed_size) + } else { + (state.literals_type, state.left_to_read) + }; + + // RLE literals consist of single byte + let (tok, literals_data) = recv(tok, literals_data_r); + let left_to_read = if (literals_type == LiteralType::RLE) { + u20:0 + } else { + left_to_read - (literals_data.length as u20) + }; + + let tok = send_if(tok, raw_literals_s, LiteralType::RAW == literals_type, literals_data); + + let rle_literals_data = RleLiteralsData { + data: (literals_data.data as u8), repeat: literals_path_ctrl.decompressed_size, last: literals_data.last + }; + let tok = send_if(tok, rle_literals_s, LiteralType::RLE == literals_type, rle_literals_data); + + let do_send_huff = ( + LiteralType::COMP == literals_type || LiteralType::COMP_4 == literals_type || + LiteralType::TREELESS == literals_type || LiteralType::TREELESS_4 == literals_type + ); + assert!(do_send_huff, "Huffmann coding not implemented yet"); + let tok = send_if(tok, huff_literals_s, false, zero!()); + + LiteralsDispatcherState { + literals_type: literals_type, + left_to_read: left_to_read, + } + } +} + +#[test_proc] +proc LiteralsDispatcher_test { + terminator: chan out; + literals_ctrl_s: chan out; + literals_data_s: chan out; + raw_literals_r: chan in; + rle_literals_r: chan in; + huff_literals_r: chan in; + + config(terminator: chan out) { + let (literals_ctrl_s, literals_ctrl_r) = chan("literals_ctrl"); + let (literals_data_s, literals_data_r) = chan("literals_data"); + let (raw_literals_s, raw_literals_r) = chan("raw_literals"); + let (rle_literals_s, rle_literals_r) = chan("rle_literals"); + let (huff_literals_s, huff_literals_r) = chan("huff_literals"); + + spawn LiteralsDispatcher( + literals_ctrl_r, + literals_data_r, + raw_literals_s, + rle_literals_s, + huff_literals_s, + ); + + ( + terminator, + literals_ctrl_s, + literals_data_s, + raw_literals_r, + rle_literals_r, + huff_literals_r, + ) + } + + init { } + + next(tok:token, state: ()) { + let test_ctrl: LiteralsPathCtrl[6] = [ + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:8, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:4, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:13, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:15, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:123, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:31, literals_type: LiteralType::RAW}, + ]; + let test_data: LiteralsData[10] = [ + // 0. RAW + LiteralsData {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, last: false}, + // 1. RLE + LiteralsData {data: LitData:0x23, length: LitLength:1, last: false}, + // 2. RLE + LiteralsData {data: LitData:0x35, length: LitLength:1, last: true}, + // 3. RAW + LiteralsData {data: LitData:0x4CFB_41C6_7B60_5370, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x009B_0F9C_E1BA_A96D, length: LitLength:7, last: false}, + // 4. RLE + LiteralsData {data: LitData:0x5A, length: LitLength:1, last: false}, + // 5. RAW + LiteralsData {data: LitData:0x6094_3E96_1834_C247, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xBC02_D0E8_D728_9ABE, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xFC19_63F1_CE21_C294, length: LitLength:7, last: true}, + ]; + let expected_raw: LiteralsData[7] = [ + // 0. + LiteralsData {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, last: false}, + // 3. + LiteralsData {data: LitData:0x4CFB_41C6_7B60_5370, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x009B_0F9C_E1BA_A96D, length: LitLength:7, last: false}, + // 5. + LiteralsData {data: LitData:0x6094_3E96_1834_C247, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xBC02_D0E8_D728_9ABE, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xFC19_63F1_CE21_C294, length: LitLength:7, last: true}, + ]; + let expected_rle: RleLiteralsData[3] = [ + // 1. + RleLiteralsData {data: RleLitData:0x23, repeat: RleLitRepeat:4, last: false}, + // 2. + RleLiteralsData {data: RleLitData:0x35, repeat: RleLitRepeat:13, last: true}, + // 4. + RleLiteralsData {data: RleLitData:0x5A, repeat: RleLitRepeat:123, last: false}, + ]; + let tok = for ((counter, test_ctrl), tok): ((u32, LiteralsPathCtrl), token) in enumerate(test_ctrl) { + let tok = send(tok, literals_ctrl_s, test_ctrl); + trace_fmt!("Send #{} literals ctrl, {:#x}", counter + u32:1, test_ctrl); + (tok) + }(tok); + + let tok = for ((counter, test_data), tok): ((u32, LiteralsData), token) in enumerate(test_data) { + let tok = send(tok, literals_data_s, test_data); + trace_fmt!("Send #{} literals data, {:#x}", counter + u32:1, test_data); + (tok) + }(tok); + + let tok_1 = for ((counter, expected_raw), tok_1): ((u32, LiteralsData), token) in enumerate(expected_raw) { + let (tok_1, raw) = recv(tok_1, raw_literals_r); + trace_fmt!("Recv #{} raw literals, {:#x}", counter + u32:1, raw); + assert_eq(expected_raw, raw); + (tok_1) + }(tok); + + let tok_2 = for ((counter, expected_rle), tok_2): ((u32, RleLiteralsData), token) in enumerate(expected_rle) { + let (tok_2, rle) = recv(tok_2, rle_literals_r); + trace_fmt!("Recv #{} rle literals, {:#x}", counter + u32:1, rle); + assert_eq(expected_rle, rle); + (tok_2) + }(tok); + + send(tok, terminator, true); + } +} From dbb2f04e54b628688c06dd4e1bab2e256471de33 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Thu, 9 May 2024 14:58:44 +0200 Subject: [PATCH 45/49] modules/zstd: Add RLE literals decoder implementation Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 77 ++++++ xls/modules/zstd/rle_literals_dec.x | 394 ++++++++++++++++++++++++++++ 2 files changed, 471 insertions(+) create mode 100644 xls/modules/zstd/rle_literals_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 7206878f78..26107937bc 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1147,3 +1147,80 @@ place_and_route( synthesized_rtl = ":literals_dispatcher_synth_asap7", target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "rle_literals_dec_dslx", + srcs = [ + "rle_literals_dec.x", + ], + deps = [ + ":common_dslx", + "//xls/modules/rle:rle_common_dslx", + "//xls/modules/rle:rle_dec_dslx", + ], +) + +xls_dslx_test( + name = "rle_literals_dec_dslx_test", + library = ":rle_literals_dec_dslx", +) + +xls_dslx_verilog( + name = "rle_literals_dec_verilog", + codegen_args = { + "module_name": "rle_literals_dec", + "delay_model": "asap7", + "pipeline_stages": "2", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "RleLiteralsDecoder", + library = ":rle_literals_dec_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__rle_literals_dec__RleLiteralsDecoder__BatchPacker_0_next", + }, + verilog_file = "rle_literals_dec.v", +) + +xls_benchmark_ir( + name = "rle_literals_dec_opt_ir_benchmark", + src = ":rle_literals_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "2", + "delay_model": "asap7", + }, +) + +verilog_library( + name = "rle_literals_dec_verilog_lib", + srcs = [ + ":rle_literals_dec.v", + ], +) + +synthesize_rtl( + name = "rle_literals_dec_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "rle_literals_dec", + deps = [ + ":rle_literals_dec_verilog_lib", + ], +) + +benchmark_synth( + name = "rle_literals_dec_benchmark_synth", + synth_target = ":rle_literals_dec_synth_asap7", +) + +place_and_route( + name = "rle_literals_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":rle_literals_dec_synth_asap7", + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/rle_literals_dec.x b/xls/modules/zstd/rle_literals_dec.x new file mode 100644 index 0000000000..cdd3ef51f2 --- /dev/null +++ b/xls/modules/zstd/rle_literals_dec.x @@ -0,0 +1,394 @@ +// 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 the implementation of RleLiteralsDecoder responsible for decoding +// ZSTD RLE Literals. More information about Rle Literals's format can be found in: +// https://datatracker.ietf.org/doc/html/rfc8878#section-3.1.1.3.1 + +import std; + +import xls.modules.zstd.common; +import xls.modules.rle.rle_dec; +import xls.modules.rle.rle_common; + +const RLE_LITERALS_DATA_WIDTH = common::RLE_LITERALS_DATA_WIDTH; +const RLE_LITERALS_REPEAT_WIDTH = common::RLE_LITERALS_REPEAT_WIDTH; +const LITERALS_DATA_WIDTH = common::LITERALS_DATA_WIDTH; +const LITERALS_LENGTH_WIDTH = common::LITERALS_LENGTH_WIDTH; + +type RleInput = rle_common::CompressedData; +type RleOutput = rle_common::PlainData; +type RleLiteralsData = common::RleLiteralsData; +type LiteralsData = common::LiteralsData; + +type RleLitData = common::RleLitData; +type RleLitRepeat = common::RleLitRepeat; +type LitData = common::LitData; +type LitLength = common::LitLength; + +struct LiteralsSyncData { + count: RleLitRepeat, + last: bool, +} + +proc RleDataPacker { + literals_data_r: chan in; + rle_data_s: chan out; + sync_s: chan out; + + config( + literals_data_r: chan in, + rle_data_s: chan out, + sync_s: chan out, + ) { + (literals_data_r, rle_data_s, sync_s) + } + + init { } + + next(tok: token, state: ()) { + let (tok, input) = recv(tok, literals_data_r); + let not_zero_repeat = (input.repeat != RleLitRepeat:0); + let rle_dec_data = RleInput { symbol: input.data, count: input.repeat, last: true }; + let data_tok = send_if(tok, rle_data_s, not_zero_repeat, rle_dec_data); + let sync_data = LiteralsSyncData { count: input.repeat, last: input.last }; + let sync_tok = send_if(data_tok, sync_s, not_zero_repeat || input.last, sync_data); + } +} + +#[test_proc] +proc RleDataPacker_test { + terminator: chan out; + in_s: chan out; + out_r: chan in; + sync_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("out"); + let (sync_s, sync_r) = chan("sync"); + + spawn RleDataPacker(in_r, out_s, sync_s); + + (terminator, in_s, out_r, sync_r) + } + + init { } + + next(tok: token, state: ()) { + let test_data: RleLiteralsData[5] = [ + RleLiteralsData {data: RleLitData:0xAB, repeat: RleLitRepeat:11, last: bool:0}, + RleLiteralsData {data: RleLitData:0xCD, repeat: RleLitRepeat:3, last: bool:0}, + RleLiteralsData {data: RleLitData:0x12, repeat: RleLitRepeat:16, last: bool:0}, + RleLiteralsData {data: RleLitData:0x34, repeat: RleLitRepeat:20, last: bool:0}, + RleLiteralsData {data: RleLitData:0x56, repeat: RleLitRepeat:2, last: bool:1}, + ]; + + let tok = for ((counter, test_data), tok): ((u32, RleLiteralsData), token) in enumerate(test_data) { + let expected_data_out = RleInput { + symbol: test_data.data, + count: test_data.repeat, + last: true, + }; + + let expected_sync_out = LiteralsSyncData { + count: test_data.repeat, + last: test_data.last, + }; + + let tok = send(tok, in_s, test_data); + trace_fmt!("Send #{} rle literals data, {:#x}", counter + u32:1, test_data); + + let (tok, data_out) = recv(tok, out_r); + trace_fmt!("Received #{} rle input data, {:#x}", counter + u32:1, data_out); + assert_eq(data_out, expected_data_out); + + let (tok, sync_out) = recv(tok, sync_r); + trace_fmt!("Received #{} sync data, {:#x}", counter + u32:1, sync_out); + assert_eq(sync_out, expected_sync_out); + + (tok) + }(tok); + + send(tok, terminator, true); + } +} + +struct BatchPackerState { + batch: LitData, + data_in_batch: LitLength, + count_left: RleLitRepeat, + sync_last: bool, +} + +// auxiliary variable used to replace multiplication with shifts +const_assert!(std::is_pow2(RLE_LITERALS_DATA_WIDTH)); +const RLE_LITERALS_DATA_WIDTH_SHIFT = std::clog2(RLE_LITERALS_DATA_WIDTH); + +proc BatchPacker { + rle_data_r: chan in; + sync_r: chan in; + literals_data_s: chan out; + + config( + rle_data_r: chan in, + sync_r: chan in, + literals_data_s: chan out, + ) { + (rle_data_r, sync_r, literals_data_s) + } + + init { zero!() } + + next(tok: token, state: BatchPackerState) { + let no_count_left = (state.count_left == RleLitRepeat:0); + let (tok, sync_data) = recv_if(tok, sync_r, no_count_left, zero!()); + let (count_left, sync_last) = if (no_count_left) { + (sync_data.count, sync_data.last) + } else { + (state.count_left, state.sync_last) + }; + + let (literals_data, do_send_batch, state) = if (count_left != RleLitRepeat:0) { + let (tok, decoded_data) = recv(tok, rle_data_r); + + let data_in_batch = state.data_in_batch; + // shift batch and append new symbol + let shift = (data_in_batch as u32) << RLE_LITERALS_DATA_WIDTH_SHIFT; + + let batch = state.batch | ((decoded_data.symbol as LitData) << shift); + let data_in_batch = data_in_batch + LitLength:1; + // send batch if it is the last batch or it is full + let do_send_batch = ( + decoded_data.last + | (((data_in_batch as u32 + u32:1) << RLE_LITERALS_DATA_WIDTH_SHIFT) > LITERALS_DATA_WIDTH) + ); + let literals_data = LiteralsData { + data: batch, + length: data_in_batch, + last: sync_last && decoded_data.last, + }; + + let state = if do_send_batch { + BatchPackerState { + count_left: count_left - RleLitRepeat:1, + sync_last: sync_last, + ..zero!() + } + } else { + BatchPackerState { + batch: batch, + data_in_batch: data_in_batch, + count_left: count_left - RleLitRepeat:1, + sync_last: sync_last, + } + }; + + (literals_data, do_send_batch, state) + } else if (sync_data.last) { + // handle empty literal with last set + ( + LiteralsData {last: true, ..zero!()}, + true, + BatchPackerState { + count_left: count_left, + sync_last: sync_last, + ..state + }, + ) + } else { + // handle empty literal with last not set + ( + zero!(), + false, + BatchPackerState { + count_left: count_left, + sync_last: sync_last, + ..state + }, + ) + }; + + let data_tok = send_if(tok, literals_data_s, do_send_batch, literals_data); + + state + } +} + +#[test_proc] +proc BatchPacker_test { + terminator: chan out; + in_s: chan out; + sync_s: chan out; + out_r: chan in; + + config(terminator: chan out) { + let (in_s, in_r) = chan("in"); + let (sync_s, sync_r) = chan("sync"); + let (out_s, out_r) = chan("out"); + + spawn BatchPacker(in_r, sync_r, out_s); + + (terminator, in_s, sync_s, out_r) + } + + init { } + + next(tok: token, state: ()) { + let test_sync_data: LiteralsSyncData[4] = [ + LiteralsSyncData {count: RleLitRepeat:1, last: false}, + LiteralsSyncData {count: RleLitRepeat:8, last: false}, + LiteralsSyncData {count: RleLitRepeat:10, last: false}, + LiteralsSyncData {count: RleLitRepeat:13, last: true}, + ]; + let test_rle_data: RleOutput[32] = [ + // 1st literal + RleOutput {symbol: RleLitData:0x11, last: true}, + // 2nd literal + RleOutput {symbol: RleLitData:0x22, last: false}, RleOutput {symbol: RleLitData:0x22, last: false}, + RleOutput {symbol: RleLitData:0x22, last: false}, RleOutput {symbol: RleLitData:0x22, last: false}, + RleOutput {symbol: RleLitData:0x22, last: false}, RleOutput {symbol: RleLitData:0x22, last: false}, + RleOutput {symbol: RleLitData:0x22, last: false}, RleOutput {symbol: RleLitData:0x22, last: true}, + // 3rd literal + RleOutput {symbol: RleLitData:0x33, last: false}, RleOutput {symbol: RleLitData:0x33, last: false}, + RleOutput {symbol: RleLitData:0x33, last: false}, RleOutput {symbol: RleLitData:0x33, last: false}, + RleOutput {symbol: RleLitData:0x33, last: false}, RleOutput {symbol: RleLitData:0x33, last: false}, + RleOutput {symbol: RleLitData:0x33, last: false}, RleOutput {symbol: RleLitData:0x33, last: false}, + RleOutput {symbol: RleLitData:0x33, last: false}, RleOutput {symbol: RleLitData:0x33, last: true}, + // 4th literal + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, + RleOutput {symbol: RleLitData:0x44, last: true}, + ]; + let test_out_data: LiteralsData[6] = [ + LiteralsData {data: LitData:0x0000_0000_0000_0011, length: LitLength:1, last: false}, + LiteralsData {data: LitData:0x2222_2222_2222_2222, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x0000_0000_0000_3333, length: LitLength:2, last: false}, + LiteralsData {data: LitData:0x4444_4444_4444_4444, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x0000_0044_4444_4444, length: LitLength:5, last: true}, + ]; + + let tok = for ((counter, sync_data), tok): ((u32, LiteralsSyncData), token) in enumerate(test_sync_data) { + let tok = send(tok, sync_s, sync_data); + trace_fmt!("Sent #{} synchronization data, {:#x}", counter + u32:1, sync_data); + (tok) + }(tok); + + let tok = for ((counter, rle_data), tok): ((u32, RleOutput), token) in enumerate(test_rle_data) { + let tok = send(tok, in_s, rle_data); + trace_fmt!("Sent #{} rle data, {:#x}", counter + u32:1, rle_data); + (tok) + }(tok); + + let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsData), token) in enumerate(test_out_data) { + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received #{} batched data, {:#x}", counter + u32:1, out_data); + assert_eq(out_data, expected_out_data); + (tok) + }(tok); + + send(tok, terminator, true); + } +} + +pub proc RleLiteralsDecoder { + input_r: chan in; + output_s: chan out; + + config(input_r: chan in, output_s: chan out) { + let (in_s, in_r) = chan("in"); + let (out_s, out_r) = chan("in"); + let (sync_s, sync_r) = chan("sync"); + + spawn RleDataPacker(input_r, in_s, sync_s); + spawn rle_dec::RunLengthDecoder(in_r, out_s); + spawn BatchPacker(out_r, sync_r, output_s); + + (input_r, output_s) + } + + init { } + + next(tok: token, state: ()) { } +} + +#[test_proc] +proc RleLiteralsDecoder_test { + 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 RleLiteralsDecoder(in_r, out_s); + + (terminator, in_s, out_r) + } + + init { } + + next(tok: token, state: ()) { + let test_rle_data: RleLiteralsData[7] = [ + RleLiteralsData {data: RleLitData:0x11, repeat: RleLitRepeat:11, last: false}, + RleLiteralsData {data: RleLitData:0x22, repeat: RleLitRepeat:3, last: false}, + RleLiteralsData {data: RleLitData:0x33, repeat: RleLitRepeat:16, last: false}, + RleLiteralsData {data: RleLitData:0x44, repeat: RleLitRepeat:0, last: false}, + RleLiteralsData {data: RleLitData:0x55, repeat: RleLitRepeat:2, last: false}, + RleLiteralsData {data: RleLitData:0x66, repeat: RleLitRepeat:20, last: false}, + RleLiteralsData {data: RleLitData:0x00, repeat: RleLitRepeat:0, last: true}, + ]; + + let test_out_data: LiteralsData[10] = [ + // 1st literal + LiteralsData {data: LitData:0x1111_1111_1111_1111, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x0000_0000_0011_1111, length: LitLength:3, last: false}, + // 2nd literal + LiteralsData {data: LitData:0x0000_0000_0022_2222, length: LitLength:3, last: false}, + // 3rd literal + LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, + // 4th literal (empty) + // 5th literal + LiteralsData {data: LitData:0x0000_0000_0000_5555, length: LitLength:2, last: false}, + // 6th literal + LiteralsData {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x0000_0000_6666_6666, length: LitLength:4, last: false}, + // 7th literal + LiteralsData {data: LitData:0x0000_0000_0000_0000, length: LitLength:0, last: true}, + ]; + + let tok = for ((counter, rle_data), tok): ((u32, RleLiteralsData), token) in enumerate(test_rle_data) { + let tok = send(tok, in_s, rle_data); + trace_fmt!("Sent #{} rle data, {:#x}", counter + u32:1, rle_data); + (tok) + }(tok); + + let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsData), token) in enumerate(test_out_data) { + let (tok, out_data) = recv(tok, out_r); + trace_fmt!("Received #{} batched data, {:#x}", counter + u32:1, out_data); + assert_eq(out_data, expected_out_data); + (tok) + }(tok); + + send(tok, terminator, true); + } +} From 9ec4ee3243f4a68994adb7fa510857194c2078e4 Mon Sep 17 00:00:00 2001 From: Ryszard Rozak Date: Tue, 14 May 2024 11:32:47 +0200 Subject: [PATCH 46/49] modules/zstd: Add RawLiteralsDecoder Internal-tag: [#56124] Signed-off-by: Ryszard Rozak --- xls/modules/zstd/BUILD | 73 ++++++++++++++++++++++++++ xls/modules/zstd/raw_literals_dec.x | 79 +++++++++++++++++++++++++++++ 2 files changed, 152 insertions(+) create mode 100644 xls/modules/zstd/raw_literals_dec.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 26107937bc..a570d26678 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1224,3 +1224,76 @@ place_and_route( synthesized_rtl = ":rle_literals_dec_synth_asap7", target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "raw_literals_dec_dslx", + srcs = ["raw_literals_dec.x"], + deps = [ + ":common_dslx", + ], +) + +xls_dslx_test( + name = "raw_literals_dec_dslx_test", + library = ":raw_literals_dec_dslx", +) + +xls_dslx_verilog( + name = "raw_literals_dec_verilog", + codegen_args = { + "module_name": "RawLiteralsDecoder", + "delay_model": "asap7", + "pipeline_stages": "1", + "reset": "rst", + "use_system_verilog": "false", + }, + dslx_top = "RawLiteralsDecoder", + library = ":raw_literals_dec_dslx", + verilog_file = "raw_literals_dec.v", +) + +xls_benchmark_ir( + name = "raw_literals_dec_opt_ir_benchmark", + src = ":raw_literals_dec_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "raw_literals_dec_verilog_benchmark", + verilog_target = "raw_literals_dec_verilog", +) + +verilog_library( + name = "raw_literals_dec_lib", + srcs = [ + ":raw_literals_dec.v", + ], +) + +synthesize_rtl( + name = "raw_literals_dec_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "RawLiteralsDecoder", + deps = [ + ":raw_literals_dec_lib", + ], +) + +benchmark_synth( + name = "raw_literals_dec_benchmark_synth", + synth_target = ":raw_literals_dec_asap7", +) + +place_and_route( + name = "raw_literals_dec_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.09", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":raw_literals_dec_asap7", + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/raw_literals_dec.x b/xls/modules/zstd/raw_literals_dec.x new file mode 100644 index 0000000000..3bcbc11ee2 --- /dev/null +++ b/xls/modules/zstd/raw_literals_dec.x @@ -0,0 +1,79 @@ +// 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. + +// The proc should just pass incoming data as literals to LiteralsBuffer. +// Packets of 0 length are not passed further and a warning is log instead. + +import xls.modules.zstd.common; + +type LiteralsData = common::LiteralsData; +type LitData = common::LitData; +type LitLength = common::LitLength; + +proc RawLiteralsDecoder { + dispatcher_r: chan in; + buffer_s: chan out; + + config(dispatcher_r: chan in, buffer_s: chan out) { + (dispatcher_r, buffer_s) + } + + init { } + + next(tok: token, state: ()) { + let (tok, resp) = recv(tok, dispatcher_r); + let do_send = if resp.length == LitLength:0 { + trace_fmt!("[WARNING] Packet of 0 length received by RawLiteralsDecoder"); + false + } else { + true + }; + let tok = send_if(tok, buffer_s, do_send, resp); + } +} + +#[test_proc] +proc RawLiteralsDecoderTest { + terminator: chan out; + dispatcher_s: chan out; + buffer_r: chan in; + + config(terminator: chan out) { + let (dispatcher_s, dispatcher_r) = chan("dispatcher"); + let (buffer_s, buffer_r) = chan("buffer"); + + spawn RawLiteralsDecoder(dispatcher_r, buffer_s); + + (terminator, dispatcher_s, buffer_r) + } + + init { } + + next(tok: token, state: ()) { + let data = LiteralsData { data: LitData:0x11_22_33_44_55_66, length: LitLength:6, last: true }; + let tok = send(tok, dispatcher_s, data); + let (tok, resp) = recv(tok, buffer_r); + assert_eq(resp, data); + + let empty_data = LiteralsData { data: LitData:0, length: LitLength:0, last: true }; + let tok = send(tok, dispatcher_s, empty_data); + + // Resend the first packet to verify that the empty packet is dropped correctly. + let tok = send(tok, dispatcher_s, data); + let (tok, resp) = recv(tok, buffer_r); + assert_eq(resp, data); + + let tok = send(tok, terminator, true); + } +} From d880116ed6def8509dac992f0920be216a8a44dc Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Wed, 5 Jun 2024 13:51:19 +0200 Subject: [PATCH 47/49] modules/zstd: Add LiteralsBuffer proc, add literals synchronization Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 115 ++- xls/modules/zstd/block_dec.x | 2 +- xls/modules/zstd/common.x | 28 +- xls/modules/zstd/dec_mux.x | 2 +- xls/modules/zstd/literals_buffer.x | 1171 ++++++++++++++++++++++++ xls/modules/zstd/literals_dispatcher.x | 96 +- xls/modules/zstd/parallel_rams.x | 705 ++++++++++++++ xls/modules/zstd/raw_literals_dec.x | 26 +- xls/modules/zstd/rle_literals_dec.x | 131 +-- xls/modules/zstd/sequence_executor.x | 790 ++-------------- xls/modules/zstd/zstd_dec.x | 2 +- 11 files changed, 2238 insertions(+), 830 deletions(-) create mode 100644 xls/modules/zstd/literals_buffer.x create mode 100644 xls/modules/zstd/parallel_rams.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index a570d26678..56b01cf67b 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -730,6 +730,20 @@ xls_dslx_test( tags = ["manual"], ) +xls_dslx_library( + name = "parallel_rams_dslx", + srcs = ["parallel_rams.x"], + deps = [ + ":common_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "parallel_rams_dslx_test", + library = ":parallel_rams_dslx", +) + xls_dslx_library( name = "sequence_executor_dslx", srcs = [ @@ -738,6 +752,7 @@ xls_dslx_library( deps = [ ":common_dslx", ":ram_printer_dslx", + ":parallel_rams_dslx", "//xls/examples:ram_dslx", ], ) @@ -834,6 +849,8 @@ place_and_route( clock_period = "750", core_padding_microns = 2, min_pin_distance = "0.5", + die_height_microns = 120, + die_width_microns = 120, placement_density = "0.30", stop_after_step = "global_routing", synthesized_rtl = ":sequence_executor_asap7", @@ -1139,8 +1156,8 @@ place_and_route( name = "literals_dispatcher_place_and_route", clock_period = "750", core_padding_microns = 2, - die_height_microns = 50, - die_width_microns = 50, + die_height_microns = 64, + die_width_microns = 64, min_pin_distance = "0.5", placement_density = "0.30", stop_after_step = "global_routing", @@ -1297,3 +1314,97 @@ place_and_route( synthesized_rtl = ":raw_literals_dec_asap7", target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "literals_buffer_dslx", + srcs = [ + "literals_buffer.x", + ], + deps = [ + ":common_dslx", + ":ram_printer_dslx", + ":parallel_rams_dslx", + "//xls/examples:ram_dslx", + ], +) + +xls_dslx_test( + name = "literals_buffer_dslx_test", + library = ":literals_buffer_dslx", +) + +xls_dslx_verilog( + name = "literals_buffer_verilog", + codegen_args = { + "module_name": "LiteralsBuffer", + "delay_model": "asap7", + "ram_configurations": ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram{}".format(num), + rd_req = "literals_buffer__rd_req_m{}_s".format(num), + rd_resp = "literals_buffer__rd_resp_m{}_r".format(num), + wr_req = "literals_buffer__wr_req_m{}_s".format(num), + wr_resp = "literals_buffer__wr_resp_m{}_r".format(num), + ) + for num in range(7) + ]), + "pipeline_stages": "6", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "LiteralsBufferInst", + library = ":literals_buffer_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__literals_buffer__LiteralsBufferInst__LiteralsBuffer__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", + }, + verilog_file = "literals_buffer.v", +) + +xls_benchmark_ir( + name = "literals_buffer_opt_ir_benchmark", + src = ":literals_buffer_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "6", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "literals_buffer_verilog_benchmark", + verilog_target = "literals_buffer_verilog", +) + +verilog_library( + name = "literals_buffer_verilog_lib", + srcs = [ + ":literals_buffer.v", + ], +) + +synthesize_rtl( + name = "literals_buffer_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "LiteralsBuffer", + deps = [ + ":literals_buffer_verilog_lib", + ], +) + +benchmark_synth( + name = "literals_buffer_benchmark_synth", + synth_target = ":literals_buffer_synth_asap7", +) + +place_and_route( + name = "literals_buffer_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":literals_buffer_synth_asap7", + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/block_dec.x b/xls/modules/zstd/block_dec.x index 6797484d59..09c34027aa 100644 --- a/xls/modules/zstd/block_dec.x +++ b/xls/modules/zstd/block_dec.x @@ -24,7 +24,7 @@ type BlockPacketLength = common::BlockPacketLength; type ExtendedBlockDataPacket = common::ExtendedBlockDataPacket; type CopyOrMatchContent = common::CopyOrMatchContent; type CopyOrMatchLength = common::CopyOrMatchLength; -type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorPacket = common::SequenceExecutorPacket; type SequenceExecutorMessageType = common::SequenceExecutorMessageType; // Proc responsible for connecting internal procs used in Block data decoding. diff --git a/xls/modules/zstd/common.x b/xls/modules/zstd/common.x index 899c852d95..e533c206ad 100644 --- a/xls/modules/zstd/common.x +++ b/xls/modules/zstd/common.x @@ -45,8 +45,8 @@ pub struct BlockDataPacket { } pub enum SequenceExecutorMessageType : u1 { - LITERAL = 0, - SEQUENCE = 1, + LITERAL = 0, + SEQUENCE = 1, } pub struct ExtendedBlockDataPacket { @@ -54,10 +54,10 @@ pub struct ExtendedBlockDataPacket { packet: BlockDataPacket, } -pub struct SequenceExecutorPacket { +pub struct SequenceExecutorPacket { msg_type: SequenceExecutorMessageType, length: CopyOrMatchLength, // Literal length or match length - content: CopyOrMatchContent, // Literal data or match offset + content: uN[DATA_WIDTH * u32:8], // Literal data or match offset last: bool, // Last packet in frame } @@ -81,6 +81,9 @@ pub type RleLitData = uN[RLE_LITERALS_DATA_WIDTH]; pub type RleLitRepeat = uN[RLE_LITERALS_REPEAT_WIDTH]; pub type LitData = uN[LITERALS_DATA_WIDTH]; pub type LitLength = uN[LITERALS_LENGTH_WIDTH]; +pub type LitID = u32; + +pub type DecompressedSize = u20; pub enum LiteralType: u3 { RAW = 0, @@ -98,7 +101,7 @@ pub struct Streams { pub struct LiteralsPathCtrl { data_conf: Streams, - decompressed_size: u20, + decompressed_size: DecompressedSize, literals_type: LiteralType, } @@ -106,6 +109,7 @@ pub struct RleLiteralsData { data: RleLitData, repeat: RleLitRepeat, last: bool, + id: LitID, } pub struct LiteralsData { @@ -113,3 +117,17 @@ pub struct LiteralsData { length: LitLength, last: bool, } + +pub struct LiteralsDataWithSync { + data: LitData, + length: LitLength, + last: bool, + id: LitID, + literals_last: bool, +} + +pub struct LiteralsBufferCtrl { + length: u32, + last: bool, +} + diff --git a/xls/modules/zstd/dec_mux.x b/xls/modules/zstd/dec_mux.x index 59778ff304..edae47114a 100644 --- a/xls/modules/zstd/dec_mux.x +++ b/xls/modules/zstd/dec_mux.x @@ -26,7 +26,7 @@ type BlockPacketLength = common::BlockPacketLength; type CopyOrMatchContent = common::CopyOrMatchContent; type CopyOrMatchLength = common::CopyOrMatchLength; type SequenceExecutorMessageType = common::SequenceExecutorMessageType; -type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorPacket = common::SequenceExecutorPacket; const MAX_ID = common::DATA_WIDTH; const DATA_WIDTH = common::DATA_WIDTH; diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x new file mode 100644 index 0000000000..a264bdf4a8 --- /dev/null +++ b/xls/modules/zstd/literals_buffer.x @@ -0,0 +1,1171 @@ +// 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 the implementation of LiteralsBuffer responsible for +// storing data received either from RAW, RLE or Huffman literals decoder and +// sending it to CommandConstructor. + +import std; + +import xls.examples.ram; +import xls.modules.zstd.common as common; +import xls.modules.zstd.parallel_rams as parallel_rams; +import xls.modules.zstd.ram_printer as ram_printer; + +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type LitData = common::LitData; +type LitID = common::LitID; +type LitLength = common::LitLength; +type LiteralsBufferCtrl = common::LiteralsBufferCtrl; +type LiteralsData = common::LiteralsData; +type LiteralsDataWithSync = common::LiteralsDataWithSync; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; + +type HistoryBufferPtr = parallel_rams::HistoryBufferPtr; +type RamNumber = parallel_rams::RamNumber; +type RamReadStart = parallel_rams::RamReadStart; +type RamRdRespHandlerData = parallel_rams::RamRdRespHandlerData; +type RamWrRespHandlerData = parallel_rams::RamWrRespHandlerData; + +// Constants calculated from RAM parameters +const RAM_NUM = parallel_rams::RAM_NUM; +const RAM_NUM_WIDTH = parallel_rams::RAM_NUM_WIDTH; +const RAM_DATA_WIDTH = common::SYMBOL_WIDTH + u32:1; // the +1 is used to store "last" flag +const RAM_WORD_PARTITION_SIZE = RAM_DATA_WIDTH; +const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); + +// Literals data with last flag +type LiteralsWithLast = uN[RAM_DATA_WIDTH * RAM_NUM]; + +// RAM related constants common for tests +const TEST_HISTORY_BUFFER_SIZE_KB = u32:1; +const TEST_RAM_SIZE = parallel_rams::ram_size(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_ADDR_WIDTH = parallel_rams::ram_addr_width(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_INITIALIZED = true; +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; + +type TestRamAddr = bits[TEST_RAM_ADDR_WIDTH]; +type TestWriteReq = ram::WriteReq; +type TestWriteResp = ram::WriteResp; +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; + +struct LiteralsBufferMuxState { + // Literals sync handling + ctrl_last: bool, + literals_id: LitID, + // Received literals + raw_literals_valid: bool, + raw_literals_data: LiteralsDataWithSync, + rle_literals_valid: bool, + rle_literals_data: LiteralsDataWithSync, + huff_literals_valid: bool, + huff_literals_data: LiteralsDataWithSync, +} + +struct LiteralsBufferWriterState { + // History Buffer handling + hyp_ptr: HistoryBufferPtr, + hb_len: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + prev_wr_comp: HistoryBufferPtr, + literals_in_ram: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], +} + +struct LiteralsBufferReaderState { + // History Buffer handling + hyp_ptr: HistoryBufferPtr, + hb_len: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + literals_in_ram: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + ctrl_last: bool, + left_to_read: u32, +} + +struct LiteralsBufferWriterToReaderSync { + literals_written: LitLength, +} + +struct LiteralsBufferReaderToWriterSync { + literals_read: LitLength, +} + +// PacketDecoder is responsible for receiving read bytes from RAMs response +// handler, removing the "last" flag from each literal and adding this flag +// to the packet. It also validates the data. +proc PacketDecoder { + literals_in_r: chan> in; + literals_out_s: chan> out; + buffer_sync_s: chan> out; + + config( + literals_in_r: chan> in, + literals_out_s: chan> out, + buffer_sync_s: chan> out, + ) { + (literals_in_r, literals_out_s, buffer_sync_s) + } + + init { } + + next (state: ()) { + let tok = join(); + let (tok, literals) = recv(tok, literals_in_r); + + // Strip flag last from literals + let literals_data = for (i, data): (u32, CopyOrMatchContent) in range(u32:0, RAM_NUM) { + bit_slice_update( + data, + common::SYMBOL_WIDTH * i, + (literals.content >> (RAM_DATA_WIDTH * i)) as uN[common::SYMBOL_WIDTH] + ) + }(CopyOrMatchContent:0); + + // Extract last and validate packet. The resulting last is set if and + // only if any of the literas has it set. Also if any literal has set + // this flag, then the flag in following literal must also be set. In + // other case, the assertion is triggered. + let (last, _, packet_valid) = for (i, (last, prev_literal_last, packet_valid)): (u32, (bool, bool, bool)) in range(u32:0, RAM_NUM) { + let literal_last = (literals.content >> (RAM_DATA_WIDTH * (i + u32:1) - u32:1)) as u1; + ( + last | literal_last, + literal_last, + packet_valid & (!prev_literal_last | literal_last), + ) + }((false, false, true)); + + assert!(packet_valid && (literals.last == last), "Invalid packet"); + + // Send literals data + let tok = send(tok, literals_out_s, SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: literals.length, + content: literals_data, + last: last + }); + + // Send sync data to buffer writer + let tok = send(tok, buffer_sync_s, LiteralsBufferReaderToWriterSync { + literals_read: literals.length as LitLength, + }); + } +} + +fn literals_content(literal: u8, last: u1, pos: u3) -> LiteralsWithLast { + ( + literal as LiteralsWithLast | + ((last as LiteralsWithLast) << common::SYMBOL_WIDTH)) << (RAM_DATA_WIDTH * (pos as u32) + ) +} + + +const TEST_LITERALS_IN: SequenceExecutorPacket[4] = [ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: literals_content(u8:0xAB, u1:1, u3:0), + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:2, + content: ( + literals_content(u8:0x12, u1:0, u3:1) | + literals_content(u8:0x34, u1:0, u3:0) + ), + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: ( + literals_content(u8:0xFE, u1:0, u3:7) | + literals_content(u8:0xDC, u1:0, u3:6) | + literals_content(u8:0xBA, u1:0, u3:5) | + literals_content(u8:0x98, u1:0, u3:4) | + literals_content(u8:0x76, u1:0, u3:3) | + literals_content(u8:0x54, u1:0, u3:2) | + literals_content(u8:0x32, u1:0, u3:1) | + literals_content(u8:0x10, u1:0, u3:0) + ), + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: ( + literals_content(u8:0xAA, u1:1, u3:3) | + literals_content(u8:0xBB, u1:1, u3:2) | + literals_content(u8:0xCC, u1:0, u3:1) | + literals_content(u8:0xDD, u1:0, u3:0) + ), + last: true, + }, +]; + +const TEST_LITERALS_OUT: SequenceExecutorPacket[4] = [ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0xAB, + last: true, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:2, + content: CopyOrMatchContent:0x1234, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xFEDC_BA98_7654_3210, + last: false, + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0xAABB_CCDD, + last: true, + }, +]; + +#[test_proc] +proc PacketDecoder_test { + terminator: chan out; + + literals_in_s: chan> out; + literals_out_r: chan> in; + buffer_sync_r: chan> in; + + config(terminator: chan out) { + let (literals_in_s, literals_in_r) = chan>("literals_in"); + let (literals_out_s, literals_out_r) = chan>("literals_out"); + let (buffer_sync_s, buffer_sync_r) = chan>("buffer_sync"); + + spawn PacketDecoder(literals_in_r, literals_out_s, buffer_sync_s); + + (terminator, literals_in_s, literals_out_r, buffer_sync_r) + } + + init { } + + next (state: ()) { + let tok = join(); + let tok = for (i, tok): (u32, token) in range(u32:0, array_size(TEST_LITERALS_IN)) { + let tok = send(tok, literals_in_s, TEST_LITERALS_IN[i]); + trace_fmt!("Sent #{} literals {:#x}", i, TEST_LITERALS_IN[i]); + tok + }(tok); + + let tok = for (i, tok): (u32, token) in range(u32:0, array_size(TEST_LITERALS_OUT)) { + let (tok, literals) = recv(tok, literals_out_r); + trace_fmt!("Received #{} literals {:#x}", i, literals); + assert_eq(TEST_LITERALS_OUT[i], literals); + tok + }(tok); + + send(tok, terminator, true); + } +} + +// Proc responsible for receiving literals from RAW, RLE and Huffman decoders +// and sending them to the writer in correct order. +proc LiteralsBufferMux { + raw_literals_r: chan in; + rle_literals_r: chan in; + huff_literals_r: chan in; + + out_literals_s: chan out; + + config( + raw_literals_r: chan in, + rle_literals_r: chan in, + huff_literals_r: chan in, + out_literals_s: chan out, + ) { + ( + raw_literals_r, rle_literals_r, huff_literals_r, + out_literals_s + ) + } + + init { zero!() } + + next (state: LiteralsBufferMuxState) { + let tok0 = join(); + // Receive literals + + let (tok1_0, raw_literals, raw_literals_valid) = recv_if_non_blocking( + tok0, raw_literals_r, !state.raw_literals_valid, state.raw_literals_data + ); + let (tok1_1, rle_literals, rle_literals_valid) = recv_if_non_blocking( + tok0, rle_literals_r, !state.rle_literals_valid, state.rle_literals_data + ); + let (tok1_2, huff_literals, huff_literals_valid) = recv_if_non_blocking( + tok0, huff_literals_r, !state.huff_literals_valid, state.huff_literals_data + ); + let state = LiteralsBufferMuxState { + raw_literals_valid: state.raw_literals_valid || raw_literals_valid, + raw_literals_data: raw_literals, + rle_literals_valid: state.rle_literals_valid || rle_literals_valid, + rle_literals_data: rle_literals, + huff_literals_valid: state.huff_literals_valid || huff_literals_valid, + huff_literals_data: huff_literals, + ..state + }; + + let tok1 = join(tok1_0, tok1_1, tok1_2); + + // Select proper literals + let sel_raw_literals = state.raw_literals_valid && state.raw_literals_data.id == state.literals_id; + let sel_rle_literals = state.rle_literals_valid && state.rle_literals_data.id == state.literals_id; + let sel_huff_literals = state.huff_literals_valid && state.huff_literals_data.id == state.literals_id; + + let literals_data = zero!(); + let literals_valid = sel_raw_literals || sel_rle_literals || sel_huff_literals; + + let (literals_data, state) = if (sel_raw_literals) { + ( + state.raw_literals_data, + LiteralsBufferMuxState { raw_literals_valid: false, ..state } + ) + } else if (sel_rle_literals) { + ( + state.rle_literals_data, + LiteralsBufferMuxState { rle_literals_valid: false, ..state } + ) + } else if (sel_huff_literals) { + ( + state.huff_literals_data, + LiteralsBufferMuxState { huff_literals_valid: false, ..state } + ) + } else { + ( + literals_data, + state + ) + }; + + send_if(tok1, out_literals_s, literals_valid, LiteralsData { + data: literals_data.data, + length: literals_data.length, + last: literals_data.last, + }); + + if (literals_data.literals_last) { + LiteralsBufferMuxState { literals_id: state.literals_id + LitID:1, ..state } + } else { + state + } + } +} + +// Proc responsible for writing received literals to RAMs +proc LiteralsBufferWriter< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + INIT_HB_PTR_ADDR: u32 = {u32:0}, + INIT_HB_PTR_RAM: u32 = {u32:0}, + INIT_HB_LENGTH: u32 = {u32:0}, + RAM_SIZE_TOTAL: u32 = {RAM_SIZE * RAM_NUM} +> { + type HistoryBufferLength = uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)]; + type RamAddr = bits[RAM_ADDR_WIDTH]; + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type State = LiteralsBufferWriterState; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + literals_r: chan in; + + ram_comp_input_s: chan> out; + ram_comp_output_r: chan> in; + + buffer_sync_r: chan in; + buffer_sync_s: chan out; + + wr_req_m0_s: chan out; + wr_req_m1_s: chan out; + wr_req_m2_s: chan out; + wr_req_m3_s: chan out; + wr_req_m4_s: chan out; + wr_req_m5_s: chan out; + wr_req_m6_s: chan out; + wr_req_m7_s: chan out; + + config ( + literals_r: chan in, + buffer_sync_r: chan in, + buffer_sync_s: chan out, + wr_req_m0_s: chan out, + wr_req_m1_s: chan out, + wr_req_m2_s: chan out, + wr_req_m3_s: chan out, + wr_req_m4_s: chan out, + wr_req_m5_s: chan out, + wr_req_m6_s: chan out, + wr_req_m7_s: chan out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + let (ram_comp_input_s, ram_comp_input_r) = chan, u32:1>("ram_comp_input"); + let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); + + spawn parallel_rams::RamWrRespHandler( + ram_comp_input_r, ram_comp_output_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ); + + ( + literals_r, + ram_comp_input_s, ram_comp_output_r, + buffer_sync_r, buffer_sync_s, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + ) + } + + init { + let INIT_HB_PTR = HistoryBufferPtr { + number: INIT_HB_PTR_RAM as RamNumber, addr: INIT_HB_PTR_ADDR as RamAddr + }; + + State { + hyp_ptr: INIT_HB_PTR, + hb_len: INIT_HB_LENGTH as uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + prev_wr_comp: INIT_HB_PTR, + ..zero!() + } + } + next (state: State) { + let tok0 = join(); + // TODO: Remove this workaround when fixed: https://github.com/google/xls/issues/1368 + type WriteReq = ram::WriteReq; + + const ZERO_WRITE_REQS = WriteReq[RAM_NUM]:[zero!(), ...]; + const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; + + // read from sync + let (_, sync_data, sync_data_valid) = recv_non_blocking(tok0, buffer_sync_r, zero!()); + + if (sync_data_valid) { + trace_fmt!("Received bufffer reader-to-writer sync data {:#x}", sync_data); + } else {}; + + // read literals + let do_recv_literals = state.hb_len as u32 < HISTORY_BUFFER_SIZE_KB << u32:10; + + let (tok1, literals_data) = recv_if(tok0, literals_r, do_recv_literals, zero!()); + + // write literals to RAM + let packet_data = for (i, data): (u32, LiteralsWithLast) in range(u32:0, RAM_NUM) { + let literal = (((literals_data.data >> (common::SYMBOL_WIDTH * i)) as uN[common::SYMBOL_WIDTH]) as LiteralsWithLast) | + ((literals_data.last as LiteralsWithLast) << common::SYMBOL_WIDTH); + data | (literal << (RAM_DATA_WIDTH * i)) + }(LiteralsWithLast:0); + + let packet = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: literals_data.length as CopyOrMatchLength, + content: packet_data, + last: literals_data.last, + }; + let (write_reqs, new_hyp_ptr) = parallel_rams::literal_packet_to_write_reqs< + HISTORY_BUFFER_SIZE_KB, RAM_ADDR_WIDTH, RAM_DATA_WIDTH + >( + state.hyp_ptr, packet + ); + let hb_add = packet.length as HistoryBufferLength; + let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as HistoryBufferLength); + + let write_reqs = if (do_recv_literals) { + write_reqs + } else { + ZERO_WRITE_REQS + }; + + // send write requests to RAMs + let tok2_0 = send_if(tok1, wr_req_m0_s, write_reqs[0].mask != RAM_REQ_MASK_NONE, write_reqs[0]); + let tok2_1 = send_if(tok1, wr_req_m1_s, write_reqs[1].mask != RAM_REQ_MASK_NONE, write_reqs[1]); + let tok2_2 = send_if(tok1, wr_req_m2_s, write_reqs[2].mask != RAM_REQ_MASK_NONE, write_reqs[2]); + let tok2_3 = send_if(tok1, wr_req_m3_s, write_reqs[3].mask != RAM_REQ_MASK_NONE, write_reqs[3]); + let tok2_4 = send_if(tok1, wr_req_m4_s, write_reqs[4].mask != RAM_REQ_MASK_NONE, write_reqs[4]); + let tok2_5 = send_if(tok1, wr_req_m5_s, write_reqs[5].mask != RAM_REQ_MASK_NONE, write_reqs[5]); + let tok2_6 = send_if(tok1, wr_req_m6_s, write_reqs[6].mask != RAM_REQ_MASK_NONE, write_reqs[6]); + let tok2_7 = send_if(tok1, wr_req_m7_s, write_reqs[7].mask != RAM_REQ_MASK_NONE, write_reqs[7]); + + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + // write completion + let (do_write, wr_resp_handler_data) = parallel_rams::create_ram_wr_data(write_reqs, state.hyp_ptr); + if do_write {trace_fmt!("Sending request to RamWrRespHandler: {:#x}", wr_resp_handler_data);} else { }; + + let tok3_0 = send_if(tok2, ram_comp_input_s, do_write, wr_resp_handler_data); + + let (tok3_1, comp_data, comp_data_valid) = recv_non_blocking(tok2, ram_comp_output_r, zero!()); + + // update RAM literals count + let literals_diff = (comp_data.number - state.prev_wr_comp.number) as LitLength; + let literals_diff = if (literals_diff == LitLength:0) { + LitLength:8 + } else { + literals_diff + }; + + // update state + let state = if (do_recv_literals) { + State { + hyp_ptr: new_hyp_ptr, + hb_len: new_hb_len, + ..state + } + } else { + state + }; + + let state = if (comp_data_valid) { + State { + literals_in_ram: state.literals_in_ram + literals_diff as uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)], + prev_wr_comp: comp_data, + ..state + } + } else { + state + }; + + let state = if (sync_data_valid) { + State { + literals_in_ram: state.literals_in_ram - sync_data.literals_read as HistoryBufferLength, + hb_len: state.hb_len - sync_data.literals_read as HistoryBufferLength, + ..state + } + } else { + state + }; + + // send sync + let tok3 = join(tok3_0, tok3_1); + + let sync_data = LiteralsBufferWriterToReaderSync { + literals_written: literals_diff, + }; + let tok4 = send_if(tok3, buffer_sync_s, comp_data_valid, sync_data); + + if (comp_data_valid) { + trace_fmt!("Sent buffer writer-to-reader sync data {:#x}", sync_data); + } else {}; + + state + } +} + +// Proc responsible for reading requestes literals from RAMs +proc LiteralsBufferReader< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + INIT_HB_PTR_ADDR: u32 = {u32:0}, + INIT_HB_PTR_RAM: u32 = {u32:0}, + INIT_HB_LENGTH: u32 = {u32:0}, + RAM_SIZE_TOTAL: u32 = {RAM_SIZE * RAM_NUM} +> { + type HistoryBufferLength = uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)]; + type RamAddr = bits[RAM_ADDR_WIDTH]; + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type State = LiteralsBufferReaderState; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + literals_buf_ctrl_r: chan in; + literals_s: chan> out; + + ram_resp_input_s: chan out; + + buffer_sync_r: chan in; + + rd_req_m0_s: chan out; + rd_req_m1_s: chan out; + rd_req_m2_s: chan out; + rd_req_m3_s: chan out; + rd_req_m4_s: chan out; + rd_req_m5_s: chan out; + rd_req_m6_s: chan out; + rd_req_m7_s: chan out; + + config ( + literals_buf_ctrl_r: chan in, + literals_s: chan> out, + buffer_sync_r: chan in, + buffer_sync_s: chan out, + rd_req_m0_s: chan out, + rd_req_m1_s: chan out, + rd_req_m2_s: chan out, + rd_req_m3_s: chan out, + rd_req_m4_s: chan out, + rd_req_m5_s: chan out, + rd_req_m6_s: chan out, + rd_req_m7_s: chan out, + rd_resp_m0_r: chan in, + rd_resp_m1_r: chan in, + rd_resp_m2_r: chan in, + rd_resp_m3_r: chan in, + rd_resp_m4_r: chan in, + rd_resp_m5_r: chan in, + rd_resp_m6_r: chan in, + rd_resp_m7_r: chan in, + ) { + let (ram_resp_input_s, ram_resp_input_r) = chan("ram_resp_input"); + let (literals_enc_s, literals_enc_r) = chan, u32:1>("literals_enc"); + + spawn parallel_rams::RamRdRespHandler( + ram_resp_input_r, literals_enc_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + ); + + spawn PacketDecoder( + literals_enc_r, literals_s, buffer_sync_s + ); + + ( + literals_buf_ctrl_r, + literals_s, + ram_resp_input_s, + buffer_sync_r, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + ) + } + + init { + let INIT_HB_PTR = HistoryBufferPtr { + number: INIT_HB_PTR_RAM as RamNumber, addr: INIT_HB_PTR_ADDR as RamAddr + }; + + State { + hyp_ptr: INIT_HB_PTR, + hb_len: INIT_HB_LENGTH as uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + ..zero!() + } + } + + next (state: State) { + let tok0 = join(); + // TODO: Remove this workaround when fixed: https://github.com/google/xls/issues/1368 + type ReadReq = ram::ReadReq; + + const ZERO_READ_REQS = ReadReq[RAM_NUM]:[zero!(), ...]; + const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; + + // read from ctrl + let (tok1, literals_buf_ctrl, literals_buf_ctrl_valid) = recv_if_non_blocking( + tok0, literals_buf_ctrl_r, state.left_to_read == u32:0, zero!() + ); + let (left_to_read, ctrl_last) = if (literals_buf_ctrl_valid) { + ( + literals_buf_ctrl.length, + literals_buf_ctrl.last + ) + } else { + ( + state.left_to_read, + state.ctrl_last + ) + }; + + // read literals from RAM + // limit read to 8 literals + let literals_to_read = if (left_to_read > (RAM_NUM as u32)) { + RAM_NUM as u32 + } else { + left_to_read + }; + // if there is not enough literals in RAMs, don't read and wait for more literals + let literals_to_read = if (literals_to_read > state.literals_in_ram as u32) { + u32:0 + } else { + literals_to_read + }; + + let packet = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: literals_to_read as CopyOrMatchLength, + content: state.hb_len as LiteralsWithLast, + last: ctrl_last, + }; + + let (read_reqs, read_start, read_len, packet, _) = parallel_rams::sequence_packet_to_read_reqs< + HISTORY_BUFFER_SIZE_KB, RAM_ADDR_WIDTH, RAM_DATA_WIDTH + >( + state.hyp_ptr, packet, state.hb_len + ); + + let (read_reqs, read_start, state) = if (literals_to_read > u32:0) { + ( + read_reqs, + read_start, + State { + hb_len: state.hb_len - literals_to_read as HistoryBufferLength, + literals_in_ram: state.literals_in_ram - literals_to_read as uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], + left_to_read: left_to_read - literals_to_read, + ctrl_last: ctrl_last, + ..state + }, + ) + } else { + ( + ZERO_READ_REQS, + RamReadStart:0, + State { + left_to_read: left_to_read, + ctrl_last: ctrl_last, + ..state + } + ) + }; + + // read requests + let tok2_0 = send_if(tok1, rd_req_m0_s, read_reqs[0].mask != RAM_REQ_MASK_NONE, read_reqs[0]); + let tok2_1 = send_if(tok1, rd_req_m1_s, read_reqs[1].mask != RAM_REQ_MASK_NONE, read_reqs[1]); + let tok2_2 = send_if(tok1, rd_req_m2_s, read_reqs[2].mask != RAM_REQ_MASK_NONE, read_reqs[2]); + let tok2_3 = send_if(tok1, rd_req_m3_s, read_reqs[3].mask != RAM_REQ_MASK_NONE, read_reqs[3]); + let tok2_4 = send_if(tok1, rd_req_m4_s, read_reqs[4].mask != RAM_REQ_MASK_NONE, read_reqs[4]); + let tok2_5 = send_if(tok1, rd_req_m5_s, read_reqs[5].mask != RAM_REQ_MASK_NONE, read_reqs[5]); + let tok2_6 = send_if(tok1, rd_req_m6_s, read_reqs[6].mask != RAM_REQ_MASK_NONE, read_reqs[6]); + let tok2_7 = send_if(tok1, rd_req_m7_s, read_reqs[7].mask != RAM_REQ_MASK_NONE, read_reqs[7]); + + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + let (do_read, rd_resp_handler_data) = + parallel_rams::create_ram_rd_data( + read_reqs, read_start, read_len, false, true + ); + if do_read { + trace_fmt!("Sending request to RamRdRespHandler: {:#x}", rd_resp_handler_data); + } else { }; + let tok3 = send_if(tok2, ram_resp_input_s, do_read, rd_resp_handler_data); + + // read from sync + let (_, sync_data, sync_data_valid) = recv_non_blocking(tok0, buffer_sync_r, zero!()); + + if (sync_data_valid) { + trace_fmt!("Received buffer writer-to-reader sync data {:#x}", sync_data); + } else {}; + + let state = if (sync_data_valid) { + State { + hyp_ptr: parallel_rams::hb_ptr_from_offset_forw( + state.hyp_ptr, sync_data.literals_written as parallel_rams::Offset + ), + hb_len: state.hb_len + sync_data.literals_written as HistoryBufferLength, + literals_in_ram: state.literals_in_ram + sync_data.literals_written as uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)], + ..state + } + } else { + state + }; + + state + } +} + +proc LiteralsBuffer< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + INIT_HB_PTR_ADDR: u32 = {u32:0}, + INIT_HB_PTR_RAM: u32 = {u32:0}, + INIT_HB_LENGTH: u32 = {u32:0}, + RAM_SIZE_TOTAL: u32 = {RAM_SIZE * RAM_NUM} +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + init { } + + config ( + raw_literals_r: chan in, + rle_literals_r: chan in, + huff_literals_r: chan in, + literals_buf_ctrl_r: chan in, + literals_s: chan> out, + rd_req_m0_s: chan out, + rd_req_m1_s: chan out, + rd_req_m2_s: chan out, + rd_req_m3_s: chan out, + rd_req_m4_s: chan out, + rd_req_m5_s: chan out, + rd_req_m6_s: chan out, + rd_req_m7_s: chan out, + rd_resp_m0_r: chan in, + rd_resp_m1_r: chan in, + rd_resp_m2_r: chan in, + rd_resp_m3_r: chan in, + rd_resp_m4_r: chan in, + rd_resp_m5_r: chan in, + rd_resp_m6_r: chan in, + rd_resp_m7_r: chan in, + wr_req_m0_s: chan out, + wr_req_m1_s: chan out, + wr_req_m2_s: chan out, + wr_req_m3_s: chan out, + wr_req_m4_s: chan out, + wr_req_m5_s: chan out, + wr_req_m6_s: chan out, + wr_req_m7_s: chan out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + type SyncWriterToReader = LiteralsBufferWriterToReaderSync; + type SyncReaderToWriter = LiteralsBufferReaderToWriterSync; + + let (buffer_sync_writer_to_reader_s, buffer_sync_writer_to_reader_r) = chan("buffer_sync_writer_to_reader"); + let (buffer_sync_reader_to_writer_s, buffer_sync_reader_to_writer_r) = chan("buffer_sync_reader_to_writer"); + let (sync_literals_s, sync_literals_r) = chan("sync_literals"); + + spawn LiteralsBufferMux ( + raw_literals_r, rle_literals_r, huff_literals_r, + sync_literals_s + ); + + spawn LiteralsBufferWriter< + HISTORY_BUFFER_SIZE_KB, RAM_SIZE, RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, INIT_HB_PTR_RAM, INIT_HB_LENGTH, RAM_SIZE_TOTAL + > ( + sync_literals_r, + buffer_sync_reader_to_writer_r, buffer_sync_writer_to_reader_s, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ); + + spawn LiteralsBufferReader< + HISTORY_BUFFER_SIZE_KB, RAM_SIZE, RAM_ADDR_WIDTH, INIT_HB_PTR_ADDR, INIT_HB_PTR_RAM, INIT_HB_LENGTH, RAM_SIZE_TOTAL + > ( + literals_buf_ctrl_r, literals_s, + buffer_sync_writer_to_reader_r, buffer_sync_reader_to_writer_s, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + ); + } + + next (state: ()) { } +} + +const ZSTD_HISTORY_BUFFER_SIZE_KB: u32 = u32:64; +const ZSTD_RAM_ADDR_WIDTH = parallel_rams::ram_addr_width(ZSTD_HISTORY_BUFFER_SIZE_KB); + +pub proc LiteralsBufferInst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + init { } + + config ( + raw_literals_r: chan in, + rle_literals_r: chan in, + huff_literals_r: chan in, + literals_buf_ctrl_r: chan in, + literals_s: chan> out, + rd_req_m0_s: chan out, + rd_req_m1_s: chan out, + rd_req_m2_s: chan out, + rd_req_m3_s: chan out, + rd_req_m4_s: chan out, + rd_req_m5_s: chan out, + rd_req_m6_s: chan out, + rd_req_m7_s: chan out, + rd_resp_m0_r: chan in, + rd_resp_m1_r: chan in, + rd_resp_m2_r: chan in, + rd_resp_m3_r: chan in, + rd_resp_m4_r: chan in, + rd_resp_m5_r: chan in, + rd_resp_m6_r: chan in, + rd_resp_m7_r: chan in, + wr_req_m0_s: chan out, + wr_req_m1_s: chan out, + wr_req_m2_s: chan out, + wr_req_m3_s: chan out, + wr_req_m4_s: chan out, + wr_req_m5_s: chan out, + wr_req_m6_s: chan out, + wr_req_m7_s: chan out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + spawn LiteralsBuffer ( + raw_literals_r, rle_literals_r, huff_literals_r, + literals_buf_ctrl_r, literals_s, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ); + } + + next (state: ()) { } +} + +enum LiteralsChannel: u2 { + RAW = 0, + RLE = 1, + HUFF = 2, +} + +const TEST_LITERALS_DATA: (LiteralsChannel, LiteralsDataWithSync)[9] = [ + (LiteralsChannel::RAW, LiteralsDataWithSync {data: LitData:0x12_3456_789A, length: LitLength:5, last: false, id: LitID:0, literals_last: true}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xBBBB_BBBB, length: LitLength:4, last: false, id: LitID:1, literals_last: true}), + (LiteralsChannel::HUFF, LiteralsDataWithSync {data: LitData:0x64, length: LitLength:1, last: false, id: LitID:2, literals_last: true}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xABCD_DCBA_1234_4321, length: LitLength:8, last: false, id: LitID:3, literals_last: true}), + (LiteralsChannel::RAW, LiteralsDataWithSync {data: LitData:0x21_4365, length: LitLength:3, last: false, id: LitID:4, literals_last: true}), + (LiteralsChannel::RLE, LiteralsDataWithSync {data: LitData:0xAA_BBBB_CCCC_DDDD, length: LitLength:7, last: false, id: LitID:5, literals_last: true}), + (LiteralsChannel::RAW, LiteralsDataWithSync {data: LitData:0xDCBA_ABCD_1234_4321, length: LitLength:8, last: false, id: LitID:6, literals_last: false}), + (LiteralsChannel::RAW, LiteralsDataWithSync {data: LitData:0x78, length: LitLength:1, last: false, id: LitID:6, literals_last: true}), + (LiteralsChannel::HUFF, LiteralsDataWithSync {data: LitData:0x26, length: LitLength:1, last: true, id: LitID:7, literals_last: true}), +]; + +const TEST_BUFFER_CTRL: LiteralsBufferCtrl[6] = [ + LiteralsBufferCtrl {length: u32:2, last: false}, + LiteralsBufferCtrl {length: u32:1, last: false}, + LiteralsBufferCtrl {length: u32:13, last: false}, + LiteralsBufferCtrl {length: u32:8, last: false}, + LiteralsBufferCtrl {length: u32:4, last: false}, + LiteralsBufferCtrl {length: u32:10, last: true}, +]; + +const TEST_EXPECTED_PACKETS: SequenceExecutorPacket[8] = [ + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:2, + content: CopyOrMatchContent:0x789A, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:1, + content: CopyOrMatchContent:0x56, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x2164_BBBB_BBBB_1234, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:5, + content: CopyOrMatchContent:0xDC_BA12_3443, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xCCDD_DD21_4365_ABCD, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:4, + content: CopyOrMatchContent:0xAABB_BBCC, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xDCBA_ABCD_1234_4321, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:2, + content: CopyOrMatchContent:0x2678, + last: true + }, +]; + +#[test_proc] +proc LiteralsBuffer_test { + terminator: chan out; + + raw_literals_s: chan out; + rle_literals_s: chan out; + huff_literals_s: chan out; + + literals_buf_ctrl_s: chan out; + literals_r: chan> in; + + print_start_s: chan<()> out; + print_finish_r: chan<()> in; + + ram_rd_req_s: chan[RAM_NUM] out; + ram_rd_resp_r: chan[RAM_NUM] in; + ram_wr_req_s: chan[RAM_NUM] out; + ram_wr_resp_r: chan[RAM_NUM] in; + + config(terminator: chan out) { + let (raw_literals_s, raw_literals_r) = chan("raw_literals"); + let (rle_literals_s, rle_literals_r) = chan("rle_literals"); + let (huff_literals_s, huff_literals_r) = chan("huff_literals"); + + let (literals_buf_ctrl_s, literals_buf_ctrl_r) = chan("literals_buf_ctrl"); + let (literals_s, literals_r) = chan>("literals"); + + let (print_start_s, print_start_r) = chan<()>("print_start"); + let (print_finish_s, print_finish_r) = chan<()>("print_finish"); + + let (ram_rd_req_s, ram_rd_req_r) = chan[RAM_NUM]("ram_rd_req"); + let (ram_rd_resp_s, ram_rd_resp_r) = chan[RAM_NUM]("ram_rd_resp"); + let (ram_wr_req_s, ram_wr_req_r) = chan[RAM_NUM]("ram_wr_req"); + let (ram_wr_resp_s, ram_wr_resp_r) = chan[RAM_NUM]("ram_wr_resp"); + + let INIT_HB_PTR_ADDR = u32:127; + + spawn LiteralsBuffer< + TEST_HISTORY_BUFFER_SIZE_KB, + TEST_RAM_SIZE, + TEST_RAM_ADDR_WIDTH, + INIT_HB_PTR_ADDR, + > ( + raw_literals_r, rle_literals_r, huff_literals_r, + literals_buf_ctrl_r, literals_s, + ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], + ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], + ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], + ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], + ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], + ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], + ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], + ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] + ); + spawn ram_printer::RamPrinter< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_NUM_PARTITIONS, + TEST_RAM_ADDR_WIDTH, RAM_NUM> + (print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r); + + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); + spawn ram::RamModel< + RAM_DATA_WIDTH, TEST_RAM_SIZE, RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + + ( + terminator, + raw_literals_s, rle_literals_s, huff_literals_s, + literals_buf_ctrl_s, literals_r, + print_start_s, print_finish_r, + ram_rd_req_s, ram_rd_resp_r, + ram_wr_req_s, ram_wr_resp_r, + ) + } + + init { } + + next (state: ()) { + let tok = join(); + // send literals + let tok = for ((i, test_literals_data), tok): ((u32, (LiteralsChannel, LiteralsDataWithSync)), token) in enumerate(TEST_LITERALS_DATA) { + let literals_channel_s = match test_literals_data.0 { + LiteralsChannel::RAW => raw_literals_s, + LiteralsChannel::RLE => rle_literals_s, + LiteralsChannel::HUFF => huff_literals_s, + }; + let tok = send(tok, literals_channel_s, test_literals_data.1); + trace_fmt!("Sent #{} literals {:#x} to channel {}", i + u32:1, test_literals_data.1, test_literals_data.0); + tok + }(tok); + + // send ctrl + let tok = for ((i, test_buf_ctrl), tok): ((u32, LiteralsBufferCtrl), token) in enumerate(TEST_BUFFER_CTRL) { + let tok = send(tok, literals_buf_ctrl_s, test_buf_ctrl); + trace_fmt!("Send #{} ctrl {:#x}", i + u32:1, test_buf_ctrl); + tok + }(tok); + + // receive and check packets + let tok = for ((i, test_exp_literals), tok): ((u32, SequenceExecutorPacket), token) in enumerate(TEST_EXPECTED_PACKETS) { + let (tok, literals) = recv(tok, literals_r); + trace_fmt!("Received #{} literals packet {:#x}", i + u32:1, literals); + assert_eq(test_exp_literals, literals); + tok + }(tok); + + // print RAM content + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + send(tok, terminator, true); + } +} diff --git a/xls/modules/zstd/literals_dispatcher.x b/xls/modules/zstd/literals_dispatcher.x index e10181d1b8..f5872d5ca4 100644 --- a/xls/modules/zstd/literals_dispatcher.x +++ b/xls/modules/zstd/literals_dispatcher.x @@ -20,36 +20,40 @@ import xls.modules.zstd.common; type LiteralsPathCtrl = common::LiteralsPathCtrl; type LiteralsData = common::LiteralsData; +type LiteralsDataWithSync = common::LiteralsDataWithSync; type RleLiteralsData = common::RleLiteralsData; type LiteralType = common::LiteralType; type Streams = common::Streams; +type DecompressedSize = common::DecompressedSize; type RleLitData = common::RleLitData; type RleLitRepeat = common::RleLitRepeat; type LitData = common::LitData; type LitLength = common::LitLength; +type LitID = common::LitID; struct LiteralsDispatcherState { // literals type received from ctrl channel literals_type: LiteralType, // number of literals to be read. The initial value is received // from ctrl channel and decreased after each read from literals channel - left_to_read: u20, + left_to_read: DecompressedSize, + literals_id: LitID, } pub proc LiteralsDispatcher { literals_ctrl_r: chan in; literals_data_r: chan in; - raw_literals_s: chan out; + raw_literals_s: chan out; rle_literals_s: chan out; - huff_literals_s: chan out; + huff_literals_s: chan out; config ( literals_ctrl_r: chan in, literals_data_r: chan in, - raw_literals_s: chan out, + raw_literals_s: chan out, rle_literals_s: chan out, - huff_literals_s: chan out, + huff_literals_s: chan out, ) { ( literals_ctrl_r, @@ -62,9 +66,10 @@ pub proc LiteralsDispatcher { init { zero!() } - next(tok: token, state: LiteralsDispatcherState ) { + next(state: LiteralsDispatcherState ) { - let do_recv_ctrl = (state.left_to_read == u20:0); + let tok = join(); + let do_recv_ctrl = (state.left_to_read == DecompressedSize:0); let (tok, literals_path_ctrl) = recv_if(tok, literals_ctrl_r, do_recv_ctrl, zero!()); let (literals_type, left_to_read) = if do_recv_ctrl { @@ -75,16 +80,31 @@ pub proc LiteralsDispatcher { // RLE literals consist of single byte let (tok, literals_data) = recv(tok, literals_data_r); + + let is_empty = left_to_read == DecompressedSize:0 && !literals_data.last; + let left_to_read = if (literals_type == LiteralType::RLE) { - u20:0 + DecompressedSize:0 } else { - left_to_read - (literals_data.length as u20) + left_to_read - (literals_data.length as DecompressedSize) + }; + + let literals_data = LiteralsDataWithSync { + data: literals_data.data, + length: literals_data.length, + last: literals_data.last, + id: state.literals_id, + literals_last: true }; let tok = send_if(tok, raw_literals_s, LiteralType::RAW == literals_type, literals_data); + let rle_literals_data = RleLiteralsData { - data: (literals_data.data as u8), repeat: literals_path_ctrl.decompressed_size, last: literals_data.last + data: (literals_data.data as u8), + repeat: literals_path_ctrl.decompressed_size, + id: if (is_empty) { LitID:0 } else { literals_data.id }, + last: literals_data.last, }; let tok = send_if(tok, rle_literals_s, LiteralType::RLE == literals_type, rle_literals_data); @@ -93,11 +113,18 @@ pub proc LiteralsDispatcher { LiteralType::TREELESS == literals_type || LiteralType::TREELESS_4 == literals_type ); assert!(do_send_huff, "Huffmann coding not implemented yet"); - let tok = send_if(tok, huff_literals_s, false, zero!()); + let tok = send_if(tok, huff_literals_s, false, zero!()); + // empty RLE literals with last not set will not be sent by RLE decoder to buffer + let literals_id = if (is_empty && state.literals_type == LiteralType::RLE) { + state.literals_id + } else { + state.literals_id + LitID:1 + }; LiteralsDispatcherState { literals_type: literals_type, left_to_read: left_to_read, + literals_id: literals_id, } } } @@ -107,16 +134,16 @@ proc LiteralsDispatcher_test { terminator: chan out; literals_ctrl_s: chan out; literals_data_s: chan out; - raw_literals_r: chan in; + raw_literals_r: chan in; rle_literals_r: chan in; - huff_literals_r: chan in; + huff_literals_r: chan in; config(terminator: chan out) { let (literals_ctrl_s, literals_ctrl_r) = chan("literals_ctrl"); let (literals_data_s, literals_data_r) = chan("literals_data"); - let (raw_literals_s, raw_literals_r) = chan("raw_literals"); + let (raw_literals_s, raw_literals_r) = chan("raw_literals"); let (rle_literals_s, rle_literals_r) = chan("rle_literals"); - let (huff_literals_s, huff_literals_r) = chan("huff_literals"); + let (huff_literals_s, huff_literals_r) = chan("huff_literals"); spawn LiteralsDispatcher( literals_ctrl_r, @@ -138,14 +165,15 @@ proc LiteralsDispatcher_test { init { } - next(tok:token, state: ()) { + next(state: ()) { + let tok = join(); let test_ctrl: LiteralsPathCtrl[6] = [ - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:8, literals_type: LiteralType::RAW}, - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:4, literals_type: LiteralType::RLE}, - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:13, literals_type: LiteralType::RLE}, - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:15, literals_type: LiteralType::RAW}, - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:123, literals_type: LiteralType::RLE}, - LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:31, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:8, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:4, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:13, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:15, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:123, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: DecompressedSize:31, literals_type: LiteralType::RAW}, ]; let test_data: LiteralsData[10] = [ // 0. RAW @@ -165,25 +193,25 @@ proc LiteralsDispatcher_test { LiteralsData {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, last: false}, LiteralsData {data: LitData:0xFC19_63F1_CE21_C294, length: LitLength:7, last: true}, ]; - let expected_raw: LiteralsData[7] = [ + let expected_raw: LiteralsDataWithSync[7] = [ // 0. - LiteralsData {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, last: false}, + LiteralsDataWithSync {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, id: LitID:0, last: false, literals_last: true}, // 3. - LiteralsData {data: LitData:0x4CFB_41C6_7B60_5370, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x009B_0F9C_E1BA_A96D, length: LitLength:7, last: false}, + LiteralsDataWithSync {data: LitData:0x4CFB_41C6_7B60_5370, length: LitLength:8, id: LitID:3, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x009B_0F9C_E1BA_A96D, length: LitLength:7, id: LitID:4, last: false, literals_last: true}, // 5. - LiteralsData {data: LitData:0x6094_3E96_1834_C247, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0xBC02_D0E8_D728_9ABE, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0xFC19_63F1_CE21_C294, length: LitLength:7, last: true}, + LiteralsDataWithSync {data: LitData:0x6094_3E96_1834_C247, length: LitLength:8, id: LitID:6, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0xBC02_D0E8_D728_9ABE, length: LitLength:8, id: LitID:7, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, id: LitID:8, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0xFC19_63F1_CE21_C294, length: LitLength:7, id: LitID:9, last: true, literals_last: true}, ]; let expected_rle: RleLiteralsData[3] = [ // 1. - RleLiteralsData {data: RleLitData:0x23, repeat: RleLitRepeat:4, last: false}, + RleLiteralsData {data: RleLitData:0x23, repeat: RleLitRepeat:4, id: LitID:1, last: false}, // 2. - RleLiteralsData {data: RleLitData:0x35, repeat: RleLitRepeat:13, last: true}, + RleLiteralsData {data: RleLitData:0x35, repeat: RleLitRepeat:13, id: LitID:2, last: true}, // 4. - RleLiteralsData {data: RleLitData:0x5A, repeat: RleLitRepeat:123, last: false}, + RleLiteralsData {data: RleLitData:0x5A, repeat: RleLitRepeat:123, id: LitID:5, last: false}, ]; let tok = for ((counter, test_ctrl), tok): ((u32, LiteralsPathCtrl), token) in enumerate(test_ctrl) { let tok = send(tok, literals_ctrl_s, test_ctrl); @@ -197,7 +225,7 @@ proc LiteralsDispatcher_test { (tok) }(tok); - let tok_1 = for ((counter, expected_raw), tok_1): ((u32, LiteralsData), token) in enumerate(expected_raw) { + let tok_1 = for ((counter, expected_raw), tok_1): ((u32, LiteralsDataWithSync), token) in enumerate(expected_raw) { let (tok_1, raw) = recv(tok_1, raw_literals_r); trace_fmt!("Recv #{} raw literals, {:#x}", counter + u32:1, raw); assert_eq(expected_raw, raw); diff --git a/xls/modules/zstd/parallel_rams.x b/xls/modules/zstd/parallel_rams.x new file mode 100644 index 0000000000..0b0b5e1ec0 --- /dev/null +++ b/xls/modules/zstd/parallel_rams.x @@ -0,0 +1,705 @@ +// Copyright 2023 The XLS Authors +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +// this file contains implementation of parallel RAMs handling + +import std; +import xls.modules.zstd.common as common; +import xls.examples.ram; + +type BlockData = common::BlockData; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type ZstdDecodedPacket = common::ZstdDecodedPacket; +type BlockPacketLength = common::BlockPacketLength; +pub type Offset = common::Offset; + +// Configurable RAM parameters, RAM_NUM has to be a power of 2 +pub const RAM_NUM = u32:8; + +// Constants calculated from RAM parameters +pub const RAM_NUM_WIDTH = std::clog2(RAM_NUM); + +pub type RamNumber = bits[RAM_NUM_WIDTH]; +pub type RamReadStart = bits[RAM_NUM_WIDTH]; +pub type RamReadLen = bits[std::clog2(RAM_NUM + u32:1)]; + +pub fn ram_size(hb_size_kb: u32) -> u32 { + (hb_size_kb * u32:1024 * u32:8) / RAM_DATA_WIDTH / RAM_NUM +} + +pub fn ram_addr_width(hb_size_kb: u32) -> u32 { + std::clog2(ram_size(hb_size_kb)) +} + +// RAM related constants common for tests +const TEST_HISTORY_BUFFER_SIZE_KB = u32:1; +const TEST_RAM_SIZE = ram_size(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_ADDR_WIDTH = ram_addr_width(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_DATA_WIDTH = common::SYMBOL_WIDTH; +const TEST_RAM_WORD_PARTITION_SIZE = TEST_RAM_DATA_WIDTH; +const TEST_RAM_NUM_PARTITIONS = ram::num_partitions(TEST_RAM_WORD_PARTITION_SIZE, TEST_RAM_DATA_WIDTH); +const TEST_RAM_INITIALIZED = true; +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; +const TEST_RAM_REQ_MASK_ALL = std::unsigned_max_value(); +const TEST_RAM_REQ_MASK_NONE = bits[TEST_RAM_NUM_PARTITIONS]:0; + +type TestRamAddr = bits[TEST_RAM_ADDR_WIDTH]; +type TestWriteReq = ram::WriteReq; +type TestWriteResp = ram::WriteResp; +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; + +pub struct HistoryBufferPtr { number: RamNumber, addr: bits[RAM_ADDR_WIDTH] } + +fn hb_ptr_from_offset_back< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)} +>( + ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { + + const_assert!(common::OFFSET_WIDTH < u32:32); + type RamAddr = bits[RAM_ADDR_WIDTH]; + + let buff_change = offset as RamNumber; + let max_row_span = (offset >> RAM_NUM_WIDTH) as RamAddr; + let addr_change = if ptr.number >= buff_change { + (max_row_span) + } else { + (max_row_span + RamAddr:1) + }; + let number = ptr.number - buff_change; + let addr = ptr.addr - addr_change; + HistoryBufferPtr { number, addr } +} + +#[test] +fn test_hb_ptr_from_offset_back() { + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), + HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), + HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), + HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), + HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:1 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:0 }); + assert_eq( + hb_ptr_from_offset_back( + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }, Offset:1), + HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }); +} + +fn hb_ptr_from_offset_forw< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)} +>(ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { + + type RamAddr = bits[RAM_ADDR_WIDTH]; + const MAX_ADDR = (RAM_SIZE - u32:1) as RamAddr; + + let buff_change = std::mod_pow2(offset as u32, RAM_NUM) as RamNumber; + let rounded_offset = std::round_up_to_nearest_pow2_unsigned(offset as u32 + u32:1, RAM_NUM as u32); + let max_row_span = std::div_pow2(rounded_offset, RAM_NUM) as RamAddr; + let (number, addr_change) = if ptr.number as u32 + buff_change as u32 < RAM_NUM { + (ptr.number + buff_change, max_row_span - RamAddr:1) + } else { + ((buff_change as u32 - (RAM_NUM - ptr.number as u32)) as RamNumber, max_row_span) + }; + + let addr = if ptr.addr + addr_change <= MAX_ADDR { + ptr.addr + addr_change + } else { + (addr_change - (MAX_ADDR - ptr.addr)) + }; + + HistoryBufferPtr { number, addr } +} + +#[test] +fn test_hb_ptr_from_offset_forw() { + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), + HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), + HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), + HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), + HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), + HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), + HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:3 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), + HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:4 }); + assert_eq( + hb_ptr_from_offset_forw( + HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }, + Offset:1), HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }); +} + +fn literal_packet_to_single_write_req< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + RAM_DATA_WIDTH: u32 = {common::SYMBOL_WIDTH}, + RAM_WORD_PARTITION_SIZE: u32 = {RAM_DATA_WIDTH}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)} +>(ptr: HistoryBufferPtr, literal: SequenceExecutorPacket, number: RamNumber) + -> ram::WriteReq { + type RamData = uN[RAM_DATA_WIDTH]; + type WriteReq = ram::WriteReq; + + let offset = std::mod_pow2(RAM_NUM - ptr.number as u32 + number as u32, RAM_NUM) as Offset; + let we = literal.length >= offset as CopyOrMatchLength + CopyOrMatchLength:1; + let hb = hb_ptr_from_offset_forw(ptr, offset); + + if (we) { + WriteReq { + data: literal.content[offset as u32 * RAM_DATA_WIDTH+:RamData] as RamData, + addr: hb.addr, + mask: std::unsigned_max_value() + } + } else { + zero!() + } +} + +#[test] +fn test_literal_packet_to_single_write_req() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | | o|77|66|55|44|33|22| + // 4 | | | | | | | | | 4 | | | | | | | | | + type RamData = uN[TEST_RAM_DATA_WIDTH]; + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:7, + content: CopyOrMatchContent:0x77_6655_4433_2211, + last: false + }; + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:0), + TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }); + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:3), + TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }); + assert_eq( + literal_packet_to_single_write_req(ptr, literals, RamNumber:6), + zero!()); +} + +fn literal_packet_to_write_reqs< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + RAM_DATA_WIDTH: u32 = {common::SYMBOL_WIDTH}, + RAM_WORD_PARTITION_SIZE: u32 = {RAM_DATA_WIDTH}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)} +>( + ptr: HistoryBufferPtr, literal: SequenceExecutorPacket +) -> (ram::WriteReq[RAM_NUM], HistoryBufferPtr) { + type WriteReq = ram::WriteReq; + let result = WriteReq[RAM_NUM]:[ + literal_packet_to_single_write_req(ptr, literal, RamNumber:0), + literal_packet_to_single_write_req(ptr, literal, RamNumber:1), + literal_packet_to_single_write_req(ptr, literal, RamNumber:2), + literal_packet_to_single_write_req(ptr, literal, RamNumber:3), + literal_packet_to_single_write_req(ptr, literal, RamNumber:4), + literal_packet_to_single_write_req(ptr, literal, RamNumber:5), + literal_packet_to_single_write_req(ptr, literal, RamNumber:6), + literal_packet_to_single_write_req(ptr, literal, RamNumber:7), + ]; + + let ptr_offset = literal.length; + (result, hb_ptr_from_offset_forw(ptr, ptr_offset as Offset)) +} + +#[test] +fn test_literal_packet_to_write_reqs() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | | | | | | | | o| + // 4 | | | | | | | | | 4 | | | | | | | | | + type RamData = uN[TEST_RAM_DATA_WIDTH]; + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:0x2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: CopyOrMatchContent:0x11, + length: CopyOrMatchLength:1, + last: false + }; + assert_eq( + literal_packet_to_write_reqs(ptr, literals), + ( + TestWriteReq[RAM_NUM]:[ + zero!(), zero!(), zero!(), + zero!(), zero!(), zero!(), + zero!(), + TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, + ], HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x3 }, + )); + + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | o| | | | | | | | 2 |11| | | | | | | | + // 3 | | | | | | | | | 3 | o|88|77|66|55|44|33|22| + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; + let literals = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + content: CopyOrMatchContent:0x8877_6655_4433_2211, + length: CopyOrMatchLength:8, + last: false + }; + assert_eq( + literal_packet_to_write_reqs(ptr, literals), + ( + TestWriteReq[RAM_NUM]:[ + TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x33, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x44, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x66, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x77, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x88, addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, + ], HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:3 }, + )); +} + +fn max_hb_ptr_for_sequence_packet< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + RAM_DATA_WIDTH: u32 = {common::SYMBOL_WIDTH}, +> ( + ptr: HistoryBufferPtr, seq: SequenceExecutorPacket +) -> HistoryBufferPtr { + hb_ptr_from_offset_back(ptr, seq.content as Offset) +} + +fn sequence_packet_to_single_read_req< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + RAM_DATA_WIDTH: u32 = {common::SYMBOL_WIDTH}, + RAM_WORD_PARTITION_SIZE: u32 = {RAM_DATA_WIDTH}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)} +> ( + ptr: HistoryBufferPtr, max_ptr: HistoryBufferPtr, + seq: SequenceExecutorPacket, number: RamNumber +) -> ram::ReadReq { + const RAM_REQ_MASK_ALL = bits[RAM_NUM_PARTITIONS]:1; + type ReadReq = ram::ReadReq; + + let offset_change = if max_ptr.number > number { + RAM_NUM as RamNumber - max_ptr.number + number + } else { + number - max_ptr.number + }; + let offset = (seq.content as Offset - offset_change as Offset) as Offset; + let re = (offset_change as CopyOrMatchLength) < seq.length; + let hb = hb_ptr_from_offset_back(ptr, offset); + + if (re) { + ReadReq { addr: hb.addr, mask: RAM_REQ_MASK_ALL } + } else { + zero!() + } +} + +#[test] +fn test_sequence_packet_to_single_read_req() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | x| x| | | | | | | 1 | | | | | | | | | + // 2 | | | | | | | x| x| 2 | | | | | | | | | + // 3 | | | | | | | o| | 3 | | | o| y| y| y| y| | + // 4 | | | | | | | | | 4 | | | | | | | | | + + let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; + let sequence = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:11, + length: CopyOrMatchLength:4, + last: false + }; + let max_ptr = max_hb_ptr_for_sequence_packet< + TEST_HISTORY_BUFFER_SIZE_KB, TEST_RAM_ADDR_WIDTH, TEST_RAM_DATA_WIDTH + >(ptr, sequence); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:0), + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:1), + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:2), zero!()); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:7), + TestReadReq { addr: TestRamAddr:0x1, mask: TEST_RAM_REQ_MASK_ALL }); + + assert_eq( + sequence_packet_to_single_read_req( + ptr, max_ptr, sequence, RamNumber:6), + TestReadReq { addr: TestRamAddr:0x1, mask: TEST_RAM_REQ_MASK_ALL }); +} + +pub fn sequence_packet_to_read_reqs< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_ADDR_WIDTH: u32 = {ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, + RAM_DATA_WIDTH: u32 = {common::SYMBOL_WIDTH}, + RAM_WORD_PARTITION_SIZE: u32 = {RAM_DATA_WIDTH}, + RAM_NUM_PARTITIONS: u32 = {ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH)} +> ( + ptr: HistoryBufferPtr, seq: SequenceExecutorPacket, hb_len: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH] +) -> (ram::ReadReq[RAM_NUM], RamReadStart, RamReadLen, SequenceExecutorPacket, bool) { + type ReadReq = ram::ReadReq; + type Packet = SequenceExecutorPacket; + + let max_len = std::umin(seq.length as u32, std::umin(RAM_NUM, hb_len as u32)); + + let (next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { + ( + Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + length: seq.length - max_len as CopyOrMatchLength, + content: seq.content, + last: seq.last + }, true, + ) + } else { + (zero!(), false) + }; + + let max_ptr = max_hb_ptr_for_sequence_packet(ptr, seq); + let req0 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:0); + let req1 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:1); + let req2 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:2); + let req3 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:3); + let req4 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:4); + let req5 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:5); + let req6 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:6); + let req7 = sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:7); + + let reqs = ReadReq[RAM_NUM]:[req0, req1, req2, req3, req4, req5, req6, req7]; + + (reqs, max_ptr.number, max_len as RamReadLen, next_seq, next_seq_valid) +} + +#[test] +fn test_sequence_packet_to_read_reqs() { + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | x| x| | | | | | | 1 | | | | | | | | | + // 2 | | | | | | | x| x| 2 | | | | | | | | | + // 3 | | | | | | | o| | 3 | | | | | | | o| | + // 4 | | | | | | | | | 4 | | | | | | | | | + type Packet = SequenceExecutorPacket; + type HistoryBufferLength = uN[TEST_RAM_ADDR_WIDTH + RAM_NUM_WIDTH]; + + let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; + let sequence = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:11, + length: CopyOrMatchLength:4, + last: false + }; + let result = sequence_packet_to_read_reqs( + ptr, sequence, HistoryBufferLength:20); + let expected = ( + TestReadReq[RAM_NUM]:[ + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, zero!(), + zero!(), zero!(), zero!(), + TestReadReq { addr: TestRamAddr:0x1, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x1, mask: TEST_RAM_REQ_MASK_ALL }, + ], + RamReadStart:6, + RamReadLen:4, + zero!(), false, + ); + assert_eq(result, expected); + + // BEFORE: AFTER: + // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 + // 1 | | | | | | | | | 1 | | | | | | | | | + // 2 | x| x| | | | | | | 2 | | | | | | | | | + // 3 | | | x| x| x| x| x| x| 3 | | x| | | | | | | + // 4 | | | | | | | | o| 4 | | | | | | | | o| + + let ptr = HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x4 }; + let sequence = Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:10, + length: CopyOrMatchLength:9, + last: false + }; + let result = sequence_packet_to_read_reqs( + ptr, sequence, HistoryBufferLength:20); + let expected = ( + TestReadReq[RAM_NUM]:[ + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x3, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, + TestReadReq { addr: TestRamAddr:0x2, mask: TEST_RAM_REQ_MASK_ALL }, + ], + RamReadStart:6, + RamReadLen:8, + Packet { + msg_type: SequenceExecutorMessageType::SEQUENCE, + content: CopyOrMatchContent:10, + length: CopyOrMatchLength:1, + last: false + }, true, + ); + assert_eq(result, expected); +} + +pub struct RamWrRespHandlerData { + resp: bool[RAM_NUM], + ptr: HistoryBufferPtr, +} + +fn create_ram_wr_data + (reqs: ram::WriteReq[RAM_NUM], ptr: HistoryBufferPtr) -> (bool, RamWrRespHandlerData) { + const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; + + let (do_write, resp) = for (i, (do_write, resp)): (u32, (bool, bool[RAM_NUM])) in range(u32:0, RAM_NUM) { + ( + do_write || reqs[i].mask, + update(resp, i, reqs[i].mask != RAM_REQ_MASK_NONE) + ) + }((false, zero!())); + + (do_write, RamWrRespHandlerData { resp, ptr }) +} + +pub proc RamWrRespHandler { + input_r: chan in; + output_s: chan out; + wr_resp_m0_r: chan in; + wr_resp_m1_r: chan in; + wr_resp_m2_r: chan in; + wr_resp_m3_r: chan in; + wr_resp_m4_r: chan in; + wr_resp_m5_r: chan in; + wr_resp_m6_r: chan in; + wr_resp_m7_r: chan in; + + config(input_r: chan> in, + output_s: chan> out, + wr_resp_m0_r: chan in, wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, wr_resp_m7_r: chan in) { + ( + input_r, output_s, wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, wr_resp_m4_r, + wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ) + } + + init { } + + next(state: ()) { + let tok0 = join(); + let (tok1, input) = recv(tok0, input_r); + + let (tok2_0, _) = recv_if(tok1, wr_resp_m0_r, input.resp[0], zero!()); + let (tok2_1, _) = recv_if(tok1, wr_resp_m1_r, input.resp[1], zero!()); + let (tok2_2, _) = recv_if(tok1, wr_resp_m2_r, input.resp[2], zero!()); + let (tok2_3, _) = recv_if(tok1, wr_resp_m3_r, input.resp[3], zero!()); + let (tok2_4, _) = recv_if(tok1, wr_resp_m4_r, input.resp[4], zero!()); + let (tok2_5, _) = recv_if(tok1, wr_resp_m5_r, input.resp[5], zero!()); + let (tok2_6, _) = recv_if(tok1, wr_resp_m6_r, input.resp[6], zero!()); + let (tok2_7, _) = recv_if(tok1, wr_resp_m7_r, input.resp[7], zero!()); + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + let tok3 = send(tok2, output_s, input.ptr); + } +} + +pub struct RamRdRespHandlerData { + resp: bool[RAM_NUM], + read_start: RamReadStart, + read_len: RamReadLen, + last: bool +} + +fn create_ram_rd_data + (reqs: ram::ReadReq[RAM_NUM], read_start: RamReadStart, read_len: RamReadLen, last: bool, next_packet_valid: bool) -> (bool, RamRdRespHandlerData) { + const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; + + let (do_read, resp) = for (i, (do_read, resp)): (u32, (bool, bool[RAM_NUM])) in range(u32:0, RAM_NUM) { + ( + do_read || reqs[i].mask, + update(resp, i, reqs[i].mask != RAM_REQ_MASK_NONE) + ) + }((false, zero!())); + + let last = (!next_packet_valid) && last; + (do_read, RamRdRespHandlerData { resp, read_start, read_len, last }) +} + +pub proc RamRdRespHandler { + input_r: chan in; + output_s: chan> out; + rd_resp_m0_r: chan> in; + rd_resp_m1_r: chan> in; + rd_resp_m2_r: chan> in; + rd_resp_m3_r: chan> in; + rd_resp_m4_r: chan> in; + rd_resp_m5_r: chan> in; + rd_resp_m6_r: chan> in; + rd_resp_m7_r: chan> in; + + config(input_r: chan in, output_s: chan> out, + rd_resp_m0_r: chan> in, + rd_resp_m1_r: chan> in, + rd_resp_m2_r: chan> in, + rd_resp_m3_r: chan> in, + rd_resp_m4_r: chan> in, + rd_resp_m5_r: chan> in, + rd_resp_m6_r: chan> in, + rd_resp_m7_r: chan> in) { + ( + input_r, output_s, rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, rd_resp_m4_r, + rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + ) + } + + init { } + + next(state: ()) { + let tok0 = join(); + type ReadResp = ram::ReadResp; + type Content = uN[RAM_DATA_WIDTH * u32:8]; + + let (tok1, input) = recv(tok0, input_r); + + let (tok2_0, resp_0) = recv_if(tok1, rd_resp_m0_r, input.resp[0], zero!()); + let (tok2_1, resp_1) = recv_if(tok1, rd_resp_m1_r, input.resp[1], zero!()); + let (tok2_2, resp_2) = recv_if(tok1, rd_resp_m2_r, input.resp[2], zero!()); + let (tok2_3, resp_3) = recv_if(tok1, rd_resp_m3_r, input.resp[3], zero!()); + let (tok2_4, resp_4) = recv_if(tok1, rd_resp_m4_r, input.resp[4], zero!()); + let (tok2_5, resp_5) = recv_if(tok1, rd_resp_m5_r, input.resp[5], zero!()); + let (tok2_6, resp_6) = recv_if(tok1, rd_resp_m6_r, input.resp[6], zero!()); + let (tok2_7, resp_7) = recv_if(tok1, rd_resp_m7_r, input.resp[7], zero!()); + let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + + let resp_data = [ + resp_0.data, resp_1.data, resp_2.data, resp_3.data, + resp_4.data, resp_5.data, resp_6.data, resp_7.data + ]; + + let content = ( + resp_data[input.read_start + u3:7] ++ + resp_data[input.read_start + u3:6] ++ + resp_data[input.read_start + u3:5] ++ + resp_data[input.read_start + u3:4] ++ + resp_data[input.read_start + u3:3] ++ + resp_data[input.read_start + u3:2] ++ + resp_data[input.read_start + u3:1] ++ + resp_data[input.read_start + u3:0] + ); + + let output_data = SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: input.read_len as CopyOrMatchLength, + content: content as Content, + last: input.last, + }; + + let tok3 = send(tok2, output_s, output_data); + } +} diff --git a/xls/modules/zstd/raw_literals_dec.x b/xls/modules/zstd/raw_literals_dec.x index 3bcbc11ee2..0845bc83b4 100644 --- a/xls/modules/zstd/raw_literals_dec.x +++ b/xls/modules/zstd/raw_literals_dec.x @@ -17,21 +17,22 @@ import xls.modules.zstd.common; -type LiteralsData = common::LiteralsData; +type LiteralsDataWithSync = common::LiteralsDataWithSync; type LitData = common::LitData; type LitLength = common::LitLength; proc RawLiteralsDecoder { - dispatcher_r: chan in; - buffer_s: chan out; + dispatcher_r: chan in; + buffer_s: chan out; - config(dispatcher_r: chan in, buffer_s: chan out) { + config(dispatcher_r: chan in, buffer_s: chan out) { (dispatcher_r, buffer_s) } init { } - next(tok: token, state: ()) { + next(state: ()) { + let tok = join(); let (tok, resp) = recv(tok, dispatcher_r); let do_send = if resp.length == LitLength:0 { trace_fmt!("[WARNING] Packet of 0 length received by RawLiteralsDecoder"); @@ -46,12 +47,12 @@ proc RawLiteralsDecoder { #[test_proc] proc RawLiteralsDecoderTest { terminator: chan out; - dispatcher_s: chan out; - buffer_r: chan in; + dispatcher_s: chan out; + buffer_r: chan in; config(terminator: chan out) { - let (dispatcher_s, dispatcher_r) = chan("dispatcher"); - let (buffer_s, buffer_r) = chan("buffer"); + let (dispatcher_s, dispatcher_r) = chan("dispatcher"); + let (buffer_s, buffer_r) = chan("buffer"); spawn RawLiteralsDecoder(dispatcher_r, buffer_s); @@ -60,13 +61,14 @@ proc RawLiteralsDecoderTest { init { } - next(tok: token, state: ()) { - let data = LiteralsData { data: LitData:0x11_22_33_44_55_66, length: LitLength:6, last: true }; + next(state: ()) { + let tok = join(); + let data = LiteralsDataWithSync { data: LitData:0x11_22_33_44_55_66, length: LitLength:6, id: u32:0, last: true, literals_last: true }; let tok = send(tok, dispatcher_s, data); let (tok, resp) = recv(tok, buffer_r); assert_eq(resp, data); - let empty_data = LiteralsData { data: LitData:0, length: LitLength:0, last: true }; + let empty_data = LiteralsDataWithSync { data: LitData:0, length: LitLength:0, id: u32:0, last: true, literals_last: true }; let tok = send(tok, dispatcher_s, empty_data); // Resend the first packet to verify that the empty packet is dropped correctly. diff --git a/xls/modules/zstd/rle_literals_dec.x b/xls/modules/zstd/rle_literals_dec.x index cdd3ef51f2..dda39f8208 100644 --- a/xls/modules/zstd/rle_literals_dec.x +++ b/xls/modules/zstd/rle_literals_dec.x @@ -30,15 +30,17 @@ const LITERALS_LENGTH_WIDTH = common::LITERALS_LENGTH_WIDTH; type RleInput = rle_common::CompressedData; type RleOutput = rle_common::PlainData; type RleLiteralsData = common::RleLiteralsData; -type LiteralsData = common::LiteralsData; +type LiteralsDataWithSync = common::LiteralsDataWithSync; type RleLitData = common::RleLitData; type RleLitRepeat = common::RleLitRepeat; type LitData = common::LitData; +type LitID = common::LitID; type LitLength = common::LitLength; struct LiteralsSyncData { count: RleLitRepeat, + id: LitID, last: bool, } @@ -57,12 +59,13 @@ proc RleDataPacker { init { } - next(tok: token, state: ()) { + next(state: ()) { + let tok = join(); let (tok, input) = recv(tok, literals_data_r); let not_zero_repeat = (input.repeat != RleLitRepeat:0); let rle_dec_data = RleInput { symbol: input.data, count: input.repeat, last: true }; let data_tok = send_if(tok, rle_data_s, not_zero_repeat, rle_dec_data); - let sync_data = LiteralsSyncData { count: input.repeat, last: input.last }; + let sync_data = LiteralsSyncData { count: input.repeat, id: input.id, last: input.last }; let sync_tok = send_if(data_tok, sync_s, not_zero_repeat || input.last, sync_data); } } @@ -86,13 +89,14 @@ proc RleDataPacker_test { init { } - next(tok: token, state: ()) { + next(state: ()) { + let tok = join(); let test_data: RleLiteralsData[5] = [ - RleLiteralsData {data: RleLitData:0xAB, repeat: RleLitRepeat:11, last: bool:0}, - RleLiteralsData {data: RleLitData:0xCD, repeat: RleLitRepeat:3, last: bool:0}, - RleLiteralsData {data: RleLitData:0x12, repeat: RleLitRepeat:16, last: bool:0}, - RleLiteralsData {data: RleLitData:0x34, repeat: RleLitRepeat:20, last: bool:0}, - RleLiteralsData {data: RleLitData:0x56, repeat: RleLitRepeat:2, last: bool:1}, + RleLiteralsData {data: RleLitData:0xAB, repeat: RleLitRepeat:11, id: LitID:0, last: bool:0}, + RleLiteralsData {data: RleLitData:0xCD, repeat: RleLitRepeat:3, id: LitID:1, last: bool:0}, + RleLiteralsData {data: RleLitData:0x12, repeat: RleLitRepeat:16, id: LitID:2, last: bool:0}, + RleLiteralsData {data: RleLitData:0x34, repeat: RleLitRepeat:20, id: LitID:3, last: bool:0}, + RleLiteralsData {data: RleLitData:0x56, repeat: RleLitRepeat:2, id: LitID:4, last: bool:1}, ]; let tok = for ((counter, test_data), tok): ((u32, RleLiteralsData), token) in enumerate(test_data) { @@ -104,6 +108,7 @@ proc RleDataPacker_test { let expected_sync_out = LiteralsSyncData { count: test_data.repeat, + id: test_data.id, last: test_data.last, }; @@ -129,6 +134,7 @@ struct BatchPackerState { batch: LitData, data_in_batch: LitLength, count_left: RleLitRepeat, + sync_id: LitID, sync_last: bool, } @@ -139,25 +145,26 @@ const RLE_LITERALS_DATA_WIDTH_SHIFT = std::clog2(RLE_LITERALS_DATA_WIDTH); proc BatchPacker { rle_data_r: chan in; sync_r: chan in; - literals_data_s: chan out; + literals_data_s: chan out; config( rle_data_r: chan in, sync_r: chan in, - literals_data_s: chan out, + literals_data_s: chan out, ) { (rle_data_r, sync_r, literals_data_s) } init { zero!() } - next(tok: token, state: BatchPackerState) { + next(state: BatchPackerState) { + let tok = join(); let no_count_left = (state.count_left == RleLitRepeat:0); let (tok, sync_data) = recv_if(tok, sync_r, no_count_left, zero!()); - let (count_left, sync_last) = if (no_count_left) { - (sync_data.count, sync_data.last) + let (count_left, sync_id, sync_last) = if (no_count_left) { + (sync_data.count, sync_data.id, sync_data.last) } else { - (state.count_left, state.sync_last) + (state.count_left, state.sync_id, state.sync_last) }; let (literals_data, do_send_batch, state) = if (count_left != RleLitRepeat:0) { @@ -174,15 +181,18 @@ proc BatchPacker { decoded_data.last | (((data_in_batch as u32 + u32:1) << RLE_LITERALS_DATA_WIDTH_SHIFT) > LITERALS_DATA_WIDTH) ); - let literals_data = LiteralsData { + let literals_data = LiteralsDataWithSync { data: batch, length: data_in_batch, last: sync_last && decoded_data.last, + id: sync_id, + literals_last: decoded_data.last, }; let state = if do_send_batch { BatchPackerState { count_left: count_left - RleLitRepeat:1, + sync_id: sync_id, sync_last: sync_last, ..zero!() } @@ -191,6 +201,7 @@ proc BatchPacker { batch: batch, data_in_batch: data_in_batch, count_left: count_left - RleLitRepeat:1, + sync_id: sync_id, sync_last: sync_last, } }; @@ -199,10 +210,11 @@ proc BatchPacker { } else if (sync_data.last) { // handle empty literal with last set ( - LiteralsData {last: true, ..zero!()}, + LiteralsDataWithSync {id: sync_id, last: true, literals_last: true, ..zero!()}, true, BatchPackerState { count_left: count_left, + sync_id: sync_id, sync_last: sync_last, ..state }, @@ -210,10 +222,11 @@ proc BatchPacker { } else { // handle empty literal with last not set ( - zero!(), + zero!(), false, BatchPackerState { count_left: count_left, + sync_id: sync_id, sync_last: sync_last, ..state }, @@ -231,12 +244,12 @@ proc BatchPacker_test { terminator: chan out; in_s: chan out; sync_s: chan out; - out_r: chan in; + out_r: chan in; config(terminator: chan out) { let (in_s, in_r) = chan("in"); let (sync_s, sync_r) = chan("sync"); - let (out_s, out_r) = chan("out"); + let (out_s, out_r) = chan("out"); spawn BatchPacker(in_r, sync_r, out_s); @@ -245,12 +258,13 @@ proc BatchPacker_test { init { } - next(tok: token, state: ()) { + next(state: ()) { + let tok = join(); let test_sync_data: LiteralsSyncData[4] = [ - LiteralsSyncData {count: RleLitRepeat:1, last: false}, - LiteralsSyncData {count: RleLitRepeat:8, last: false}, - LiteralsSyncData {count: RleLitRepeat:10, last: false}, - LiteralsSyncData {count: RleLitRepeat:13, last: true}, + LiteralsSyncData {count: RleLitRepeat:1, id: LitID:0, last: false}, + LiteralsSyncData {count: RleLitRepeat:8, id: LitID:1, last: false}, + LiteralsSyncData {count: RleLitRepeat:10, id: LitID:2, last: false}, + LiteralsSyncData {count: RleLitRepeat:13, id: LitID:3, last: true}, ]; let test_rle_data: RleOutput[32] = [ // 1st literal @@ -275,13 +289,13 @@ proc BatchPacker_test { RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: false}, RleOutput {symbol: RleLitData:0x44, last: true}, ]; - let test_out_data: LiteralsData[6] = [ - LiteralsData {data: LitData:0x0000_0000_0000_0011, length: LitLength:1, last: false}, - LiteralsData {data: LitData:0x2222_2222_2222_2222, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x0000_0000_0000_3333, length: LitLength:2, last: false}, - LiteralsData {data: LitData:0x4444_4444_4444_4444, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x0000_0044_4444_4444, length: LitLength:5, last: true}, + let test_out_data: LiteralsDataWithSync[6] = [ + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_0011, length: LitLength:1, id: LitID:0, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x2222_2222_2222_2222, length: LitLength:8, id: LitID:1, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, id: LitID:2, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_3333, length: LitLength:2, id: LitID:2, last: false, literals_last: true}, + LiteralsDataWithSync {data: LitData:0x4444_4444_4444_4444, length: LitLength:8, id: LitID:3, last: false, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0044_4444_4444, length: LitLength:5, id: LitID:3, last: true, literals_last: true}, ]; let tok = for ((counter, sync_data), tok): ((u32, LiteralsSyncData), token) in enumerate(test_sync_data) { @@ -296,7 +310,7 @@ proc BatchPacker_test { (tok) }(tok); - let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsData), token) in enumerate(test_out_data) { + let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsDataWithSync), token) in enumerate(test_out_data) { let (tok, out_data) = recv(tok, out_r); trace_fmt!("Received #{} batched data, {:#x}", counter + u32:1, out_data); assert_eq(out_data, expected_out_data); @@ -309,9 +323,9 @@ proc BatchPacker_test { pub proc RleLiteralsDecoder { input_r: chan in; - output_s: chan out; + output_s: chan out; - config(input_r: chan in, output_s: chan out) { + config(input_r: chan in, output_s: chan out) { let (in_s, in_r) = chan("in"); let (out_s, out_r) = chan("in"); let (sync_s, sync_r) = chan("sync"); @@ -325,18 +339,18 @@ pub proc RleLiteralsDecoder { init { } - next(tok: token, state: ()) { } + next(state: ()) { } } #[test_proc] proc RleLiteralsDecoder_test { terminator: chan out; in_s: chan out; - out_r: chan in; + out_r: chan in; config (terminator: chan out) { let (in_s, in_r) = chan("in"); - let (out_s, out_r) = chan("out"); + let (out_s, out_r) = chan("out"); spawn RleLiteralsDecoder(in_r, out_s); @@ -345,35 +359,36 @@ proc RleLiteralsDecoder_test { init { } - next(tok: token, state: ()) { + next(state: ()) { + let tok = join(); let test_rle_data: RleLiteralsData[7] = [ - RleLiteralsData {data: RleLitData:0x11, repeat: RleLitRepeat:11, last: false}, - RleLiteralsData {data: RleLitData:0x22, repeat: RleLitRepeat:3, last: false}, - RleLiteralsData {data: RleLitData:0x33, repeat: RleLitRepeat:16, last: false}, - RleLiteralsData {data: RleLitData:0x44, repeat: RleLitRepeat:0, last: false}, - RleLiteralsData {data: RleLitData:0x55, repeat: RleLitRepeat:2, last: false}, - RleLiteralsData {data: RleLitData:0x66, repeat: RleLitRepeat:20, last: false}, - RleLiteralsData {data: RleLitData:0x00, repeat: RleLitRepeat:0, last: true}, + RleLiteralsData {data: RleLitData:0x11, repeat: RleLitRepeat:11, id: LitID:0, last: false}, + RleLiteralsData {data: RleLitData:0x22, repeat: RleLitRepeat:3, id: LitID:1, last: false}, + RleLiteralsData {data: RleLitData:0x33, repeat: RleLitRepeat:16, id: LitID:2, last: false}, + RleLiteralsData {data: RleLitData:0x44, repeat: RleLitRepeat:0, id: LitID:0, last: false}, + RleLiteralsData {data: RleLitData:0x55, repeat: RleLitRepeat:2, id: LitID:3, last: false}, + RleLiteralsData {data: RleLitData:0x66, repeat: RleLitRepeat:20, id: LitID:4, last: false}, + RleLiteralsData {data: RleLitData:0x00, repeat: RleLitRepeat:0, id: LitID:5, last: true}, ]; - let test_out_data: LiteralsData[10] = [ + let test_out_data: LiteralsDataWithSync[10] = [ // 1st literal - LiteralsData {data: LitData:0x1111_1111_1111_1111, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x0000_0000_0011_1111, length: LitLength:3, last: false}, + LiteralsDataWithSync {data: LitData:0x1111_1111_1111_1111, length: LitLength:8, last: false, id: LitID:0, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0011_1111, length: LitLength:3, last: false, id: LitID:0, literals_last: true}, // 2nd literal - LiteralsData {data: LitData:0x0000_0000_0022_2222, length: LitLength:3, last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0022_2222, length: LitLength:3, last: false, id: LitID:1, literals_last: true}, // 3rd literal - LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false}, + LiteralsDataWithSync {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false, id: LitID:2, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x3333_3333_3333_3333, length: LitLength:8, last: false, id: LitID:2, literals_last: true}, // 4th literal (empty) // 5th literal - LiteralsData {data: LitData:0x0000_0000_0000_5555, length: LitLength:2, last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_5555, length: LitLength:2, last: false, id: LitID:3, literals_last: true}, // 6th literal - LiteralsData {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false}, - LiteralsData {data: LitData:0x0000_0000_6666_6666, length: LitLength:4, last: false}, + LiteralsDataWithSync {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false, id: LitID:4, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x6666_6666_6666_6666, length: LitLength:8, last: false, id: LitID:4, literals_last: false}, + LiteralsDataWithSync {data: LitData:0x0000_0000_6666_6666, length: LitLength:4, last: false, id: LitID:4, literals_last: true}, // 7th literal - LiteralsData {data: LitData:0x0000_0000_0000_0000, length: LitLength:0, last: true}, + LiteralsDataWithSync {data: LitData:0x0000_0000_0000_0000, length: LitLength:0, last: true, id: LitID:5, literals_last: true}, ]; let tok = for ((counter, rle_data), tok): ((u32, RleLiteralsData), token) in enumerate(test_rle_data) { @@ -382,7 +397,7 @@ proc RleLiteralsDecoder_test { (tok) }(tok); - let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsData), token) in enumerate(test_out_data) { + let tok = for ((counter, expected_out_data), tok): ((u32, LiteralsDataWithSync), token) in enumerate(test_out_data) { let (tok, out_data) = recv(tok, out_r); trace_fmt!("Received #{} batched data, {:#x}", counter + u32:1, out_data); assert_eq(out_data, expected_out_data); diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 7d5e7a8d08..5ac772fde3 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -14,28 +14,24 @@ import std; import xls.modules.zstd.common as common; +import xls.modules.zstd.parallel_rams as parallel_rams; import xls.modules.zstd.ram_printer as ram_printer; import xls.examples.ram; +// Configurable RAM parameters +pub const RAM_DATA_WIDTH = common::SYMBOL_WIDTH; +const RAM_NUM = u32:8; +const RAM_NUM_CLOG2 = std::clog2(RAM_NUM); + type BlockData = common::BlockData; type SequenceExecutorMessageType = common::SequenceExecutorMessageType; -type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorPacket = common::SequenceExecutorPacket; type CopyOrMatchContent = common::CopyOrMatchContent; type CopyOrMatchLength = common::CopyOrMatchLength; type ZstdDecodedPacket = common::ZstdDecodedPacket; type BlockPacketLength = common::BlockPacketLength; type Offset = common::Offset; -fn calculate_ram_addr_width(hb_size_kb: u32, ram_data_width: u32, ram_num: u32) -> u32 { - ((hb_size_kb * u32:1024 * u32:8) / ram_data_width) / ram_num -} - -// Configurable RAM parameters -pub const RAM_DATA_WIDTH = common::SYMBOL_WIDTH; -const RAM_NUM = u32:8; - -type RamData = bits[RAM_DATA_WIDTH]; - // Constants calculated from RAM parameters const RAM_NUM_WIDTH = std::clog2(RAM_NUM); pub const RAM_WORD_PARTITION_SIZE = RAM_DATA_WIDTH; @@ -44,7 +40,6 @@ pub const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_ const RAM_REQ_MASK_ALL = std::unsigned_max_value(); const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; -type RamNumber = bits[RAM_NUM_WIDTH]; type RamOrder = bits[RAM_ORDER_WIDTH]; pub fn ram_size(hb_size_kb: u32) -> u32 { (hb_size_kb * u32:1024 * u32:8) / RAM_DATA_WIDTH / RAM_NUM } @@ -64,9 +59,13 @@ type TestWriteResp = ram::WriteResp; type TestReadReq = ram::ReadReq; type TestReadResp = ram::ReadResp; -struct HistoryBufferPtr { number: RamNumber, addr: bits[RAM_ADDR_WIDTH] } - -type HistoryBufferLength = u32; +type HistoryBufferPtr = parallel_rams::HistoryBufferPtr; +type RamWrRespHandlerData = parallel_rams::RamWrRespHandlerData; +type RamRdRespHandlerData = parallel_rams::RamRdRespHandlerData; +type RamData = uN[RAM_DATA_WIDTH]; +type RamNumber = parallel_rams::RamNumber; +type RamReadStart = parallel_rams::RamReadStart; +type RamReadLen = parallel_rams::RamReadLen; enum SequenceExecutorStatus : u2 { IDLE = 0, @@ -83,7 +82,7 @@ struct SequenceExecutorState { // History Buffer handling hyp_ptr: HistoryBufferPtr, real_ptr: HistoryBufferPtr, - hb_len: HistoryBufferLength, + hb_len: uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2], // Repeat Offset handling repeat_offsets: Offset[3], repeat_req: bool, @@ -92,14 +91,14 @@ struct SequenceExecutorState { fn decode_literal_packet(packet: SequenceExecutorPacket) -> ZstdDecodedPacket { ZstdDecodedPacket { - data: packet.content, length: packet.length as BlockPacketLength, last: packet.last + data: packet.content, length: (packet.length << u32:3) as BlockPacketLength, last: packet.last } } #[test] fn test_decode_literal_packet() { - let content = CopyOrMatchContent:0xAA00BB11CC22DD33; - let length = CopyOrMatchLength:64; + let content = CopyOrMatchContent:0xAA00_BB11_CC22_DD33; + let length = CopyOrMatchLength:8; let last = false; assert_eq( @@ -109,672 +108,30 @@ fn test_decode_literal_packet() { length, content, last }), ZstdDecodedPacket { - length: length as BlockPacketLength, + length: length as BlockPacketLength << u32:3, data: content, last }) } -fn round_up_to_pow2(x: uN[N]) -> uN[N] { - let base = x[Y_CLOG2 as s32:]; - let reminder = x[0:Y_CLOG2 as s32] != bits[Y_CLOG2]:0; - (base as uN[N] + reminder as uN[N]) << Y_CLOG2 -} - -#[test] -fn test_round_up_to_pow2() { - assert_eq(round_up_to_pow2(u16:0), u16:0); - assert_eq(round_up_to_pow2(u16:1), u16:8); - assert_eq(round_up_to_pow2(u16:7), u16:8); - assert_eq(round_up_to_pow2(u16:8), u16:8); - assert_eq(round_up_to_pow2(u16:9), u16:16); - assert_eq(round_up_to_pow2(u16:9), u16:16); -} - -fn hb_ptr_from_offset_back - - (ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { - - const_assert!(common::OFFSET_WIDTH < u32:32); - type RamAddr = bits[RAM_ADDR_WIDTH]; - - let buff_change = std::mod_pow2(offset as u32, RAM_NUM) as RamNumber; - let rounded_offset = round_up_to_pow2(offset as u32 + u32:1); - let max_row_span = std::div_pow2(rounded_offset, RAM_NUM) as RamAddr; - let (number, addr_change) = if ptr.number >= buff_change { - (ptr.number - buff_change, max_row_span - RamAddr:1) - } else { - ((RAM_NUM + ptr.number as u32 - buff_change as u32) as RamNumber, max_row_span) - }; - let addr = if ptr.addr > addr_change { - ptr.addr - addr_change - } else { - (RAM_SIZE + ptr.addr as u32 - addr_change as u32) as RamAddr - }; - HistoryBufferPtr { number, addr } -} - -#[test] -fn test_hb_ptr_from_offset_back() { - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), - HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), - HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), - HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), - HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), - HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:1 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), - HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:1 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), - HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:1 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:1 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), - HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:0 }); - assert_eq( - hb_ptr_from_offset_back( - HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }, Offset:1), - HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }); -} - -fn hb_ptr_from_offset_forw - - (ptr: HistoryBufferPtr, offset: Offset) -> HistoryBufferPtr { - - type RamAddr = bits[RAM_ADDR_WIDTH]; - const MAX_ADDR = (RAM_SIZE - u32:1) as RamAddr; - - let buff_change = std::mod_pow2(offset as u32, RAM_NUM) as RamNumber; - let rounded_offset = round_up_to_pow2(offset as u32 + u32:1); - let max_row_span = std::div_pow2(rounded_offset, RAM_NUM) as RamAddr; - let (number, addr_change) = if ptr.number as u32 + buff_change as u32 < RAM_NUM { - (ptr.number + buff_change, max_row_span - RamAddr:1) - } else { - ((buff_change as u32 - (RAM_NUM - ptr.number as u32)) as RamNumber, max_row_span) - }; - - let addr = if ptr.addr + addr_change <= MAX_ADDR { - ptr.addr + addr_change - } else { - (addr_change - (MAX_ADDR - ptr.addr)) - }; - - HistoryBufferPtr { number, addr } -} - -#[test] -fn test_hb_ptr_from_offset_forw() { - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:0), - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:1), - HistoryBufferPtr { number: RamNumber:5, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:2), - HistoryBufferPtr { number: RamNumber:6, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:3), - HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:4), - HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:3 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:5), - HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:3 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:6), - HistoryBufferPtr { number: RamNumber:2, addr: TestRamAddr:3 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:7), - HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:3 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:8), - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:3 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:4, addr: TestRamAddr:2 }, Offset:15), - HistoryBufferPtr { number: RamNumber:3, addr: TestRamAddr:4 }); - assert_eq( - hb_ptr_from_offset_forw( - HistoryBufferPtr { number: RamNumber:7, addr: (TEST_RAM_SIZE - u32:1) as TestRamAddr }, - Offset:1), HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0 }); -} - -fn literal_packet_to_single_write_req - - (ptr: HistoryBufferPtr, literal: SequenceExecutorPacket, number: RamNumber) - -> ram::WriteReq { - - let offset = std::mod_pow2(RAM_NUM - ptr.number as u32 + number as u32, RAM_NUM) as Offset; - let we = literal.length >= (offset as CopyOrMatchLength + CopyOrMatchLength:1) << CopyOrMatchLength:3; - let hb = hb_ptr_from_offset_forw(ptr, offset); - - if we { - ram::WriteReq { - data: literal.content[offset as u32 << u32:3+:RamData] as RamData, - addr: hb.addr, - mask: std::unsigned_max_value() - } - } else { - ram::WriteReq { - addr: bits[RAM_ADDR_WIDTH]:0, - data: bits[RAM_DATA_WIDTH]:0, - mask: bits[RAM_NUM_PARTITIONS]:0 - } - } -} - -#[test] -fn test_literal_packet_to_single_write_req() { - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | | | | | | | | | 1 | | | | | | | | | - // 2 | o| | | | | | | | 2 |11| | | | | | | | - // 3 | | | | | | | | | 3 | | o|77|66|55|44|33|22| - // 4 | | | | | | | | | 4 | | | | | | | | | - - let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; - let literals = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - content: CopyOrMatchContent:0x77_6655_4433_2211, - length: CopyOrMatchLength:56, - last: false - }; - assert_eq( - literal_packet_to_single_write_req(ptr, literals, RamNumber:0), - TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }); - assert_eq( - literal_packet_to_single_write_req(ptr, literals, RamNumber:3), - TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }); - assert_eq( - literal_packet_to_single_write_req(ptr, literals, RamNumber:6), - zero!()); -} - -fn literal_packet_to_write_reqs - - (ptr: HistoryBufferPtr, literal: SequenceExecutorPacket) - -> (ram::WriteReq[RAM_NUM], HistoryBufferPtr) { - type WriteReq = ram::WriteReq; - let result = WriteReq[RAM_NUM]:[ - literal_packet_to_single_write_req(ptr, literal, RamNumber:0), - literal_packet_to_single_write_req(ptr, literal, RamNumber:1), - literal_packet_to_single_write_req(ptr, literal, RamNumber:2), - literal_packet_to_single_write_req(ptr, literal, RamNumber:3), - literal_packet_to_single_write_req(ptr, literal, RamNumber:4), - literal_packet_to_single_write_req(ptr, literal, RamNumber:5), - literal_packet_to_single_write_req(ptr, literal, RamNumber:6), - literal_packet_to_single_write_req(ptr, literal, RamNumber:7), - ]; - - let ptr_offset = literal.length >> CopyOrMatchLength:3; - (result, hb_ptr_from_offset_forw(ptr, ptr_offset as Offset)) -} - -#[test] -fn test_literal_packet_to_write_reqs() { - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | | | | | | | | | 1 | | | | | | | | | - // 2 | o| | | | | | | | 2 |11| | | | | | | | - // 3 | | | | | | | | | 3 | | | | | | | | o| - // 4 | | | | | | | | | 4 | | | | | | | | | - - let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:0x2 }; - let literals = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - content: CopyOrMatchContent:0x11, - length: CopyOrMatchLength:8, - last: false - }; - assert_eq( - literal_packet_to_write_reqs(ptr, literals), - ( - TestWriteReq[RAM_NUM]:[ - zero!(), zero!(), zero!(), - zero!(), zero!(), zero!(), - zero!(), - TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, - ], HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x3 }, - )); - - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | | | | | | | | | 1 | | | | | | | | | - // 2 | o| | | | | | | | 2 |11| | | | | | | | - // 3 | | | | | | | | | 3 | o|88|77|66|55|44|33|22| - // 4 | | | | | | | | | 4 | | | | | | | | | - - let ptr = HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:2 }; - let literals = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - content: CopyOrMatchContent:0x8877_6655_4433_2211, - length: CopyOrMatchLength:64, - last: false - }; - assert_eq( - literal_packet_to_write_reqs(ptr, literals), - ( - TestWriteReq[RAM_NUM]:[ - TestWriteReq { data: RamData:0x22, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x33, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x44, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x55, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x66, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x77, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x88, addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestWriteReq { data: RamData:0x11, addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, - ], HistoryBufferPtr { number: RamNumber:7, addr: TestRamAddr:3 }, - )); -} - -fn max_hb_ptr_for_sequence_packet - - (ptr: HistoryBufferPtr, seq: SequenceExecutorPacket) - -> HistoryBufferPtr { - hb_ptr_from_offset_back(ptr, seq.content as Offset) -} - -fn sequence_packet_to_single_read_req - - (ptr: HistoryBufferPtr, max_ptr: HistoryBufferPtr, - seq: SequenceExecutorPacket, number: RamNumber) - -> (ram::ReadReq, RamOrder) { - type ReadReq = ram::ReadReq; - let offset_change = if max_ptr.number > number { - RAM_NUM - max_ptr.number as u32 + number as u32 - } else { - number as u32 - max_ptr.number as u32 - }; - let offset = (seq.content as u32 - offset_change) as Offset; - let re = (offset_change as CopyOrMatchLength) < seq.length; - let hb = hb_ptr_from_offset_back(ptr, offset); - - if re { - (ReadReq { addr: hb.addr, mask: RAM_REQ_MASK_ALL }, offset_change as RamOrder) - } else { - (zero!(), RamOrder:0) - } -} - -#[test] -fn test_sequence_packet_to_single_read_req() { - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | x| x| | | | | | | 1 | | | | | | | | | - // 2 | | | | | | | x| x| 2 | | | | | | | | | - // 3 | | | | | | | o| | 3 | | | o| y| y| y| y| | - // 4 | | | | | | | | | 4 | | | | | | | | | - - let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; - let sequence = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - content: CopyOrMatchContent:11, - length: CopyOrMatchLength:4, - last: false - }; - let max_ptr = max_hb_ptr_for_sequence_packet(ptr, sequence); - - assert_eq( - sequence_packet_to_single_read_req( - ptr, max_ptr, sequence, RamNumber:0), - (TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, RamOrder:2)); - - assert_eq( - sequence_packet_to_single_read_req( - ptr, max_ptr, sequence, RamNumber:1), - (TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, RamOrder:3)); - - assert_eq( - sequence_packet_to_single_read_req( - ptr, max_ptr, sequence, RamNumber:2), (zero!(), RamOrder:0)); - - assert_eq( - sequence_packet_to_single_read_req( - ptr, max_ptr, sequence, RamNumber:7), - (TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, RamOrder:1)); - - assert_eq( - sequence_packet_to_single_read_req( - ptr, max_ptr, sequence, RamNumber:6), - (TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, RamOrder:0)); -} - -fn sequence_packet_to_read_reqs - - (ptr: HistoryBufferPtr, seq: SequenceExecutorPacket, hb_len: HistoryBufferLength) - -> (ram::ReadReq[RAM_NUM], RamOrder[RAM_NUM], SequenceExecutorPacket, bool) { - type ReadReq = ram::ReadReq; - - let max_len = std::umin(seq.length as u32, std::umin(RAM_NUM, hb_len)); - - let (next_seq, next_seq_valid) = if seq.length > max_len as CopyOrMatchLength { - ( - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - length: seq.length - max_len as CopyOrMatchLength, - content: seq.content, - last: seq.last - }, true, - ) - } else { - (zero!(), false) - }; - - let max_ptr = max_hb_ptr_for_sequence_packet(ptr, seq); - let (req0, order0) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:0); - let (req1, order1) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:1); - let (req2, order2) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:2); - let (req3, order3) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:3); - let (req4, order4) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:4); - let (req5, order5) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:5); - let (req6, order6) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:6); - let (req7, order7) = - sequence_packet_to_single_read_req(ptr, max_ptr, seq, RamNumber:7); - - let reqs = ReadReq[RAM_NUM]:[req0, req1, req2, req3, req4, req5, req6, req7]; - let orders = RamOrder[RAM_NUM]:[order0, order1, order2, order3, order4, order5, order6, order7]; - (reqs, orders, next_seq, next_seq_valid) -} - -#[test] -fn test_sequence_packet_to_read_reqs() { - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | x| x| | | | | | | 1 | | | | | | | | | - // 2 | | | | | | | x| x| 2 | | | | | | | | | - // 3 | | | | | | | o| | 3 | | | | | | | o| | - // 4 | | | | | | | | | 4 | | | | | | | | | - - let ptr = HistoryBufferPtr { number: RamNumber:1, addr: TestRamAddr:0x3 }; - let sequence = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - content: CopyOrMatchContent:11, - length: CopyOrMatchLength:4, - last: false - }; - let result = sequence_packet_to_read_reqs( - ptr, sequence, HistoryBufferLength:20); - let expected = ( - TestReadReq[RAM_NUM]:[ - TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, zero!(), - zero!(), zero!(), zero!(), - TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x1, mask: RAM_REQ_MASK_ALL }, - ], - RamOrder[RAM_NUM]:[ - RamOrder:2, RamOrder:3, zero!(), zero!(), zero!(), - zero!(), RamOrder:0, RamOrder:1, - ], zero!(), false, - ); - assert_eq(result, expected); - - // BEFORE: AFTER: - // 7 6 5 4 3 2 1 0 7 6 5 4 3 2 1 0 - // 1 | | | | | | | | | 1 | | | | | | | | | - // 2 | x| x| | | | | | | 2 | | | | | | | | | - // 3 | | | x| x| x| x| x| x| 3 | | x| | | | | | | - // 4 | | | | | | | | o| 4 | | | | | | | | o| - - let ptr = HistoryBufferPtr { number: RamNumber:0, addr: TestRamAddr:0x4 }; - let sequence = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - content: CopyOrMatchContent:10, - length: CopyOrMatchLength:9, - last: false - }; - let result = sequence_packet_to_read_reqs( - ptr, sequence, HistoryBufferLength:20); - let expected = ( - TestReadReq[RAM_NUM]:[ - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x3, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, - TestReadReq { addr: TestRamAddr:0x2, mask: RAM_REQ_MASK_ALL }, - ], - RamOrder[RAM_NUM]:[ - RamOrder:2, RamOrder:3, RamOrder:4, RamOrder:5, RamOrder:6, RamOrder:7, RamOrder:0, - RamOrder:1, - ], - SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::SEQUENCE, - content: CopyOrMatchContent:10, - length: CopyOrMatchLength:1, - last: false - }, true, - ); - assert_eq(result, expected); -} - -struct RamWrRespHandlerData { - resp: bool[RAM_NUM], - ptr: HistoryBufferPtr, -} - -fn create_ram_wr_data - (reqs: ram::WriteReq[RAM_NUM], ptr: HistoryBufferPtr) -> (bool, RamWrRespHandlerData) { - let do_write = for (i, do_write): (u32, bool) in range(u32:0, RAM_NUM) { - do_write || reqs[i].mask - }(false); - - let resp = bool[RAM_NUM]:[ - ((reqs[0]).mask != RAM_REQ_MASK_NONE), - ((reqs[1]).mask != RAM_REQ_MASK_NONE), - ((reqs[2]).mask != RAM_REQ_MASK_NONE), - ((reqs[3]).mask != RAM_REQ_MASK_NONE), - ((reqs[4]).mask != RAM_REQ_MASK_NONE), - ((reqs[5]).mask != RAM_REQ_MASK_NONE), - ((reqs[6]).mask != RAM_REQ_MASK_NONE), - ((reqs[7]).mask != RAM_REQ_MASK_NONE), - ]; - - (do_write, RamWrRespHandlerData { resp, ptr }) -} - -proc RamWrRespHandler { - input_r: chan in; - output_s: chan out; - wr_resp_m0_r: chan in; - wr_resp_m1_r: chan in; - wr_resp_m2_r: chan in; - wr_resp_m3_r: chan in; - wr_resp_m4_r: chan in; - wr_resp_m5_r: chan in; - wr_resp_m6_r: chan in; - wr_resp_m7_r: chan in; - - config(input_r: chan> in, - output_s: chan> out, - wr_resp_m0_r: chan in, wr_resp_m1_r: chan in, - wr_resp_m2_r: chan in, wr_resp_m3_r: chan in, - wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, - wr_resp_m6_r: chan in, wr_resp_m7_r: chan in) { - ( - input_r, output_s, wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, wr_resp_m4_r, - wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, - ) - } - - init { } - - next(state: ()) { - let tok0 = join(); - let (tok1, input) = recv(tok0, input_r); - - let (tok2_0, _) = recv_if(tok1, wr_resp_m0_r, input.resp[0], zero!()); - let (tok2_1, _) = recv_if(tok1, wr_resp_m1_r, input.resp[1], zero!()); - let (tok2_2, _) = recv_if(tok1, wr_resp_m2_r, input.resp[2], zero!()); - let (tok2_3, _) = recv_if(tok1, wr_resp_m3_r, input.resp[3], zero!()); - let (tok2_4, _) = recv_if(tok1, wr_resp_m4_r, input.resp[4], zero!()); - let (tok2_5, _) = recv_if(tok1, wr_resp_m5_r, input.resp[5], zero!()); - let (tok2_6, _) = recv_if(tok1, wr_resp_m6_r, input.resp[6], zero!()); - let (tok2_7, _) = recv_if(tok1, wr_resp_m7_r, input.resp[7], zero!()); - let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); - - let tok3 = send(tok2, output_s, input.ptr); - } -} - -struct RamRdRespHandlerData { - resp: bool[RAM_NUM], - order: RamOrder[RAM_NUM], - last: bool -} - -fn create_ram_rd_data - (reqs: ram::ReadReq[RAM_NUM], order: RamOrder[RAM_NUM], last: bool, next_packet_valid: bool) -> (bool, RamRdRespHandlerData) { - let do_read = for (i, do_read): (u32, bool) in range(u32:0, RAM_NUM) { - do_read || reqs[i].mask - }(false); - - let resp = bool[RAM_NUM]:[ - ((reqs[0]).mask != RAM_REQ_MASK_NONE), - ((reqs[1]).mask != RAM_REQ_MASK_NONE), - ((reqs[2]).mask != RAM_REQ_MASK_NONE), - ((reqs[3]).mask != RAM_REQ_MASK_NONE), - ((reqs[4]).mask != RAM_REQ_MASK_NONE), - ((reqs[5]).mask != RAM_REQ_MASK_NONE), - ((reqs[6]).mask != RAM_REQ_MASK_NONE), - ((reqs[7]).mask != RAM_REQ_MASK_NONE), - ]; - - let last = if next_packet_valid { false } else { last }; - (do_read, RamRdRespHandlerData { resp, order, last }) -} - -proc RamRdRespHandler { - input_r: chan in; - output_s: chan out; - rd_resp_m0_r: chan> in; - rd_resp_m1_r: chan> in; - rd_resp_m2_r: chan> in; - rd_resp_m3_r: chan> in; - rd_resp_m4_r: chan> in; - rd_resp_m5_r: chan> in; - rd_resp_m6_r: chan> in; - rd_resp_m7_r: chan> in; - - config(input_r: chan in, output_s: chan out, - rd_resp_m0_r: chan> in, - rd_resp_m1_r: chan> in, - rd_resp_m2_r: chan> in, - rd_resp_m3_r: chan> in, - rd_resp_m4_r: chan> in, - rd_resp_m5_r: chan> in, - rd_resp_m6_r: chan> in, - rd_resp_m7_r: chan> in) { - ( - input_r, output_s, rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, rd_resp_m4_r, - rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, - ) - } - - init { } - - next(state: ()) { - let tok0 = join(); - type ReadResp = ram::ReadResp; - - let (tok1, input) = recv(tok0, input_r); - - let (tok2_0, resp_0) = recv_if(tok1, rd_resp_m0_r, input.resp[0], zero!()); - let (tok2_1, resp_1) = recv_if(tok1, rd_resp_m1_r, input.resp[1], zero!()); - let (tok2_2, resp_2) = recv_if(tok1, rd_resp_m2_r, input.resp[2], zero!()); - let (tok2_3, resp_3) = recv_if(tok1, rd_resp_m3_r, input.resp[3], zero!()); - let (tok2_4, resp_4) = recv_if(tok1, rd_resp_m4_r, input.resp[4], zero!()); - let (tok2_5, resp_5) = recv_if(tok1, rd_resp_m5_r, input.resp[5], zero!()); - let (tok2_6, resp_6) = recv_if(tok1, rd_resp_m6_r, input.resp[6], zero!()); - let (tok2_7, resp_7) = recv_if(tok1, rd_resp_m7_r, input.resp[7], zero!()); - let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); - - let content = (resp_0.data as CopyOrMatchContent) << (input.order[0] as CopyOrMatchContent << 3) | - (resp_1.data as CopyOrMatchContent) << (input.order[1] as CopyOrMatchContent << 3) | - (resp_2.data as CopyOrMatchContent) << (input.order[2] as CopyOrMatchContent << 3) | - (resp_3.data as CopyOrMatchContent) << (input.order[3] as CopyOrMatchContent << 3) | - (resp_4.data as CopyOrMatchContent) << (input.order[4] as CopyOrMatchContent << 3) | - (resp_5.data as CopyOrMatchContent) << (input.order[5] as CopyOrMatchContent << 3) | - (resp_6.data as CopyOrMatchContent) << (input.order[6] as CopyOrMatchContent << 3) | - (resp_7.data as CopyOrMatchContent) << (input.order[7] as CopyOrMatchContent << 3); - - let converted = std::convert_to_bits_msb0(input.resp); - let length = std::popcount(converted) << 3; - - let output_data = SequenceExecutorPacket { - msg_type: SequenceExecutorMessageType::LITERAL, - length: length as CopyOrMatchLength, - content: content as CopyOrMatchContent, - last: input.last, - }; - - let tok3 = send(tok2, output_s, output_data); - } -} - -fn handle_reapeated_offset_for_sequences - (seq: SequenceExecutorPacket, repeat_offsets: Offset[3], repeat_req: bool) - -> (SequenceExecutorPacket, Offset[3]) { +pub fn handle_repeated_offset_for_sequences + (seq: SequenceExecutorPacket, repeat_offsets: Offset[3], repeat_req: bool) + -> (SequenceExecutorPacket, Offset[3]) { + type Packet = SequenceExecutorPacket; + type Content = uN[RAM_DATA_WIDTH * u32:8]; let modified_repeat_offsets = if repeat_req { Offset[3]:[repeat_offsets[1], repeat_offsets[2], repeat_offsets[0] - Offset:1] } else { repeat_offsets }; - let (seq, final_repeat_offsets) = if seq.content == CopyOrMatchContent:0 { + let (seq, final_repeat_offsets) = if seq.content == Content:0 { fail!( "match_offset_zero_not_allowed", - (zero!(), Offset[3]:[Offset:0, ...])) - } else if seq.content == CopyOrMatchContent:1 { + (zero!(), Offset[3]:[Offset:0, ...])) + } else if seq.content == Content:1 { let offset = modified_repeat_offsets[0]; ( - SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Packet { content: offset as Content, ..seq }, Offset[3]:[ offset, repeat_offsets[1], repeat_offsets[2], ], @@ -782,7 +139,7 @@ fn handle_reapeated_offset_for_sequences } else if seq.content == CopyOrMatchContent:2 { let offset = modified_repeat_offsets[1]; ( - SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Packet { content: offset as Content, ..seq }, Offset[3]:[ offset, repeat_offsets[0], repeat_offsets[2], ], @@ -790,7 +147,7 @@ fn handle_reapeated_offset_for_sequences } else if seq.content == CopyOrMatchContent:3 { let offset = modified_repeat_offsets[2]; ( - SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Packet { content: offset as Content, ..seq }, Offset[3]:[ offset, repeat_offsets[0], repeat_offsets[1], ], @@ -798,7 +155,7 @@ fn handle_reapeated_offset_for_sequences } else { let offset = seq.content as Offset - Offset:3; ( - SequenceExecutorPacket { content: offset as CopyOrMatchContent, ..seq }, + Packet { content: offset as Content, ..seq }, Offset[3]:[ offset, repeat_offsets[0], repeat_offsets[1], ], @@ -808,10 +165,10 @@ fn handle_reapeated_offset_for_sequences } proc SequenceExecutor { input_r: chan in; @@ -879,12 +236,12 @@ proc SequenceExecutor, u32:1>("ram_comp_output"); let (ram_resp_input_s, ram_resp_input_r) = chan("ram_resp_input"); - spawn RamWrRespHandler( + spawn parallel_rams::RamWrRespHandler( ram_comp_input_r, ram_comp_output_s, wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r); - spawn RamRdRespHandler( + spawn parallel_rams::RamRdRespHandler( ram_resp_input_r, ram_resp_output_s, rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r); @@ -914,7 +271,7 @@ proc SequenceExecutor; type WriteReq = ram::WriteReq; type WriteResp = ram::WriteResp; + type HistoryBufferLength = uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2]; const ZERO_READ_REQS = ReadReq[RAM_NUM]:[zero!(), ...]; const ZERO_WRITE_REQS = WriteReq[RAM_NUM]:[zero!(), ...]; - const ZERO_ORDER = RamOrder[RAM_NUM]:[RamOrder:0, ...]; // Recieve literals and sequences from the input channel ... let do_recv_input = !state.packet_valid && state.status != Status::SEQUENCE_READ && @@ -984,19 +341,20 @@ proc SequenceExecutor { trace_fmt!("SequenceExecutor:: Handling LITERAL packet in LITERAL_WRITE step"); let (write_reqs, new_hyp_ptr) = - literal_packet_to_write_reqs(state.hyp_ptr, packet); + parallel_rams::literal_packet_to_write_reqs(state.hyp_ptr, packet); let new_repeat_req = packet.length == CopyOrMatchLength:0; - let hb_add = (packet.length >> 3) as HistoryBufferLength; - let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL); + let hb_add = packet.length as HistoryBufferLength; + let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2]); + ( - write_reqs, ZERO_READ_REQS, ZERO_ORDER, + write_reqs, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, State { status: Status::LITERAL_WRITE, packet: zero!(), @@ -1014,7 +372,7 @@ proc SequenceExecutor { trace_fmt!("Handling SEQUENCE in SEQUENCE_READ state"); let (packet, new_repeat_offsets) = if !state.seq_cnt { - handle_reapeated_offset_for_sequences( + handle_repeated_offset_for_sequences( packet, state.repeat_offsets, state.repeat_req) } else { (packet, state.repeat_offsets) }; - let (read_reqs, order, packet, packet_valid) = sequence_packet_to_read_reqs< + let (read_reqs, read_start, read_len, packet, packet_valid) = parallel_rams::sequence_packet_to_read_reqs< HISTORY_BUFFER_SIZE_KB>( state.hyp_ptr, packet, state.hb_len); ( - ZERO_WRITE_REQS, read_reqs, order, + ZERO_WRITE_REQS, read_reqs, read_start, read_len, SequenceExecutorState { status: Status::SEQUENCE_WRITE, packet, @@ -1047,19 +405,19 @@ proc SequenceExecutor { - let ZERO_RETURN = (ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, zero!()); - fail!("should_no_happen", (ZERO_RETURN)) + let ZERO_RETURN = (ZERO_WRITE_REQS, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, zero!()); + fail!("should_not_happen", (ZERO_RETURN)) }, // Handling SEQUENCE_WRITE (Status::SEQUENCE_WRITE, true, MsgType::LITERAL) => { trace_fmt!("Handling LITERAL in SEQUENCE_WRITE state: {}", status); let (write_reqs, new_hyp_ptr) = - literal_packet_to_write_reqs(state.hyp_ptr, packet); + parallel_rams::literal_packet_to_write_reqs(state.hyp_ptr, packet); let hb_add = packet.length as HistoryBufferLength; - let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL); + let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as uN[RAM_ADDR_WIDTH + RAM_NUM_CLOG2]); ( - write_reqs, ZERO_READ_REQS, ZERO_ORDER, + write_reqs, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, SequenceExecutorState { status: zero!(), packet: state.packet, @@ -1082,7 +440,7 @@ proc SequenceExecutor { let status = Status::IDLE; ( - ZERO_WRITE_REQS, ZERO_READ_REQS, ZERO_ORDER, + ZERO_WRITE_REQS, ZERO_READ_REQS, RamReadStart:0, RamReadLen:0, State { status, ..NO_VALID_PACKET_STATE }, ) }, @@ -1106,7 +464,7 @@ proc SequenceExecutor - (read_reqs, order, packet.last, new_state.packet_valid); + parallel_rams::create_ram_rd_data + (read_reqs, read_start, read_len, packet.last, new_state.packet_valid); if do_read { trace_fmt!("Sending request to RamRdRespHandler: {:#x}", rd_resp_handler_data); } else { }; @@ -1140,8 +498,8 @@ proc SequenceExecutor out; - input_s: chan> out; + input_s: chan out; output_r: chan in; print_start_s: chan<()> out; @@ -1312,7 +670,7 @@ proc SequenceExecutorLiteralsTest { ram_wr_resp_r: chan[RAM_NUM] in; config(terminator: chan out) { - let (input_s, input_r) = chan>("input"); + let (input_s, input_r) = chan("input"); let (output_s, output_r) = chan("output"); let (looped_channel_s, looped_channel_r) = chan("looped_channels"); diff --git a/xls/modules/zstd/zstd_dec.x b/xls/modules/zstd/zstd_dec.x index 259361de8f..acb68d4d2b 100644 --- a/xls/modules/zstd/zstd_dec.x +++ b/xls/modules/zstd/zstd_dec.x @@ -32,7 +32,7 @@ type Buffer = buff::Buffer; type BlockDataPacket = common::BlockDataPacket; type BlockData = common::BlockData; type BlockSize = common::BlockSize; -type SequenceExecutorPacket = common::SequenceExecutorPacket; +type SequenceExecutorPacket = common::SequenceExecutorPacket; type ZstdDecodedPacket = common::ZstdDecodedPacket; // TODO: all of this porboably should be in common.x From 7143a28f6626aa030c3d257c0e9102154c6a3e04 Mon Sep 17 00:00:00 2001 From: Maciej Torhan Date: Thu, 27 Jun 2024 11:35:59 +0200 Subject: [PATCH 48/49] modules/zstd: Add LiteralsDecoder implementation Signed-off-by: Maciej Torhan --- xls/modules/zstd/BUILD | 98 ++++ xls/modules/zstd/literals_buffer.x | 43 +- xls/modules/zstd/literals_decoder.x | 708 +++++++++++++++++++++++++ xls/modules/zstd/literals_dispatcher.x | 1 - xls/modules/zstd/parallel_rams.x | 14 +- xls/modules/zstd/sequence_executor.x | 13 +- 6 files changed, 841 insertions(+), 36 deletions(-) create mode 100644 xls/modules/zstd/literals_decoder.x diff --git a/xls/modules/zstd/BUILD b/xls/modules/zstd/BUILD index 56b01cf67b..1c1d7fc5fe 100644 --- a/xls/modules/zstd/BUILD +++ b/xls/modules/zstd/BUILD @@ -1408,3 +1408,101 @@ place_and_route( synthesized_rtl = ":literals_buffer_synth_asap7", target_die_utilization_percentage = "10", ) + +xls_dslx_library( + name = "literals_decoder_dslx", + srcs = [ + "literals_decoder.x", + ], + deps = [ + "//xls/examples:ram_dslx", + ":common_dslx", + ":literals_buffer_dslx", + ":literals_dispatcher_dslx", + ":parallel_rams_dslx", + ":ram_printer_dslx", + ":raw_literals_dec_dslx", + ":rle_literals_dec_dslx", + ], +) + +xls_dslx_test( + name = "literals_decoder_dslx_test", + library = ":literals_decoder_dslx", +) + +xls_dslx_verilog( + name = "literals_decoder_verilog", + codegen_args = { + "module_name": "LiteralsDecoder", + "delay_model": "asap7", + "ram_configurations": ",".join([ + "{ram_name}:1R1W:{rd_req}:{rd_resp}:{wr_req}:{wr_resp}:{latency}".format( + latency = 5, + ram_name = "ram{}".format(num), + rd_req = "literals_decoder__rd_req_m{}_s".format(num), + rd_resp = "literals_decoder__rd_resp_m{}_r".format(num), + wr_req = "literals_decoder__wr_req_m{}_s".format(num), + wr_resp = "literals_decoder__wr_resp_m{}_r".format(num), + ) + for num in range(7) + ]), + "pipeline_stages": "8", + "reset": "rst", + "worst_case_throughput": "1", + "use_system_verilog": "false", + }, + dslx_top = "LiteralsDecoderInst", + library = ":literals_decoder_dslx", + opt_ir_args = { + "inline_procs": "true", + "top": "__xls_modules_zstd_literals_buffer__LiteralsDecoderInst__LiteralsDecoder__LiteralsBuffer__LiteralsBufferReader_0__64_0_0_0_13_8192_65536_next", + }, + verilog_file = "literals_decoder.v", +) + +xls_benchmark_ir( + name = "literals_decoder_opt_ir_benchmark", + src = ":literals_decoder_verilog.opt.ir", + benchmark_ir_args = { + "pipeline_stages": "10", + "delay_model": "asap7", + }, +) + +xls_benchmark_verilog( + name = "literals_decoder_verilog_benchmark", + verilog_target = "literals_decoder_verilog", +) + +verilog_library( + name = "literals_decoder_verilog_lib", + srcs = [ + ":literals_decoder.v", + ], +) + +synthesize_rtl( + name = "literals_decoder_synth_asap7", + standard_cells = "@org_theopenroadproject_asap7sc7p5t_28//:asap7-sc7p5t_rev28_rvt", + top_module = "LiteralsDecoder", + deps = [ + ":literals_decoder_verilog_lib", + ], +) + +benchmark_synth( + name = "literals_decoder_benchmark_synth", + synth_target = ":literals_decoder_synth_asap7", +) + +place_and_route( + name = "literals_decoder_place_and_route", + clock_period = "750", + core_padding_microns = 2, + min_pin_distance = "0.5", + placement_density = "0.30", + stop_after_step = "global_routing", + synthesized_rtl = ":literals_decoder_synth_asap7", + target_die_utilization_percentage = "10", +) diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index a264bdf4a8..1f6dddcdcc 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -39,13 +39,14 @@ type RamNumber = parallel_rams::RamNumber; type RamReadStart = parallel_rams::RamReadStart; type RamRdRespHandlerData = parallel_rams::RamRdRespHandlerData; type RamWrRespHandlerData = parallel_rams::RamWrRespHandlerData; +type RamWrRespHandlerResp = parallel_rams::RamWrRespHandlerResp; // Constants calculated from RAM parameters -const RAM_NUM = parallel_rams::RAM_NUM; +pub const RAM_NUM = parallel_rams::RAM_NUM; const RAM_NUM_WIDTH = parallel_rams::RAM_NUM_WIDTH; -const RAM_DATA_WIDTH = common::SYMBOL_WIDTH + u32:1; // the +1 is used to store "last" flag -const RAM_WORD_PARTITION_SIZE = RAM_DATA_WIDTH; -const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); +pub const RAM_DATA_WIDTH = common::SYMBOL_WIDTH + u32:1; // the +1 is used to store "last" flag +pub const RAM_WORD_PARTITION_SIZE = RAM_DATA_WIDTH; +pub const RAM_NUM_PARTITIONS = ram::num_partitions(RAM_WORD_PARTITION_SIZE, RAM_DATA_WIDTH); // Literals data with last flag type LiteralsWithLast = uN[RAM_DATA_WIDTH * RAM_NUM]; @@ -80,7 +81,6 @@ struct LiteralsBufferWriterState { // History Buffer handling hyp_ptr: HistoryBufferPtr, hb_len: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], - prev_wr_comp: HistoryBufferPtr, literals_in_ram: uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], } @@ -339,17 +339,17 @@ proc LiteralsBufferMux { let (literals_data, state) = if (sel_raw_literals) { ( state.raw_literals_data, - LiteralsBufferMuxState { raw_literals_valid: false, ..state } + LiteralsBufferMuxState { raw_literals_valid: false, ..state } ) } else if (sel_rle_literals) { ( state.rle_literals_data, - LiteralsBufferMuxState { rle_literals_valid: false, ..state } + LiteralsBufferMuxState { rle_literals_valid: false, ..state } ) } else if (sel_huff_literals) { ( state.huff_literals_data, - LiteralsBufferMuxState { huff_literals_valid: false, ..state } + LiteralsBufferMuxState { huff_literals_valid: false, ..state } ) } else { ( @@ -393,7 +393,7 @@ proc LiteralsBufferWriter< literals_r: chan in; ram_comp_input_s: chan> out; - ram_comp_output_r: chan> in; + ram_comp_output_r: chan> in; buffer_sync_r: chan in; buffer_sync_s: chan out; @@ -429,7 +429,7 @@ proc LiteralsBufferWriter< wr_resp_m7_r: chan in ) { let (ram_comp_input_s, ram_comp_input_r) = chan, u32:1>("ram_comp_input"); - let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); + let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); spawn parallel_rams::RamWrRespHandler( ram_comp_input_r, ram_comp_output_s, @@ -454,7 +454,6 @@ proc LiteralsBufferWriter< State { hyp_ptr: INIT_HB_PTR, hb_len: INIT_HB_LENGTH as uN[RAM_ADDR_WIDTH + RAM_NUM_WIDTH], - prev_wr_comp: INIT_HB_PTR, ..zero!() } } @@ -476,7 +475,7 @@ proc LiteralsBufferWriter< // read literals let do_recv_literals = state.hb_len as u32 < HISTORY_BUFFER_SIZE_KB << u32:10; - let (tok1, literals_data) = recv_if(tok0, literals_r, do_recv_literals, zero!()); + let (tok1, literals_data, literals_data_valid) = recv_if_non_blocking(tok0, literals_r, do_recv_literals, zero!()); // write literals to RAM let packet_data = for (i, data): (u32, LiteralsWithLast) in range(u32:0, RAM_NUM) { @@ -499,7 +498,7 @@ proc LiteralsBufferWriter< let hb_add = packet.length as HistoryBufferLength; let new_hb_len = std::mod_pow2(state.hb_len + hb_add, RAM_SIZE_TOTAL as HistoryBufferLength); - let write_reqs = if (do_recv_literals) { + let write_reqs = if (literals_data_valid) { write_reqs } else { ZERO_WRITE_REQS @@ -523,18 +522,10 @@ proc LiteralsBufferWriter< let tok3_0 = send_if(tok2, ram_comp_input_s, do_write, wr_resp_handler_data); - let (tok3_1, comp_data, comp_data_valid) = recv_non_blocking(tok2, ram_comp_output_r, zero!()); - - // update RAM literals count - let literals_diff = (comp_data.number - state.prev_wr_comp.number) as LitLength; - let literals_diff = if (literals_diff == LitLength:0) { - LitLength:8 - } else { - literals_diff - }; + let (tok3_1, comp_data, comp_data_valid) = recv_non_blocking(tok2, ram_comp_output_r, zero!()); // update state - let state = if (do_recv_literals) { + let state = if (literals_data_valid) { State { hyp_ptr: new_hyp_ptr, hb_len: new_hb_len, @@ -545,9 +536,9 @@ proc LiteralsBufferWriter< }; let state = if (comp_data_valid) { + trace_fmt!("COMP {:#x}", comp_data); State { - literals_in_ram: state.literals_in_ram + literals_diff as uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)], - prev_wr_comp: comp_data, + literals_in_ram: state.literals_in_ram + comp_data.length as uN[RAM_ADDR_WIDTH + std::clog2(RAM_NUM)], ..state } } else { @@ -568,7 +559,7 @@ proc LiteralsBufferWriter< let tok3 = join(tok3_0, tok3_1); let sync_data = LiteralsBufferWriterToReaderSync { - literals_written: literals_diff, + literals_written: comp_data.length, }; let tok4 = send_if(tok3, buffer_sync_s, comp_data_valid, sync_data); diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x new file mode 100644 index 0000000000..25e562d28a --- /dev/null +++ b/xls/modules/zstd/literals_decoder.x @@ -0,0 +1,708 @@ +// 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 the implementation of LiteralsDecoder. + +import std; + +import xls.examples.ram; +import xls.modules.zstd.common as common; +import xls.modules.zstd.literals_buffer as literals_buffer; +import xls.modules.zstd.literals_dispatcher as literals_dispatcher; +import xls.modules.zstd.parallel_rams as parallel_rams; +import xls.modules.zstd.ram_printer as ram_printer; +import xls.modules.zstd.raw_literals_dec as raw_literals_dec; +import xls.modules.zstd.rle_literals_dec as rle_literals_dec; + +type CopyOrMatchContent = common::CopyOrMatchContent; +type CopyOrMatchLength = common::CopyOrMatchLength; +type LitData = common::LitData; +type LitLength = common::LitLength; +type LiteralType = common::LiteralType; +type LiteralsBufferCtrl = common::LiteralsBufferCtrl; +type LiteralsData = common::LiteralsData; +type LiteralsDataWithSync = common::LiteralsDataWithSync; +type LiteralsPathCtrl = common::LiteralsPathCtrl; +type RleLiteralsData = common::RleLiteralsData; +type SequenceExecutorMessageType = common::SequenceExecutorMessageType; +type SequenceExecutorPacket = common::SequenceExecutorPacket; +type Streams = common::Streams; + +proc LiteralsDecoder< + HISTORY_BUFFER_SIZE_KB: u32, + RAM_SIZE: u32 = {parallel_rams::ram_size(HISTORY_BUFFER_SIZE_KB)}, + RAM_ADDR_WIDTH: u32 = {parallel_rams::ram_addr_width(HISTORY_BUFFER_SIZE_KB)}, +> { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + literals_ctrl_r: chan in; + literals_data_r: chan in; + literals_buf_ctrl_r: chan in; + literals_s: chan out; + + config ( + literals_ctrl_r: chan in, + literals_data_r: chan in, + literals_buf_ctrl_r: chan in, + literals_s: chan out, + rd_req_m0_s: chan out, + rd_req_m1_s: chan out, + rd_req_m2_s: chan out, + rd_req_m3_s: chan out, + rd_req_m4_s: chan out, + rd_req_m5_s: chan out, + rd_req_m6_s: chan out, + rd_req_m7_s: chan out, + rd_resp_m0_r: chan in, + rd_resp_m1_r: chan in, + rd_resp_m2_r: chan in, + rd_resp_m3_r: chan in, + rd_resp_m4_r: chan in, + rd_resp_m5_r: chan in, + rd_resp_m6_r: chan in, + rd_resp_m7_r: chan in, + wr_req_m0_s: chan out, + wr_req_m1_s: chan out, + wr_req_m2_s: chan out, + wr_req_m3_s: chan out, + wr_req_m4_s: chan out, + wr_req_m5_s: chan out, + wr_req_m6_s: chan out, + wr_req_m7_s: chan out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + let (raw_literals_s, raw_literals_r) = chan("raw_literals"); + let (rle_literals_s, rle_literals_r) = chan("rle_literals"); + let (huff_literals_s, huff_literals_r) = chan("huff_literals"); + + let (decoded_raw_literals_s, decoded_raw_literals_r) = chan("decoded_raw_literals"); + let (decoded_rle_literals_s, decoded_rle_literals_r) = chan("decoded_rle_literals"); + + spawn literals_dispatcher::LiteralsDispatcher( + literals_ctrl_r, literals_data_r, + raw_literals_s, rle_literals_s, huff_literals_s, + ); + + spawn raw_literals_dec::RawLiteralsDecoder(raw_literals_r, decoded_raw_literals_s); + + spawn rle_literals_dec::RleLiteralsDecoder(rle_literals_r, decoded_rle_literals_s); + + spawn literals_buffer::LiteralsBuffer ( + decoded_raw_literals_r, decoded_rle_literals_r, huff_literals_r, + literals_buf_ctrl_r, literals_s, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ); + + ( + literals_ctrl_r, literals_data_r, + literals_buf_ctrl_r, literals_s, + ) + } + + init { } + + next (state: ()) { } +} + +const ZSTD_HISTORY_BUFFER_SIZE_KB: u32 = u32:64; +const ZSTD_RAM_ADDR_WIDTH: u32 = parallel_rams::ram_addr_width(ZSTD_HISTORY_BUFFER_SIZE_KB); + +proc LiteralsDecoderInst { + type ReadReq = ram::ReadReq; + type ReadResp = ram::ReadResp; + type WriteReq = ram::WriteReq; + type WriteResp = ram::WriteResp; + + config ( + literals_ctrl_r: chan in, + literals_data_r: chan in, + literals_buf_ctrl_r: chan in, + literals_s: chan out, + rd_req_m0_s: chan out, + rd_req_m1_s: chan out, + rd_req_m2_s: chan out, + rd_req_m3_s: chan out, + rd_req_m4_s: chan out, + rd_req_m5_s: chan out, + rd_req_m6_s: chan out, + rd_req_m7_s: chan out, + rd_resp_m0_r: chan in, + rd_resp_m1_r: chan in, + rd_resp_m2_r: chan in, + rd_resp_m3_r: chan in, + rd_resp_m4_r: chan in, + rd_resp_m5_r: chan in, + rd_resp_m6_r: chan in, + rd_resp_m7_r: chan in, + wr_req_m0_s: chan out, + wr_req_m1_s: chan out, + wr_req_m2_s: chan out, + wr_req_m3_s: chan out, + wr_req_m4_s: chan out, + wr_req_m5_s: chan out, + wr_req_m6_s: chan out, + wr_req_m7_s: chan out, + wr_resp_m0_r: chan in, + wr_resp_m1_r: chan in, + wr_resp_m2_r: chan in, + wr_resp_m3_r: chan in, + wr_resp_m4_r: chan in, + wr_resp_m5_r: chan in, + wr_resp_m6_r: chan in, + wr_resp_m7_r: chan in + ) { + spawn LiteralsDecoder ( + literals_ctrl_r, literals_data_r, + literals_buf_ctrl_r, literals_s, + rd_req_m0_s, rd_req_m1_s, rd_req_m2_s, rd_req_m3_s, + rd_req_m4_s, rd_req_m5_s, rd_req_m6_s, rd_req_m7_s, + rd_resp_m0_r, rd_resp_m1_r, rd_resp_m2_r, rd_resp_m3_r, + rd_resp_m4_r, rd_resp_m5_r, rd_resp_m6_r, rd_resp_m7_r, + wr_req_m0_s, wr_req_m1_s, wr_req_m2_s, wr_req_m3_s, + wr_req_m4_s, wr_req_m5_s, wr_req_m6_s, wr_req_m7_s, + wr_resp_m0_r, wr_resp_m1_r, wr_resp_m2_r, wr_resp_m3_r, + wr_resp_m4_r, wr_resp_m5_r, wr_resp_m6_r, wr_resp_m7_r, + ); + } + + init {} + + next (state: ()) {} +} + +// RAM related constants common for tests +const TEST_HISTORY_BUFFER_SIZE_KB = u32:1; +const TEST_RAM_SIZE = parallel_rams::ram_size(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_ADDR_WIDTH = parallel_rams::ram_addr_width(TEST_HISTORY_BUFFER_SIZE_KB); +const TEST_RAM_INITIALIZED = true; +const TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR = ram::SimultaneousReadWriteBehavior::READ_BEFORE_WRITE; + +type TestRamAddr = bits[TEST_RAM_ADDR_WIDTH]; +type TestWriteReq = ram::WriteReq; +type TestWriteResp = ram::WriteResp; +type TestReadReq = ram::ReadReq; +type TestReadResp = ram::ReadResp; + +const TEST_CTRL: LiteralsPathCtrl[7] = [ + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:8, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:4, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:2, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:15, literals_type: LiteralType::RAW}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:12, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:0, literals_type: LiteralType::RLE}, + LiteralsPathCtrl {data_conf: zero!(), decompressed_size: u20:31, literals_type: LiteralType::RAW}, +]; + +const TEST_DATA: LiteralsData[11] = [ + // 0. RAW + LiteralsData {data: LitData:0x1657_3465_A6DB_5DB0, length: LitLength:8, last: false}, + // 1. RLE + LiteralsData {data: LitData:0x23, length: LitLength:1, last: false}, + // 2. RLE + LiteralsData {data: LitData:0x35, length: LitLength:1, last: false}, + // 3. RAW + LiteralsData {data: LitData:0x4CFB_41C6_7B60_5370, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x009B_0F9C_E1BA_A96D, length: LitLength:7, last: true}, + // 4. RLE + LiteralsData {data: LitData:0x5A, length: LitLength:1, last: false}, + // 5. RLE + LiteralsData {data: LitData:0xFF, length: LitLength:1, last: false}, + // 6. RAW + LiteralsData {data: LitData:0x6094_3E96_1834_C247, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xBC02_D0E8_D728_9ABE, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0xF864_C38B_E1FA_8D12, length: LitLength:8, last: false}, + LiteralsData {data: LitData:0x0019_63F1_CE21_C294, length: LitLength:7, last: true}, +]; + +const TEST_BUF_CTRL: LiteralsBufferCtrl[5] = [ + LiteralsBufferCtrl {length: u32:11, last: false}, + LiteralsBufferCtrl {length: u32:2, last: false}, + LiteralsBufferCtrl {length: u32:16, last: false}, + LiteralsBufferCtrl {length: u32:11, last: false}, + LiteralsBufferCtrl {length: u32:32, last: true}, +]; + +const TEST_EXPECTED_LITERALS: SequenceExecutorPacket[11] = [ + // ctrl 0 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x1657_3465_A6DB_5DB0, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:0x23_2323, + last: false + }, + // ctrl 1 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:2, + content: CopyOrMatchContent:0x35_23, + last: false + }, + // ctrl 2 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0xFB41_C67B_6053_7035, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x9B0F_9CE1_BAA9_6D4C, + last: true + }, + // ctrl 3 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x5A5A_5A5A_5A5A_5A5A, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:3, + content: CopyOrMatchContent:0x5A_5A5A, + last: false + }, + // ctrl 4 + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x943E_9618_34C2_475A, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x02D0_E8D7_289A_BE60, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x64C3_8BE1_FA8D_12BC, + last: false + }, + SequenceExecutorPacket { + msg_type: SequenceExecutorMessageType::LITERAL, + length: CopyOrMatchLength:8, + content: CopyOrMatchContent:0x1963_F1CE_21C2_94F8, + last: true + }, +]; + +#[test_proc] +proc LiteralsDecoder_test { + terminator: chan out; + + literals_ctrl_s: chan out; + literals_data_s: chan out; + literals_buf_ctrl_s: chan out; + literals_r: chan in; + + print_start_s: chan<()> out; + print_finish_r: chan<()> in; + + config (terminator: chan out) { + let (literals_ctrl_s, literals_ctrl_r) = chan("literals_ctrl"); + let (literals_data_s, literals_data_r) = chan("literals_data"); + let (literals_buf_ctrl_s, literals_buf_ctrl_r) = chan("literals_buf_ctrl"); + let (literals_s, literals_r) = chan("literals"); + + let (print_start_s, print_start_r) = chan<()>("print_start"); + let (print_finish_s, print_finish_r) = chan<()>("print_finish"); + + let (ram_rd_req_s, ram_rd_req_r) = chan[literals_buffer::RAM_NUM]("ram_rd_req"); + let (ram_rd_resp_s, ram_rd_resp_r) = chan[literals_buffer::RAM_NUM]("ram_rd_resp"); + let (ram_wr_req_s, ram_wr_req_r) = chan[literals_buffer::RAM_NUM]("ram_wr_req"); + let (ram_wr_resp_s, ram_wr_resp_r) = chan[literals_buffer::RAM_NUM]("ram_wr_resp"); + + spawn LiteralsDecoder( + literals_ctrl_r, literals_data_r, + literals_buf_ctrl_r, literals_s, + ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], + ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], + ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], + ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], + ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], + ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], + ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], + ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] + ); + + spawn ram_printer::RamPrinter< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_NUM_PARTITIONS, + TEST_RAM_ADDR_WIDTH, literals_buffer::RAM_NUM> + (print_start_r, print_finish_s, ram_rd_req_s, ram_rd_resp_r); + + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); + spawn ram::RamModel< + literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, + TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> + (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + + ( + terminator, + literals_ctrl_s, literals_data_s, + literals_buf_ctrl_s, literals_r, + print_start_s, print_finish_r, + ) + } + + init { } + + next (state: ()) { + // send literals + let tok = for ((i, test_data), tok): ((u32, LiteralsData), token) in enumerate(TEST_DATA) { + let tok = send(tok, literals_data_s, test_data); + trace_fmt!("Sent #{} literals data, {:#x}", i + u32:1, test_data); + tok + }(tok); + + // send ctrl + let tok = for ((i, test_ctrl), tok): ((u32, LiteralsPathCtrl), token) in enumerate(TEST_CTRL) { + let tok = send(tok, literals_ctrl_s, test_ctrl); + trace_fmt!("Sent #{} literals ctrl, {:#x}", i + u32:1, test_ctrl); + tok + }(tok); + + // send buffer ctrl + let tok = for ((i, test_buf_ctrl), tok): ((u32, LiteralsBufferCtrl), token) in enumerate(TEST_BUF_CTRL) { + let tok = send(tok, literals_buf_ctrl_s, test_buf_ctrl); + trace_fmt!("Sent #{} ctrl {:#x}", i + u32:1, test_buf_ctrl); + tok + }(tok); + + // receive and check packets + let tok = for ((i, test_exp_literals), tok): ((u32, SequenceExecutorPacket), token) in enumerate(TEST_EXPECTED_LITERALS) { + let (tok, literals) = recv(tok, literals_r); + trace_fmt!("Received #{} literals packet {:#x}", i + u32:1, literals); + assert_eq(test_exp_literals, literals); + tok + }(tok); + + // print RAM content + let tok = send(tok, print_start_s, ()); + let (tok, _) = recv(tok, print_finish_r); + + send(tok, terminator, true); + } +} + +// TODO: Uncomment this test when fixed: https://github.com/google/xls/issues/1502 +// type RamData = uN[literals_buffer::RAM_DATA_WIDTH]; + +// // Expected RAM content after each ctrl +// const TEST_EXPECTED_RAM_CONTENT = RamData[literals_buffer::RAM_NUM][10][7]:[ +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData: 0x0, RamData: 0x0, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x05a], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData:0x05a], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// [ +// [RamData:0x016, RamData:0x057, RamData:0x034, RamData:0x065, RamData:0x0a6, RamData:0x0db, RamData:0x05d, RamData:0x0b0], +// [RamData:0x053, RamData:0x070, RamData:0x035, RamData:0x035, RamData:0x023, RamData:0x023, RamData:0x023, RamData:0x023], +// [RamData:0x1a9, RamData:0x16d, RamData:0x04c, RamData:0x0fb, RamData:0x041, RamData:0x0c6, RamData:0x07b, RamData:0x060], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x19b, RamData:0x10f, RamData:0x19c, RamData:0x1e1, RamData:0x1ba], +// [RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a, RamData:0x05a], +// [RamData:0x094, RamData:0x03e, RamData:0x096, RamData:0x018, RamData:0x034, RamData:0x0c2, RamData:0x047, RamData:0x05a], +// [RamData:0x002, RamData:0x0d0, RamData:0x0e8, RamData:0x0d7, RamData:0x028, RamData:0x09a, RamData:0x0be, RamData:0x060], +// [RamData:0x064, RamData:0x0c3, RamData:0x08b, RamData:0x0e1, RamData:0x0fa, RamData:0x08d, RamData:0x012, RamData:0x0bc], +// [RamData:0x119, RamData:0x163, RamData:0x1f1, RamData:0x1ce, RamData:0x121, RamData:0x1c2, RamData:0x194, RamData:0x0f8], +// [RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0, RamData: 0x0], +// ], +// ]; + +// const CYCLES_PER_RAM_READ = u32:16; + +// #[test_proc] +// proc LiteralsDecoderRamContent_test { +// terminator: chan out; + +// literals_ctrl_s: chan out; +// literals_data_s: chan out; +// literals_buf_ctrl_s: chan out; +// literals_r: chan in; + +// ram_rd_req_m0_s: chan out; +// ram_rd_req_m1_s: chan out; +// ram_rd_req_m2_s: chan out; +// ram_rd_req_m3_s: chan out; +// ram_rd_req_m4_s: chan out; +// ram_rd_req_m5_s: chan out; +// ram_rd_req_m6_s: chan out; +// ram_rd_req_m7_s: chan out; + +// ram_rd_resp_m0_r: chan in; +// ram_rd_resp_m1_r: chan in; +// ram_rd_resp_m2_r: chan in; +// ram_rd_resp_m3_r: chan in; +// ram_rd_resp_m4_r: chan in; +// ram_rd_resp_m5_r: chan in; +// ram_rd_resp_m6_r: chan in; +// ram_rd_resp_m7_r: chan in; + +// config (terminator: chan out) { +// let (literals_ctrl_s, literals_ctrl_r) = chan("literals_ctrl"); +// let (literals_data_s, literals_data_r) = chan("literals_data"); +// let (literals_buf_ctrl_s, literals_buf_ctrl_r) = chan("literals_buf_ctrl"); +// let (literals_s, literals_r) = chan("literals"); + +// let (ram_rd_req_s, ram_rd_req_r) = chan[literals_buffer::RAM_NUM]("ram_rd_req"); +// let (ram_rd_resp_s, ram_rd_resp_r) = chan[literals_buffer::RAM_NUM]("ram_rd_resp"); +// let (ram_wr_req_s, ram_wr_req_r) = chan[literals_buffer::RAM_NUM]("ram_wr_req"); +// let (ram_wr_resp_s, ram_wr_resp_r) = chan[literals_buffer::RAM_NUM]("ram_wr_resp"); + +// spawn LiteralsDecoder( +// literals_ctrl_r, literals_data_r, +// literals_buf_ctrl_r, literals_s, +// ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], +// ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], +// ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], +// ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], +// ram_wr_req_s[0], ram_wr_req_s[1], ram_wr_req_s[2], ram_wr_req_s[3], +// ram_wr_req_s[4], ram_wr_req_s[5], ram_wr_req_s[6], ram_wr_req_s[7], +// ram_wr_resp_r[0], ram_wr_resp_r[1], ram_wr_resp_r[2], ram_wr_resp_r[3], +// ram_wr_resp_r[4], ram_wr_resp_r[5], ram_wr_resp_r[6], ram_wr_resp_r[7] +// ); + +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[0], ram_rd_resp_s[0], ram_wr_req_r[0], ram_wr_resp_s[0]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[1], ram_rd_resp_s[1], ram_wr_req_r[1], ram_wr_resp_s[1]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[2], ram_rd_resp_s[2], ram_wr_req_r[2], ram_wr_resp_s[2]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[3], ram_rd_resp_s[3], ram_wr_req_r[3], ram_wr_resp_s[3]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[4], ram_rd_resp_s[4], ram_wr_req_r[4], ram_wr_resp_s[4]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[5], ram_rd_resp_s[5], ram_wr_req_r[5], ram_wr_resp_s[5]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[6], ram_rd_resp_s[6], ram_wr_req_r[6], ram_wr_resp_s[6]); +// spawn ram::RamModel< +// literals_buffer::RAM_DATA_WIDTH, TEST_RAM_SIZE, literals_buffer::RAM_WORD_PARTITION_SIZE, +// TEST_RAM_SIMULTANEOUS_READ_WRITE_BEHAVIOR, TEST_RAM_INITIALIZED> +// (ram_rd_req_r[7], ram_rd_resp_s[7], ram_wr_req_r[7], ram_wr_resp_s[7]); + +// ( +// terminator, +// literals_ctrl_s, literals_data_s, +// literals_buf_ctrl_s, literals_r, +// ram_rd_req_s[0], ram_rd_req_s[1], ram_rd_req_s[2], ram_rd_req_s[3], +// ram_rd_req_s[4], ram_rd_req_s[5], ram_rd_req_s[6], ram_rd_req_s[7], +// ram_rd_resp_r[0], ram_rd_resp_r[1], ram_rd_resp_r[2], ram_rd_resp_r[3], +// ram_rd_resp_r[4], ram_rd_resp_r[5], ram_rd_resp_r[6], ram_rd_resp_r[7], +// ) +// } + +// init { u32:0 } + +// next (state: u32) { +// // send literals +// let ok = if (state == u32:0) { +// for ((i, test_data), tok): ((u32, LiteralsData), token) in enumerate(TEST_DATA) { +// let tok = send(tok, literals_data_s, test_data); +// trace_fmt!("Sent #{} literals data, {:#x}", i + u32:1, test_data); +// tok +// }(tok) +// } else { tok }; + +// // send ctrl and read RAM content +// let tok = for ((i, test_ctrl), tok): ((u32, LiteralsPathCtrl), token) in enumerate(TEST_CTRL) { +// if (state == i * CYCLES_PER_RAM_READ) { +// let tok = send(tok, literals_ctrl_s, test_ctrl); +// trace_fmt!("Sent #{} literals ctrl, {:#x}", i + u32:1, test_ctrl); +// tok +// } else if (state == (i + u32:1) * CYCLES_PER_RAM_READ - u32:1) { +// for (addr, tok): (u32, token) in range(u32:0, u32:10) { +// let read_req = TestReadReq { +// addr: addr as uN[TEST_RAM_ADDR_WIDTH], +// mask: u1:1 +// }; + +// let tok = send(tok, ram_rd_req_m0_s, read_req); +// let tok = send(tok, ram_rd_req_m1_s, read_req); +// let tok = send(tok, ram_rd_req_m2_s, read_req); +// let tok = send(tok, ram_rd_req_m3_s, read_req); +// let tok = send(tok, ram_rd_req_m4_s, read_req); +// let tok = send(tok, ram_rd_req_m5_s, read_req); +// let tok = send(tok, ram_rd_req_m6_s, read_req); +// let tok = send(tok, ram_rd_req_m7_s, read_req); + +// let (tok, ram_rd_resp_m0) = recv(tok, ram_rd_resp_m0_r); +// let (tok, ram_rd_resp_m1) = recv(tok, ram_rd_resp_m1_r); +// let (tok, ram_rd_resp_m2) = recv(tok, ram_rd_resp_m2_r); +// let (tok, ram_rd_resp_m3) = recv(tok, ram_rd_resp_m3_r); +// let (tok, ram_rd_resp_m4) = recv(tok, ram_rd_resp_m4_r); +// let (tok, ram_rd_resp_m5) = recv(tok, ram_rd_resp_m5_r); +// let (tok, ram_rd_resp_m6) = recv(tok, ram_rd_resp_m6_r); +// let (tok, ram_rd_resp_m7) = recv(tok, ram_rd_resp_m7_r); +// trace_fmt!( +// "Received RAM read responses: [{:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}, {:#x}]", +// ram_rd_resp_m7.data, ram_rd_resp_m6.data, ram_rd_resp_m5.data, ram_rd_resp_m4.data, +// ram_rd_resp_m3.data, ram_rd_resp_m2.data, ram_rd_resp_m1.data, ram_rd_resp_m0.data, +// ); + +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][7], ram_rd_resp_m0.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][6], ram_rd_resp_m1.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][5], ram_rd_resp_m2.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][4], ram_rd_resp_m3.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][3], ram_rd_resp_m4.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][2], ram_rd_resp_m5.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][1], ram_rd_resp_m6.data); +// assert_eq(TEST_EXPECTED_RAM_CONTENT[i][addr][0], ram_rd_resp_m7.data); + +// tok +// }(tok) +// } else { +// tok +// } +// }(tok); + +// send_if(tok, terminator, state == array_size(TEST_CTRL) * CYCLES_PER_RAM_READ, true); + +// state + u32:1 +// } +// } diff --git a/xls/modules/zstd/literals_dispatcher.x b/xls/modules/zstd/literals_dispatcher.x index f5872d5ca4..6a9d9a6dd8 100644 --- a/xls/modules/zstd/literals_dispatcher.x +++ b/xls/modules/zstd/literals_dispatcher.x @@ -99,7 +99,6 @@ pub proc LiteralsDispatcher { let tok = send_if(tok, raw_literals_s, LiteralType::RAW == literals_type, literals_data); - let rle_literals_data = RleLiteralsData { data: (literals_data.data as u8), repeat: literals_path_ctrl.decompressed_size, diff --git a/xls/modules/zstd/parallel_rams.x b/xls/modules/zstd/parallel_rams.x index 0b0b5e1ec0..5eae5d9675 100644 --- a/xls/modules/zstd/parallel_rams.x +++ b/xls/modules/zstd/parallel_rams.x @@ -551,6 +551,11 @@ pub struct RamWrRespHandlerData { ptr: HistoryBufferPtr, } +pub struct RamWrRespHandlerResp { + length: uN[std::clog2(RAM_NUM + u32:1)], + ptr: HistoryBufferPtr, +} + fn create_ram_wr_data (reqs: ram::WriteReq[RAM_NUM], ptr: HistoryBufferPtr) -> (bool, RamWrRespHandlerData) { const RAM_REQ_MASK_NONE = bits[RAM_NUM_PARTITIONS]:0; @@ -567,7 +572,7 @@ fn create_ram_wr_data { input_r: chan in; - output_s: chan out; + output_s: chan out; wr_resp_m0_r: chan in; wr_resp_m1_r: chan in; wr_resp_m2_r: chan in; @@ -578,7 +583,7 @@ pub proc RamWrRespHandler { wr_resp_m7_r: chan in; config(input_r: chan> in, - output_s: chan> out, + output_s: chan> out, wr_resp_m0_r: chan in, wr_resp_m1_r: chan in, wr_resp_m2_r: chan in, wr_resp_m3_r: chan in, wr_resp_m4_r: chan in, wr_resp_m5_r: chan in, @@ -605,7 +610,10 @@ pub proc RamWrRespHandler { let (tok2_7, _) = recv_if(tok1, wr_resp_m7_r, input.resp[7], zero!()); let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); - let tok3 = send(tok2, output_s, input.ptr); + let tok3 = send(tok2, output_s, RamWrRespHandlerResp { + length: std::popcount(std::convert_to_bits_msb0(input.resp)) as uN[std::clog2(RAM_NUM + u32:1)], + ptr: input.ptr + }); } } diff --git a/xls/modules/zstd/sequence_executor.x b/xls/modules/zstd/sequence_executor.x index 5ac772fde3..7b790d8bf2 100644 --- a/xls/modules/zstd/sequence_executor.x +++ b/xls/modules/zstd/sequence_executor.x @@ -61,6 +61,7 @@ type TestReadResp = ram::ReadResp; type HistoryBufferPtr = parallel_rams::HistoryBufferPtr; type RamWrRespHandlerData = parallel_rams::RamWrRespHandlerData; +type RamWrRespHandlerResp = parallel_rams::RamWrRespHandlerResp; type RamRdRespHandlerData = parallel_rams::RamRdRespHandlerData; type RamData = uN[RAM_DATA_WIDTH]; type RamNumber = parallel_rams::RamNumber; @@ -174,7 +175,7 @@ proc SequenceExecutor in; output_s: chan out; ram_comp_input_s: chan> out; - ram_comp_output_r: chan> in; + ram_comp_output_r: chan> in; ram_resp_input_s: chan out; ram_resp_output_r: chan in; rd_req_m0_s: chan> out; @@ -233,7 +234,7 @@ proc SequenceExecutor in ) { let (ram_comp_input_s, ram_comp_input_r) = chan, u32:1>("ram_comp_input"); - let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); + let (ram_comp_output_s, ram_comp_output_r) = chan, u32:1>("ram_comp_output"); let (ram_resp_input_s, ram_resp_input_r) = chan("ram_resp_input"); spawn parallel_rams::RamWrRespHandler( @@ -310,13 +311,13 @@ proc SequenceExecutor()); - if real_ptr_valid { + let (tok1_2, wr_resp, wr_resp_valid) = + recv_non_blocking(tok0, ram_comp_output_r, zero!()); + if wr_resp_valid { trace_fmt!("SequenceExecutor:: Received completion update"); } else { }; - let real_ptr = if real_ptr_valid { real_ptr } else { state.real_ptr }; + let real_ptr = if wr_resp_valid { wr_resp.ptr } else { state.real_ptr }; let tok1 = join(tok1_0, tok1_1, tok1_2); // Since we either get data from input, from frame, or from state, From 6d31c72181a7f142de0a43e1204892c5ba42cf9d Mon Sep 17 00:00:00 2001 From: Maciej Dudek Date: Thu, 25 Jul 2024 15:51:01 +0200 Subject: [PATCH 49/49] Fix tests inside literals_buffer.x Signed-off-by: Maciej Dudek --- xls/modules/zstd/literals_buffer.x | 36 ++++++++++++++++++++------ xls/modules/zstd/literals_decoder.x | 1 + xls/modules/zstd/literals_dispatcher.x | 2 +- 3 files changed, 30 insertions(+), 9 deletions(-) diff --git a/xls/modules/zstd/literals_buffer.x b/xls/modules/zstd/literals_buffer.x index 1f6dddcdcc..81e4f5b08e 100644 --- a/xls/modules/zstd/literals_buffer.x +++ b/xls/modules/zstd/literals_buffer.x @@ -136,14 +136,29 @@ proc PacketDecoder { // only if any of the literas has it set. Also if any literal has set // this flag, then the flag in following literal must also be set. In // other case, the assertion is triggered. - let (last, _, packet_valid) = for (i, (last, prev_literal_last, packet_valid)): (u32, (bool, bool, bool)) in range(u32:0, RAM_NUM) { + let (last, _, packet_valid, _) = for (i, (last, prev_literal_last, packet_valid, prev_calc)): (u32, (bool, bool, bool, bool)) in range(u32:0, RAM_NUM) { let literal_last = (literals.content >> (RAM_DATA_WIDTH * (i + u32:1) - u32:1)) as u1; - ( + let calc = if (i == literals.length as uN[32]) { + false + } else { + prev_calc + }; + if (calc) { + ( last | literal_last, literal_last, packet_valid & (!prev_literal_last | literal_last), - ) - }((false, false, true)); + calc + ) + } else { + ( + last, + literal_last, + packet_valid, + calc + ) + } + }((false, false, true, true)); assert!(packet_valid && (literals.last == last), "Invalid packet"); @@ -174,7 +189,7 @@ const TEST_LITERALS_IN: SequenceExecutorPacket[4] = [ SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:1, - content: literals_content(u8:0xAB, u1:1, u3:0), + content: literals_content(u8:0xAB, u1:0, u3:0), last: false, }, SequenceExecutorPacket { @@ -219,7 +234,7 @@ const TEST_LITERALS_OUT: SequenceExecutorPacket[4] = [ msg_type: SequenceExecutorMessageType::LITERAL, length: CopyOrMatchLength:1, content: CopyOrMatchContent:0xAB, - last: true, + last: false, }, SequenceExecutorPacket { msg_type: SequenceExecutorMessageType::LITERAL, @@ -469,7 +484,7 @@ proc LiteralsBufferWriter< let (_, sync_data, sync_data_valid) = recv_non_blocking(tok0, buffer_sync_r, zero!()); if (sync_data_valid) { - trace_fmt!("Received bufffer reader-to-writer sync data {:#x}", sync_data); + trace_fmt!("Received buffer reader-to-writer sync data {:#x}", sync_data); } else {}; // read literals @@ -748,10 +763,15 @@ proc LiteralsBufferReader< let tok2_7 = send_if(tok1, rd_req_m7_s, read_reqs[7].mask != RAM_REQ_MASK_NONE, read_reqs[7]); let tok2 = join(tok2_0, tok2_1, tok2_2, tok2_3, tok2_4, tok2_5, tok2_6, tok2_7); + let last_access = if (state.left_to_read > u32:0) { + false + } else { + state.ctrl_last + }; let (do_read, rd_resp_handler_data) = parallel_rams::create_ram_rd_data( - read_reqs, read_start, read_len, false, true + read_reqs, read_start, read_len, last_access, !last_access ); if do_read { trace_fmt!("Sending request to RamRdRespHandler: {:#x}", rd_resp_handler_data); diff --git a/xls/modules/zstd/literals_decoder.x b/xls/modules/zstd/literals_decoder.x index 25e562d28a..7d74390007 100644 --- a/xls/modules/zstd/literals_decoder.x +++ b/xls/modules/zstd/literals_decoder.x @@ -412,6 +412,7 @@ proc LiteralsDecoder_test { init { } next (state: ()) { + let tok = join(); // send literals let tok = for ((i, test_data), tok): ((u32, LiteralsData), token) in enumerate(TEST_DATA) { let tok = send(tok, literals_data_s, test_data); diff --git a/xls/modules/zstd/literals_dispatcher.x b/xls/modules/zstd/literals_dispatcher.x index 6a9d9a6dd8..39fe949321 100644 --- a/xls/modules/zstd/literals_dispatcher.x +++ b/xls/modules/zstd/literals_dispatcher.x @@ -111,7 +111,7 @@ pub proc LiteralsDispatcher { LiteralType::COMP == literals_type || LiteralType::COMP_4 == literals_type || LiteralType::TREELESS == literals_type || LiteralType::TREELESS_4 == literals_type ); - assert!(do_send_huff, "Huffmann coding not implemented yet"); + assert!(!do_send_huff, "Huffmann coding not implemented yet"); let tok = send_if(tok, huff_literals_s, false, zero!()); // empty RLE literals with last not set will not be sent by RLE decoder to buffer